Directx Audio Exposed Interactive Audio Developmen
Directx Audio Exposed Interactive Audio Developmen
Exposed: Interactive
Audio Development
Fay, Todd M.
DirectX 9 audio exposed: interactive audio development / edited by Todd M. Fay.
p. cm.
ISBN 1-55622-288-2 (paperback, companion CD-ROM)
1. Sound—Recording and reproducing—Digital techniques. 2. Computer
games—Programming. 3. DirectX. I. Fay, Todd M.
TK7881.4.D56 2003
794.8'165—dc22 2003014463
CIP
ISBN 1-55622-288-2
10 9 8 7 6 5 4 3 2 1
0307
DirectX, DirectMusic, and DirectSound are registered trademarks of Microsoft Corporation in the United States and/or
other countries.
All screen shots and game titles used in this book remain the property of their respective companies.
All brand names and product names mentioned in this book are trademarks or service marks of their respective companies.
Any omission or misuse (of any kind) of service marks or trademarks should not be regarded as intent to infringe on the
property of others. The publisher recognizes and respects all marks used by companies, manufacturers, and developers as a
means to distinguish their products.
This book is sold as is, without warranty of any kind, either express or implied, respecting the contents of this book and any
disks or programs that may accompany it, including but not limited to implied warranties for the book’s quality, performance,
merchantability, or fitness for any particular purpose. Neither Wordware Publishing, Inc. nor its dealers or distributors shall
be liable to the purchaser or any other person or entity with respect to any liability, loss, or damage caused or alleged to have
been caused directly or indirectly by this book.
All inquiries for volume purchases of this book should be addressed to Wordware
Publishing, Inc., at the above address. Telephone inquiries may be made by calling:
(972) 423-0090
Contents
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . xix
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . xxi
iii
Contents
Chapter 3 Variation . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Variable Playback/Variations . . . . . . . . . . . . . . . . . 41
Auditioning Variations in DirectMusic Producer . . . . . . . 44
Variation Shortcuts . . . . . . . . . . . . . . . . . . . . . . 45
Specifying Variation Behavior Settings . . . . . . . . . . . . . 46
Variation Switch Points . . . . . . . . . . . . . . . . . . . . 50
Influencing Variation Choices . . . . . . . . . . . . . . . . . 51
Tips and Further Options for Variability . . . . . . . . . . . . 53
iv
Contents
v
Contents
ClearCache() . . . . . . . . . . . . . . . . . . . . . . . 157
ReleaseObject()/ReleaseObjectByUnknown() . . . . . . . . 158
Garbage Collection . . . . . . . . . . . . . . . . . . . . . 158
LoaderView Sample Application . . . . . . . . . . . . . . . 159
LoaderView Source . . . . . . . . . . . . . . . . . . . . . 161
Predefined Object Types . . . . . . . . . . . . . . . . . 162
CAudio Class. . . . . . . . . . . . . . . . . . . . . . . 162
Initialization . . . . . . . . . . . . . . . . . . . . . . . 164
SetObjectType() . . . . . . . . . . . . . . . . . . . . . . 165
GetObjectInfo() . . . . . . . . . . . . . . . . . . . . . . 166
ReleaseItem() . . . . . . . . . . . . . . . . . . . . . . . 167
ReleaseAll() . . . . . . . . . . . . . . . . . . . . . . . . 168
ChangeCacheStatus(). . . . . . . . . . . . . . . . . . . 168
ScanDirectory() . . . . . . . . . . . . . . . . . . . . . . 168
Loading from a Resource . . . . . . . . . . . . . . . . . . 169
vi
Contents
vii
Contents
viii
Contents
ix
Contents
x
Contents
Chapter 18 A DirectMusic Case Study for Interactive Music on the Web . . 433
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . 433
Background . . . . . . . . . . . . . . . . . . . . . . . 433
The Wide Sounds DMX Control . . . . . . . . . . . . . . 436
Functionality . . . . . . . . . . . . . . . . . . . . . . . 436
The Web Site . . . . . . . . . . . . . . . . . . . . . . . . 438
The Adaptive Audio Design . . . . . . . . . . . . . . . . . 439
Style and Aesthetics — What Is Right for the Site? . . . . . 439
Form and Function . . . . . . . . . . . . . . . . . . . . 440
Finding the Hooks . . . . . . . . . . . . . . . . . . . 440
Matching Music to Site Content . . . . . . . . . . . . . 441
Thinking as the User . . . . . . . . . . . . . . . . . . 441
Content Creation . . . . . . . . . . . . . . . . . . . . . . 441
DLS Instrumentation . . . . . . . . . . . . . . . . . . . 442
Sample Format and Compression . . . . . . . . . . . . . 442
Instrument Choices . . . . . . . . . . . . . . . . . . . . 443
The Styles and their Patterns. . . . . . . . . . . . . . . . 444
Styles for Each Content Zone . . . . . . . . . . . . . . . 445
Pattern Structure . . . . . . . . . . . . . . . . . . . . . 445
Groove Levels . . . . . . . . . . . . . . . . . . . . . . 446
The Primary Segments . . . . . . . . . . . . . . . . . . 447
Chord Tracks and ChordMaps . . . . . . . . . . . . . . 448
The Secondary Segments . . . . . . . . . . . . . . . . . 450
AudioPaths and Effects . . . . . . . . . . . . . . . . . . 451
Testing the Functionality . . . . . . . . . . . . . . . . . . 452
Scripting . . . . . . . . . . . . . . . . . . . . . . . . . . 453
Creating the Script . . . . . . . . . . . . . . . . . . . . 453
xi
Contents
xii
Contents
Chapter 21 Beyond Games: Bringing DirectMusic into the Living Room . . 501
From “Sound Capture” to “Music Production…” . . . . . . . 502
“…to Nonlinear Music Format” . . . . . . . . . . . . . . . 505
Nonlinear Music Design . . . . . . . . . . . . . . . . . . . 508
Role . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Functionality . . . . . . . . . . . . . . . . . . . . . . . 511
Granularity . . . . . . . . . . . . . . . . . . . . . . . . 512
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . 514
xiii
This page intentionally left blank.
Foreword
xv
Foreword
xvi
Foreword
xvii
This page intentionally left blank.
Acknowledgments
Peter Walsh for inspiring me to produce this book in the first place!
Scott Selfon and Todor and Melissa Fay, for their enormous contribu-
tions to this project. Jason Booth, Scott Morgan, Guy Whitmore,
Bjorn Lynne, Jay Weinland, Marty O’Donnell, Ciaran Walsh, and
Tobin Buttram for lending their amazing insight and authorship to
this book. Jim Hill, Wes Beckwith, Beth Kohler, and the rest of the
team at Wordware for their hard work. Dugan Porter, Scott Snyder,
Shane Kneip, Andrew Barnabas, Mike Verrette, and David Yackley
for advising on this project. The members, sponsors, and directors of
G.A.N.G. for their dedication to excellent interactive audio. Kent and
Kim Quirk for giving me my first title to score. Tommy Tallarico,
Aaron Marks, Laddie Ervin, and Jack Wall for their mentorship and
direction, both personally and professionally. Gary Gottlieb for allow-
ing me to research game audio at UMass. Chuck Schuldiner (R.I.P.),
Claude Debussy, Iron Maiden, BT, and the dozens of brilliant game
composers whose work I’ve enjoyed over the years for their musical
inspiration. Stephen and Kristina Guberski, Kevin Callahan, Rich
Borgatti Jr., the Murphy family, Elizabeth Steffen, Katie Valinch,
Brian Redmond, Greg Capolino, and Ted Regulski for their endless
encouragement. Oh, and Tom Russo for being from Boston.
And to my mother, father, brothers, friends, and family for believ-
ing in all of my crazy ideas (or at least pretending to…)
Cheers!
— Todd M. Fay (aka LAX)
xix
This page intentionally left blank.
Introduction
by Todd M. Fay
DirectMusic is software that allows composers, sound designers, and
audio engineers to create digital musical content with interactive,
variable, and adaptive playback properties. What does that mean?
Moreover, who cares? Chances are that Joe Musician is not terribly
familiar with the terms “interactive,” “variable,” and “adaptive” as
they relate to audio production — and for good reason; musical
pieces featuring these properties have yet to enter the musical main-
stream. Well, we are here to change all of that…
xxi
Introduction
xxii
Introduction
xxiii
Introduction
xxiv
Introduction
xxv
Introduction
xxvi
Unit I
Producing Audio
with DirectMusic
1
This page intentionally left blank.
Chapter 1
DirectMusic Concepts
Interactivity
“Interactivity” is both the most overused and misused term in game
audio. Often, when someone speaks of interactive audio, he is refer-
ring either to adaptive audio (audio that changes characteristics, such
as pitch, tempo, instrumentation, etc., in reaction to changes in game
conditions) or to the art and science of game audio in general. A
better definition for interactive audio is any sound event resulting
from action taken by the audience (or a player in the case of a game
or a surfer in the case of a web page, etc.). When a game player
presses the “fire” button, his weapon sounds off. The sound of the
gun firing is interactive. If a player rings a doorbell in a game world,
the ringing of the bell is also an interactive sound. If someone rolls
3
4 n Chapter 1
the mouse over an object on a web site and triggers a sound, that
sound is interactive. Basically, any sound event, whether it be a
one-off, a musical event, or even an ambient pad change, if it comes
into play because of something the audience does (directly or indi-
rectly), it is classified as interactive audio.
Variability
An interesting side effect of the performance of recorded music pro-
duction is the absence of variation in playback. Songs on a CD play
back the same every single time. Music written to take advantage of
DirectMusic’s variability properties can be different every time it
plays. Variation is particularly useful in producing music for games,
since there is often a little bit of music that needs to stretch over
many hours of gameplay. Chapter 21 discusses applications of varia-
tion in music production outside the realm of games.
The imperfection of the living, breathing musician creates the
human element of live musical performances. Humans are incapable
of reproducing a musical performance with 100 percent fidelity.
Therefore, every time you hear a band play your favorite song, no
matter how much they practice, it differs from the last time they
played it live, however subtle the differences. Repetition is per-
ceived as being something unnatural (not necessarily undesirable,
but unnatural nonetheless) and is easily detectable by the human ear.
Variability also plays a role in song form and improvisation. Again,
when a song is committed to a recording, it has the same form and
solos every time someone plays that recording. However, when per-
formed by musicians at a live venue, they may choose to alter the
form or improvise in a way that is different from that used in the
recording.
DirectMusic allows a composer to inject variability into a pre-
pared piece of digital audio, whether a violin concerto or an audio
design modeled to mimic the sounds of a South American rain forest.
Composers and sound designers can introduce variability on differ-
ent levels, from the instrument level (altering characteristics such as
velocity, timbre, pitch, etc.) to the song level (manipulating overall
instrumentation choices, musical style choices, song form, etc.).
Using DirectMusic’s power of variability, composers can create
DirectMusic Concepts n 5
Unit
allowing their music to reinvent itself upon every listening session.
Avoidance of audio content repetition in games is often impor-
tant. When asked about music for games, someone once said, “At no
time in history have so few notes been heard so many times.” Repe-
tition is arguably the single biggest deterrent to the enjoyment of
audio (both sound effects and music) in games. Unlike traditional lin-
ear media like film, there are typically no set times or durations for
specific game events. A “scene” might take five minutes in one
instance and hours in another. Furthermore, there is no guarantee
that particular events will occur in a specific order, will not repeat, or
will not be skipped entirely. Coupled with the modest storage space
(also known as the “footprint”) budgeted for audio on the media and
in memory, this leaves the audio producer in a bit of a quandary. For
the issue of underscore, a game title with hours of gameplay might
only be budgeted for a few minutes worth of linear music. The audio
director must develop creative ways to keep this music fresh —
alternative versions, version branching, and so on. Audio program-
mers can investigate and implement these solutions using
DirectMusic.
As to events triggering ambience, dialog, and specific sound
effects, these may repeat numerous times, adding the challenge of
avoiding the kind of obvious repetition that can spoil a game’s real-
ism for the player. As we discuss in more detail, DirectMusic
provides numerous methods for helping to avoid repetition. On the
most basic level, audio programmers can specify variations in pitch
and multiple versions of wave, note, and controller data. Even game
content (specific scripted events in the game for instance) can spec-
ify orderings for playback (such as shuffling, no repeats, and so on)
that DirectMusic tracks as the game progresses. Using advanced
features, chord progressions can maintain numerous potential pro-
gression paths, allowing a limited amount of source material to
remain fresh even longer.
6 n Chapter 1
Adaptability
Adaptive audio is audio that changes according to the state of its
playback environment. In many ways, it is like interactive audio in
that it responds to a particular event. The difference is that instead
of responding to feedback from the listener/player, the audio changes
according to changes occurring within the game or playback environ-
ment. Say for instance that a game’s musical score shifts keys with
the rising and setting of the sun within the game world. The player
isn’t causing the sun to set; the game is. Therefore, the score
“adapts” to changes happening within the game’s world. A famous
example of adaptive audio in games occurs in Tetris. The music plays
at a specified tempo, but that tempo will double if the player is in
danger of losing the game.
Avoiding repetition is an excellent first step in a strong audio
implementation for games, as well as reintroducing music listeners
to some potentially intriguing performance characteristics lost when
listening to linear music. Continuing to focus on audio for games for
a moment, audio content triggered out of context to game events is
in many ways less desirable than simple repetition. For instance,
using data compression and/or high-capacity media, a composer
might be able to create hours and hours of underscore, but if this lin-
ear music is played with no regard to the state of the game or the
current events in the game’s plot, it could be misleading or distract-
ing to the user. Adaptive audio (audio that changes according to
changes in certain conditions) provides an important first step for
creating interactivity in underscore.
Do not confuse adaptive audio with variability — if, for instance,
20 variations of a gunshot are created and a single one is randomly
chosen when the player fires a gun, that is variable but not necessar-
ily adaptive. If the gunshot’s reverberation characteristics modulate
as the player navigates various and differing environments, then we
have adaptive sound design. Likewise, a game’s score could have
infinite variability, never playing the same way twice, but it becomes
adaptive when the sultry slow jazz tune fades in over the rock theme
during the player’s introduction to the game’s film noir femme fatale
antagonist. Now contrast variability and adaptation with interactivity;
a character that stops talking when shot by the player is an example
of interactive audio.
DirectMusic Concepts n 7
Unit
shooter, for instance. In such a style of game, where the breakneck
pace of the action constantly modulates the game state, the user can
begin to notice the interactivity of the score. This is dangerous, as
the subtlety of the score is lost on the player, potentially damaging
the designer’s carefully crafted game experience. In a game like
Halo, you do not want the player performing music via an exploit in
the interactive music engine. For this reason, musical changes are
often most satisfying when “immediate” changes are reserved for
important game events and more subtle interactivity is used for
other events and situations.
Groove Levels
One of the more powerful ways that DirectMusic exposes adaptive
audio is with the groove level. Groove level is a musical or emotional
intensity setting that adjusts in real time according to rules you
define as part of your playback environment. You can set up different
moods within a DirectMusic piece and assign those moods to differ-
ent groove levels. For instance, someone could produce a
progressive trance piece with sparse musical activity on groove level
1 and increase the intensity of the music through various groove lev-
els. This can be achieved by adding more notes and instruments, or
by setting higher volumes. You can then assign the different inten-
sity (groove) levels to trigger upon changes in the playback
environment. Say you create a DirectMusic piece for stand-alone
music listening; you could set a rule that switches groove levels
according to the time of day or even the number of windows the lis-
tener has open on the desktop. The possibilities are truly endless.
We discuss groove levels in much more detail in the section on
Style-based Segments in Chapter 4.
8 n Chapter 1
Content-Driven Audio
Adaptive audio becomes really interesting when the audio gets
behind the wheel and drives the playback environment to behave
and/or change in a particular manner. This is commonly referred to
as content-driven audio. DirectMusic’s sequencer provides notifica-
tions that can be used to manipulate the playback environment,
allowing for the interesting possibility of music driving gameplay
rather than the opposite. For instance, a monster around the corner
could wait to jump out at you until an appropriate moment in the
score. Content-driven audio is a particularly unexplored area of
DirectMusic, one that could be put to some interesting uses.
Playback Standardization
DirectMusic can play wave files. The great thing about wave files is
that they can be CD quality, and (speaker quality not withstanding)
they sound the same when played back on any computer. The prob-
lem with wave files is that they are big when compared to MIDI files,
and they often limit adaptability and variation. A partial solution is to
create a custom sample bank that is distributed with the MIDI
sequence data in your DirectMusic file. While in most cases you are
still forced to use minimal file sizes (restricting quality), you don’t
have to worry about different listeners hearing your sequenced
music through different synthesizers/samplers; you’ve given them
the sample bank that you created as part of the DirectMusic file.
Before this standardization took place, you could listen to a MIDI file
of Beethoven’s Fifth through your SoundBlaster, while we listened
to it through the sample set on our Turtle Beach sound card. The
result: We both would have heard very different renditions. This is
an audio producer’s nightmare, since you have no control over the
instruments on which your sequence plays. Luckily, this is no longer
a problem, thanks to DLS-2.
DLS-2 (the Downloadable Sounds format, level 2) is a sound for-
mat used for creating and storing samples, much like the popular
Akai sample format. DirectMusic can use DLS-2 samples. The great
DirectMusic Concepts n 9
Unit
(as opposed to relying on their sound card or soft synth rendering
the sequence data). DLS also specifies basic synthesis abilities —
specification of which waves comprise an Instrument and how to
modify the source wave data according to MIDI parameters like
pitch bend, modulation, and so on. DirectMusic, or more specifically
the Microsoft Software Synthesizer that DirectMusic uses, supports
DLS-2, which adds numerous additional features, including six stage
envelopes for volume and pitch, two low-frequency oscillators, and
MIDI-controllable filtering per voice. When using DirectMusic,
these sample-based Instruments are stored in DLS Collections,
which can be self-contained files (generally using the .dls file exten-
sion) or embedded directly within pieces of music, similar to
traditional MOD files and the newer .rmid extension to standard
MIDI files, where sampled Instruments can be embedded within the
same file as the note information.
3D Spatialized Audio
Our world is one of three dimensions. Most audio that you’ve heard
played over the radio or television exists in one or two dimensions.
Even surround sound lacks a vertical component (sound coming
from above or below). Audio engineers have developed a technique
that synthesizes the way we hear sound in 3D space. DirectX Audio
has this functionality. This means that a sound can be mixed in space
not only to sound closer, farther away, or to the left or right but also
from above, below, and even behind us, all using just two speakers!
We do not go into detail here on how or why DX Audio is able to do
this. Just know that you have the ability to mix sound in 3D using
DirectX Audio.
10 n Chapter 1
Unit
there are now additional assets to manage and track. Consider
playing sound effects, ambience, and other audio cues that do not
respond to tempo on one performance (with constant tempo),
while music-oriented sounds will be played on another perfor-
mance (with variable tempo).
Crossfades
Programmers and audio producers should note that crossfades are
not a built-in DirectMusic function for transitioning between Seg-
ments. For now, it is a fact, and so you are going to have to fudge
them. It’s not all bad, as you do have options. These options include
AudioPath volume manipulation, MIDI volume controller use, or
authoring volume crossfades directly into content. Remember that
only one primary Segment can play at a time, which precludes the
possibility of using primary Segments exclusively for crossfades.
Unless multiple DirectMusic Performances are used, at least one of
the Segments we are crossfading between will need to be a second-
ary Segment. For ease of use, we suggest that both be secondary
Segments. The one-tempo limitation we discussed above makes
crossfades between two pieces of music with different tempos diffi-
cult. One of the pieces will be played faster or slower (unless
multiple DirectMusic Performances are used). Of course, this limita-
tion really only applies for sequenced music; prerendered wave files
using different tempos play fine.
Pause/Resume
“Pause” is another feature not implicitly built into DirectMusic.
However, a programmer can track the time when a Segment starts,
as well as its offset when he stops that Segment. Using this informa-
tion (and keeping in mind that the Segment may have been looped),
the programmer can typically restart a Segment from the same loca-
tion to create “resume” behavior. However, this only works for
Segments that use music time. Note that content that includes
numerous tempo changes may be less accurate in restart position
than content with a single tempo.
While this works for waves, MIDI and DLS limitations do not
allow this for sequenced note data. The MIDI standard for note infor-
mation consists of a “note on” and corresponding “note off” event.
Starting playback between the two events will not trigger the note.
Even if it did, remember that DLS Instruments cannot be started
from an offset; they can only be played from the beginning. This
won’t really be an issue for short notes in a dense musical passage.
However, if you have long sustained notes (for instance, a string
pad), keep this in mind if you want to be able to pause/resume your
DirectMusic Concepts n 13
Unit
Memory Footprint
Memory is typically the most precious resource for games, whether
on the PC or a console. The amount of memory taken up by the
resources of a program or a particular component of a program is
called the footprint. Making use of streaming wave data can help
keep the memory footprint small; only a small amount of any playing
wave is actually in memory at a time. DirectMusic supports wave
streaming within Wave Tracks, with content-specified read-ahead
settings that determine how much of the wave is in memory. How-
ever, the DLS format does not support streaming, so any DLS
Collection instruments play entirely from memory. There are several
optimizations available that we will discuss in Chapter 2 when we
cover Bands (a DirectMusic file that stores DLS Instruments), but
keep in mind that DLS Instruments will occupy a footprint as long as
they are downloaded.
Again, this isn’t a big deal if you are creating music for stand-
alone listening. You’ll have the free range of the system memory to
work with for samples (normally 128MB of memory — more mem-
ory than any pro hardware synthesizer on the 2003 market) on top of
all the streaming that the audience’s PC can handle. But in games,
you are very limited in the amount of memory you can use. Just keep
that in mind.
Unit
PC AudioPaths can include DirectX Media Objects (DMOs), which
are software audio processing effects. AudioPaths also define the
behavior of content being played onto them, such as whether they
can be positioned in 3D space (kind of like surround sound but differ-
ent — more on this later), whether they should be played as stereo
or mono, and so on. As far as MIDI is concerned, each AudioPath
gets its own unique set of “physical” pchannels (granted, this is all in
software, so we’re not talking about tangible channels). For instance,
if two sequences are authored, both using pchannels 1 through 16,
playing them on separate AudioPaths will keep the two from inter-
acting. If one Segment on AudioPath A has a piano on pchannel 1 and
another Segment on AudioPath B has a French horn on pchannel 1,
they will both play happily. If we tried to play these two Segments
onto a single AudioPath, one of the patch changes would win out, and
one Segment would be playing the wrong instruments.
That said, sometimes playing multiple pieces of content onto the
same AudioPath is desirable. For instance, a Segment could be
authored simply to alter the mix of music played by another Segment
(using volume controller curves). Alternatively, in the example of 3D
audio above, we probably would want to play everything that will
emanate from this single position onto the same AudioPath.
Therefore, we have a solution for multiple simultaneous pieces
of music. But what about MIDI controller curve interaction? Of
course, if the pchannels are kept unique (either at authoring time or
by playing the content onto separate AudioPaths), the MIDI control-
lers won’t conflict. But what about the above example of a Segment
where we just want to alter the mix? If the Segment it’s altering has
its own volume controller curves, the two will conflict, and we might
get the volume jumping rapidly between the two. The classic solu-
tion is to use volume curves in one Segment and expression curves
in the other Segment. This is a common approach in sequenced
music, as both affect perceived volume and apply additively. This way
the audio producer sets Volume (MIDI Continuous Controller #7) in
the primary piece of music, and uses Expression (MIDI Continuous
16 n Chapter 1
Linear Playback
þ Note At this point, if you have not already, you must install DirectX
9.0 as well as DirectMusic Producer, which are available on the
companion CD.
Unit
moved, bit by bit, through the buffer and read by DirectMusic. The
CD player in your PC uses streaming for playback. If it weren’t for
streaming, you’d have to load the entire music file into your com-
puter’s RAM, which in most cases simply isn’t an option.
DirectMusic uses the following rule for streaming: The wave
streams with 500 msec readahead if it is more than five seconds long. If
it is shorter than five seconds, it resides and plays from memory (RAM).
Readahead determines the size of the buffer. For 500 msec, our 44.1
kHz 16-bit stereo wave file will use 88.2KB of memory (.5 sec x
44100 samples/sec x 2 bytes/sample x 2 channels), a big difference
when compared to 10MB! Memory usage is reduced by a factor of
more than 100! You can override this behavior, choosing to load all
wave data to memory, or specify a different readahead value in the
Compression/Streaming tab of a wave’s property page in
DirectMusic Producer. To get to any Track’s property page, simply
right-click on it in the Track window.
the same pchannel without any problem. The reason a Wave Track
part can be assigned to a pchannel is that MIDI controllers can still
be used to drive wave playback (for instance, pan on a mono wave,
pitch bend, volume, etc.). The pchannel that a Wave Track is
assigned to can be altered in the wave part’s property page.
This brings us to the concept of layers, or the various lettered
rows (a, b, c, etc.) that you see displayed in DirectMusic Producer
for a single Wave Track part. As mentioned, you can play as many
waves at one time as you wish. Therefore, layers are purely an aid
for placing waves against the Segment timeline, rather than having
many waves overlap on a tiny area of the screen and not be able to
easily edit them (or tell which wave started and finished when).
Waves can be placed on different layers within a part for easier legi-
bility. All layers are equal, are played simultaneously, and do not take
any additional resources (processor power or memory) when played.
As a last bit of terminology for now, certain parts are actually subdi-
vided even further into strips. In particular, the parts for MIDI-
supporting Tracks (Pattern Tracks and Sequence Tracks) have
separate strips for note information and continuous controller infor-
mation. Pattern Tracks also have an additional variation switch point
strip, which we cover in Chapter 3 when we discuss variations.
Linear Playback n 21
Unit
ple. In DirectMusic Producer, follow File>Import File into
Project>MIDI File as Segment… . This creates a DirectMusic
Segment from a MIDI file, importing note, MIDI controller, and
other pertinent data along the way. Let’s examine some new Track
types related to MIDI:
n Tempo Track: Specifies the current tempo for the piece of
music. You can override this by playing a primary or controlling
secondary Segment with its own tempo settings.
n Time Signature Track: Sets the time signature for the piece.
Use this to track where measures fall as well as how many subdi-
visions (grids) to give each beat.
n Chord Track: Specifies the key as well as specific chord pro-
gressions for a piece of music. Typically, for an imported MIDI
file, this will just indicate a C major chord. DirectMusic Producer
does not attempt to analyze the imported MIDI file for chordal
information.
n Sequence Track: A sequence is what its name implies.
Sequence Tracks house MIDI sequences. This is where the
majority of MIDI information is imported. Notice that as with
Wave Tracks, Sequence Tracks can (and typically do) consist of
multiple parts. By default, each MIDI channel is brought in as a
separate part on the corresponding pchannel. In addition, each
part can contain its own continuous controller information.
Unlike Pattern Tracks (more on these in Chapter 3), Sequence
Tracks are linear, non-variable sequences; they play the same
every time, and they do not respond to chord changes (though
they do respond to tempo changes).
22 n Chapter 2
Unit
lection, such as the gm.dls collection that comes with Windows
machines. Otherwise, it will be trying to play instruments that don’t
exist on the end user’s machine, and these instruments would be
played silently. If we wanted to use our own instruments, we would
want to build one or more DLS Collections. DirectMusic Producer
provides this authoring ability in DLS Designer. Alternatively, the
DLS-2 format is a widespread standard, and there are several tools
out there for converting from other common wavetable synthesizer
formats to the DLS format.
Creating DLS Collections is often one of the more challenging
tasks when you decide to create real-time rendered (versus
streamed prerendered) audio. Remember that unlike streamed audio,
your DLS Collection will occupy system memory (RAM), which is
typically one of the most precious commodities for an application.
For this reason, you’ll want to create collections that get you the
most bang for your buck in terms of memory versus instrument
range and quality.
Let’s create our first DLS Collection. From the File menu, select
New, and from the dialog that appears, choose DLS Collection and
hit OK. We’re presented with a project tree entry for our DLS Col-
lection (using the design-time .dlp file extension), which has two
subfolders, Instruments and Waves.
Unit
Figure 2-6: The Wave Editor. You can specify whether waveform selections
(made by clicking and dragging) should snap to zero crossings via Snap To
Zero, and you can specify a loop point by selecting Set Loop From Selection.
You’ll notice that each instrument is assigned its own instrument ID,
a unique combination of three values (MSB, LSB, and Patch) that
allows for more than 2 million instruments — a bit more freeing than
the traditional 128 MIDI patch changes. DirectMusic Producer will
make sure that all loaded DLS Collection instruments use unique
instrument IDs, but you should take care if you author your DLS
Collections in different projects to make sure that they don’t conflict.
Otherwise, if two instruments with identical IDs are loaded at the
same time, DirectMusic will have no way of knowing which one you
want to use. The General MIDI DLS Collection (gm.dls) that is
found on all Windows machines uses 0,0,0 through 0,0,128, so our
new instrument probably defaulted to instrument ID (0,1,0).
Let’s open up the Instrument editor by double-clicking on the
instrument in the project tree.
There are lots of options here that really demonstrate the power of a
DLS-2 synthesizer, but for now let’s just start with our basic instru-
ment. The area above the piano keyboard graphic is where we define
I
which wave or waves should be played for a given note. Each wave
Unit
assigned to a range of notes is called a DLS region. As you can see,
our new instrument defaults to a single region, which plays the wave
BritePiano_C5, over the entire range of MIDI notes. The piano key-
board note with a black dot on it (C5) indicates the root note for the
region. Remember that the root note will play the wave as it was
authored, notes above the root note will pitch the wave up, and notes
below the root note will pitch the wave down.
Looking along the left edge of our region, you can see the labels
DLS1, 1, 2, and so on. These labels identify the various layers of this
instrument. Layers allow you to create overlapping regions, one of
the nice features of the DLS-2 specification. One of the more com-
mon uses is for adding multiple velocity versions of a wave, where as
the note is struck harder, the instrument will switch to a different
wave file. This is particularly effective for percussion instruments.
Notice that each region has a velocity range that can be set in the
Region frame. Multiple layers also mean that a single MIDI note
could trigger a much more complex instrument composed of several
simultaneously played wave files. Remember that regions on a single
layer cannot overlap. The DLS1 layer is the single layer that was
supported in the original DLS-1 specification, and is generally still
used to author any single-layer DLS instruments. For this simple
instrument, we won’t worry about using multiple layers for the
moment.
Our first step is to create regions for the rest of our piano waves.
We want to resize that existing region so there is room on the layer
for the other waves (that, and we probably don’t want to try to play
this wave over the entire range of the MIDI keyboard!). If you move
the mouse over either edge of the region, you’ll see that it turns into
a “resize” icon, and you can then drag in the outer boundaries of the
region.
You can resize the region as long as the mouse is held down and
dragged (or, as before, resize the region by grabbing a side edge of
it). As an alternative to drawing a region with the mouse, you can
right-click and choose Insert Region from the menu that appears.
Every region that we create defaults to use the same wave that
the first region did. So we’ll want to choose a more appropriate wave
from the drop-down aptly labeled Wave. Let’s assign BritePiano_E5
to this new region we’ve created.
We repeat the process for the rest of our waves to build up our
instrument. Notice as you click on each region that you can view its
particular properties for wave, range, root note, etc., in the Region
frame.
There are several potential issues to discuss here. First, should all
instruments span the entire keyboard? On the plus side, the instru-
ment would make a sound no matter what note was played. On the
minus side, when the source wave is extremely repitched (as
Linear Playback n 29
Unit
acceptable range for their instruments. If it does, the music can be
adjusted (or the DLS instrument expanded to include additional
regions).
A second question is how far above and/or below the root note a
region should span. That is, does the wave maintain the quality of
the source instrument more as it is pitched up or down? This can
vary significantly from wave to wave (and the aesthetic opinions of
composers also differ quite a bit). For the above example, we did a bit
of both — the bottom three regions encircle notes both above and
below the root note, while the highest piano region (our BritePiano_
E5 wave) extends upward from the root note. How far you can
“stretch” a note can vary quite significantly based on the kind of
instrument and the source waves.
Once you’ve created your instrument, you can try auditioning it
in several ways. An attached MIDI keyboard will allow you to trigger
notes (and indeed, small red dots will pop up on the keyboard graphic
as notes are played). You can also click on the “notes” of the key-
board graphic to trigger the note. The Audition Options menu allows
you to set the velocity of this mouse click and choose whether to
audition all layers or only the selected one. (In the case of our exam-
ple, we only have one layer, so the two options don’t matter.)
Try out the piano instrument, paying particular attention to the tran-
sitions between regions. These are often difficult to match smoothly,
as the pitch shifting of the waves begins to alter their characteristics
and the instruments can begin to develop musical “breaks” where
the tone quality changes significantly. If you are writing a purely
tonal piece of music, it is often useful to author your DLS Collections
such that the tonic and dominant tones (the scale root and the 5th
degree) are the root notes of your regions. That way, the “best” ver-
sion of each wave (not repitched) will be played for what are typically
the most commonly heard tones in the music.
30 n Chapter 2
just set up the instrument-wide articulation for this piano. Since our
original sample already has most of the volume aspects of the enve-
lope built in, we’ll just add a release envelope. This means that when
I
a MIDI note ends, the instrument will fade out over a period of time
Unit
rather than cutting off immediately, much like an actual piano. By
dragging the blue envelope point to the left in the Instrument Articu-
lation frame, we have set the release to be .450 seconds, so this
instrument’s regions will fade out over roughly a half-second when a
MIDI note ends.
Figure 2-12: To add any per-region articulations (presumably different from the
global instrument articulation), right-click on the region and choose Insert
Articulation List.
Unit
Figure 2-14: Making our piano’s right channel
wave play in sync with the left channel and on
the right speaker. Both channels would be set
to the same Phase Group ID, and one of them
should have the Master check box checked.
played onto the channel (“Oct” is for octaves and “Int” is for inter-
vals within an octave). PB Range lets you control how far the wave
will be bent by Pitch Bend MIDI controllers.
I
Range is a somewhat interesting option. It instructs DirectMusic
Unit
that we will only play notes within a certain range, and therefore that
we only need the regions from this instrument that are in that range.
While this can cut down on the number of DLS instrument regions
that are in memory (and thus possibly the size of wave data in mem-
ory), it does mean that notes played outside of this range will fall
silent. Because transposition, chord usage, or other interactive
music features might cause our content to play in a wider-than-
authored range, Range is generally not used (and thus left
unchecked).
Getting back to the task at hand, this channel is currently using
General MIDI patch 89 (remember that the other two aspects of an
instrument ID, the MSB and LSB in the DLS Collection, are typi-
cally zero for General MIDI collections). We want it to instead play
our new instrument, so we click on the button displaying the name
of the current instrument, choose Other DLS… from the drop-
down that appears (the other options are all of the instruments from
gm.dls), and select our DLS Collection and instrument to use.
If we now play our Segment, Performance channel one will use our
newly created piano instrument.
36 n Chapter 2
Transitions
Now that we’ve got basic linear pieces of music, let’s discuss moving
from one piece of linear music to another. How do we move from one
piece of music (the “source”) to the next (the “destination”)?
DirectMusic provides an abundance of choices in how such transi-
tions occur. You can specify a Segment’s default behavior in its
property page (right-click on the Segment) on the Boundary tab. On
the positive side, using these settings means you don’t need to tell a
programmer how your musical components should transition
between each other. On the negative side, this locks a Segment into
one specific transition behavior, which is often not desired; it’s quite
common to use different transition types based on both the Segment
that we’re coming from and the Segment that we’re going to. Over-
riding can be a chore, particularly when using DirectX Audio
Scripting. More often than not, audio producers end up having to
communicate their ideas for transitions directly to the project’s
Linear Playback n 37
Unit
fairly straightforward — Immediate, Grid (regarding the tempo grid;
remember that the size of a grid can be specified in the Time Signa-
ture Track), Beat, and Measure. End of Segment and End of
Segment Queue are also fairly easily understood — the former start-
ing this Segment when the previous one ends and the latter starting
this Segment after any other Segments that were queued up to play
when the current Segment ended.
Marker transitions introduce us to another Track type that we
can add to an existing Segment, aptly named the Marker Track. After
adding a Marker Track to a Segment, an audio producer can add
markers (also known as exit switch points) wherever it is deemed
appropriate in the Segment, which will postpone any requested tran-
sition until a specific “legal” point is reached. Just to clarify, the
transition is specified on the Segment that we’re going to, even
though the boundary used will be based on the currently playing
Segment that we’re coming from.
Transitions do not have to occur directly from one piece of music
to another. The content author can create specific (typically short)
pieces of music that make the transition smoother, often “ramping
down” or “ramping up” the emotional intensity, altering the tempo,
or blending elements of the two Segments. A programmer or
scripter can then specify a transition Segment be used in conjunction
with any of the boundaries discussed above.
As a more advanced behavior, when using Style-based playback,
the programmer or scripter can specify particular patterns from the
source and/or destination Segments (for instance, End and Intro,
which plays an ending pattern from the source Segment’s Style and
an intro pattern from the destination Segment’s Style) to similarly
make the transition more convincing. We cover these embellishment
patterns more in depth when we get into Style-based playback in
Chapter 4.
By default, transitions go from the next legal boundary in the
source Segment to the beginning of the destination Segment. There
are, of course, cases where we might want the destination Segment
to start from a position other than the beginning. Most commonly,
38 n Chapter 2
we could intend for the destination Segment to start playing from the
same relative position, picking up at the same measure and beat as
the source Segment that it is replacing.
For this kind of transition, alignment can be used, again in con-
junction with the various boundaries and transition Segment options
above. Align to Segment does exactly what we outlined above. As an
example, if the source Segment is at bar ten beat two, the destina-
tion Segment will start playing at its own bar ten beat two. Align to
Barline looks only at the beat in the source Segment and can be used
to help maintain downbeat relationships. Using the same example as
above, the destination Segment would start in its first bar but at beat
two to align to barline with the source Segment. As another tech-
nique for starting a Segment at a position other than its beginning,
enter switch points (specified in the “lower half” of Marker Tracks)
can be used. In this case, whenever a transition is intended to occur,
the destination Segment will actually force the source Segment to
keep playing until an appropriate enter switch point is reached in the
destination Segment. Alignment generally provides sufficiently satis-
factory transitions with less confusion, so enter switch points are
rarely used. Remember in both cases that the limitations outlined
earlier for pause/resume apply here; starting a Segment from a posi-
tion other than the beginning will not pick up any already sustained
MIDI notes, though it will pick up waves in Wave Tracks, as well as
subsequent MIDI notes.
Transition types can be auditioned within DirectMusic Producer
by using the Transition (A|B) button.
By right-clicking on the button, you can set all of the above options
(and then some!).
Linear Playback n 39
Unit
Figure 2-20: The Transition Options window
grants access to various transition settings.
Then while playing back a Segment, click the Segment you want to
be your destination, and then click the Transition button (or Alt+”,
its keyboard shortcut). The transition will occur as it would in an
application using DirectMusic.
Figure 2-22: Highlight the Segment to which you want to transition, and
press the Transition button.
Chapter 3
Variation
Variable Playback/Variations
Content variability can be achieved on several different levels. On a
“microscopic” level, content can specify ranges for volume and pitch
on individual waves played in Wave Tracks, as well as randomized
volume, start time, and duration on notes in Pattern Tracks.
41
42 n Chapter 3
Unit
indicates that it has some information in it.
The main thing to remember about this aspect of the buttons is that
it is solely for editing and auditioning purposes in DirectMusic Pro-
ducer — whether a button is pressed or not has no bearing on how
the variations will behave in a DirectMusic player or in a game at run
time. Pressing buttons provides for two purposes:
n Editing one or more variations simultaneously. Pressing a
single variation button means that you are now editing only that
variation. Paste from the clipboard, add a note, or drag a wave
into the part, and that data is assigned solely to that variation. If
you select multiple variations, notes and waves added will be
identically added to each of the selected variations.
n Auditioning variations within DirectMusic Producer. Let’s
say you created a four-part piece of music, and each part had ten
enabled variations. You listen to it a few times and notice that
there’s an incorrect note in one variation of the bass part. Rather
than forcing you to keep repeatedly playing the Segment until
that variation gets picked again, DirectMusic Producer has an
audition mode, where the same variation (whichever one is
“pressed”) will be chosen every time the Segment is played.
Only one part can be in audition mode at a time. To select a part
for audition mode, just click anywhere on it. The part that is in
audition mode is highlighted in a slight yellow shade.
Variation n 45
Unit
Figure 3-7: Part three (piano) is in audition mode.
Within DirectMusic Producer, all other parts will
randomly choose which variation to play from the
enabled variation buttons. Part three will always
play whatever variation button is pressed (until it is
no longer in audition mode).
Variation Shortcuts
There are a few shortcuts provided for quick selection of which par-
ticular variations you are editing. The vertical bar to the left of the
32 variation buttons selects (and with a second click deselects) all
enabled variations. Double-clicking on a variation will “solo” it (only
46 n Chapter 3
Unit
Figure 3-9: Selecting per-part variation behavior
for a Wave Track part.
is picked up. This is where the Reset Variation Order on Play option
(available in The Wave Track and Pattern Track property pages) can
be used to control whether DirectMusic “remembers” which varia-
tions were previously played the next time the Segment starts. If,
for instance, a Segment was set at In Order from First and its Pat-
tern Track had Reset Variation Order on Play checked, every time
the Segment was played, it would start from variation 1. If it looped,
it would then play variation 2, 3, and so on.
There are, of course, instances where the audio producer would pre-
fer for two or more parts to always pick their variations identically.
For instance, in a DirectMusic arrangement of a jazz trio, there
might be several “solos” for each of the instruments (parts). We do
not want two instruments to “compete” by playing their solos at the
same time. For these kinds of situations, Lock ID functionality pro-
vides a way to control how different parts choose their variations.
Every part that has the same Lock ID will choose its variation to be
the same. Therefore, for the above scenario, if each part has three
solos to choose from, we could place the drum part’s solos on varia-
tions 1-3, the guitar’s solos on variations 4-6, and the piano’s solos
on variations 7-9, for example. For the variations where the parts do
not have solos, they would either “lay out” or just keep time. When
the Segment was played, each of the parts would choose its varia-
tions in step with the other parts, so only one solo would be played
at a time.
Variation n 49
Unit
Figure 3-11: This Wave Track part’s Lock ID is set
to 1. Any other Wave Track parts in the Segment
that are set to Lock ID 1 will choose the same
variation as this part.
Note that Lock ID does respond to the other settings that we’ve dis-
cussed that control how variations are chosen; if one locked part has
variation 10 disabled, that variation will never be chosen for that
entire Lock ID group. Similarly, if one part is set to play its variations
in order, all of the parts will follow that part’s lead. Mixing and
matching between various part variation settings (for instance, one
part set to shuffle, another part set to play in order) within a Lock ID
group can lead to somewhat unpredictable variation choices, so it’s
generally recommended that all parts in a Lock ID group use the
same settings.
Another important concept to remember is that Lock ID is
unique per Track, not per Segment. That is, several parts in a Pat-
tern Track that use the same Lock ID will choose the same variation.
However, a Pattern Track part and a Wave Track part using the same
Lock ID will not lock to each other. A limited subset of this kind of
functionality is available in the Wave Track’s property page with the
Lock Variations to Pattern Track option. Checking this box means
that each pchannel of the Wave Track is now locked to the corre-
sponding pchannel of the Pattern Track; a Wave Track part on
pchannel 1 will always choose the same variation as the Pattern
Track part on pchannel 1 if one is present.
50 n Chapter 3
Unit
Figure 3-13: A variation using variation switch points. This variation can jump to
any other variation at the position of the exit switch point (red blocks), shown as
the dark blocks in the Var Switch Points line. Similarly, this variation can start
playing mid-Segment if any other variation has an exit switch point
corresponding to this variation’s enter switch point (green blocks), the lighter
blocks in the Var Switch Points line.
going to be. If the current chord is leading to the tonic (->I), a domi-
nant chord (->V), or any other chord (->Oth), you can instruct a
variation to never play in one or more of these situations.
I
Unit
Figure 3-15: One possible configuration for a variation.
Interactive and
Adaptive Audio
S o far, we have covered playing back basic linear audio and adding
aspects of variability to that music. The next step is to make the
audio dynamic — that is, allow it to respond to events that the user
or program creates (i.e., interactive and adaptive audio). For
instance, computer games often want to convey a certain emotional
level based on what the current game state is. If the player’s health
is low and many enemies are around, the composer would often like
the music to be more frantic. This general concept of an “intensity
level” is often difficult to communicate to the programmer and even
more challenging to implement convincingly in an application.
55
56 n Chapter 4
In short, for most cases, secondary Segments are much more useful
and convenient than motifs.
So we’re typically going to have a Style with a single Band, no
I
motifs, and several patterns. Our default Style has a single pattern. If
Unit
we double-click it, the Pattern Editor (looking quite similar to the
Segment Designer window) opens. MIDI controller editing is identi-
cal to the behavior in Pattern Tracks in Segments.
There is a new Track at the top of the part called the Chords for
Composition Track. This Track, covered in more detail in Chapter 6,
tells DirectMusic what the original function of the music was so that
DirectMusic can make an educated choice when revoicing the music
to work over a new chord or key. For now, we leave it at the C major
default.
þ Note DirectMusic Style files only support Pattern Tracks and the
Chords for Composition Track and therefore DLS Instruments. In
particular, Styles cannot be used in conjunction with Wave Tracks, so
consider Segment-based branching if you are using prerendered
and/or streamed music.
Unit
son, destination range is rarely used.
The Embellishment area allows you to add even further control
to how a pattern is chosen. Embellishments are generally specific pat-
terns that you can call for without changing groove levels. For
instance, you might be playing a piece of music at groove level 50
and you want to transition to a new piece of music, also at groove
level 50. Rather than temporarily changing the groove level to pick a
convincing ending pattern, you could create a pattern still assigned
to groove level 50 that is an end embellishment. To further strengthen
the flow of the transition, the new Style could have an intro embel-
lishment pattern. Then when the programmer or scripter wants to
start playing the second piece of music, he can say to play ending and
introductory embellishments, and these specially assigned patterns
will be played between the two pieces of music. The naming conven-
tions for embellishments (intro, end, fill, break, and custom) are
actually somewhat arbitrary — from a programmer’s or scripter’s
point of view, there’s no difference between most of them. The
exceptions are that an intro embellishment is always picked from the
Style you are going to, and the end embellishment is always picked
from the Style that you are coming from. Of course, if you’re sticking
with the same Style and just wanted a brief pattern change, even
these have no difference in function. Note that custom embellish-
ment types (with a range from 100-199 to help keep them from
becoming confused with groove ranges) can only be requested pro-
grammatically or within content; DirectX Audio Scripting does not
support them.
The Chord Rhythm option adds yet another layer of control over
how DirectMusic chooses which pattern to play, assuming the Seg-
ment uses chords (in Chord Tracks or via ChordMaps). The audio
producer might have one pattern that is more ideal to a rapidly
changing progression and another that works better under a static
chord. DirectMusic will look for a pattern with a chord change
rhythm that maps closest to the currently playing Segment’s chord
rhythm.
60 n Chapter 4
Building Up Styles
The most basic use for Styles is what we like to call additive instru-
mentation. Each pattern is authored identically to the previous
(lower groove level) one but adds another instrument to the mix.
This can work quite effectively and is relatively easy to implement.
The audio producer creates a low groove level pattern (which could
be silence, basic rhythm, or perhaps just subtle ambience). They
then create additional patterns, each at a higher groove level and
each adding a new instrumental line or additional rhythmic
complexity.
Primarily due to the pattern-choosing rules discussed above, it is
often easiest to create every basic pattern with the same length.
Embellishment patterns of differing lengths can help break up the
Interactive and Adaptive Audio n 61
rhythm somewhat, though all patterns must use the Style’s global
time signature for their playback length. Individual parts in a pattern
can be varying lengths and time signatures to help distract from the
I
feeling of constant time signature.
Unit
By the way, groove level and pattern length can be viewed in a
somewhat easier-to-read interface via the Style Designer window.
The window is opened by double-clicking on the Style itself in the
project tree rather than any of its patterns and displays information
on all of the patterns (their length, groove ranges, embellishments
they are assigned to, and so on).
Figure 4-4: The Style Designer window for a Style with five patterns for varying
groove levels. The highlighted pattern is an ending embellishment (as
indicated by the E in the Embellishment column).
Unit
Figure 4-5: A typical Style-based Segment. Groove level 1 is specified in the
first bar. When the Style was inserted, the other tracks were created and/or
modified automatically.
Unit
nique is to use alignment to Segment transitions. Remember, in this
kind of transition, we jump to the same relative location in the desti-
nation Segment as we were at in the previous Segment. So if we
were at bar four beat two, we jump to bar four beat two of the new
piece of music. In the particular case of adjusting groove levels, both
the source and destination Segment are the same piece of music.
The retriggering is enough to bump us over to a new pattern more
appropriate for the current groove level. But with the use of align-
ment, we transition over in mid-pattern rather than restarting the
Segment. This technique is a bit trickier to manage than the previ-
ous one. Remember that if we’re starting in the middle of a pattern,
any notes that would have started playing prior to that point would
not be retriggered — so for instance, if our destination had some
nice string pads that had started on the downbeat, we wouldn’t hear
them if our transition occurred on beat two. An added complexity
occurs if our patterns had different lengths, as they now may transi-
tion to musically awkward positions in each other. This is another
reason why it’s often easiest to keep all patterns at the same length.
Branching Playback
In the case of branching Segments, we change the Segment we are
playing at a musically appropriate boundary, based on some event
that changes the emotional level we want to convey. In the simplest
implementation, branching just consists of authoring complete
pieces of linear music for each emotional intensity, and then picking
logical places where one piece could jump to the beginning of the
next. Often measures are appropriate boundaries; if not every mea-
sure is, the composer can use markers to dictate which are.
Sometimes the above can result in abrupt and unsatisfying tran-
sitions, particularly if the two pieces of music were not written with
each other in mind. The jolt from playing a slow-moving piece of
music to a pulse-poundingly fast one might be appropriate in some
circumstances, but in others it can distract more than intended. A
potential solution here is to use transition Segments. As we previ-
ously discussed, when you tell a new Segment to start playing, you
can specify a transition Segment that should be played before this
new Segment starts. A common solution to the abruptness issue is
to create quick little “bridges” that ramp up or down the tempo,
smooth out any instrument entrances and exits, and so on. Of
course, this can lead to a bit of extra work on the composer’s part —
you might find yourself writing transition Segments for every possi-
ble configuration (slow to fast, fast to slow, slow piece 1 to slow piece
2, slow piece 2 to slow piece 1, and so on) unless you’re clear as to
what pieces could possibly jump to other pieces. Building up a
roadmap beforehand can aid both in the planning and authoring of
transition Segments.
Interactive and Adaptive Audio n 67
Unit
Figure 4-7: A sample diagram for five pieces of music. Each arrow indicates
that a piece of music will need to be able to transition to another piece of
music. For this scenario, we assume that you have to somehow “engage”
enemies in combat, so you’ll never jump right to the “fighting” music from
the “no enemies” music. Similarly, if you decide to flee the room, you’ll
disengage from the enemies first, so you don’t have to worry about the
“fighting” music jumping directly to the hallway “ambient” music.
Layered Segments
The other effective Segment-based tool for creating dynamic music
is layering. Here we keep playing the same basic piece of music, but
we layer other pieces of music over the top. This involves the use of
secondary Segments. Remember that only one primary Segment can
be playing at a time, and playing a new primary Segment automati-
cally stops any currently playing ones. But you can layer as many
secondary Segments on top of a primary Segment as you like. Don’t
forget that whether a Segment is played as primary or secondary is
in the hands of the programmer or scripter, so the composer needs
to communicate this information beforehand.
A common use of layered secondary Segments is as motific ele-
ments. Indeed, these secondary Segments replace the somewhat
outdated Style-based motifs, which were much less flexible and
required extra effort from the programmer or scripter in order to use
them. As an example, let’s say our character’s love interest enters
the room, and the love theme plays on top of the basic music track.
The use of Chord Tracks (described in more detail in Chapter 6) can
make this all the more effective. The love theme can be played in the
appropriate key, and even repitch itself based on the chord progres-
sion of the underlying music.
As a brief example, let’s take any old Segment we’ve already
authored and add a Chord Track to it if it doesn’t already have one.
(Right-click on Segment > Add Track(s)…, and choose Chord
Track from the list.) Now let’s right-click on the Chord Track (the
leftmost part of the track that says “Chords”) and bring up its prop-
erties. Odds are the track thinks our piece of music is in the key of C
because we haven’t told it otherwise. Mine happens to be in Eb, so I
update the property page like so:
Interactive and Adaptive Audio n 69
Unit
Figure 4-8: Chord Track Properties dialog.
We’d probably also want to adjust that C major 7 chord that appears
in measure 1 by default. Let’s make it an Eb major 7 instead. (I’m not
going to bother entering the full chord progression, but we’ll use this
particular chord in a moment.) We right-click on the CM7, choose
Properties, and modify the property page like so:
Now let’s create our romantic theme. We create a new Segment with
a Pattern Track and a Band Track (making sure that the Band and
pattern pchannels don’t interfere with any from our original piece of
music). As far as the actual music, I’ll just borrow a recognizable
theme from Tchaikovsky’s Romeo and Juliet:
Figure 4-10: Our (well, Tchaikovsky’s) love theme. Note that we’ve authored it in
the key of C (not having a Chord Track implies this) and named the Segment
“LoveTheme.” The Pattern Track part uses performance channel 17 (which can be
adjusted from the property page), and we disabled all variations except our
theme on variation 1. Our band similarly just has an entry for performance
channel 17 telling it what instrument to play.
Unit
play our love theme from the secondary Segment toolbar’s Play but-
ton. The secondary Segment transposes to fit the key of our primary
Segment. Similarly, this theme could be played against any of our lin-
ear compositions in the proper key just by authoring a Chord Track
into those Segments and specifying the proper key and scale in a
chord in the track.
Transposition is one thing, but as a more advanced use, we might
want our theme to actually follow the chord progression of our pri-
mary Segment. Let’s add another chord to our primary Segment and
start off with a dominant-tonic (V-I) progression. We’ll move that Eb
chord to bar two (by dragging or copying and pasting) and insert a Bb
chord in bar 1.
Figure 4-14: The Default Play Mode area determines how our
secondary Segment responds to chord changes. In the default
behavior, chord and scale are used to determine appropriate
notes, but there are also settings to only transpose according
to scale, to remain fixed to the authored notes (useful for drum
tracks), and so on.
Unit
Figure 4-15: Adding a new Chord Track and C major chord to our
secondary Segment.
DirectMusic Producer
W ow! That was a lot of theory to take in. Now we’re ready to dig
into using the program that does it all: DirectMusic Producer.
DirectMusic Producer is a complex program with a steep learning
curve. While this section is not meant to be artistic, it demonstrates
DirectMusic’s basics, as well as the fundamentals required to use the
program in a production environment. We also revisit some basic
ideas key to using DirectMusic, including variation, styles, and chord
changes. Our goal is to create a simple blues that uses DirectMusic’s
recomposition features to follow chord changes.
Getting Started
Insert the CD included with this book into your CD-ROM drive, as
we refer to MIDI files on this CD for the note data in this chapter.
Open DirectMusic Producer and create a new project by selecting
File>New from the menu and selecting a project in the dialog box.
Name your project, and choose a storage location for its data on your
hard drive. Select File>Import File into Project>MIDI File as
Segment from the menu. Navigate to the Unit I\Chapter 5\Tutorial
Part One directory on the CD and select the file Tutorial_Bass_
Melody.mid. The Segment opens automatically in the main screen
of DirectMusic Producer.
75
76 n Chapter 5
This Segment has several tracks of data in it. The first is a Tempo
Track, which controls the speed of playback. The second track is a
Chord Track, which controls how DirectMusic transposes other Seg-
ments over this Segment. The next two tracks are Sequence Tracks
containing note data. The final track is a Band Track, which controls
the instrument patching.
Often, when importing data from sequencing programs, the data
requires some amount of cleanup. In this case, there are several
problems; first, the Segment is two bars long, yet the music in the
Sequence Tracks is only one bar long. To fix this problem, right-click
on the title bar of the window and select Properties.
Click on the button next to Length, and set the number of measures
to one.
DirectMusic Producer n 77
Patterns
I
The music we imported from the MIDI file is incredibly simplistic. It
Unit
is a single bar of a bass line and melody and hardly interesting at
that. However, music often exists as the collective of small patterns
like this one, which play over various chord changes. Our next step
is to make this small piece of music play over a series of chord
changes, using DirectMusic’s transposition functionality to adjust
the notes to match the new chords.
Currently, our music is stored as a Sequence Track in our Seg-
ment. Sequence Tracks do not transpose to follow chords, and play
exactly as the author wrote them. To get our melody and bass line to
transpose over chord changes, we need to use a Pattern Track
instead.
To create a new Pattern Track:
1. Select File>New.
2. Set the number of measures to one.
3. Select Pattern Track from the menu.
4. Select the Segment1.sgp (which was created) from the
menu on the left-hand side.
5. Right-click and select Rename.
6. Rename the Segment to BassSegment.sgp.
We need to copy the data from our old Segment into the new Seg-
ment’s Pattern Track. Select the Tutorial_Bass_Melody.sgt
Segment and maximize track one’s window. This opens the roll edi-
tor, where you can view and edit note data.
78 n Chapter 5
Select the first note in the window and press Ctrl+A to select all
the notes. Press Ctrl+C to copy this data. Open Pattern Track one
in our new Bass pattern. Click on the first beat in the measure, and
press Ctrl+V to paste the data into the track. You may have to scroll
the track up or down to see the note data.
Create a new Segment named Melody.sgt with a Pattern Track,
and repeat this process with track two. At the end of the process,
you need to point the Pattern Track to channel two by right-clicking
on the Pattern Track to bring up the properties dialog box and set
the pchannel to two.
Unit
Figure 5-4: We define the chord and scale reference in the Chord
Properties page. Here we use a C7 chord with a C Mixolydian scale.
On the bottom piano roll, set the chord to C7. Do this by clicking on
the appropriate notes on the piano roll (C2, E2, G2, and Bb2). Now
adjust the scale by changing the B natural to a Bb. Repeat this pro-
cess for the second part as well.
þ Note You can copy the chord from the Chord Track and paste it in
the other Segment’s Chord Track, rather than resetting the notes
manually.
Play the Segment and notice how the notes now transpose to follow
the chord progression.
Unit
inversion of the chord. For the G7 chord, set the second chord to the
second inversion as well (in this case, C, E, G, and B). Your chord
should look like this:
Figure 5-7: Setting up alternate ways for DirectMusic to interpret the G7 chord.
Variations
Now that we have a basic piece of music playing over some chord
changes, we want to add some variation to the playback so that each
time our bass line plays, it plays a little bit differently. Open the
BassSegment.sgt that we created earlier, and open the Pattern
Track. Notice the variation bar. By default, DirectMusic Producer
fills in all of these variations with the same data.
Select only the variation to work on. Notice in the above figure that
we selected all variations because every number in the bar is
depressed. You can quickly select or unselect all variations by click-
ing the vertical bar on the leftmost edge of the variation bar. Click
this bar so that every variation number is not depressed, then select
variation 2. Your variation bar should look like this:
In the pattern, raise the note on the second beat to an E, and lower
the last note to an E. Your pattern should look like this:
DirectMusic Producer n 83
Unit
Figure 5-11: Our first bass line variation.
If we play the project now, we would only hear this variation one out
of 32 times, since DirectMusic fills out our 32 Segments for us by
default. To have the variation play more often, disable all variations
except for variations 1 and 2 by deselecting variation 2 and selecting
variations 3 and higher. Right-click on the Variation tab, and select
Disable. Your Variation tab should now look like this:
Figure 5-12: Variations 1 and 2 are enabled, while all others are disabled.
Play the master Segment, and notice how both our original bass line
and our variation play.
Again, we saved a completed version of this section of the tuto-
rial on the companion CD in the folder Unit I\Chapter 5\Tutorial Part
Two.
84 n Chapter 5
Styles
In the previous two sections of this chapter, we stored our musical
data in Pattern Tracks. Pattern Tracks have a lot of power, as they
can follow chord changes in multiple ways and contain up to 32 varia-
tions. However, even more powerful than patterns are Styles. As
defined in Chapter 4, a Style is a collection of patterns, which
DirectMusic chooses based on various parameters. Each pattern
within a Style acts just like Pattern Tracks, containing chord
mappings and variations. Moreover, because you can place multiple
patterns within a Style, they allow you an unlimited number of varia-
tions. More importantly, they respond to the groove level.
While groove level can be used in many ways, it is easiest to
think of it as an intensity filter. Each pattern within a Style has a
groove range in which it is valid. Our melodic line might be simpler
at groove levels one through ten, while a more active one is used
from 11 to 20. When the Style is choosing the pattern to play, it looks
at the current groove level and determines which patterns are valid
within the Style. A typical game implementation involves tying
groove level to the intensity of the current game situation. If there is
a lot of action going on, the groove level raises and the music inten-
sifies, and when the action dies down, the groove level lowers and
the music calms down.
Unit
Figure 5-13: Our accompaniment pattern.
Play the master Segment, and notice that only pattern two is
used. This is because our groove level is set to 1, which is not a valid
groove level for pattern one to play. Now set the groove level to 6 in
our Groove Track, and play the master Segment. You will notice that
both patterns now play. This is because both patterns are valid, so
DirectMusic will choose between them every measure.
Set the groove level to 11 in our Groove Track. You will notice
that only pattern one plays, because pattern two’s groove range is
too low to play. Save your project.
The wonderful thing about using groove levels to control your
music is that it’s very easy to work with and test on the musical end
and provides a simple mapping for a game to use that doesn’t require
your programmers to understand how your music is composed.
Often, groove level can be tied to something very simple for a game
engine to understand, such as the number of monsters in the area,
and the composer can decide what the music should do in these
cases.
Once again, we saved a completed version of this section of the
tutorial on the companion CD in the folder Unit I\Chapter 5\Tutorial
Part Three.
This chapter offered some insight into the basic mechanics of using
DirectMusic Producer. While DirectMusic Producer can seem daunt-
ing at first, once you become familiar with the interface, you will find
it efficient and well designed. Remember to start small and be pre-
pared to build several projects as a learning experience.
Chapter 6
Chord Tracks
This section demonstrates designing music that utilizes nonvariable
Chord Tracks. Two Segments are created using the same pattern,
but each will have their own chords and scales.
First, create a pattern:
1. Create a Style called SimpleStyle.
87
88 n Chapter 6
changing the label from M to min. The chord type labels have no
effect on playback; they are there for you to customize. If you prefer
another way of labeling chords, feel free to use that.
I
When we edit the notes on the keyboard display in the Chord
Unit
Properties window, we only want to edit the notes in the row labeled
1 on the left-hand side. Later we will go into what the four chord lev-
els are for. We can edit the chord tones and the underlying scale. To
edit the third chord, click the F# in the chord section. This deletes
that chord tone. Click the F key to give us a D minor chord. It is
okay to leave the chord with only two tones, but in this case, we
want D minor. Here is what it should look like when you are finished:
Figure 6-3: Play the Segment to hear the pattern cycle through all the chords.
We are still not done with the G chord. The chord is correct, but
the scale is still not. Notice when you changed the Bb to B natural
that the B natural in the scale side turned blue. This is Direct-
I
Music’s way of warning you that there is a chord tone that does not
Unit
have a matching scale degree. You should always include the chord
tones in your scale, or DirectMusic may get confused about how the
scale tones relate to the chord tones. Click the blue B natural to add
it to the scale. It should turn red like the other tones. Lastly, click
the Bb to remove it. The scale is now C harmonic minor. Copy the
first four bars into the last four, like we did with the major Segment.
Transpose the C minor chord in bar five down to Ab, and make it
major instead of minor. This is the final chord progression:
| Cmin | Fmin| Dmin7b5 | GM | Abmaj | Fmin | Dmin7b5 | GM |
In this section, we used DirectMusic chords to take a simple, one-
measure pattern and created two different chord progressions with
it. This can be a quick way to write music if all that is required is to
move the music through different chord progressions. This section
serves as the foundation for the next section on variable chord
progressions.
ChordMaps
ChordMaps are sets of potential chords or chord paths for chord pro-
gressions to follow. Introducing a ChordMap into a Segment allows
DirectMusic to generate chord progressions on the fly, adding a new
element of variability to DirectMusic songs. DirectMusic pieces the
chord progression together within the boundaries provided by the
content creator’s ChordMap parameters. This section includes
examples of variable chord progressions and instructions on how to
use them.
Create a ChordMap called SignPostsOnly by using Ctrl+N and
selecting ChordMap. If you have seen ChordMaps before, you may
have noticed the charts with lots of little connecting lines going
everywhere. The truth is, you do not need to put anything into that
area of the ChordMap to get a variable chord progression.
The most vital thing in a ChordMap is the signpost. Signposts are
groups of chords from which DirectMusic chooses while generating a
92 n Chapter 6
Unit
Figure 6-4: The signpost list.
You are probably itching to play with all the neat graphs, so let’s do
that next!
You can define different paths for the chords to take when mov-
ing from one signpost to another. Do this by mapping out chords
between the different signpost chords and drawing lines from chord
to chord. You can experiment with the map that we just created by
dragging all the chords from group two to bar one and all the chords
from group three to bar three. Right-click each of the chords in mea-
sure one and make them beginning signpost chords by selecting
Toggle beginning signpost from the pop-up menu that appears.
Beginning signposts are signified by a green arrow. Similarly,
right-click all the chords in measure three and make them all ending
signpost chords. Ending signposts are signified by a red circle. Put
whatever chords you want into bar two, and start drawing lines from
one chord to another. The lines are drawn from the lowest gray box
on the end of the chord. An empty box is always on the bottom since
a new one is added every time you create a new connection path.
Unit
room in the Segment. The area between bars five and seven is the
only place where there is a move from signpost two to signpost
three, and there is a measure open for the connecting chord. If you
want the chord progression generated by the ChordMap to be able to
choose randomly between going directly from signpost to signpost
and using the connecting chords, draw a line that connects the two
signposts directly. Otherwise, DirectMusic will always use the con-
necting chords if there is room. To delete a connection, click on the
line and press the Delete key.
For another example of connecting chords, open ChordMap-
Tutorial.pro on the companion CD. Check out the bookmark
Connecting Chords. The ChordMap only uses one signpost chord
that is placed into groups one and two. The map (see Figure 6-7) is
meant for a Segment that is five measures in length with signpost
one on bar one and signpost two on bar five. The connecting chords
play on bars two, three, and four. The strategy here is that bar five is
never actually played. The Segment is set up so that it loops after
bar four. Bar five is only there to hold the target signpost so the con-
necting chords can be composed. This is a good strategy for looping
Segments that need to transition back to bar one. You can’t compose
connecting chords back to bar one, so you have to make a duplicate
that never plays.
If you look at any pattern in a Style, you will see a Chord Track
along the top. This defines the context of the pattern’s notes. All the
notes in the pattern are compared to the notes in the chord above
I
them. If you change the chord to Gb minor, the notes will be inter-
Unit
preted in the context of Gb minor. You could even write in Lydian b7
and have that interpreted correctly by the DirectMusic engine.
Look at the bookmark ChordForComposition. The chord for
composition for the pattern is E7#11, and the music uses the notes
in that scale and chord. In this context, an A# is a natural four
because A# is the fourth scale degree of the underlying scale of the
chord for composition. If this pattern is played over a C major chord,
the A# will be played as an F, since F is the natural four in C major.
Play the Segment above the pattern to hear the unusual E Lydian b7
scale be converted perfectly to C major, C minor, and C Lydian b7.
The last chord is the same as the chord for composition, E7#11.
Naturally, the output is the same as the notes that were originally
entered into the pattern with that chord.
You are not required to stick with one chord for composition. You
can treat the Chords for Composition Track as a regular Chord Track
and enter as many chords as you want. Whatever notes are in the
pattern below the Chord Track are interpreted in the context of their
corresponding chords. Check out the MultipleChordForComposition
bookmark for an example. The pattern has a C major arpeggio fol-
lowed by a G major arpeggio. The chords for composition are C
major and G major, respectively, but the Segment that plays the pat-
tern has only one C major chord. As a result, the G chord is played as
a C major chord also.
Keep in mind that when you run-time save your files, all chords
for composition information are lost, since it is only for the author’s
benefit. DirectMusic remembers notes in terms of functionality, not
MIDI notes (in most cases; we go over the other cases later). We
have had to recover lost files from the run-time version before and
restore our chords for composition information. It can be scary to
open your project and hear all the work you did with lots of strange
harmonies played in a C major context.
The problem above is easy enough to address. If you right-click
the Chords for Composition Track and change the Keep Constant
option from MIDI Notes (M) to Functionality (F), you can enter in
your original chord for composition and everything will be restored
98 n Chapter 6
immediately. Make sure you change the setting back to MIDI Notes
though. MIDI Notes can ruin a project if you accidentally change the
Chords for Composition Track or pattern key, and all your music
gets moved around inappropriately. If you keep the MIDI Notes set-
ting on, the notes in the pattern will never change visibly. If you
change a chord for composition, DirectMusic reevaluates the notes
in the new context.
If you have Keep Functionality selected and you change a chord
for composition, all the notes will move to keep the same functional-
ity with the new chord that it had with the old chord. For instance, if
you have a C major chord for composition with an E in the pattern,
the note is remembered as the third. If you change the chord for
composition to G major with Keep Functionality selected, the E will
transpose to a B since B is third of G major. If you have MIDI Notes
selected, the note will stay E, but it will now be remembered as a
sixth, since E is a sixth in G.
There is a problem using Pattern Tracks, as opposed to patterns
in a Style. The problem comes from the fact that the Segment’s
Chord Track functions as a Chords for Composition Track for the Pat-
tern Track. This can be very confusing, since the notes will be
shown on screen according to whatever chord is in the track at the
time you open the Segment. If you hit Play and the Segment
recomposes, the notes already displayed do not appear to change.
When you begin editing notes, remember to look at your Chord
Track first. Make sure that you are thinking about that chord. If you
recompose, the context will change again. It is probably better to
turn off recomposing while editing a Pattern Track. Even better, put
the Pattern Track in a separate secondary Segment; that way, you
always know that the chords for composition will stay the same.
PlayModes
The chord for composition sets the context of our pattern, but there
are other features that we can use to direct the DirectMusic engine
on how our patterns should respond to chords. Some of these fea-
tures are playmodes, note properties, chord levels, pattern chord
rhythms, and variation choices. We go through them one by one,
learning what each does and how to use them.
Working with Chord Tracks and ChordMaps n 99
Unit
Chord/Scale selected, both the chord position and scale degrees are
considered when deciding what note is played over a certain chord.
The chord position is which chord tone we are talking about. For
instance, in a C major chord, C is chord position one, E is chord posi-
tion two, and G is chord position three. If it were C7, Bb would be
chord position four.
In the sample project, visit the Playmode bookmark for a demon-
stration of the Chord/Scale playmode. Notice that the Segment has a
chord that is not a traditional triad. It is D2, A2, and D2. When
SimpleStyle is played with this chord, the notes are stretched out to
fit the chord because the Chord/Scale playmode specifies that we
take the chord into account as well as the scale. The Sequence
Track’s purpose in this Segment is to show you the notes that play
when the Style’s pattern is played through the playmode Segment.
The notes are a doubling of the Style’s output an octave lower.
Sequence Tracks ignore chords and always play the same thing.
Back to the topic; let’s look at how the engine plays the scale
section of the pattern (starting on beat three) when played through
the playmode Segment. If you do not have the project handy, refer
back to Figure 6-1 for Simple Style’s notes. The first two notes are
what one would expect, but from there it looks confusing. You proba-
bly expect to hear D, E, F, G, A, B, C, D since the pattern has a
straight scale, but because DirectMusic has been told to take chord
position and scale into account, it does things differently. It plays D,
E, A, B, D, E, C, D. The A is played as the third note because A is
the second chord tone in the chord D-A-D. In C major, E was the
second chord tone. So when you are in C major and you write an E,
DirectMusic remembers that as the second chord tone, not as the
third scale degree. Thus, you hear A because with D-A-D, the sec-
ond chord tone is A. In C major, F is one scale degree above the
second chord tone, so over D-A-D, B is one degree above the second
chord tone. This also explains the D and E after that. The D in the
next octave is the third chord tone, like G was in C major, and E is
one above that, just like A was in C major. So why are the last two
notes lower? We have run out of chord tones and are going to the
100 n Chapter 6
next octave. These two notes are actually what we expected; they
just seem strange since they are lower than the two before them.
The second measure plays, ignoring the chord positions because
the default playmode of the Scale Style is set to the scale playmode
instead of the chord/scale playmode. The scale mode obviously only
takes scale position into account. The pattern starts on the root of
the chord and plays the same scale degrees as the original pattern.
Here is a summary of the way pitches are retained when the
chord/scale playmode is specified in a pattern. Pitches have three
components in the chord/scale playmode: chord position + scale off-
set + chromatic offset. So a D# in C major is what in our D-A-D
chord? Well, D# is the second chord position (E) lowered by one
chromatic tone in C major. In D-A-D, A is the second chord tone. G#
is one half-step lower than that, so the answer is G#. In this case,
there was no scale offset.
The difference between the chord playmode and the chord/scale
playmode is in how these playmodes cause the engine to handle sev-
enths, or the fourth chord tone. If chord playmode is specified and
the chord given in the Chord Track does not contain a seventh or
fourth chord tone, that tone will be omitted during playback. This
could be a good thing if you are trying to avoid sevenths when they
are not specified. On the other hand, the chord playmode could have
a negative impact if you have melodic lines that depend on sevenths.
PedalPoint is another playmode that will ignore the chord’s root
and round to the nearest scale note. Unless you really want a true
pedal point in the sense of one note, this may not work harmonically
with what you are doing. PedalPointChord is a really good option for
melodic lines, since it shifts notes to the nearest chord tone. That
doesn’t mean that every note will be a chord tone, though. It means
the generated note will have the same offset from a chord tone as
the original note in the pattern. For instance, a C in a C major pat-
tern (chord pos 1) shifts up to a B in a G major chord (chord pos 2),
and a D in a C major pattern (chord pos 1+1 scale degree) will
become a C in a G major pattern (chord pos 2+1 scale degree). In
English that means if you write a chord tone, it will always play a
chord tone. If you write a non-chord tone, it will play another
non-chord tone. Listen to the flute part in the PedalPointChord Seg-
ment in the ChordMap tutorial for an example.
Working with Chord Tracks and ChordMaps n 101
Unit
Note Tweaking
Another way to use the pedal point chord playmode is with second-
ary Segments or motifs. If you play a really long note with the Don’t
Cut Off and Regenerate on Chord Change flags specified in the
note’s properties page, some cool harmonies can result. In the tuto-
rial project, visit the Regeneration bookmark. Play the Segment
featuring the Chord Track using the main Play button, and then play
the other Segment with the secondary Segment toolbar. The notes
continue past the end of the Segment, adjusting to every new chord
that comes along! In this case, the pedal point chord playmode set-
ting is not specified in the part but on the notes themselves.
There are other things that you can do to individual notes using
their properties pages. You can tell the engine to harmonize a note to
a chord that has not arrived by tweaking the map to chord value.
This is great for pickups. You can avoid having one note randomly
transpose an octave because it hit a range limit. Do this by setting
groups of notes to transpose together with the Override Inv. Group
feature. You can tweak the scale and chromatic offsets that we dis-
cussed earlier in the Note Properties box. You can tell a note when it
is appropriate for it to cut off. For example, you can tell DirectMusic
to cut off the note if it is not in the new chord’s scale or chord tone
list.
to place the basic chord tones in the first level and the upper exten-
sions into the upper chord levels. Open the ChordLevel bookmark to
see an example. The first three chords are simple and have the same
note for each level. When the more complex chords start in bar four,
only the trumpets play the dissonant tones. This is because in the
pattern the trumpet part is set to chord level three via its properties
page under Default Play Mode. All the extension notes are placed in
level three in the chords (see Figure 6-9). That way, the trombones
and bass play the fundamental chord level one tones, and the trum-
pets play the extended level three tones.
It is also on the DirectX SDK CD. Run this file to install it:
E:\DXF\DXSDK\essentls\DMusProd\DemoContent\DMPDemoCont
ent.exe.
I
Unit
Pattern Length and Chord Rhythm
Now let’s discuss chord rhythm, pattern length, and how Direct-
Music chooses which pattern is appropriate to play a given set of
chords. So far, every Style that we have dealt with had only one pat-
tern, but it is often better to have several patterns to add variety.
One chord change every measure with the same musical motifs
repeatedly can get tiresome. You can compose separate patterns for
each level of chord activity. By default, DirectMusic picks the longest
pattern that fits the chord rhythm in the Segment. This default
behavior only holds true if you have a Groove Track with a groove
level specified. Adding a Groove Track will not hurt your content if
you do not plan to use grooves. Just put any number in the first mea-
sure. For an example, look at the bookmark AdvancedTechniques.
The Style has two patterns. One is one measure long, and the other
is four. What really matters is the chord rhythm. Go to the properties
page of the pattern 4Bar. At the bottom is the chord rhythm section.
You can click the Set button to change the setting. After adjusting
the setting, you can then click the boxes to put an x in every place
where there should be a chord change. The four-measure pattern is
chosen every time that there is no chord change for four bars
because the pattern follows only one chord for four measures. If you
put an x at the beginning of every measure, the 4Bar pattern always
plays because it is the longest pattern with the correct chord rhythm
in all cases.
There are two variations in 4Bar. One variation is a single chord,
but the other variation moves every two beats. Patterns with only
one chord change are a good way to take some liberty with the
chords in your patterns. You can have different chord rhythms in
every variation. Faster, syncopated, and less regular chord rhythms
are sometimes better written out exactly without being subject to
Chord Track’s control. Chord rhythm variability is very easy with
this pattern, since there is only one part and there is no need to do
variation locking to keep the harmonies together.
104 n Chapter 6
You can still build inherent chord progressions into different vari-
ations if you have multiple parts. All you need to do is take advantage
of locking variations. To get this setup to work, you need to make
sure that the ChordMap can play for a longer period of time with only
one chord. In this example’s ChordMap (shown in Figure 6-10), you
will notice a connection that has been drawn from the first signpost
directly to the second target signpost. If there were no connecting
chords, this would not be necessary, but since there are connection
chords, DirectMusic will only take paths that are specified when
moving between these two signposts.
Unit
Figure 6-11: Variation Choices window.
aware of the time remaining and how far apart the two teams’ scores
are. All of this is accomplished by using scripting variables, where
data can be passed in both directions; the script can tell the applica-
I
tion when an event has occurred in the music, and the application
Unit
can tell the script what the user is doing and the current state of the
application.
But wait — isn’t all of this scripting a form of programming? Are
we turning the composer into a programmer? In some respects we
are, but hopefully it is with a straightforward language that allows
the composer to do more of what he wants without having to depend
on the programmer. As you’ll see below, many of the most powerful
functions (namely, playing or stopping a piece of music) are very
straightforward. Feel free to cut and paste from both of these exam-
ples, those in the demo project supplied with DirectMusic Producer,
and sample scripts found in the DirectX Software Development Kit
(SDK).
Creating a Script
The first step to building our script is to create a new one in Direct-
Music Producer. As with other file types, you can create a new script
via the File menu by selecting New… and then choosing Script in
the dialog that opens.
The first thing we notice is that a script looks an awful lot like a
DirectMusic container file, with separate embed and reference fold-
ers. In fact, script files are a specialized kind of container file. A
script is able to act on any other file that it knows about, as well as
manipulate parameters for the global DirectMusic engine (tempo,
groove level, and so on). With files placed in the Embed Runtime
folder, the content will actually be saved within the Script file when it
is run-time saved, just as they were in a container’s Embed Runtime
110 n Chapter 7
Unit
Figure 7-3: The setup for our upcoming examples. Our two
Segments happen to refer to their own DLS Collections, but they
could also (or instead) use General MIDI instruments.
Note first off that the wave My Name Is is referenced by the script
as MyNameIs — without the spaces. The name a script uses to ref-
erence an object is not allowed to contain any spaces, so these were
removed. Other restrictions to be aware of are that a script object
cannot begin with a number, and you can’t have two waves/Segments
with the same name (if you do, DirectMusic Producer will automati-
cally add a number to the end of the name of the second one
inserted).
Now let’s take a look at the Script Designer window. If it isn’t
already open, double-click on the script in the project tree.
112 n Chapter 7
There are three frames in the Script Designer. The Source frame is
where we type in the actual routines for this script. When we want
to try out these routines, we double-click them over in the Routines
frame. This simulates the exact function that the programmer will
perform when they “trigger” a script routine. The Variables frame
will show any variables that our script uses along with their current
values. Again, we can click on these to update them just as the pro-
grammer would in the actual application.
Scripting Languages
DirectMusic Producer supports any ActiveX scripting language for
creating scripts, but the two most commonly used languages are
Visual Basic Scripting Edition and AudioVBScript. AudioVBScript is
a DirectMusic-optimized language, specially geared toward music
content playback and occupying a very small memory footprint.
Alternatively, a composer could use the full Visual Basic Scripting
Edition language (“VBScript” for short). While this language occu-
pies more memory and potentially could take more processing
power, it is a fully featured language, providing added flexibility and
additional features, such as string concatenation (the ability to merge
several pieces of text for display or script usage). Generally, start
with AudioVBScript and move up to VBScript if you find that you
DirectX Audio Scripting n 113
Unit
Music code.
The language that a specific script uses can be selected from that
script’s properties page.
If you click anywhere else in the Script Designer window (or in the
project tree), notice that IntroduceCharacter pops up in the Routines
frame at the top right of the window.
The period (.) is how DirectX Audio Scripting separates the object
that we’re using (in this case, a wave) from the function that we want
to call on it (in this case, playing it). Remember, the script only
knows about objects that were dragged into its embed or reference
folders; if we tried to play a Segment or wave that wasn’t in those
folders, we would get a syntax error. The above routine will play
Hello using all default properties; it will play as a primary Segment,
and it will play at the default authored boundary (most typically,
immediately, but this could be set in the Boundary tab of the Seg-
ment’s properties page). Again, we could try this routine out by
double-clicking on it over in the Routines frame. Now it makes
noise!
DirectX Audio Scripting n 115
Unit
among other things, describing what the routine does.
Sub IntroduceCharacter
’We trigger this routine whenever our character meets
’other characters.
Hello.play
End Sub
You can see the list of parameters for playing a Segment by looking
in the DirectMusic Scripting Reference guide, under the topic Seg-
ment.Play. When the Script Designer window is open, a shortcut to
the guide can be found in the Help menu and is also available via the
Ctrl+F2 shortcut. Parameters are separated by commas, though if
you don’t use all of the parameters, their default settings will be
used. Therefore, for these early examples, we will just stick with the
first parameter.
For the flags parameter of Segment.Play, multiple flags can be
combined simply by “adding” them (using the + sign between
them). For instance, we can create a new routine that starts our
room ambience when we enter the room. If any music is playing, we
want the ambience to sneak in at a measure boundary. We play it as a
secondary Segment so that the background score continues to play.
(Recall that only one primary Segment can be playing at a time.)
Sub EnterRoom
RoomAmbience.Play (IsSecondary + AtMeasure)
End Sub
Granted, our dialog example has the same issue; we probably don’t
want our dialog to become the sole primary Segment. Since it
becomes a bit more complex to solve this problem for dialog stitch-
ing, we come back to that in a moment.
Unit
indicate that it should be ignored and use the default.
Sub StartActionMusic
ActionMusic.Play (AtMeasure, nothing, RampUpAction)
End Sub
Both versions of the routine would do the same thing — at the next
measure of the primary Segment, play the RampUpAction Segment
that we’ve authored as a transition Segment; when it finishes, play
ActionMusic.
Sub IncreaseIntensityLevel
SetMasterGrooveLevel (GetMasterGrooveLevel + 1)
End Sub
Unit
you were using. You try to fire off a routine to play that Segment and
you get silence or an older version of the Segment plays. This is
most frequently caused by editing a Segment after setting up your
script. In order to allow for script routine auditioning within Direct-
Music Producer, the script is actually “running” even as you edit it.
But when you edit a piece of music that the script was using,
DirectMusic Producer has a tough choice — the script could update
with each edit to the Segments (which might cause a significant
slowdown to edits if the Segment and/or script were large) or the
script can just accept that it might get out of sync, at which point the
content creator can force a refresh. DirectMusic Producer opts for
the latter. You can force the script to resynchronize with all of its
source content by hitting the Refresh button above the list of rou-
tines. If that is not successful, you can save and reload your script by
right-clicking on it, choosing Save, and then again right-clicking and
choosing Revert to Saved.
Figure 7-8: The status bar displays the exact line and
column number that your cursor is currently on.
For an example of a run-time error, let’s use that same routine but
try to play a Segment that we haven’t added to either of the script’s
folders (for instance, a typo in the name of the Segment that we
intended to play).
Sub ExitRoom
'Try to play a Segment the script doesn’t know about
SomeOtherSegment.Play (AtBeat)
End Sub
Unit
Figure 7-9: The Message Window displays an error regarding
an unlocatable Segment.
This lets us know that the script didn’t know where to find the Seg-
ment, so we should add it to the reference or embed the run-time
folder.
The Message Window has one additional use; you can place
statements in your routines that provide information similar to
comment statements but let you know when a routine (or more par-
ticularly, a specific portion of a routine) has been run. These com-
mands are known as trace statements. A programmer can actually
“listen” for these trace statements in a game if you wanted to pro-
vide information (for instance, for karaoke, song title information,
and so on).
Going back to our queued-up dialog, we could add a trace state-
ment that lets us know the script routine completed.
Sub IntroduceCharacter
’We trigger this routine whenever our character meets
’other characters.
Hello.play
MyNameIs.play (AtFinish)
Bob.play (AtFinish)
Trace “Queued up Bob’s introduction.”
End Sub
Also make sure that you don’t name a variable the same name as one
of your other scripting objects (Segments, waves, and so on). Again,
the Message Window will let you know if this problem occurs.
DirectX Audio Scripting n 123
Once a variable has been declared, if you click away from the
Sources tab, you’ll see the variable appear in the lower-right frame of
the Script Designer window, as in Figure 7-11.
I
Unit
Figure 7-11: Variables list in the lower-right frame of the Script Designer window.
You can now click on the value field to set the value, just as the pro-
grammer would do in the application. Clicking the Reinitialize button
resets all values to Empty.
Conditional Statements
Now that we’ve created a variable, we use conditional statements to
act based on their value. With conditional statements, the script per-
forms some comparison on the variable to decide how to act (for
instance, what piece of music to play and when to play it). The sim-
plest conditional statements are if … then statements, where if a
condition is true, you will perform one or more actions. The syntax
for this is:
If (expression) then
[Action1]
[Action2]
[...]
End if
Sub PlayDeathMusic
if (PlayerHealth = 0) then
DeathMusic.Play (AtImmediate)
end if
end sub
124 n Chapter 7
If the programmer tried to trigger the death music when the player
still had health, nothing would happen; only if health was zero would
we hear the Segment called DeathMusic play.
More complex statements will tend to use the if … then/elseif …
then/else format: If a condition is true, do some action; otherwise,
check to see if another condition is true (elseif) and perform their
actions; otherwise, if all of these conditions are false (else), do some
final action. For our character introduction example above, our code
might look like this (let’s simplify to three characters instead of 20):
dim CharacterNumber
'We have three possible options for our character
'1=Bob
'2=Joe
'3=Bill
Sub IntroduceCharacter
'We trigger this routine whenever our character meets
'other characters.
Hello.play
MyNameIs.play AtFinish
if (CharacterNumber=1) then
Bob.play (AtFinish)
elseif (CharacterNumber=2) then
Joe.play (AtFinish)
elseif (CharacterNumber=3) then
Bill.play (AtFinish)
else
Trace "Illegal character number."
end if
End Sub
We can test out the above routine by setting our variable in the
lower-right frame of the Script Designer (just as the programmer
would in application code) and then double-click a routine to trigger
it, again, just as the programmer would.
Notice the final else statement in the above example. Using trace
statements here often comes in handy for debugging; you are alerted
that something caused your variable’s value to go outside of the
range that you expected it to fall into.
DirectX Audio Scripting n 125
Local Variables
Any time a variable is declared with a dim statement, it is a global
I
variable that the programmer can see and manipulate. There are
Unit
often situations where global variables are not necessary. For
instance, the scripter might do some basic math on several global
variables to decide what piece of music to play. Rather than having to
create another global variable for this value, scripters can use local
variables. Local variables have two primary differences from global
variables — they cannot be seen by the programmer, and they do not
have to be declared. They are implicitly created when they are first
used.
A good example of local variable use is adding randomization to
scripted playback. AudioVBScript has a rand function that can be
used to generate a random number from 1 to the value you specify.
For instance, if we had three pieces of music that would be appropri-
ate for the current scene, we could construct a script routine like the
following:
Sub PlaySceneMusic
x = rand(3)
’x is a local variable – we did not have a
’ “dim x” statement.
’The programmer cannot see it, and its value is
’ not stored after the routine finishes running.
If (x=1) then
LowLevelMusicThematic.Play
ElseIf (x=2) then
LowLevelMusicRhythmic.Play
ElseIf (x=3) then
LowLevelMusic.Play
End if
End Sub
Sub StartTorch
Torch.Play (IsSecondary)
NumberOfTorchesPlayed = NumberOfTorchesPlayed + 1
end sub
Sub StartMyTorch
’We only carry one torch, so there’s only one
’PlayingSegment we need to keep track of
Set MyPlayingTorch = Torch.Play (IsSecondary)
end sub
Sub StartRoomTorch
Torch.Play IsSecondary
DirectX Audio Scripting n 127
End sub
Sub StopMyTorch I
’Will only stop our own torch; room torches will
’continue to play.
Unit
MyPlayingTorch.Stop
End Sub
Sub StopAllTorches
Torch.Stop
End Sub
Note that the syntax for using objects in variables is slightly modified
from numeric variables — a Set statement is used when you are
assigning something to an object, rather than a numeric variable. In
this example, we now have a personal torch that we track the
instance of, and if our character extinguishes it, that single instance
can be stopped without affecting other torches in the room. Mean-
while, the room might be filled with other torches that we don’t need
to be able to stop individually, so the script doesn’t keep track of
their individual PlayingSegment objects. The StopAllTorches method
would stop every torch, including our own.
Sub IntroduceCharacter
End Sub
Figure 7-12: Every four bars, the script routine CheckNumEnemies is called. If this
Segment was our primary Segment, and the routine CheckNumEnemies fired off
another primary Segment, this Segment would of course stop playing.
DirectX Audio Scripting n 129
Unit
mer having to code anything. Or the music can poll a variable every
so often and respond accordingly without needing the programmer to
let the script explicitly know that the variable has been updated.
Such a technique is used in the baseball example included with the
DirectX Software Development Kit — an infinitely looped secondary
Segment consisting solely of a script track checks to see if the score
has changed and whether the crowd has responded appropriately
with cheers or boos (without needing the programmer’s involvement
in the least). See the baseball.spt file (which can be opened in
DirectMusic Producer and re-expanded into its source files) for more
details on how this was implemented.
Sub FreeWaveData
MySegment.UnloadSoundData
’Make sure you unload as many times as you download
’for a Segment, as wave data won’t be freed until then.
End Sub
embed any content that was placed in the Embed Runtime folder and
knows how to use any separately delivered content that had been
dragged into the Reference Runtime folder. When scripts are used in
I
a DirectMusic application, encourage your programmer to give you
Unit
the ability to manually fire off every script routine in some manner
while running the application. For instance, if the bad guy music only
gets triggered at the very end of a game, you probably don’t want to
have to go all the way through a level just to hear if the music trig-
gered at an appropriate boundary.
DirectX Audio Scripting provides another tool to the composer
or sound designer for controlling audio behavior without needing to
overly involve the programmer and, more importantly, the ability to
make changes without having to rebuild the entire application. While
the functionality provided is a subset of the functionality made avail-
able programmatically, the functions allow for the vast majority of
basic and intermediate scenarios to be handled solely by the content
author.
This page intentionally left blank.
Unit II
Programming with
DirectX Audio
133
This page intentionally left blank.
Chapter 8
DirectX Audio
Programming 101
135
136 n Chapter 8
Host Application
DirectMusic
Performance
DirectMusic DirectSound
Core
Host Application
DirectMusic Performance
II
Unit
AudioPath
AudioPath
AudioPath
DirectMusic DirectSound
Core/Synth
With DirectX 8.0 and later, the Performance Layer controls one or
more AudioPaths, each of which manages the flow of MIDI and wave
data into the DirectMusic software synthesizer, on through multiple
audio channels into DirectSound, where they are processed through
effects, and through their final mix stage into the audio device (see
Figure 8-2).
This integration of DirectSound and DirectMusic, delivering
sounds and music through the Performance Layer via AudioPaths, is
what we call DirectX Audio.
138 n Chapter 8
Sounds like a lot of stuff to learn to use, eh? There is no denying that
all this power comes with plenty of new concepts and programming
interfaces to grok. The good news is you only need to know what
you need to know. Therefore, it is quite easy to make sounds (in fact,
a lot easier than doing the same with the original DirectMusic or
DirectSound APIs) and then, if you want to learn to do more, just dig
into the following chapters!
In that spirit, we shall write a simple application that makes a
sound using DirectX Audio. First, let’s acquaint ourselves with the
basic DirectX Audio building blocks.
Segment
A Segment represents any playable audio. It might be a MIDI or wave
file, or it might be a DirectMusic Segment file authored in Direct-
Music Producer. You can load any number of Segments from disk,
and you can play any number of them at the same time. You can even
play the same Segment multiple times overlapping itself.
DirectX Audio Programming 101 n 139
AudioPath
An AudioPath represents the journey from Segment to synthesizer/
mix engine to audio channel to final mix. A DirectSound Buffer man-
ages the audio channel, since a Buffer really represents one hard-
ware (or software) audio channel. There can be any number of
AudioPaths active at any time and any number of Segments can play
on the same AudioPath at once.
Performance
The Performance manages the scheduling of Segments. Think of it as
the heart of the DirectX Audio system. It allocates the AudioPaths
and connects them, and when a Segment plays, it connects the Seg- II
ment to an AudioPath and schedules its playback.
Unit
Loader
Finally, the Loader manages all file input/output. This is intentionally
separate from the Performance, so applications can override the
loading functionality, should they desire.
COM
COM is short for Component Object Model. Like all DirectX objects,
the Segment, AudioPath, Performance, and Loader are COM objects.
DirectX Audio’s usage of COM is at the most elementary level. Let’s
discuss COM now:
n Individual function interfaces represent each object as a table of
predefined functions that you can call. There is no direct access
to the internal data and functions of the object.
n Interfaces and objects are not the same. An interface provides an
understood way to communicate with an object. Importantly, it is
not limited to one object. For example, there is an interface,
IPersistStream, that provides methods for reading and writing
data to a file. Any COM object that includes an IPersistStream
interface can receive commands to read and write data from any
component that understands IPersistStream.
140 n Chapter 8
Unit
Therefore, from your perspective, COM programming in DirectX
Audio means you use CoCreateInstance() to create a few objects (the
rest are created automatically by other DirectX Audio objects),
Release() to get rid of them, and on the rare occasion Query-
Interface() to access additional features.
That’s enough explanation about COM. Let’s get on with the
program.
HelloWorld!
No introduction to any API is complete without that ancient but use-
ful ritual known as the “Hello World” application. Our first applica-
tion loads a wave file and plays it. Before we do anything, we need to
hook up the correct include files. DirectX Audio only requires
dmusici.h, as it has all the interfaces, data structures, and GUIDs for
the Performance Layer. In turn, it pulls in the dsound.h and dmusic.h
lower-level header files, so we get everything we need. Here is what
we need to put at the top of the application:
#include <dmusici.h>
142 n Chapter 8
þ Note You need to link with the GUID library that comes with DirectX.
Be sure to include dxguid.lib in your project’s linker settings or you will
get a ton of errors for each IID or CLSID used in your code but not
found by the linker. In VC 6, open the Project Settings… dialog, select
the Link tab, and add dxguid.lib to the Object/Library Modules edit box.
Of course, this has already been set up properly with the projects
included on the companion CD.
þ Note If you are working with DirectPlay and using the same thread,
you should instead call CoInitializeEx(NULL,COINIT_MULTITHREADED),
which is what DirectPlay requires. DirectMusic is completely thread-safe,
so it works well with either.
CoCreateInstance(
CLSID_DirectMusicLoader, // Class ID of the DirectMusic loader.
NULL, // Ignore COM aggregation.
CLSCTX_INPROC, // Must run within this application.
IID_IDirectMusicLoader8, // Request the IDirectMusicLoader8 interface.
(void**)&pLoader); // Return pointer to interface here.
Unit
AudioPath type you want to use by default and how many channels
you need on that default path. An AudioPath represents a signal path
through which the sounds (Segments) play. AudioPaths can include
real-time effects, such as reverb or compression. You usually create
AudioPaths from configuration files that define their layout, but you
can also create predefined AudioPaths directly.
In our example, we create one stereo AudioPath with no effects,
using the DMUS_APATH_DYNAMIC_STEREO standard AudioPath
identifier.
// Initialize the Performance.
pPerformance->InitAudio(NULL,NULL,NULL,
DMUS_APATH_DYNAMIC_STEREO, // Default AudioPath type.
2, // Only two pchannels needed for this wave.
DMUS_AUDIOF_ALL,NULL);
Now it is time to use the Loader to read the wave file from disk. This
creates a Segment. Note that the Loader’s LoadObjectFromFile()
method has a lot in common with the system CoCreateInstance()
command. Again, you provide a class ID for the type of object you
wish to load, and you provide an interface ID to indicate which inter-
face you expect to use. Not surprisingly, the Loader turns around and
calls CoCreateInstance() to create the object you requested.
Provide the name of the file to the Loader. In our HelloWorld
application, it is, naturally, a wave file titled Hello.wav.
144 n Chapter 8
// Load the demo wave. We are making the assumption that it is in the
// working directory.
// In VC6, this is set in the Project Settings/Debug/General/Working directory: field.
if (SUCCEEDED(pLoader->LoadObjectFromFile(
CLSID_DirectMusicSegment, // Class identifier.
IID_IDirectMusicSegment8, // ID of desired interface.
L"Hello.wav", // Filename.
(LPVOID*) &pSegment // Pointer that receives interface.
)))
{
Once the Loader reads the wave into a Segment, we still need to
prep it for play. The Download() command moves any audio data
(waves and DLS instruments) required by the Segment into the syn-
thesizer, priming it so it can play instantly.
// Install the wave data in the synth
pSegment->Download(pPerformance);
Wow! Sound!
PlaySegmentEx() returns immediately as the sound starts play-
ing. Normally, this would be no problem, since the application would
continue running. However, HelloWorld does not do anything after
playing the sound, so it needs to wait patiently for eight seconds
(roughly the duration of the sound) before cleaning up and closing
down.
// Wait eight seconds to let it play.
Sleep(8000);
// Done with the performance. Close it down, and then release it.
pPerformance->CloseDown();
pPerformance->Release();
If you compile this and have trouble getting it to make any sound, the
problem is almost certainly that it cannot find the wave file. Make
sure that your project’s working directory is set to the Media direc- II
tory, where Hello.wav resides. Refer to your compiler’s user manual
Unit
for information on how to set your working directory. Double-click
on the version in the bin directory; it successfully plays because I
placed a copy of Hello.wav there.
It’s a 3D World
Now that we have loaded and played a sound using DirectX Audio,
let’s see how easy it is to add a little code to move that sound in 3D.
To do this, we need to start working with AudioPaths. We create
a 3D AudioPath that mixes all audio into a DirectSound 3D Buffer.
We then use the IDirectSound3D interface on the buffer to move the
sound as it plays.
Go back to the InitAudio() call, and change it to create a 3D
AudioPath as the default path.
// Initialize the performance with a default 3D AudioPath.
pPerformance->InitAudio(NULL,NULL,NULL,
DMUS_APATH_DYNAMIC_3D, // Default AudioPath Type.
2, // Only two pchannels needed for this wave.
DMUS_AUDIOF_ALL,NULL);
Now that we have the 3D interface, we can change the spatial posi-
tion of any Segments played on this path. Before we play our sound,
set its initial position in 3D space.
// Initialize the 3D position so the sound does not jump
// the first time we move it.
p3DBuffer->SetPosition(-2,0.2,0.2,DS3D_IMMEDIATE);
Using the CD II
This is a good time to introduce the Unit II material on the compan-
Unit
ion CD.
Take a look in the Unit II folder on the CD. It includes source
directories for each project discussed in these chapters. Each direc-
tory is labeled with the chapter number and project name. So, for
example, this chapter’s programming projects are 8_Hello and 8_
Hello3D. The Bin directory contains the final executables of each
project, the SourceMedia folder has the DirectMusic Producer
source files to create the content, and the Media directory contains
the final run-time media files for the sample projects.
You can either copy these files by hand or you can run the Setup
program, also in the Unit II folder, to automatically install these files
on your hard drive. It includes an uninstaller, so you can install, play
with the materials, and when done wipe them out to make room for
more on your hard drive.
This page intentionally left blank.
Chapter 9
The Loader
149
150 n Chapter 9
Wave
Wave
DLS Collection
Style
Support Customization
Applications like games often keep resources bundled up in one large
file. Although DirectMusic’s container format provides an easy way II
to accomplish that, it still does not provide encryption or compres-
sion features that a game might require. Because the Loader uses a
Unit
publicly defined interface, you can create your own Loader and have
it retrieve the data in any way that it pleases.
Loading a File
So, how do you use the Loader to load files? As we saw in the
HelloWorld example, you must first use the COM CoCreate-
Instance() call to create a Loader.
CoCreateInstance(CLSID_DirectMusicLoader, NULL,
CLSCTX_INPROC, IID_IDirectMusicLoader8,
(void**)&pLoader);
SetSearchDirectory()
SetSearchDirectory() tells the Loader which directory to go to when
searching for a file that is referenced by the file currently being
loaded.
HRESULT SetSearchDirectory(
REFGUID rguidClass,
WCHAR* pwszPath,
BOOL fClear
);
LoadObjectFromFile()
You can use the Loader to read a file in two ways: GetObject() and
LoadObjectFromFile(). LoadObjectFromFile() is the more conve-
nient approach. GetObject() has all kinds of clever options for loading
from files, streams, or memory. However, LoadObjectFromFile() pro-
vides a very straightforward way to read DirectX Audio objects
directly from files.
HRESULT LoadObjectFromFile(
REFGUID rguidClassID,
REFIID iidInterfaceID,
WCHAR *pwzFilePath,
void ** ppObject
);
II
n REFGUID rguidClassID: This is the class ID for the type of
Unit
object to load. The Loader does not know anything about how to
load a particular type of file. Instead, it calls COM to create an
instance of the object, which then does the actual file reading
and, when finished, returns the object to the caller. For example,
to load a Segment, this parameter is CLSID_DirectMusic-
Segment.
GetObject()
While LoadObjectFromFile() does the job 90 percent of the time,
there are scenarios where a little more flexibility is needed.
n The Loader can identify every file with alternate information,
including a name, version, date stamp, unique GUID, and, of
course, file name. The application may need to use one or more
of these other variables to search for the file, especially if it is
already cached. LoadObjectFromFile() can only use the file
name.
n The data might be stored in a memory buffer or stream instead
of a file. LoadObjectFromFile() can only load from a file.
GetObject() provides a way to read from alternate sources. It does so
by using the DMUS_OBJECTDESC structure, which allows for a
very thorough description of the object.
typedef struct _DMUS_OBJECTDESC {
DWORD dwSize; // Size of this, should it grow in later versions.
DWORD dwValidData; // Flags indicating which fields have valid data.
GUID guidObject; // Every object can have a unique ID.
// This is saved in the file.
GUID guidClass; // Every object must belong to a predefined class.
FILETIME ftDate; // The date when the object was created.
DMUS_VERSION vVersion; // Major and minor version info.
WCHAR wszName[DMUS_MAX_NAME]; // Friendly name of object.
WCHAR wszCategory[DMUS_MAX_CATEGORY]; // Optional category name.
WCHAR wszFileName[DMUS_MAX_FILENAME]; // The file name, optionally with path.
LONGLONG llMemLength; // If memory is used instead of a file, sets length.
LPBYTE pbMemData; // Pointer to memory data.
IStream *pStream // Or, stream to read data from.
} DMUS_OBJECTDESC, *LPDMUS_OBJECTDESC;
Flag bits in dwValidData indicate which fields are valid. For example,
the flag DMUS_OBJ_NAME means that the string in wszName is a
valid name for the object. Conversely, a cleared bit in dwValidData
indicates that the corresponding data field is empty. The fields that
are most important for tracking resources are wszFileName and
guidObject. These help identify and validate the correct file. The
GUID is guaranteed to be unique but must be authored into the files,
typically via DirectMusic Producer.
In addition to identification fields, the caller can use pbMemData
and pStream to present data in a memory or stream format. We
The Loader n 155
Unit
Instance(), you must provide the interface ID for the interface
that you intend to get back. For example, to get back a Segment
interface, pass in IID_DirectMusicSegment8.
n void **ppObject: The loaded object returns in ppObject, which
is a pointer to the variable that receives the returned object
interface.
SetObject()
Sometimes, you need to point the Loader at a file source before
another object references it. This could be because the file path is
broken or, more likely, the reference object loads from memory or an
IStream. Therefore, you need to point the Loader directly at it using
SetObject(). This is almost identical to GetObject(), except it does
not actually load anything. It just tells the Loader where the object
is, and the Loader pulls it in later when needed.
HRESULT SetObject(
LPDMUS_OBJECTDESC pDesc
);
ScanDirectory()
ScanDirectory() searches a directory for a specific type of file, as
defined by its class ID and file extension (the * wildcard is sup-
ported.) For example, you might use it to search for all Style files
within a particular directory. This does not load any of the files.
Instead, it adds all their location information to the file list. To do
this, it parses each file header, looking for all pertinent information
to fill the DMUS_OBJECTDESC record for each file. Therefore,
internal information, including object name and GUID, is retrieved
and stored in the list.
ScanDirectory() is useful primarily in conjunction with Enum-
Object(), which lists the objects one by one. This is useful for
applications that need to display all available files of a particular type.
This is great for media playback and authoring applications but not
needed for games that usually have dedicated soundtracks. As long
as file names are unchanged and SetSearchDirectory() is used appro-
priately to establish where to find them, ScanDirectory() incurs
unnecessary overhead, so avoid using it when possible.
The Loader n 157
EnumObject()
EnumObject() scans through the file list and retrieves information
about all files of a specific file type, identified by its class ID. For
example, an application might use this to present the user with a list
of all playable media files. It would do so by calling ScanDirectory()
to scan for all Segment files (which can include wave and MIDI files
as well) and then calling EnumObject() to list them.
EnableCache()
EnableCache() and ClearCache() are very important for effective
memory management.
When the Loader opens a file, it keeps an internal reference to II
the loaded object in its file list. The Loader effectively caches the
Unit
file, so a subsequent request simply receives a pointer to the original
object. This is all fine and well, but it has its cost. Some file types,
like Segments, are rarely, if ever, referenced by anything else. There-
fore, it is usually just a memory waste to cache them. EnableCache()
lets the application decide which file types the Loader should cache.
File types that typically are referenced by others, like Styles and
DLS Collections, are great candidates for Loader caching since it
ensures that just one instance of a Style (for example) is created and
then referenced by all objects that use it. Segments are not.
þ Note It is not always the case that there are no objects referencing
Segments. Both Script objects and Segment Trigger Tracks can reference
Segments. In those situations, enabling caching may have merit. Even
then, it is typically rare to have multiple Scripts or Segments referencing
the same Segment.
ClearCache()
ClearCache() provides a quick mechanism for clearing out all objects
of a particular type. For example, you might call ClearCache() for
some or even all file types when moving from one scene to another
in a game.
158 n Chapter 9
ReleaseObject()/ReleaseObjectByUnknown()
ReleaseObject() and ReleaseObjectByUnknown() can be used in lieu
of ClearCache() to release a specific object from the cache. Use
these when you need to remove one but not all objects from the
cache.
These methods are identical in functionality. ReleaseObject()
requires the IDirectMusicObject interface, which all loadable objects
must support. However, it is obnoxious to have to call Query-
Interface() to get that interface in order to then call ReleaseObject().
So, with DirectX 8.0, ReleaseObjectByUnknown() was added to the
API. It lets you use the regular interface for the object, and it inter-
nally queries for the IDirectMusicObject interface.
Garbage Collection
In DX8, DirectMusic introduced three features that could easily
cause reference loops. These are Scripting, Script Tracks, and Seg-
ment Playback Tracks. For example, one script might reference
another script that calls back into the first. On the other hand, a Seg-
ment Trigger Track triggers the same Segment or another that
triggers the original Segment again. These are all perfectly reason-
able scenarios, and they do load and run appropriately. However,
when it comes time to clean up, a self-referencing loop keeps the ref-
erence counts of all objects in the loop incremented by at least one,
and so they never go away, even though nothing actually references
them outside the loop.
The CollectGarbage() method solves this problem. Collect-
Garbage() scans for all objects that have been completely released by
the Loader as well as the application and calls an internal method on
the object, forcing it to release its references to other objects and
The Loader n 159
Unit
the object lists using the standard Loader methods and observe the
behavior. You can run LoaderView by either going to the Unit II\
9_Loader directory and building LoaderView or running it directly
from the Bin directory. LoaderView opens a simple dialog window
with commands for loading a Segment and managing the Loader (see
Figure 9-2).
Search for a Segment, MIDI, or wave file to load. Files that reference
other files are particularly interesting. For example, EvilDonuts.sgt
has references to Style, DLS, and wave files.
When you click on the Open… button, the Loader’s GetObject-
FromFile() method is called to load the Segment. Once the file is
loaded, its name is listed in the box underneath the Open… button.
Click on the Play and Stop buttons to audition the Segment. We talk
a lot more about playing Segments in the next chapter, but at least
we can audition our Segment for now. With the Loader being as dull
a subject as it is, it helps to be able to make noise.
Now, let’s look at what is in the Loader’s cache.
The list box below the Type drop-down displays all files of the media
type that you requested that the Loader is currently managing.
Choose a list that displays one or more files. For example, “Evil
Donut” uses two DLS Collections, so you might try that. Note that
they all have “-cached” after their name. This indicates that the
Loader currently has a reference to an instance of the object loaded
in memory.
You can turn off caching. Uncheck the Enable Cache option.
This calls the EnableCache() method with a false flag to disable it.
Disabling a cache also automatically clears it, so notice now that the
display lists these files no longer as cached. Remember: Once the
Loader does not have the file cached, it must reload the file when the
application subsequently requests the file. This incurs file and CPU
overhead and extra memory overhead if the object is still in use
II
since there are two copies of the object in memory once the second
Unit
instance loads.
Go back and press the Play button. Notice that the music still
plays. Although the Loader no longer has its references to the
objects in memory (Style, DLS, etc.), the Segment still does, so it
plays flawlessly. Later, when the Loader and application finally
release the Segment, it in turn releases all objects that it references.
Ah, such is the power and beauty of COM.
Likewise, you can release an individual item. Go to another list,
such as DLS Collections, that has two or more objects. Click on one
to select it, and then click on the Release Item button. Note that
just the one object loses its “cached” designation.
Finally, click on the Scan Files button. This calls ScanDirec-
tory() for the selected file type in the current search directory. The
list updates with all additional files found in the directory. Notice that
ScanDirectory() only retrieves the file information and does not load
and cache the file objects themselves.
LoaderView Source
The CAudio class manages all audio and Loader functionality. The
LoaderView UI (CLoaderDlg) makes calls into CAudio to load and
play Segments. I wrote the code using MFC (Microsoft Foundation
Classes). We do not discuss the UI code here. I do my best to keep
162 n Chapter 9
the UI code and the audio code separated in all programming exam-
ples, so we can focus on just the audio portions. Of course, I include
the UI source in the LoaderView source code project in the Unit II\
9_Loader directory on the companion CD.
CAudio also tracks a table of object descriptors (DMUS_
OBJECTDESC) for the currently selected object type and provides
calls to manipulate the list. This is not enormously useful for regular
applications, but it is useful for exploring the Loader’s caching
behavior, which is what LoaderView is all about. We walk through
the layout of CAudio in just a second.
enum OBJECT_TYPE
{
OT_AUDIOPATH = 0, // AudioPath configuration file.
OT_BAND = 1, // Band file.
OT_CHORDMAP = 2, // ChordMap file.
OT_CONTAINER = 3, // Container - carries a collection of other objects.
OT_DLS = 4, // DLS Instrument Collection file.
OT_SEGMENT = 5, // Segment file.
OT_STYLE = 6, // Style file.
OT_WAVE = 7, // Wave file.
OT_GRAPH = 8 // ToolGraph file.
} ;
#define OBJECT_TYPES 9
CAudio Class
The CAudio class manages the Loader, the Performance, and one
Segment. It has routines for initialization and shutdown. It has rou-
tines to load and play Segments. It has a bunch of routines dedicated
to exploring and managing the Loader’s file lists. Since we cover ini-
tialization and playback in other chapters, here we focus just on the
code for managing the Loader.
Here is the full definition of the CAudio class:
The Loader n 163
class CAudio
{
public:
// Initialization and shutdown methods.
CAudio();
~CAudio();
HRESULT Init();
void Close();
// Convenience methods for accessing DirectX Audio interfaces.
IDirectMusicPerformance8 * GetPerformance() { return m_pPerformance; };
IDirectMusicLoader8 * GetLoader() { return m_pLoader; };
// Methods for loading, playing, stopping, and removing Segments.
IDirectMusicSegment8 * LoadSegment(WCHAR *pwzFileName);
IDirectMusicSegment8 * GetSegment() { return m_pSegment; };
void PlaySegment();
void StopSegment();
void ClearSegment();
// Methods for managing the loader's file lists and retrieving data from it.
II
DWORD SetObjectType(OBJECT_TYPE otSelected);
Unit
void GetObjectInfo(DWORD dwIndex, char *pszName, BOOL *pfCached);
BOOL GetCacheStatus() { return m_afCacheEnabled[m_otSelected]; };
void ChangeCacheStatus();
void ReleaseAll();
void ReleaseItem(DWORD dwItem);
void ScanDirectory();
private:
// DirectX Audio interfaces for performance, loader, and Segment.
IDirectMusicPerformance8 * m_pPerformance; // The Performance.
IDirectMusicLoader8 * m_pLoader; // The Loader.
IDirectMusicSegment8 * m_pSegment; // Currently loaded Segment.
// Only one object type is active at a time. This reflects the
// current choice in LoaderView's type drop-down list.
OBJECT_TYPE m_otSelected; // Currently selected object type.
// We maintain a table of enumerated objects of the current selected type.
DMUS_OBJECTDESC * m_aObjectTable; // Table of object descriptors.
DWORD m_dwObjectCount; // Size of table.
// We use a static array of class IDs to identify class type in calls to loader.
static const GUID * m_saClassIDs[OBJECT_TYPES];
// We need a flag for each object type to indicate whether caching is enabled.
BOOL m_afCacheEnabled[OBJECT_TYPES];
// Current search directory.
WCHAR m_wzSearchDirectory[DMUS_MAX_FILENAME];
};
Initialization
CAudio’s Init() method creates the Loader and Performance and
initializes both. It also disables caching for Segments to conserve
memory.
HRESULT CAudio::Init()
{
//Init COM.
CoInitialize(NULL);
// Create loader.
HRESULT hr = CoCreateInstance(
CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC,
IID_IDirectMusicLoader8, (void**)&m_pLoader);
if (SUCCEEDED(hr))
{
// Turn off caching of Segments.
m_pLoader->EnableCache(CLSID_DirectMusicSegment,false);
// Create performance.
hr = CoCreateInstance(
CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC,
IID_IDirectMusicPerformance8, (void**)&m_pPerformance);
}
if (SUCCEEDED(hr))
{
// Once the performance is created, initialize it.
// We'll use the standard music reverb audio path and give it 128 pchannels.
hr = m_pPerformance->InitAudio(NULL,NULL,NULL,
DMUS_APATH_SHARED_STEREOPLUSREVERB, // Default AudioPath type.
128,DMUS_AUDIOF_ALL,NULL); }
The Loader n 165
if (FAILED(hr))
{
// No luck, give up.
Close();
}
return hr;
}
We also have a static array of class IDs that translate from predefined
integers to GUIDS, since it is much more convenient to work with
predefined integers because they directly correlate with positions in
the UI’s drop-down list of object types.
const GUID *CAudio::m_saClassIDs[OBJECT_TYPES] = {
&CLSID_DirectMusicAudioPathConfig, // OT_AUDIOPATH
&CLSID_DirectMusicBand, // OT_BAND
&CLSID_DirectMusicChordMap, // OT_CHORDMAP II
&CLSID_DirectMusicContainer, // OT_CONTAINER
Unit
&CLSID_DirectMusicCollection, // OT_DLS
&CLSID_DirectMusicSegment, // OT_SEGMENT
&CLSID_DirectMusicStyle, // OT_STYLE
&CLSID_DirectMusicGraph, // OT_WAVE
&CLSID_DirectSoundWave // OT_GRAPH
};
SetObjectType()
SetObjectType() is called when a new file class is requested for dis-
play. It sets the variable m_otSelected to the new object type and
then builds the m_aObjectTable table of DMUS_OBJECTDESC
structures by using the Loader’s EnumObject() method.
DWORD CAudio::SetObjectType(OBJECT_TYPE otSelected)
{
// We track the objects in an array, so delete the current array.
delete [] m_aObjectTable;
m_aObjectTable = NULL;
m_dwObjectCount = 0;
m_otSelected = otSelected;
HRESULT hr = S_OK;
We need to know how many objects the Loader has of the current
type before we can allocate the memory for the array. However, the
Loader does not provide a routine for directly returning this informa-
tion. Therefore, we use the enumeration method and scan until it
returns S_FALSE, indicating that we passed the range.
166 n Chapter 9
Finally, return the number of objects counted. The UI can use this
information when displaying the list of objects.
return m_dwObjectCount;
}
GetObjectInfo()
Once the UI has called SetObjectType() to create the table of object
descriptors, it needs to populate its list view of those same objects.
It calls GetObjectInfo() to iterate through the table and retrieve the
name and cache status of each object.
void CAudio::GetObjectInfo(DWORD dwIndex, char *pszName, BOOL *pfCached)
{
if (m_aObjectTable && (dwIndex < m_dwObjectCount))
{
The Loader n 167
Unit
// Uh oh. No name.
else
{
strcpy(pszName,"<No Name>");
}
// The DMUS_OBJ_LOADED flag indicates that the resource is currently cached.
*pfCached = (m_aObjectTable[dwIndex].dwValidData & DMUS_OBJ_LOADED) && TRUE;
}
}
ReleaseItem()
ReleaseItem() tells the Loader to release the selected item from its
cache. The item will continue to be listed, but the Loader will no lon-
ger point to it and have it AddRef()’d. Should the item be requested a
second time, the Loader will create a new instance and load it.
void CAudio::ReleaseItem(DWORD dwItem)
{
IDirectMusicObject *pObject;
if (SUCCEEDED(m_pLoader->GetObject(&m_aObjectTable[dwItem],
IID_IDirectMusicObject,
(void **) &pObject)))
{
m_pLoader->ReleaseObject(pObject);
pObject->Release();
}
}
168 n Chapter 9
ReleaseAll()
ReleaseAll() tells the Loader to clear all objects of the currently
selected type from its cache. All objects not currently AddRef()’d by
the application will go away. This is the same as calling Release-
Item() for every item in the list.
void CAudio::ReleaseAll()
{
m_pLoader->ClearCache(*m_saClassIDs[m_otSelected]);
}
ChangeCacheStatus()
You can tell the Loader to enable or disable caching on a class-by-
class basis. ChangeCacheStatus() tells the Loader to flip the caching
for the currently selected type. It does so by tracking the cache state
with an array of Booleans, one for each object type. It flips the state
and calls the Loader’s EnableCache() method to enable or disable the
caching.
void CAudio::ChangeCacheStatus()
{
// Flip the state in the m_afCacheEnabled table.
m_afCacheEnabled[m_otSelected] = !m_afCacheEnabled[m_otSelected];
// Call EnableCache with the new state.
m_pLoader->EnableCache(*m_saClassIDs[m_otSelected],m_afCacheEnabled[m_otSelected]);
}
ScanDirectory()
ScanDirectory() tells the Loader to look in the current working
directory and read all files of the currently selected type. To do so, it
calls the Loader’s ScanDirectory() method and passes it the wildcard
* extension. This tells the Loader to open every single file in the
directory and attempt to parse it with the object defined by the class
ID. If the object cannot parse the file, the Loader does not include it
in the list. For example, the Segment object knows how to parse
Segment, MIDI, and wave files. It fails with all other files, and so the
Loader does not include the failed files in the resulting list. There is
one case where multiple types can parse the same file. Both the
The Loader n 169
Unit
As you probably know, all buttons, menus, dialogs, and other
user interface elements are stored in the program’s executable as
resources. You create resources in a resource editor that provides
tools for visually laying out the program’s user interface. The
resource editor is part of the development environment. The final
stage of program compilation binds the resource data into the exe-
cutable. The application makes standard calls to access and use the
resource data. The mechanism for storing and retrieving resource
elements is not limited to menus, icons, and their ilk. You can store
just about any data you want in a resource and have your application
access it when needed. This provides the convenience of binding all
data directly into the executable so there are no extra files to distrib-
ute with the program.
To demonstrate this, let’s use our familiar HelloWorld example
from the previous chapter and embed the wave as a binary resource.
Once it uses resources, our HelloWorld application can no longer be
a console app (a console application is a simple command-line pro-
gram that does not make any calls into the Windows libraries). Calls
to retrieve resources require the Windows APIs. The biggest change
in that regard is that main() is replaced with WinMain(). Then, to
insert the wave, use the resource editor’s option to import a binary
file as a resource (it actually has a specific option for wave files but
not for other DirectMusic media types).
170 n Chapter 9
// Initialize COM
CoInitialize(NULL);
// Create the loader. This is used to load audio objects from files.
CoCreateInstance(CLSID_DirectMusicLoader, NULL,
CLSCTX_INPROC, IID_IDirectMusicLoader8,
(void**)&pLoader);
// Create the performance. This manages the playback of sound and music.
CoCreateInstance(CLSID_DirectMusicPerformance, NULL,
CLSCTX_INPROC, IID_IDirectMusicPerformance8,
(void**)&pPerformance );
ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC);
The Loader n 171
// Must let the loader know what kind of object this is.
ObjDesc.guidClass = CLSID_DirectMusicSegment;
// The only valid fields are the class ID and memory pointer.
ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY;
// Get the memory pointer from the resource.
ObjDesc.pbMemData = (BYTE *) LockResource(hRes);
// Get the memory length from the resource.
ObjDesc.llMemLength = SizeofResource(NULL, hFound);
// Now, read the Segment from the resource.
if (SUCCEEDED(pLoader->GetObject(
&ObjDesc, IID_IDirectMusicSegment8,
(void**) &pSegment )))
{
// Install the wave data in the synth.
pSegment->Download(pPerformance);
Unit
NULL,NULL,0,0,NULL,NULL,NULL);
// Done with the performance. Close it down, and then release it.
pPerformance->CloseDown();
pPerformance->Release();
This all works fine and dandy if the resource you are loading is com-
pletely self contained, which is the case of our example wave file.
However, if the Segment references other files, they too need to be
prepared as resources and handed to the Loader using SetObject()
prior to the call to GetObject(). Follow exactly the same steps to pre-
pare the referenced resources to initialize the resource and
172 n Chapter 9
{
IDirectMusicSegment8 *pSegment = NULL;
DMUS_OBJECTDESC ObjDesc;
ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC);
// Must let the loader know what kind of object this is.
ObjDesc.guidClass = CLSID_DirectMusicSegment;
// The only valid fields are the class ID and memory pointer.
ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY;
// Assign the memory pointer.
ObjDesc.pbMemData = pbData;
// Get the memory length from the resource.
ObjDesc.llMemLength = dwLength;
// Now, read the Segment from the resource.
if (SUCCEEDED(g_pLoader->GetObject(
&ObjDesc, IID_IDirectMusicSegment8,
The Loader n 173
Unit
NULL,NULL,0,0,NULL,NULL,NULL);
}
// Return the Segment. Be sure to unload and release it later.
return pSegment;
}
Segments
175
176 n Chapter 10
Voice-over
Music Music
Music Effect
Music Effect
Time
But, that’s not enough. In any interactive sound scenario, there are
requirements for reactivity, context, variability, and structure for
both music and sound effects. Reactivity means that the sounds or
music must be capable of responding to immediate actions and
changes in the story. Context means that new sounds or musical
phrases take into consideration the rest of the audio score, so they
reinforce each other, rather than clash. Variability means that sounds
and music don’t sound exactly the same every time, which would
quickly become unnatural. Structure means that sounds and espe-
cially music have different levels of inherent timelines.
Let’s look at sound effects first. By definition, sound effects are
reactive, driven by the unfolding story. When something happens, be
it a car crashing or a monster belching, the game immediately plays
the appropriate sound. But even then, greater sophistication is
highly desirable. The human ear is remarkably quick to identify a
perfectly repeated sound, and after the fourth belch it starts to sound
as if the monster swallowed a tape recorder instead of a deliciously
seasoned and sautéed princess. So, variation in the sound is impor-
tant. Context is important too. If the monster is sad, it would help to
reinforce the emotion in the sound, perhaps by bending the belch
down in pitch.
Music is significantly more challenging. In order to be effective
at reinforcing the user’s experience, a nonlinear score should track
the flow of the media that it supports. Yet, music has multiple layers
Segments n 177
Unit
What does this all mean? The Segment architecture must be rel-
atively sophisticated in order to support not just playing sounds and
music snippets in reaction to events in the application (i.e., game-
play) but also to play them in such a way that they still seem to have
structure, with variation and respect for context. While we are at it,
let’s add another requirement. The Segment mechanism must sup-
port different sound- and music-producing technologies, from MIDI
and DLS to wave playback, from style-based playback to straight
sequencing.
The DirectX Audio Segment architecture was designed to solve
all of these requirements. The Segment, which is manipulated via
the IDirectMusicSegment8 interface, is capable of handling a wide
range of sound-producing technologies, from individual waves to
Style playback. Multiple Segments can be scheduled for playback in
ways that allow their sounds and music to merge, overlap, align, and
even control each other.
Anatomy of a Segment
A Segment is little more than a simple structure that defines a set of
playback parameters, such as duration and loop points for the music
or sounds that it will play. But a Segment can contain any number of
Tracks, each representing a different technology. By technology, I
mean Tracks that play Styles, Tracks that set the tempo, and much
178 n Chapter 10
more. It is choice of Track types and data placed within them that
define a Segment.
If you are familiar with creating Segments in DirectMusic Pro-
ducer, you know that a Segment is constructed by choosing from a
wide palette of Tracks and adding them to the Segment. You know
that some Track types generate sound (Sequence, Style, Wave, etc.),
while others silently provide control information (Chord, Parameter,
Time Signature, etc.), and yet others actively control playback (Seg-
ment Trigger, Script). There are currently about a dozen Track types
included with DirectMusic.
I like to think of a Segment as a little box that manages a list of
Tracks, each represented by a long box indicating its list of actions
over time.
IDirectSegment8
Segment
Wave Track
Band Track
Sequence Track
Time
The Segment in Figure 10-2 has a Wave Track that triggers one or
more waves to play across the duration of the Segment. It also has a
Time Signature track, which provides timing information. It has a
Band Track, which determines which DLS instruments to play and
their initial volume and pan, and it has a Sequence Track, which plays
a sequence of MIDI notes. So, this Segment plays a sequence of
waves as well as MIDI notes, and it provides time signature informa-
tion that the Performance can use to control playback of this
Segment. In a while, we learn that this can also control the playback
of other Segments.
Segments n 179
Unit
by a class ID and associated data chunk. For each Track in the file, it
calls CoCreateInstance() to create the Track object. It then calls the
Track’s IPersistStream::Load() method and passes it the Track’s data
chunk in the form of the IStream at the current file position (see
Chapter 9 to better understand file I/O and the Loader). The Track
reads its data and passes control back to the Segment, which contin-
ues to the next Track, assembling a list of created and loaded Tracks
as it goes.
þ Note The Track types do not show up as cached object types in the
Loader. Because they are always embedded within the Segment, there is
no need for the Loader’s linking and caching mechanisms.
Performance Channels
At this point, it’s probably a good idea to introduce the concept of
Performance channels, or pchannels. Since everything that the Per-
formance plays goes through a MIDI-style synthesizer, a lot of MIDI
conventions are used. One is the concept of a MIDI channel.
In the MIDI world, each synthesizer can have up to 16 channels.
Each channel represents a musical instrument. Up to 16 different
instruments can be played at the same time by assigning each to one
of the 16 channels. Every performance MIDI message includes the
address of the channel. So, the program change MIDI message tells
the channel which instrument to play. The MIDI Note On and Note
Off messages trigger playing notes using that instrument. More than
one note can play at a time on one channel. Additional MIDI mes-
sages adjust the volume, pan, pitch bend, and many other parameters
for all notes playing on the addressed channel.
There are some powerful things that you can do with this.
Because each message carries its channel address, you can inject
messages into the stream that modify the performance. For example,
you can adjust the volume or reverb of a MIDI performance by send-
ing down the appropriate MIDI control change messages without
touching the notes themselves.
This is all marvelous, but 16 MIDI channels is not enough.
There’s an historical reason that MIDI is limited to 16 channels.
When the MIDI specification was created back around 1982, it was
purely a hardware protocol designed to connect electronic musical
instruments together via a standard cable. To maximize bandwidth,
the number of channels was limited to four bits, or 16. At the time, it
was rare to find a musical device that could play more than one
instrument at a time, let alone 16, so it was reasonable. Twenty
years later, a 16-instrument limit is horribly inadequate.
DirectMusic’s Core Layer addresses this by introducing the con-
cept of channel groups. Each channel group is equivalent to one set
of 16 MIDI channels. In turn, the Performance supports unlimited
channels, called pchannels, and manages the mapping of these from
an AudioPath pchannel (each AudioPath has its own virtual channel
space) to a unique channel group + MIDI channel on the synth.
What this means is every Track in every Segment that plays on the
same AudioPath is sending commands to the synth on the same
Segments n 181
Unit
Inter-Track Communication
While some Track types generate messages on pchannels to send to
the synth, others provide control information. Examples of control
information are time signatures, which are used to identify the next
beat and measure. Groove level is another example, which controls
the intensity level of Style playback Tracks.
As it turns out, control information is always provided by one
track to be used by another. Groove Level, Chord, and Mute Tracks,
for example, are used to determine the playback behavior of a Style
Track. At first glance, it might make sense to just put the groove,
chord, and mute information directly in the Style Track to keep
things simple. But, it turns out that by sharing this information with
multiple customer Tracks, some really powerful things can be
accomplished. For example, multiple Segments can play at the same
time and follow the same chord progression. More on how this
works in a bit…
So, we have a mechanism for cross-track communication. It’s
done via an API for requesting control information, and it’s called
GetParam(). GetParam() methods are provided on the IDirect-
MusicPerformance, IDirectMusicSegment, and IDirectMusicTrack
interfaces. When a Track needs to know a particular parameter, it
calls GetParam() on the Performance, which in turn identifies the
appropriate Segment and calls GetParam() on it. That, in turn, calls
182 n Chapter 10
Primary Segment
A primary Segment is intended primarily for music. Only one pri-
mary Segment may be playing at a time, and it is considered the core
of the music performance. In addition to the music that it plays, the
primary Segment provides the underlying Tempo, Time Signature,
and, if applicable, Chord and Groove Level Tracks. By doing so, it
establishes these parameters for all music in all Segments. So, any
Playback Tracks within that same Segment retrieve their control
information from the neighboring Control Tracks as they play. No
two primary Segments can play at the same time because that would
cause the Control Tracks to conflict. If a primary Segment is playing
and a new one is scheduled to play, the currently playing Segment
stops exactly at the moment that the new Segment starts.
Segments n 183
Controlling Segment
A controlling Segment has the unique ability to override the primary
Segment’s Control Tracks without interrupting its playback. For
example, a controlling Segment with an alternate chord progression
can be played over a primary Segment and all music shifts to the
replacement key and chord until the controlling Segment is stopped.
So, suddenly the tonal character of the music can make a shift in an
emotionally different direction. Figure 10-3 demonstrates how a con-
trolling Segment with an alternate chord progression overrides the
chords in two subsequent primary Segments.
Likewise, Tempo, Muting, and Groove Level can be overridden.
This is a very powerful way to alter the music in real time, perhaps
in response to stimuli from the host application (i.e., in response to II
gameplay). Like the primary Segment, the controlling Segment is
Unit
primarily for use with music, not sound effects.
Em F#M
CM7 Dm Em F#M Dm GM
Secondary Segment
A secondary Segment also has sound-producing Tracks, but it is
devoid of Control Tracks. Importantly, any number of secondary Seg-
ments can be played at the same time. This is important for playing
sound effects as well as music. With sound effects, the usage is sim-
ple. Each secondary Segment contributes additional sounds on top of
the others playing in the same AudioPath. With music, it can be a
184 n Chapter 10
Playing Segments
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.
SegmentState
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 Seg-
ment 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 pro-
vides 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
Segments n 185
PlaySegmentEx()
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,
DWORD dwFlags,
// Optional transition Segment.
// Control flags.
II
__int64 i64StartTime, // Optional start time.
Unit
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.
);
Unit
StopEx()
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:
n An individual instance of the playing Segment (the
SegmentState).
n All instances of the playing Segment.
n All Segments playing on a specific AudioPath.
n 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.
);
Introducing Jones
This chapter’s programming example is the first installment of a pro-
gram named Jones. Jones starts by loading Segments and exploring
all the options and permutations for playing them (see Figure 10-4).
As we progress through the book, Jones grows to add functionality,
including scripting and AudioPaths, as we dig deeper into DirectX
Audio features.
Segments n 189
II
Unit
Figure 10-4. Jones demonstrates loading, configuring,
and playing Segments.
Starting Jones
You can run Jones by either going to the Unit II\10_Segments direc-
tory and building Jones or running SegmentJones.exe directly from
the Bin directory.
Loading a Segment
Use the Open… button to read a Segment into Jones. This opens a
File dialog, where you can choose a Segment, MIDI, or wave file to
load. For the purposes of this walk-through, try
StyleSegment.sgt from the Media directory.
The tree view displays the loaded Segment.
To the left of the Segment is a plus sign, indicat-
ing that it has children Segments. Go ahead and
click on it.
190 n Chapter 10
These are also Segments that you can play, and indeed they are
designed to be played at the same time as the main Segment. Where
did they come from? If the Segment references any Styles (which
StyleSegment.sgt does), then the Style’s Bands and motifs are con-
verted into Segments and displayed as additional Segments under
the Segment you loaded from a file. You can play any of these Seg-
ments. This feature in Jones quickly gets you closer to the inherent
interactivity of DirectMusic.
Styles typically include a series of Bands and motifs, all designed
to be played in conjunction with the main Style Segment but with
very different intentions. The Bands change the timbre set. In other
words, each provides a different set of instruments with a different
mix so that by changing Bands you can quickly make strong changes
to the music as it plays. The motifs are melodic fragments that are
designed with the Style in mind and layer effectively on top of it. By
playing the motifs, you add musical elements to the performance. So,
by displaying all of the Bands and motifs that come packaged with
the Style, Jones immediately gives you a quick way to start interac-
tively jamming and appreciating the layered and interactive approach
to delivering music.
Use these controls to set up how you want the currently selected
Segment to play. Click on the Play button to hear the Segment. You
can also double-click on the Segment’s name in the tree view to play
it.
Segments n 191
Unit
represents a different stage in the process of playing a Segment. We
start at the current time and work backward through the sound pro-
duction process:
n Current time (black): This is the time at which you hear the
sound. Notice that at the very moment that a Segment rectangle
hits this, you start hearing it play. It would be simpler if we could
use just this for all our timing and ignore latency, queue, and pre-
pare times. But DirectMusic spreads out the different operations
both to increase efficiency (some operations can be done in large
chunks to reduce overhead) and to provide control opportunities
(for example, tools that can adjust the timing of notes in either
direction).
n Latency time (red): This is the time at which the DirectMusic
synth, which is implemented in software, actually does its work.
Depending on the hardware, this is anywhere from a few milli-
seconds to 70ms after the current time. The synth wakes up at
regular intervals, typically every ten or so milliseconds, and pro-
cesses another buffer of data to feed out the AudioPath into the
effects filters. However, because the MIDI events and waves are
all time stamped, the synth places the sounds appropriately in
the buffer at sample accurate resolution, even though it pro-
cesses the audio in relatively large lumps. Latency time is partic-
ularly important to understand because it represents the earliest
192 n Chapter 10
Jones Code
The source code for Jones can be split into two categories: UI and
DirectX Audio management. Since this book is about programming
DirectX Audio (not programming Windows with MFC), the source
code for Jones’ UI is not discussed. Extra effort has been made to
separate functionality so that just about everything we care about is
covered by the audio management code (starting with the CAudio
class), and there’s no need to discuss the UI. Of course, the UI code II
is included with the projects on the companion CD and it is docu-
Unit
mented, so you are welcome to use it in any way you’d like.
}
~CSegState()
{
m_pSegState->Release();
}
// We keep a linked list of SegmentStates.
CSegState *GetNext() { return (CSegState *) CMyNode::GetNext(); };
// Access methods.
CSegment *GetSegment() { return m_pSegment; };
IDirectMusicSegmentState8 *GetSegState() { return m_pSegState; };
private:
CSegment * m_pSegment; // The Segment that this is playing.
IDirectMusicSegmentState8 * m_pSegState; // The DirectMusic SegmentState object.
};
/* CSegStateList
Based on CMyList, this manages a linked list of CSegStates.
*/
// We create Segments from file as well as extract them from embedded Bands and motifs.
// We'll keep track of how a Segment was created for display purposes.
SEGMENT_BAND = 1,
SEGMENT_MOTIF = 2,
SEGMENT_FILE =3
} SEGMENT_SOURCE;
/* CSegment
This manages one Segment.
Segments can also carry a list of children Segments.
*/
Unit
void SetFlags(DWORD dwFlags) { m_dwPlayFlags = dwFlags; };
DWORD GetFlags() { return m_dwPlayFlags; };
void GetName(char *pszBuffer);
SEGMENT_SOURCE GetType() { return m_ssType; };
CSegment * EnumChildren(DWORD dwIndex) {
return (CSegment *) m_ChildSegments.GetAtIndex(dwIndex); };
bool HasChildren() { return !m_ChildSegments.IsEmpty(); };
CSegment *GetTransition() { return m_pTransition; };
void SetTransition(CSegment * pTransition) { m_pTransition = pTransition; };
bool HasEmbeddedAudioPath();
IDirectMusicSegment8 * GetSegment() { return m_pSegment; };
private:
IDirectMusicSegment8 * m_pSegment; // The DirectMusic Segment that
// this manages.
DWORD m_dwPlayFlags; // DMUS_SEGF_ Playback flags.
DMUS_OBJECTDESC m_Desc; // DirectMusic object descriptor.
// Used by Loader.
CSegmentList m_ChildSegments; // Children Segments of this one.
CSegment * m_pTransition; // Transition Segment to
// autotransition to this one.
SEGMENT_SOURCE m_ssType; // Type of Segment (how it was
// created.)
};
Now that we’ve covered the data structures, let’s walk through the
different parts of the program and examine the code.
Unit
pendently as secondary Segments to layer melodies on top (motifs)
and change the instruments (Bands).
Let’s look at the CAudio::LoadSegment() routine. CAudio::Load-
Segment() first takes the file name to be loaded and extracts the
path, which it hands to the Loader via a call to IDirectMusicLoader::
SetSearchDirectory(). This ensures that referenced files, such as
Styles, and DLS instrument collections will be easily found and con-
nected to the Segment. CAudio::LoadSegment() then loads the
Segment, calls Init() on the Segment, and places it in CAudio’s Seg-
ment list.
CSegment *CAudio::LoadSegment(WCHAR *pwzFileName)
{
wcscpy(m_wzSearchDirectory,pwzFileName);
WCHAR *pwzEnd = wcsrchr(m_wzSearchDirectory,'\\');
if (pwzEnd)
{
// If pwzFileName includes a directory path, use it to set up the search
// directory in the Loader.
// The Loader will look here for linked files, including Styles and DLS
// instruments.
*pwzEnd = 0;
m_pLoader->SetSearchDirectory(GUID_DirectMusicAllTypes,m_wzSearchDirectory,FALSE);
}
CSegment *pSegment = NULL;
IDirectMusicSegment8 *pISegment;
// Now, load the Segment.
if (SUCCEEDED(m_pLoader->LoadObjectFromFile(CLSID_DirectMusicSegment,
198 n Chapter 10
IID_IDirectMusicSegment8,
pwzFileName,
(void **) &pISegment)))
{
// Create a CSegment object to manage playback.
// This also recursively searches for embedded Band and Style motif
// Segments which can be played as secondary Segments.
pSegment = new CSegment(pISegment,m_pPerformance,SEGMENT_FILE);
if (pSegment)
{
m_SegmentList.AddTail(pSegment);
}
pISegment->Release();
}
return pSegment;
}
{
Segments n 199
Unit
// If this is a motif, assume secondary Segment and allow it to trigger
// to the time signature even when there is no primary Segment playing.
if (ssType == SEGMENT_MOTIF)
{
m_dwPlayFlags |= DMUS_SEGF_SECONDARY | DMUS_SEGF_TIMESIG_ALWAYS;
}
// If this is a band, just assume secondary.
else if (ssType == SEGMENT_BAND)
{
m_dwPlayFlags |= DMUS_SEGF_SECONDARY;
}
m_dwPlayFlags &= ~DMUS_SEGF_AUTOTRANSITION;
// Get the object descriptor from the Segment. This includes the name.
IDirectMusicObject *pIObject = NULL;
pISegment->QueryInterface(IID_IDirectMusicObject,(void **)&pIObject);
if (pIObject)
{
pIObject->GetDescriptor(&m_Desc);
pIObject->Release();
}
m_ssType = ssType;
// Now, the really fun part. We're going to see if this has any child Segments
// that would come with the Style, if this is a Style playback Segment.
// If so, we can get the motifs and Bands from the Styles and install them as
// Segments.
IDirectMusicStyle8 *pStyle = NULL;
if (SUCCEEDED(m_pSegment->GetParam(
GUID_IDirectMusicStyle,-1,0,0,NULL,(void *)&pStyle)))
{
200 n Chapter 10
{
pSegment->Init(pIMotif,pPerf,SEGMENT_MOTIF);
wcscpy(pSegment->m_Desc.wszName,wszName);
pSegment->m_Desc.dwValidData = DMUS_OBJ_NAME;
m_ChildSegments.AddHead(pSegment);
}
pIMotif->Release();
}
}
else
{
break;
}
}
pStyle->Release();
}
} II
þ
Unit
Note If this seems like a lot to do just to get a Segment ready to play,
rest assured that your perception is correct and, no, you wouldn’t
normally need to do all these things. As we’ve already seen in previous
chapters, all you really need to do is load and then download your
Segment. So, don’t feel like memorizing these steps is a prerequisite to
DirectMusic guruhood. However, walking through all this extra fancy
stuff, especially extracting the Bands and motifs, helps to better grasp
the flexibility and inherent opportunities presented by the API.
Segment Options
When you click on a Segment in the tree view, Jones displays every-
thing it knows about the Segment in the box to the right. Almost all
of this is the data from CSegment’s m_dwPlayFlags field, which is
used in the call to PlaySegmentEx(). The display breaks the flags
into mutually exclusive groupings. For example, a Segment can
either play as a primary, secondary, or controlling Segment, so the
two flags that set this are assigned as determined by the choice in
the Mode drop-down list. Notice that StyleSegment.sgt has been set
to play as a primary, whereas the motif and Band Styles underneath
it have all been set to secondary.
Let’s walk through the Segment options and discuss each selection
and how it controls the flags that are passed to PlaySegmentEx(),
starting with the Mode control.
Mode
notice that where the second Segment starts, the first Segment
stays red, since it no longer plays beyond that point.
Unit
changes in the primary Segment so that the motifs always sound
musically correct.
Resolution
There is a whole series of options available for when you’d like the
new Segment to begin playing. For musical applications, it’s very
important that the new Segment line up in an appropriate manner.
Typically, you want the Segment to play as soon as possible, yet each
Segment has different criteria as to when it should rhythmically line
up. While one Segment might sound great synchronized on a beat
boundary, another might need to align with a measure in order to
work well. Select this with the Resolution control:
Unit
it’s too long to wait for the end of the measure to start a change
in the music. Set this with the DMUS_SEGF_BEAT flag.
n Measure: This sets the start time to line up with the next mea-
sure, or bar. This is typically the most useful for transitioning
between Segments because it keeps the musical meter intact.
Some secondary Segments, which have relatively long and com-
plicated phrasing, might require alignment to the measure to
sound right. Set this with the DMUS_SEGF_MEASURE flag.
n Marker: This sets the start time to line up with a specific
marker placed in a Marker Track in the currently playing primary
Segment. This is useful in situations where transitions on mea-
sure boundaries are not acceptable because they might break up
the musical phrases. Since markers can be placed anywhere,
regardless of the time signature, they can be used in different
ways. For example, markers can be used to set trigger points for
secondary Segments on specific beats. Set this with the DMUS_
SEGF_MARKER flag.
n Segment End: You can set a Segment to start playback when
the current primary Segment finishes. This is useful in two
ways: Segment End can be used to schedule a Segment to play
when the current one finishes or it can be used in conjunction
with the alignment flags (see the section titled “Aligned Cut In”)
206 n Chapter 10
to switch between two Segments that start at the same time. Set
this with the DMUS_SEGF_SEGMENTEND flag.
n End of Queue: We have one final option that looks remarkably
similar to Segment End but is useful in a different way. If you’d
like to queue up a series of Segments to play one after the other,
play them with the DMUS_SEGF_QUEUE flag. This only
applies to primary Segments. This is very useful if you have a
series of Segments that make up the music for a scene and you’d
like to just queue them all up at the beginning and then forget
about it. You can even set up a song arrangement with repeats of
the same Segments in different places. DirectMusic will play the
Segments back in the order in which you queued them with the
PlaySegmentEx() call. However, you can break out of the queue
as soon as you need to. Play a primary Segment that does not
have the DMUS_SEGF_QUEUE flag set, and DirectMusic
immediately flushes all Segments queued in the future and
replaces them with the new Segment.
Delay
You can specify the earliest time to use to calculate the timing reso-
lution for when a Segment should begin playing. Choose a Segment
and, as you read about each delay option, try it out. Watch the
timeline display to see the different delays.
Segments n 207
Unit
some point after the Queue Time. Queue Time is important
because it is the last chance to invalidate (turn off) pmsgs just
before they go down to the synth. This is useful if the start of the
new Segment causes invalidation of another Segment, either
because as a primary Segment it overrides the previously play-
ing primary Segment or as a controlling Segment it requires a
regeneration of notes in currently playing Segments. This is the
default timing for primary and controlling Segments.
n Prepare Time: This option sets the DMUS_SEGF_AFTER-
PREPARETIME flag, indicating that the new Segment must not
start playing until after Prepare Time. Although setting Prepare
Time incurs greater delay, it can be useful in several situations:
• If the invalidation caused by starting after Queue Time is
unsatisfactory. Sometimes, invalidations caused by controlling
Segments or new primary Segments chop off and restart notes
in ways that don’t work.
• When a Segment has a streaming wave, it needs more time to
preload the start of the wave if downloading is disabled for that
wave (which dramatically reduces memory overhead) or if it
picks up partway through the Segment.
208 n Chapter 10
Time Signature
Transition
Aligned Cut In
Sometimes it would be nice to start playback of a Segment immedi-
ately, yet keep it rhythmically aligned with a larger resolution. For
example, you might have a motif that adds a musical layer in
response to some user action that needs to align to the measure but
needs to be heard immediately. Or you’d like to transition to a new
primary Segment immediately, yet keep the meter aligned.
In order to use this feature, you need to do two things:
1. Set the timing resolution for the logical start of the new Seg-
ment. In other words, set the timing resolution as if the new
Segment played from the start. To do this, use one of the
resolution flags that we have already discussed. This ensures
that the new Segment will still line up rhythmically, even II
though it may cut in anywhere in the Segment.
Unit
2. Set the resolution at which the cut-in may occur. This should
be a smaller size than the resolution flag. Why? Resolution
ensures that the two Segments line up rhythmically, allowing
a cut-in to occur at a finer resolution (in other words sooner),
which is the whole purpose of this feature.
The Aligned Cut In menu enables this feature and provides choices
for the timing of the cut.
predefined point partway into it. You know it starts out partway
because the start of the Segment is green, indicating it was never
played. Also note that the start of the new Segment is all the way off
the left side of the screen because it has aligned with the start of the
first instance of it playing.
Unit
ing partway through.
Invalidation
By default, when a primary or controlling Segment plays, it causes
an invalidation of all currently playing Segments. This means that all
notes that were generated at prepare time but not yet committed to
the synth at queue time are flushed, and the Segment is told to
regenerate the notes. Why? Because a change in primary or control-
ling Segment usually means that something changed, and new notes
need to be generated to reflect that. For example, if the groove level
or chord changes, then previously generated notes are probably
wrong, causing cacophony. There are times when you don’t want
invalidation. Invalidation can cause an audible glitch when there are
sustained sounds, be they DLS instruments or straight waves.
Because of this, the introduction of a controlling or primary Segment
can sometimes have an unfortunate effect on other aspects of the
sound environment, sound effects in particular, which couldn’t care
less what the underlying chord change or groove level is.
There are several ways to deal with the various problems caused
by invalidation:
n Set the new Segment to play after prepare time, in which case
there are no invalidations. If the delay is painful, you can adjust it
downward with a call to SetPrepareTime(), but keep an ear out
for timing glitches. Numbers in the 200 to 300ms range, which
212 n Chapter 10
Use Path
A Segment can be authored with an embedded AudioPath. However,
that AudioPath is not automatically created and invoked each time
the Segment plays. The reason is simple: Creating multiple Audio-
Paths can be very CPU intensive, especially if they invoke lots of
audio effects. If you are going to play the same Segment over and
over again or if you intend to share that AudioPath with multiple
Segments, then it makes more sense to directly create the
AudioPath and manage its usage explicitly. But there are situations
where you would like an embedded AudioPath to be automatically
used, so there’s a flag to enable that. When a Segment has its
AudioPath embedded within itself, it carries everything necessary to
perform. There is no need to externally match up the Segment with
an appropriate AudioPath that may happen to have filters, etc., set
Segments n 213
Unit
in the Segment file at author time.
The DirectMusic Segment file format does not have a way to
define transition Segments, but the PlaySegmentEx() call does take
such a parameter. So, CSegment has a field, m_pTransition, which is
a pointer to a CSegment to be played as a transition into the parent
CSegment. m_pTransition is managed via the SetTransition() and
GetTransition() calls, which simply access the variable.
To display the name of the Segment, we have a slightly more
complex routine that retrieves the name from the DMUS_OBJECT-
DESC descriptor that was retrieved at the time the file was loaded
and converts from Unicode to ASCII. In some cases, the Segment
may not have a name because none was authored into it (typically
the case with MIDI and wave files). If so, it uses the file name. If all
else fails, it returns a descriptive error message based on the type of
Segment.
void CSegment::GetName(char *pszBuffer)
{
pszBuffer[0] = 0;
if (m_Desc.dwValidData & DMUS_OBJ_NAME)
{
// Convert from Unicode to ASCII.
wcstombs( pszBuffer, m_Desc.wszName, DMUS_MAX_NAME );
}
else if (m_Desc.dwValidData & DMUS_OBJ_FILENAME)
{
wcstombs( pszBuffer, m_Desc.wszFileName, DMUS_MAX_NAME );
214 n Chapter 10
In order to enable the Use Path check box, we have to find out
whether the Segment actually has an AudioPath configuration
embedded within it. The only way to do this is to actually call the
DirectMusic API to retrieve the configuration and return true if one
exists.
bool CSegment::HasEmbeddedAudioPath()
{
IUnknown *pConfig;
if (SUCCEEDED(m_pSegment->GetAudioPathConfig(&pConfig)))
{
pConfig->Release();
return true;
}
return false;
}
That covers all the routines for managing the Segment’s display data.
Now, let’s actually play the dang thing.
Segment playback is handled by the CAudio::PlaySegment()
method. This is relatively simple because we’re just retrieving the
parameters we need, calling DirectMusic’s PlaySegmentEx() rou-
tine, and then storing the returned IDirectMusicSegmentState.
First, check to see if there is a transition Segment. If so, get the
IDirectMusicSegment interface and set the DMUS_SEGF_
AUTOTRANSITION flag. Then, call PlaySegmentEx() and pass it
the transition Segment as well as an empty SegmentState interface
(IDirectMusicSegmentState). If the call succeeded, create a
CSegState object to track both the SegmentState and Segment that
spawned it. This will be used for tracking and eventually stopping it
later.
void CAudio::PlaySegment(CSegment *pSegment)
{
if (pSegment)
Segments n 215
{
// Is there a transition Segment?
IDirectMusicSegment8 *pTransition = NULL;
if (pSegment->GetTransition())
{
pTransition = pSegment->GetTransition()->GetSegment();
}
DWORD dwFlags = pSegment->GetFlags();
if (pTransition)
{
dwFlags |= DMUS_SEGF_AUTOTRANSITION;
}
IDirectMusicSegmentState8 *pISegState;
if (SUCCEEDED(m_pPerformance->PlaySegmentEx(
pSegment->GetSegment(), // Returns IDirectMusicSegment
NULL,pTransition, // Use the transition, if it exists.
dwFlags,
0,
// Playback flags.
// No time stamp.
II
(IDirectMusicSegmentState **) &pISegState, // Returned SegState.
Unit
NULL, // No prior Segment to stop.
NULL))) // No AudioPath, just use default.
{
CSegState *pSegState = new CSegState(pSegment,pISegState);
if (pSegState)
{
m_SegStateList.AddHead(pSegState);
}
pISegState->Release();
}
}
}
To stop the Segment, CAudio has two methods. You can stop an indi-
vidual instance of a playing Segment with CAudio::StopSegState(), or
you can stop all playing instances of the Segment with a call to
CAudio::StopSegment(). StopSegState() stops just the individual
instance of a playing Segment as managed by the CSegState object.
It uses the same timing resolution flags that were used to play the
Segment. For example, if a Segment has been set to play on a mea-
sure boundary, it will also stop on a measure boundary. Only some of
the play flags are legal for Stop. These are defined by STOP_FLAGS.
#define STOP_FLAGS (DMUS_SEGF_BEAT | DMUS_SEGF_DEFAULT | DMUS_SEGF_GRID | \
DMUS_SEGF_MEASURE | DMUS_SEGF_REFTIME | DMUS_SEGF_MARKER)
Unit
methods for retrieving each of the parameters via IDirectMusic-
Performance::GetParam() and converting the data into text strings
for display.
CAudio::GetTimeSig() finds out what the current time signature
is and writes it in a string. It calls IDirectMusicPerformance::Get-
Param with the GUID_TimeSignature command and DMUS_
TIMESIGNATURE structure to retrieve the time signature from
whatever Segment is generating it.
(long)TimeSig.bBeat,(long)TimeSig.wGridsPerBeat);
}
else
{
strcpy(pszText,"None");
}
return true;
}
return false;
}
Unit
DMUS_TEMPO_PARAM Tempo;
if (SUCCEEDED(m_pPerformance->GetParam(GUID_TempoParam,
-1,0,mtTime,NULL,(void *) &Tempo)))
{
// Tempo is a floating point number.
sprintf(pszText,"%3.2f",Tempo.dblTempo);
}
else
{
strcpy(pszText,"None");
}
return true;
}
return false;
}
However, for the purposes of this exercise, we will happily only dis-
play the chord name with the root of the first chord, and leave it at
that.
bool CAudio::GetChord(char *pszText)
{
if (m_pPerformance)
{
// Since we can get parameters across a broad range of time,
// we need to get the current play time, in music format, for our request.
MUSIC_TIME mtTime;
m_pPerformance->GetTime(NULL,&mtTime);
// Use GUID_ChordParam command and DMUS_CHORD_KEY structure
DMUS_CHORD_KEY Chord;
if (SUCCEEDED(m_pPerformance->GetParam(GUID_ChordParam,-1,0,mtTime,NULL,
(void *) &Chord)))
{
Segments n 221
// Display the root of the chord as well as its name. For the root, use a
// lookup table of note names.
static char szRoots[][12] =
{ "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B" };
DWORD dwRoot = Chord.SubChordList[0].bChordRoot + 12;
wsprintf(pszText,"%s%d%s",szRoots[dwRoot%12],dwRoot/12,Chord.wszName);
}
else
{
strcpy(pszText,"None");
}
return true;
}
return false;
}
II
We’ve learned a lot in this chapter. Segment playback is understand-
ably at the heart of DirectX Audio. When used for creating music, it
Unit
can be quite sophisticated. We’ve covered the scheduling and control
of Segment playback, in particular from a music perspective. If you
are using DirectMusic’s deeper music features, I encourage you to
play with Jones and refer back to this chapter as you proceed. A good
understanding of how these work will save you from a lot of head
scratching and trial-by-error programming later on.
This page intentionally left blank.
Chapter 11
AudioPaths
223
224 n Chapter 11
In fact, this was the status quo with DirectX 7.0. AudioPaths, intro-
duced in DirectX 8.0, set out to solve these issues.
Anatomy of an AudioPath
So what is an AudioPath? An AudioPath is a DirectMusic object that
manages the route that a musical element or sound effect takes. It
defines everything that is needed to get from the point when a Seg-
ment Track emits a pmsg (Performance message) to when the final
rendered audio finally leaves DirectSound as PCM wave data. This
includes everything from how many pchannels to use to the place-
ment of audio effects and their configuration.
An AudioPath can be as simple or sophisticated as you want it to
be. It can be a 3D path with nothing more than a DirectSound3D
module at the end for positioning, or it can be a complex path with
different pchannels assigned to different audio channels with real-
time audio effects processing.
Let’s dissect the AudioPath from top to bottom and learn about
all the possible steps. Because there’s so much that it can do, you
might find this a bit overwhelming. It’s not important that you mem-
orize all the steps. Just read this and get a feel for the possibilities.
Later, if you want to zero in on a particular feature, you can read
through it again.
The AudioPath can be broken down into two phases, before and
after the synth (see Figure 11-1). Before the synth, the play data is
managed in pmsg form inside DirectMusic’s Performance Layer.
Each pmsg identifies a particular sound to start or stop or perhaps a
control, such as which instrument to play or its volume at any point
in time. The synth converts the pmsg into wave data, at which point
we are in the second phase of the AudioPath where raw wave data is
fed through multiple streaming DirectSound buffers to be processed
via effects, optionally positioned in 3D, and finally mixed to generate
the output.
AudioPaths n 225
AudioPath
Unit
AudioPath
Performance
Segment
State Graph Graph Map Graph Map Synth
Wow! Does that look complicated. Is there danger of the pmsg mak-
ing it through in time for lunch, let alone alive? Have no fear; it’s not
as crazy as it looks. If you are worried about extra latency of CPU
overhead, don’t be. The internal implementation is lightweight and
efficient. Also, most of the objects in the path are optional, so their
steps are simply skipped when they don’t exist.
Let’s walk through the steps:
1. Track emits a pmsg. The pmsg is filled with appropriate data
and time stamped. There is a wide range of available pmsg
types, from MIDI note to wave.
226 n Chapter 11
7. Finally, the synth receives the MIDI data and renders it into
one or more channels of wave data. Note that even the type
of synthesizer and which pchannels it connects to is managed
by the AudioPath.
The cool thing is the AudioPath has access and control over almost
all of the steps in this process. This means that via the AudioPath
API, you can access most of these as well.
Let’s continue with the second phase of the AudioPath.
Unit
way.
AudioPath
Buffer
DMO Send
Mix-in
Buffer DMO
AudioPath Configurations
Now that we’ve seen all the wonder of what an AudioPath can do, we
need some way to define them. There are two ways to do this: II
n Create from a configuration file loaded from disk
Unit
n Create from a predefined set of useful AudioPath definitions
Predefined Paths
Because there are some really useful standard AudioPath designs
that people usually need, DirectMusic comes with a handful of pre-
defined AudioPaths that you can just ask for without first loading a
configuration file. These include:
n DMUS_APATH_DYNAMIC_3D: One bus that feeds into a
mono 3D Buffer. Every instance has its own unique Buffer.
Although you can play stereo waves and DLS instruments into
this path, they will always be rendered in mono, since Direct-
Sound 3D needs to place a mono sound in 3D space (so there is
no way to play stereo sounds in 3D.).
n DMUS_APATH_DYNAMIC_MONO: One bus feeding into a II
mono Buffer without 3D. Again, each instance has a unique
Buffer. Since there is only one bus out, MIDI pan control does
Unit
nothing. But you can use the Buffer Pan command to pan the
entire Buffer in the final mix.
n DMUS_APATH_DYNAMIC_STEREO: Two buses to a stereo
Buffer. The Buffer is not shared, so each instance is unique.
MIDI pan commands work here.
n DMUS_APATH_SHARED_STEREOPLUSREVERB: This is
a standard music AudioPath. It has two Buffers, both of which are
defined as shared, which means that no matter how many Audio-
Paths you create, they all share the same audio processing buff-
ers. The two Buffers are Dry Stereo, which receives the regular
panned left and right signals, and Reverb, which holds a reverb
DMO configured for music and receives the MIDI-controlled
reverb send as a mono input, with stereo out to the mixer. (The
reverb algorithm takes a mono signal and spreads it across the
stereo spectrum, among other things.)
To create a predefined AudioPath, call the Performance’s Create-
StandardAudioPath() method and pass it the identifier for the
predefined type that you’d like. Unlike AudioPath configurations,
which have the number of pchannels built in, the standard types do
not. One music AudioPath might need only 16 pchannels, while
another might require 100. So, CreateStandardAudioPath() also
requires a requested number of pchannels as a parameter.
232 n Chapter 11
Note that if there are multiple copies of the same AudioPath, you
don’t need to download to each one. Downloading to one will get the
instruments and waves in place in the synth for use by all. This is a
common misconception. Also, it’s actually very rare to be using
more than one synth. So, in the 99 percent chance where you are
only using the standard Microsoft DLS2 synth, the easiest solution is
to download to the Performance and not worry about the AudioPaths.
In fact, that is exactly what Jones does.
Playing on an AudioPath
You can play Segments directly on an AudioPath. Or, you can make it
the default AudioPath for the Performance, in which case all Seg-
ments will default to playing on it. To play a Segment directly on the II
AudioPath, pass it as a parameter to PlaySegmentEx():
Unit
pPerformance->PlaySegmentEx(
pSegment,NULL,NULL,0,0,NULL,NULL,pAudioPath);
Embedded AudioPaths
Using DirectMusic Producer, it is possible to embed an AudioPath in
a Segment. This is handy because it lets you attach everything nec-
essary to play a Segment to the Segment itself. For example, if the
Segment were designed to play with a particular reverb on some of
the MIDI channels and a combination of compression and parametric
EQ on some other channels, you could create an AudioPath configu-
ration with the required reverb, compression, and EQ settings and
place it within the Segment. Then, on playback, you’d get exactly the
configuration of effects that was designed with the Segment in mind.
Once an AudioPath has been embedded in a Segment, there are
two ways to use it. You can either request the AudioPath configura-
tion directly by calling the Segment’s GetAudioPathConfig() method
or telling PlaySegmentEx() to automatically use the AudioPath.
Here’s how you use the configuration in the Segment:
// Get the AudioPath configuration from the Segment.
pSegment->GetAudioPathConfig(&pAudioPathConfig);
// Create an AudioPath from the configuration.
pPerformance->CreateAudioPath(pAudioPathConfig,true,&pAudioPath);
// Done with the configuration.
pAudioPathConfig->Release();
// Play the Segment on the AudioPath.
pPerformance->PlaySegmentEx(pSegment,NULL,NULL,0,0,0,0,pAudioPath);
// Release the AudioPath. It will go away when the Segment finishes.
pAudioPath->Release();
At first glance, it would seem like the latter is always the preferable
solution. Certainly, it is a lot more convenient. However, it is not as
flexible and brings with it a performance cost. Creating and using an
AudioPath, especially if it has its own dynamic buffers and DMO
effects, takes CPU cycles. So, if you intend to use the AudioPath for
the playback of one or more Segments, it quickly becomes smarter
to manage it directly.
Unit
So, there’s a special method, GetObjectInPath(), that you use to
access the objects in an AudioPath both before Segments play on it
as well as once it is actively processing playing Segments. Since
there are so many different types of objects that can reside in an
AudioPath and each has its own interface, GetObjectInPath()
approaches this in a generic way, using a series of parameters to
identify the instance and position of the object and an IID to request
the desired interface.
HRESULT hr = pPath->GetObjectInPath(
DWORD dwPChannel, // Pchannel to search.
DWORD dwStage, // Position in AudioPath.
DWORD dwBuffer, // Which buffer, if in DSound.
REFGUID guidObject, // Class ID of object.
DWORD dwIndex, // Nth item.
REFGUID iidInterface, // Requested interface IID.
void ** ppObject // Returned interface.
);
Unit
The AudioPath interface has a very useful method, SetVolume(), that
changes the volume of everything playing on it. SetVolume() takes as
parameters the requested volume setting (which is always a negative
number because it is really attenuation) and the amount of time to
fade or rise to the requested level.
// Drop the volume to -6db and take 200ms to do so.
pAudioPath->SetVolume(-600,200);
// Bye bye...
pPath->Release();
In some cases, you might want to keep the AudioPath around for a
short while between uses, but keep it deactivated so CPU is not
wasted. You can do so by deactivating and then reactivating the
AudioPath. Call the AudioPath’s Activate() method, which takes a
Boolean variable to activate or deactivate.
// Don't need the path for a while...
pPath->Activate(false);
// Okay, fire it back up, we need it again...
pPath->Activate(true);
II
Unit
Figure 11-4: Jones with AudioPaths.
Let’s walk through this and learn a bit about the code.
share the same two Buffers. All other predefined types have
dynamic buffers, where each instance gets a new buffer.
n 3D: Creates a predefined 3D AudioPath. This path has one mono
buffer with DirectSound 3D capabilities.
n Mono: Creates a predefined Mono AudioPath. This path has one
mono buffer.
n Stereo: Creates a predefined Stereo AudioPath. This path has
one stereo buffer.
Make your choice from the menu, and then click on Create. The
new AudioPath appears in the list just under the Create button. This
list shows all created AudioPaths.
If you’d like, experiment by creating a few paths of different
types. You might try the AudioPath configuration file BigPath.aud,
which comes with many Buffers and DMOs. It is designed with the
Segment AudioPath.sgt in mind.
Click on an AudioPath in the list. This selects it for display as
well as playback. Load a Segment and play it via the AudioPath sim-
ply by selecting both in their respective lists. Experiment by playing
the same Segment in different AudioPaths. Be sure to try
AudioPath.sgt.
The box to the right displays the name of the current path at the
top along with some statistics about it. The statistics itemize the
PChannels, Tools, Buffers, and DMOs that are in the AudioPath.
Below the statistics are lists of components that you can access and
edit within the AudioPath. These include the AudioPath volume as
well as any Tools, DMOs, and Buffers that exist in the path. Double-
click on any item in this list. Most items will open a property page
that you can then edit. For example, click on Volume to edit the vol-
ume in the Volume dialog.
AudioPaths n 241
Unit
While the music plays, you can adjust the reverb parameters and
hear the result immediately. Press the Apply button to activate the
changes.
Unit
It adds two methods for creating AudioPaths.
CAudioPath *CreateAudioPathFromConfig(WCHAR *pwzFileName);
CAudioPath *CreateStandardAudioPath(DWORD dwType, DWORD dwChannels);
Creating an AudioPath
CAudio::CreateAudioPathFromConfig() loads the AudioPath configu-
ration from the passed file path and uses it to create an AudioPath.
First, it loads the configuration from the file. Then, it retrieves the
name of the configuration by QI’ing for the IDirectMusicObject
interface and using that to get the object descriptor. This normally
wouldn’t be required in an application, but we want to display the
name of the AudioPath in Jones. Next, CreateAudioPathFromConfig()
calls IDirectMusicPerformance8::CreateAudioPath() to create the
AudioPath and, assuming the call succeeds, creates a CAudioPath
244 n Chapter 11
class to wrap the AudioPath. It places the new CAudioPath in its list
of AudioPaths, m_AudioPathList.
Note that CreateAudioPathFromConfig() doesn’t keep the
AudioPath configuration around. Doing so would be redundant, since
the Loader keeps a copy in its own cache. The next time we try to
create an AudioPath from this configuration, the Loader avoids a file
load, so we get the efficiency we need.
CAudioPath *CAudio::CreateAudioPathFromConfig(WCHAR *pwzFileName)
{
CAudioPath *pPath = NULL;
IUnknown *pIConfig;
// First, load the configuration.
if (SUCCEEDED(m_pLoader->LoadObjectFromFile(
CLSID_DirectMusicAudioPathConfig, // AudioPath config class ID.
IID_IUnknown, // No special interface.
pwzFileName, // File path.
(void **) &pIConfig))) // Config returned in pIConfig.
{
// Get the config's name by retrieving the object
// descriptor from the configuration.
DMUS_OBJECTDESC Desc;
Desc.dwSize = sizeof(Desc);
wcscpy(Desc.wszName,L"No Name");
IDirectMusicObject *pIObject = NULL;
pIConfig->QueryInterface(IID_IDirectMusicObject,(void **)&pIObject);
if (pIObject)
{
pIObject->GetDescriptor(&Desc);
pIObject->Release();
}
// Now, use this to create a live AudioPath.
IDirectMusicAudioPath *pIPath = NULL;
m_pPerformance->CreateAudioPath(pIConfig,true,&pIPath);
if (pIPath)
{
// Create a CAudioPath object to manage it.
pPath = new CAudioPath(pIPath,Desc.wszName);
if (pPath)
{
m_AudioPathList.AddTail(pPath);
}
pIPath->Release();
}
pIConfig->Release();
}
return pPath;
}
AudioPaths n 245
The constructor for CAudioPath simply stores the AudioPath and its
name. The constructor keeps the name in ASCII format because
that’s all we need for the UI.
CAudioPath::CAudioPath(IDirectMusicAudioPath *pAudioPath,WCHAR *pzwName)
{
m_pAudioPath = pAudioPath;
pAudioPath->AddRef();
wcstombs(m_szName,pzwName,sizeof(m_szName));
m_lVolume = 0;
}
Unit
methods for reading an AudioPath’s capabilities because the assump-
tion is that every AudioPath is designed specifically for the applica-
tion that uses it, so there would be no reason to interrogate it.
However, for an application like Jones, where we can load any arbi-
trary AudioPath and look at it, we need such a method.
No problem. Ve haff meanss for makink ze AudioPath talk!
PChannel Count
First, we create a method (CAudioPath::GetPChannelCount()) that
figures out how many pchannels belong to the AudioPath in a some-
what hokey way. IDirectMusicAudioPath has a method, Convert-
PChannel(), that is used to convert any pchannel from the AudioPath
virtual pchannel space to the absolute pchannel value in the Perfor-
mance. We call ConvertPChannel() with virtual pchannel values of 0
and counting, up until it fails. This assumes that the pchannels are 0
based. I’ve never run into a situation where they aren’t, so it’s rela-
tively safe, and since this is only used for display, it’s not the end of
the world if we miss some esoteric non-zero-based pchannel
assigments.
DWORD CAudioPath::GetPChannelCount()
{
DWORD dwCount = 0;
if (m_pAudioPath)
{
246 n Chapter 11
HRESULT hr = S_OK;
for (;hr == S_OK;dwCount++)
{
DWORD dwResult;
// Normally, we'd use this to convert from the AudioPath's
// pchannel to its equivalent value in the Performance.
hr = m_pAudioPath->ConvertPChannel(dwCount,&dwResult);
}
dwCount--;
}
return dwCount;
}
Tool Count
To find out how many Tools are embedded in the AudioPath, CAudio-
Path::GetToolCount() calls GetObjectInPath(), iterating through
Tools in the DMUS_PATH_AUDIOPATH_TOOL stage until it fails.
This works because the generic class ID, GUID_All_Objects, is used
instead of a specific Tool class ID.
DWORD CAudioPath::GetToolCount()
{
DWORD dwCount = 0;
for (dwCount = 0; ;dwCount++)
{
IUnknown *pUnknown;
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
DMUS_PCHANNEL_ALL, // Search all pchannels.
DMUS_PATH_AUDIOPATH_TOOL, // Look in the Tool stage.
0, // No buffer!
GUID_All_Objects, // All Tools.
dwCount, // Nth Tool.
IID_IUnknown, // Generic interface.
(void **)&pUnknown)))
{
// We found another Tool, so continue.
pUnknown->Release();
}
else
{
// No more Tools. Quit.
break;
}
}
return dwCount;
}
AudioPaths n 247
Buffer Count
To figure out how many DirectSound buffers are included in the
AudioPath, we use a similar technique. CAudioPath::GetBuffer-
Count() uses GetObjectInPath() to scan for Buffers. However, it is
complicated by the fact that there are two sets of Buffers: Sink-in,
which take their input from the synth, and Mix-in, which take their
input from other Buffers. To handle this and avoid redundant code,
GetBufferCount() receives one parameter, fMixIn, which determines
whether to look in the DMUS_PATH_MIXIN_BUFFER or DMUS_
PATH_BUFFER stage. It iterates through the Buffers by calling
GetObjectInPath() until it fails.
DWORD CAudioPath::GetBufferCount(bool fMixin)
{
II
DWORD dwPChannel;
Unit
DWORD dwStage;
// If we are searching Mix-in Buffers...
if (fMixin)
{
// Pchannel must be 0.
dwPChannel = 0;
// Set stage to Mix-in Buffers.
dwStage = DMUS_PATH_MIXIN_BUFFER;
}
else
{
// Pchannel must be all channels.
dwPChannel = DMUS_PCHANNEL_ALL;
// Set stage to Sink-in Buffers.
dwStage = DMUS_PATH_BUFFER;
}
DWORD dwCount = 0;
for (dwCount = 0; ;dwCount++)
{
IUnknown *pUnknown;
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
dwPChannel, // Look on all pchannels.
dwStage, // Searching the appropriate stage.
dwCount, // Nth buffer.
GUID_All_Objects, // Ignore class ID (can only get Buffers).
0, // No index.
IID_IUnknown, // Generic interface.
(void **)&pUnknown)))
{
// Hah! We found another Buffer!
pUnknown->Release();
248 n Chapter 11
}
else
{
// No more Buffers. Quit.
break;
}
}
return dwCount;
}
DMO Count
CAudioPath::GetDMOCount() figures out how many DMOs exist in
the AudioPath. It does so by using GetObjectInPath() to scan for
DMOs in each of the Sink-in and Mix-in Buffers.
DWORD CAudioPath::GetDMOCount()
{
DWORD dwCount = 0;
DWORD dwBufferCount = GetBufferCount(false);
// There will be two passes. First the Sink-in Buffers (which
// read from the synth). Then, the Mix-in Buffers, which receive
// from other Buffers.
DWORD dwDMOStage[2] = {
DMUS_PATH_BUFFER_DMO,
DMUS_PATH_MIXIN_BUFFER_DMO };
DWORD dwPChannel[2] = { DMUS_PCHANNEL_ALL, 0 };
for (DWORD dwBufferType = 0; dwBufferType < 2; dwBufferType++)
{
DWORD dwBuffer;
for (dwBuffer = 0; dwBuffer < dwBufferCount;dwBuffer++)
{
// Now, for each Buffer, iterate through the DMOs.
for (DWORD dwDMO = 0; ;dwDMO++)
{
IUnknown *pUnknown;
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
dwPChannel[dwBufferType], // Search all pchannels.
dwDMOStage[dwBufferType], // Buffer DMO stage.
dwBuffer, // Which Buffer to search.
GUID_All_Objects, // Search for all object types.
dwDMO, // Index of DMO.
IID_IUnknown, // Look for base interface.
(void **)&pUnknown)))
{
// DMO was found! Increment.
dwCount++;
pUnknown->Release();
}
AudioPaths n 249
else
{
// No DMO, move on to the next Buffer.
break;
}
}
}
// Prepare Buffer count for second pass.
dwBufferCount = GetBufferCount(true);
}
return dwCount;
}
Scanning an AudioPath
Jones needs a way to iterate through all of the objects that were
authored into an AudioPath so they can be displayed and double-
II
clicked for editing. These include any DMOs and Tools, as well as
Unit
the Buffer parameters, including 3D. Normally, this is not particu-
larly useful because an application should know what it needs to
access and request them directly. Following that philosophy,
DirectMusic’s AudioPath API was not designed with this sort of
inspection in mind. In other words, there is no direct API that you
can call and query for the nth object in the AudioPath. You can sort of
do it with GetObjectInPath(), but you must iterate through stages,
buffers, and then objects within the buffers.
CAudioPath::GetObjectInfo() does just that, but it gets all the
extra work out of the way for you. It iterates through all objects
within an AudioPath for which we can subsequently throw up a UI of
some sort. It uses a data structure, called AudioPathItemInfo, to
return all of the parameters (stage, buffer, index) that were needed
to access the nth object in the AudioPath, and it stores an identifier
for the type of object (Tool, DMO, Buffer, or 3D) in AudioPathItem-
Info. A subsequent call to GetObject() with the AudioPathItemInfo
structure can quickly access the specified object.
GetObjectInfo() also returns a name for the object. Unfortu-
nately, none of the objects have a way to return a friendly name. So,
GetObjectInfo() improvises as best it can. For DMOs, it looks at the
class ID to see if it is one of the familiar DMOs that ships with
DirectX and, if so, sticks in the name. Otherwise, it just gives
generic names, like “DMO 3,” etc.
250 n Chapter 11
Here’s the code. First, we define the types of objects that we are
looking for. These really reflect UI operations that we know we can
do in Jones with these particular items.
typedef enum _AP_ITEM_TYPE
{
AUDIOPATH_VOLUME = 1, // Set the AudioPath volume.
AUDIOPATH_TOOL = 2, // Open a Tool's property page.
AUDIOPATH_DMO = 3, // Open a DMO's property page.
AUDIOPATH_BUFFER = 4, // Set Buffer parameters.
AUDIOPATH_3D =5 // Set 3D parameters.
} AP_ITEM_TYPE;
Finally, here’s the big kahuna, GetObjectInfo(). This scans for the nth
item (within the set of things it is looking for) in the AudioPath. To
do so, it starts at the top and keeps decrementing the passed iterator,
dwIndex, until it hits zero, at which point it returns the current item.
bool CAudioPath::GetObjectInfo(
DWORD dwIndex, // Nth item to look for.
char *pszName, // Returned name.
AudioPathItemInfo *pItem) // Returned parameters needed to retrieve item.
{
// Initialize the AudioPathItemInfo.
pItem->dwBuffer = 0;
pItem->dwIndex = 0;
pItem->dwStage = 0;
// If this is the very start, return the volume parameter.
if (dwIndex == 0)
{
strcpy(pszName,"Volume");
pItem->ItemType = AUDIOPATH_VOLUME;
return true;
}
dwIndex--;
// Okay, now see if this is a tool.
AudioPaths n 251
Unit
DMUS_PATH_BUFFER_DMO,
DMUS_PATH_MIXIN_BUFFER_DMO };
// And, there are two Buffer stages.
DWORD dwBufferStage[2] = { DMUS_PATH_BUFFER, DMUS_PATH_MIXIN_BUFFER };
// Pchannels are handled differently for Sink-in vs Mix-in Buffers.
DWORD dwPChannel[2] = { DMUS_PCHANNEL_ALL, 0 };
// Variable to track the two passes.
DWORD dwBufferType;
// Okay, let's do it.
for (dwBufferType = 0; dwBufferType < 2; dwBufferType++)
{
// How many Buffers of this type?
DWORD dwBufferCount = GetBufferCount(dwBufferType == 1);
// Get the pchannel to use for this Buffer type.
pItem->dwPChannel = dwPChannel[dwBufferType];
// For each Buffer (there can easily be more than one...)
pItem->dwBuffer = 0;
for (;pItem->dwBuffer < dwBufferCount;pItem->dwBuffer++)
{
// First, check if we are at the Buffer itself. If so,
// just return success.
if (dwIndex == 0)
{
// Buffers don't have names, so just use a counter.
if (dwBufferType)
{
sprintf(pszName,"MixBuffer %d",pItem->dwBuffer);
}
else
{
sprintf(pszName,"SinkBuffer %d",pItem->dwBuffer);
252 n Chapter 11
}
pItem->dwStage = dwBufferStage[dwBufferType];
pItem->ItemType = AUDIOPATH_BUFFER;
return true;
}
dwIndex--;
IUnknown *pUnknown;
// Okay, it's not the Buffer. Now, iterate through the DMOs.
for (pItem->dwIndex = 0;;pItem->dwIndex++)
{
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
pItem->dwPChannel, // Pchannels.
dwDMOStage[dwBufferType], // Which Buffer DMO stage.
pItem->dwBuffer, // Which Buffer are we looking at?
GUID_All_Objects, // Scan all DMOs.
pItem->dwIndex, // Look for the Nth DMO.
IID_IUnknown, // Just the base interface.
(void **)&pUnknown)))
{
// If index is 0, we've found our DMO.
if (dwIndex == 0)
{
// We'll use IPersist to get the class ID.
IPersist *pPersist;
// Put in a default name.
sprintf(pszName," DMO %d",pItem->dwIndex+1);
// See if it has an IPersist and, if so, get the class ID.
if (SUCCEEDED(pUnknown->QueryInterface(IID_IPersist,
(void **) &pPersist)))
{
CLSID clsid;
pPersist->GetClassID(&clsid);
// With the class ID, we might recognize one
// of our standard DMOs.
ClassIDToName(clsid,pszName);
pPersist->Release();
}
// Fill in the rest.
pItem->dwStage = dwDMOStage[dwBufferType];
pItem->ItemType = AUDIOPATH_DMO;
pUnknown->Release();
return true;
}
pUnknown->Release();
dwIndex--;
}
else
{
// Ran out of DMOs, so go to next Buffer.
break;
AudioPaths n 253
}
}
pItem->dwIndex = 0;
// Check to see if this Buffer supports 3D.
IDirectSound3DBuffer *p3DBuffer;
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
pItem->dwPChannel, // Pchannel.
dwBufferStage[dwBufferType], // Which Buffer stage.
pItem->dwBuffer, // Which Buffer.
GUID_All_Objects,0, // Don't care about object class.
IID_IDirectSound3DBuffer, // Does it have the 3D interface?
(void **)&p3DBuffer)))
{
// If there's a 3D interface, this must be a 3D Buffer.
p3DBuffer->Release();
if (dwIndex == 0)
{
// Ooh, goody, we got it.
II
strcpy(pszName," 3D");
Unit
pItem->dwStage = dwBufferStage[dwBufferType];
pItem->ItemType = AUDIOPATH_3D;
return true;
}
dwIndex--;
}
}
}
return false;
}
long *plVolume,
long *plPan,
DWORD *pdwFrequency)
{
bool fSuccess = false;
// We'll be retrieving an IDirectSoundBuffer interface.
IDirectSoundBuffer *pBuffer = NULL;
// Use GetObjectInPath to retrieve the Buffer.
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
dwPChannel, // The pchannel.
dwStage, // Mix-in or Sink-in stage.
dwBuffer, // Which Buffer.
GUID_All_Objects,0, // No need for class ID.
IID_IDirectSoundBuffer,
(void **)&pBuffer)))
{
// We got it. Go ahead and read the three parameters.
II
pBuffer->GetFrequency(pdwFrequency);
Unit
pBuffer->GetPan(plPan);
pBuffer->GetVolume(plVolume);
pBuffer->Release();
fSuccess = true;
}
return fSuccess;
}
/* CAudioPath::SetBufferParams
SetBufferParams is a convenience function that sets the
volume, pan, and frequency for the Buffer.
The Buffer is defined by buffer index, pchannel, and stage, since this
could be a Sink-in or Mix-in Buffer.
*/
{
bool fSuccess = false;
// We'll be using an IDirectSoundBuffer interface.
IDirectSoundBuffer *pBuffer = NULL;
// Use GetObjectInPath to retrieve the Buffer.
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
dwPChannel, // The pchannel.
dwStage, // Mix-in or Sink-in stage.
dwBuffer, // Which Buffer.
256 n Chapter 11
Accessing 3D Parameters
CAudioPath has two methods (Get3DParams() and Set3DParams())
for reading and writing the 3D position of a 3D DirectSound Buffer.
Use Set3DParams() to continuously update the position of a 3D
object that is being tracked in space by a 3D AudioPath.
/* CAudioPath::Get3DParams
Get3DParams is a convenience function that retrieves the current
3D parameters from a 3D Buffer.
*/
{
bool fSuccess = false;
// We'll be using an IDirectSound3DBuffer interface.
IDirectSound3DBuffer *p3DBuffer = NULL;
// Use GetObjectInPath to retrieve the Buffer.
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
dwPChannel,
dwStage,
dwBuffer,
GUID_All_Objects,0,
IID_IDirectSound3DBuffer,
(void **)&p3DBuffer)))
{
// Okay, we got the Buffer. Read it.
fSuccess = true;
p3DBuffer->GetAllParameters(p3DParams);
p3DBuffer->Release();
}
AudioPaths n 257
return fSuccess;
}
/* CAudioPath::Set3DParams
Set3DParams is a convenience function that writes
3D parameters to a 3D Buffer.
*/
{
bool fSuccess = false;
// We'll be using an IDirectSound3DBuffer interface.
IDirectSound3DBuffer *p3DBuffer = NULL;
// Use GetObjectInPath to retrieve the Buffer.
II
if (SUCCEEDED(m_pAudioPath->GetObjectInPath(
Unit
dwPChannel,
dwStage,
dwBuffer,
GUID_All_Objects,0,
IID_IDirectSound3DBuffer,
(void **)&p3DBuffer)))
{
// Okay, we got the Buffer. Write to it.
fSuccess = true;
p3DBuffer->SetAllParameters(p3DParams,DS3D_IMMEDIATE);
p3DBuffer->Release();
}
return fSuccess;
}
Accessing Volume
Volume is a special case because the command to adjust the volume
is built directly into the IDirectMusicAudioPath interface. For com-
pleteness, CAudioPath provides methods for getting and setting the
volume.
void CAudioPath::SetVolume(long lVolume)
{
m_pAudioPath->SetVolume(lVolume,0);
m_lVolume = lVolume;
}
Unit
Playing a Segment with an AudioPath is simple; just pass the
AudioPath as a parameter to PlaySegmentEx(). So, we update
CAudio::PlaySegment() to include a CAudioPath as a parameter:
void CAudio::PlaySegment(CSegment *pSegment,CAudioPath *pPath)
{
if (pSegment)
{
IDirectMusicAudioPath *pIPath = NULL;
if (pPath)
{
pIPath = pPath->GetAudioPath();
}
// Is there a transition Segment?
IDirectMusicSegment8 *pTransition = NULL;
if (pSegment->GetTransition())
{
pTransition = pSegment->GetTransition()->GetSegment();
}
DWORD dwFlags = pSegment->GetFlags();
if (pTransition)
{
dwFlags |= DMUS_SEGF_AUTOTRANSITION;
}
IDirectMusicSegmentState8 *pISegState;
if (SUCCEEDED(m_pPerformance->PlaySegmentEx(
pSegment->GetSegment(), // Returns IDirectMusicSegment
NULL,pTransition, // Use the transition, if it exists.
dwFlags, // Playback flags.
260 n Chapter 11
0, // No time stamp.
(IDirectMusicSegmentState **) &pISegState, // Returned SegState.
NULL, // No prior Segment to stop.
pIPath))) // Use AudioPath, if supplied.
{
CSegState *pSegState = new CSegState(pSegment,pISegState);
if (pSegState)
{
m_SegStateList.AddHead(pSegState);
}
pISegState->Release();
}
}
}
Scripting
261
262 n Chapter 12
Anatomy of a Script
DirectMusic’s scripting support is all built around the Script object.
The CLSID_DirectMusicScript object with its IDirectMusicScript
interface represents the Script object. Like most DirectX Audio
objects, the Loader reads the Script object from a file. Once it has
loaded a script, the application can make direct calls into the script to
run its routines, as well as pass variables back and forth between the
script and the application.
So think of a script as a discrete object that is made up of:
n Routines to call
n Variables that store data as well as provide a means to pass the
data between the application and the script
n Embedded and/or linked content (Segments, waves, and any
other DirectX Audio media) to be referenced by variables and
manipulated by routines
Figure 12-1 shows an example of a script with its internal routines,
variables, and content. The application manages the script and can
make calls to trigger the routines and access the variables. In turn,
the routines and variables access the embedded and linked Seg-
ments, AudioPaths, and other content.
App Script
Segment
Routines Content
ref
Segment
Style
DLS
Variables Collection
AudioPath
ref
Figure 12-1: A script with its internal routines, variables, and content.
Scripting n 263
Routines
The heart of scripting is, of course, in the code. The code in a Direct-
Music script is all stored in routines. There is no primary routine
that is called first, unlike the required main() in a C program. Instead,
any routine can be called in any order. Each routine exposes some
specific functionality of the Script object. For example, two routines
might be named EnterLevel or MonsterDies. The first would be
called upon entering a level in a game. The second would be called
when a monster dies. The beauty is that the programmer and con-
tent creator only need to agree on the names EnterLevel and
MonsterDies and call them at the appropriate times.
Each routine has a unique name, and that name is used to invoke
the routine via the IDirectMusicScript::CallRoutine() method. II
Unit
HRESULT CallRoutine(
WCHAR *pwszRoutineName,
DMUS_SCRIPT_ERRORINFO *pErrInfo
);
Pass only two parameters — the name of the routine and an optional
structure that is used to return errors. If the routine does indeed
exist (which should always be the case in a debugged project),
CallRoutine() invokes it immediately and does not return until the
routine has completed.
Therefore, the code to call a routine is typically very simple:
// Invoke the script handler when the princess eats the frog by accident
pScript->CallRoutine(L”EatFrog”,NULL); // Yum yum.
Variables
There really are three uses for variables in a script:
n Parameter passing variables: Declare a variable in the script
and use it to pass information back and forth between the appli-
cation and the script. An example might be a variable used to
track the number of hopping objects in a scene, which in turn
influences the musical intensity.
n Internal variables: Declare a variable in the script and use it
purely internal to the script. An example might be an integer
that keeps track of how many times the princess steps on frogs
before the fairy godmother really gets pissed.
n Content referencing variables: Link or embed content (Seg-
ments, AudioPaths, etc.) in the script, and the script automati-
cally represents each item as a variable. For example, if the
Segment FrogCrunch.sgt is embedded in the script, the variable
FrogCrunch is automatically allocated to provide access to the
Segment.
Variables used in any of these three ways are equally visible to the
application calling into the script.
Within the scripting language’s internal implementation, all vari-
ables are handled by the variant data type. A variant is a shape-
shifting type that can masquerade as anything from byte to pointer.
To do so, it stores both the data and tag that indicates which data
type it is. This is why you can create a variable via the “dim” key-
word in a scripting or Basic language and then use it without first
specifying its type. That is all fine and good in scripting, but it is not
a natural way to work in C++. Therefore, IDirectMusicScript gives
you three ways to work with variables and translates from variant
appropriately:
n Number: A 32-bit long, used for tracking numeric values like
intensity, quantity, level, etc.
n Object: An interface pointer to an object. Typically, this is used
to pass objects back and forth with the application. In addition, all
embedded and referenced content is handled as an object
variable.
Scripting n 265
n Variant: You still have the option to work with variants if you
need to. This is useful for passing character strings and other
types that cannot be translated into interfaces or longs. Fortu-
nately, this is rare.
To accommodate all three types, IDirectMusicScript has Get and Set
methods for each. They are GetVariableNumber(), GetVariable-
Object(), and GetVariableVariant() to retrieve a variable from the
script and SetVariableNumber(), SetVariableObject(), and Set-
VariableVariant() to assign a value to a variable in the script.
Each Get or Set call passes the name of the variable along with
the data itself. For example, the parameters for GetVariable-
Number(), which retrieves a numeric value, are: the Unicode name,
a pointer to a long to fill, and an optional error structure in case II
there’s a failure.
Unit
HRESULT // GetVariableNumber(
WCHAR *pwszVariableName,
LONG *plValue,
DMUS_SCRIPT_ERRORINFO *pErrInfo
);
{
pScript->SetVariableObject("FrogPath",pPath,NULL);
}
// Remember to release the path when done with it. The script will
// release its pointer to the path on its own.
Content
Obviously, it is important that scripts be able to directly manipulate
the DirectMusic objects that they control. To deal with this, key
objects that you would want to manipulate from within a script all
have scripting extensions. These are the AudioPath, AudioPath Con-
figuration, Performance, Segment, and SegmentState (called a
“playing Segment”). They all exhibit methods that can be called
directly from within the script (internally, this is done via support for
the IDispatch interface). Documentation for these scripting exten-
sions can be found in the DirectMusic Scripting Reference section of
the DirectMusic Producer help file, not the regular DirectX program-
ming SDK.
It is also very important that a script be able to introduce its own
content. It’s not enough to load files externally and present them to
the script with SetVariableObject() because that implies that the
application knows about all of the objects needed to run the script,
which gets us back to the content creator writing down a long list of
instructions for the programmer, etc. Scripting should directly con-
trol which files are needed and when.
The Script object also supports linking and embedding objects at
authoring time. A linked object simply references objects that are
stored in files outside the script file. This is necessary to avoid
redundancy if an object is shared by multiple scripts. If the script is
Scripting n 267
II
Unit
You can have everything you need for a particular section in your
application all wrapped up in one script file, which is wonderfully
clean. Be careful, though. By default, script files automatically down-
load all their DLS and wave instruments to the synthesizer when the
script is initialized. If you have more stuff than you want downloaded
at any one time, you need to manage this directly in your scripting
code and avoid the automatic downloading.
If you ever wondered why you could not simply open a script file
in a text editor, the embedded and linked content is the reason.
Although the code portion of the script is indeed text, the linked and
embedded data objects are all binary in RIFF format, which cannot
be altered in a text editor.
EnumVariable() does the same for each variable, but it’s a little more
involved because it enumerates all declared variables as well as all
variables that were automatically created for linked and embedded
objects. There is a little trick that you can use to figure out which
type a variable is. Make a call to GetVariableLong(), and if it suc-
ceeds, the variable must be a declared variable; otherwise, it must be
a linked or embedded object. Jones uses this technique.
You can also use GetVariableObject() to search for specific object
types, since it requires a specific interface ID. Here is a routine that
will scan a script looking for all objects that support IDirectMusic-
Object and display their name and type. This should display
everything that is linked or embedded content and can be manipu-
lated as a variable.
þ Note Some content (for example, styles and DLS Collections) cannot
be directly manipulated by scripting, so they do not have variables
assigned to them.
{
DWORD dwIndex;
HRESULT hr = S_OK;
// Enumerate through all variables
for (dwIndex = 0; hr == S_OK; dwIndex++)
{
WCHAR wzName[DMUS_MAX_NAME];
hr = pScript->EnumVariable(dwIndex,wzName);
// hr == S_FALSE when the list is finished.
if (hr == S_OK)
{
IDirectMusicObject *pObject;
Scripting n 269
}
wcstombs(szName,Desc.wszName,DMUS_MAX_NAME); II
else
Unit
{
strcpy(szName,"<unnamed>");
}
// This should be a Segment, AudioPath configuration,
// or another script because only these support IDispatch
// and can be loaded from file.
if (Desc.guidClass == CLSID_DirectMusicSegment)
{
strcat(szName,": Segment");
}
else if (Desc.guidClass == CLSID_DirectMusicAudioPathConfig)
{
strcat(szName,": AudioPath Config");
}
else if (Desc.guidClass == CLSID_DirectMusicScript)
{
strcat(szName,": Script");
}
strcat(szName,"\n");
OutputDebugString(szName);
}
pObject->Release();
}
}
}
}
270 n Chapter 12
Error Handling
Sometimes tracking errors with scripting can be frustrating. Variable
or routine names may be wrong, in which case calls to them fail.
There can be errors in the routine code, but there is no way to step
into the routines when debugging. Therefore, it helps to have a
mechanism for figuring out what went wrong.
Enter the DMUS_SCRIPT_ERRORINFO structure. As we have
seen already, CallRoutine() and all of the methods for manipulating
variables can pass this as an option.
typedef struct _DMUS_SCRIPT_ERRORINFO {
DWORD dwSize;
HRESULT hr;
ULONG ulLineNumber;
LONG ichCharPosition;
WCHAR wszSourceFile[DMUS_MAX_FILENAME];
WCHAR wszSourceComponent[DMUS_MAX_FILENAME];
WCHAR wszDescription[DMUS_MAX_FILENAME];
WCHAR wszSourceLineText[DMUS_MAX_FILENAME];
} DMUS_SCRIPT_ERRORINFO;
Script Tracks
You can also trigger the calling of script routines by using a Script
Track in a Segment. This is very powerful because it gives the
opportunity to have time-stamped scripting. You can even use script-
ing to seamlessly control the flow of music playback by calling
routines at decision points in the Segments to decide what to play
next based on current state variables.
Script Tracks are very straightforward to author and use. In
DirectMusic Producer, open a Segment and use the Add Tracks com-
mand to add a Script Track to it. At the point in the timeline where
you want the script routine called, insert a script event. Then choose
from a pull-down menu in the Properties window which routine from
which script to call. Each script event also has options for timing.
The script routine can be called shortly ahead of the time stamp or
Scripting n 271
Script Language II
DirectMusic’s scripting lets you choose between two language
Unit
implementations. It does this by internally supporting a standard
COM interface for managing scripted language, called IActiveScript.
Theoretically, that means that you can use any language supported
by IActiveScript, which includes a wide range of options from
JavaScript to Perl. However, DirectMusic Producer offers content
creators exactly two options, VBScript and AudioVBScript. This is
just fine because they really are the best choices.
VBScript is a standard scripting implementation that Microsoft
provides as part of the Windows operating system. VBScript is a
very full-featured language. However, it is also big, requiring close to
a megabyte to load.
AudioVBScript is a very light and nimble basic scripting language
that was developed specifically for DirectMusic scripting. It is fast
and very small and ships as part of the API. However, it does not
have many of the more sophisticated features found in VBScript.
Typically, scripters should use AudioVBScript. If the scripter runs up
against a brick wall because AudioVBScript does not have a feature
they need (like arrays, for example), then the scripter can simply
switch to VBScript and continue. Since AudioVBScript is a subset of
VBScript, this should be effortless.
Unit
IDirectMusicScript *GetScript() {return m_pScript; };
void Init(IDirectMusicScript *pScript,
IDirectMusicPerformance8 *pPerformance);
char *GetName() { return m_szName; };
private:
IDirectMusicScript * m_pScript;
char m_szName[DMUS_MAX_NAME]; // Name, for display
};
That is it.
Unit
if (pIObject)
{
DMUS_OBJECTDESC Desc;
Desc.dwSize = sizeof(Desc);
pIObject->GetDescriptor(&Desc);
pIObject->Release();
m_szName[0] = 0;
if (Desc.dwValidData & DMUS_OBJ_NAME)
{
wcstombs( m_szName, Desc.wszName, DMUS_MAX_NAME );
}
else if (Desc.dwValidData & DMUS_OBJ_FILENAME)
{
wcstombs( m_szName, Desc.wszFileName, DMUS_MAX_NAME );
// Get rid of any file path.
char *pszName = strrchr( m_szName,'\\');
if (pszName) strcpy(m_szName,++pszName);
}
else
{
strcpy( m_szName,"Script (no name)");
}
}
}
276 n Chapter 12
To play the routine, Jones retrieves the routine name from the rou-
tine list and uses it to call CallRoutine().
// Convert routine name to Unicode.
WCHAR wszName[MAX_PATH];
mbstowcs(wszName,szName,MAX_PATH);
Scripting n 277
II
Click on a variable to show its value in the edit box. Type over the
Unit
variable value with a new value and Jones automatically sends the
new value to the script.
Scripting supports three ways to access variables: objects, vari-
ants, and numbers. However, to keep things simple in Jones, we are
only supporting numbers. So, when we enumerate through the vari-
ables using IDirectMusicScript::EnumVariable(), we need to make
sure that the variable is valid as a number and we want to retrieve
the current value, which we can store in our list. We do both with a
call to IDirectMusicScript::GetVariableNumber(). If the call suc-
ceeds, we can display the variable.
DWORD dwIndex;
HRESULT hr = S_OK;
for (dwIndex = 0; hr == S_OK; dwIndex++)
{
WCHAR wzName[DMUS_MAX_NAME];
// Enumerate through all variables
hr = pScript->GetScript()->EnumVariable(dwIndex,wzName);
if (hr == S_OK)
{
long lData = 0;
// But verify that each variable is capable of providing a number.
// If not, ignore it.
hr = pScript->GetScript()->GetVariableNumber(wzName,&lData,NULL);
if (SUCCEEDED(hr))
{
// Success. Convert to ASCII and place in the list box.
char szName[DMUS_MAX_NAME] = "";
wcstombs( szName, wzName, DMUS_MAX_NAME );
278 n Chapter 12
A Scripting Sample
To really understand programming with scripting, it helps to mess
around with the scripting side of things too. To that end, I created a
wacky little script called SimpleScript.spt.
Bad Dream
SimpleScript endeavors to provide a soundtrack to a very strange
scene. Imagine a mechanical world filled with moving metal parts,
and large train-like objects fly by at random intervals with a spray of
steam and grinding metal. You are being hunted by a very annoying
translucent wasp made of cellophane. As the story progresses, there
are more and more mechanical objects to dodge, and as you find
yourself increasingly cornered, your level of hysteria increases.
Occasionally, you have a good idea that causes time to stand still, but
then chaos falls back around your ears and the chase ensues. Even-
tually, you wake up in a cold sweat, and it’s over. To build this
soundscape, we need the following:
n Theme music that plays forever and responds to both changes in
“activity” and “hysteria”
n A routine to start the music
Scripting n 279
Unit
dim Activity ' Game supplied Activity level
dim Hysteria ' Game supplied Hysteria level
Theme Music
The Segment SkaNSeven.sgt supplies the theme music. It uses a
Style for playback (thanks to Microsoft’s Kelly Craven for creating a
great Style), which has the advantage that Style Segments can
respond to global groove level changes. Because Style Segments are
chord based, other musical effects can track them harmonically.
SkaNSeven.sgt is 42 measures long with measures 10 through 42
looping infinitely, so once it starts playing, it just loops and loops. In
truth, you’d want more variety, but hey, this is called “SimpleScript”
after all.
The first routine, StartLevel, is called by the application when
we first enter the scene. StartLevel establishes the initial Activity
and Hysteria levels by setting these values to zero and then calling
the routine Update (more on that in a second.) Finally, StartLevel
plays SkaNSeven.sgt to get the music looping.
' StartLevel is called when we enter the scene.
sub StartLevel
Activity = 0 ' Init the Activity
OldActivity = 0 ' Init the previous as well
Hysteria = 0 ' Init the Hysteria
Update ' Now use these to set tempo and groove
' Finally, play the music
280 n Chapter 12
SkanSeven.Play AtMeasure
end sub
end if
if (Activity > 6) then
Activity = 6
end if
' If the activity is increasing, play a motif.
if (OldActivity < Activity) then
Change.play IsSecondary+AtBeat
end if
' Store the activity for next time around.
OldActivity = Activity
' Then, set the levels
SetMasterGrooveLevel Activity*25 - 50
SetMasterTempo Hysteria*2 + 98
end sub
To test this, first change the Activity or Hysteria variable, and then
run the Update routine. In Jones, this means clicking on the variable II
name, editing its value, and then double-clicking on Update.
Unit
Wasp Flies By
When the wasp flies by, the routine Wasp can be called. Wasp plays a
secondary Segment written with a Pattern Track, so it can transpose
on top of the current chord and key. The Segment has many different
variations so that the melody it plays is always a little different. This
wasp has personality! On the other hand, when Activity is high, it
seems intuitive that the wasp should be a little louder and little
angrier. So, the Wasp routine checks the current Activity level before
deciding which of two Segments to play.
' When a Wasp flies by in the game, call the Wasp routine which plays a secondary
' Segment. If the Activity is above 2, play a slightly louder and more intense Wasp.
' In either case, align with the beat.
sub Wasp
if (Activity > 2) then
BigWasp.play IsSecondary+AtBeat
else
LittleWasp.play IsSecondary+ AtBeat
end if
end sub
Train
Now let’s script the behaviors for the train. When a train lumbers by
and the user gets close to the Tracks, the routine NearTracks is
called to play something inspiring. This is a simple sequence with
282 n Chapter 12
some weird train-like sound effects. Since this isn’t musical at all, it
can play immediately and not sound odd.
' When close to train tracks, call NearTracks.
' This isn't rhythmically attached to the music, so
' play it immediately.
sub NearTracks
Train.play IsSecondary+AtImmediate
end sub
Thinking
I don’t know about you, but when I have an original thought, the
world stops and takes notice. Time stands still, multicolored flashing
lights flutter around my head, and fog comes out of my nose. The
Thinking routine attempts to recreate this otherworldly experience.
It does so in an interesting way. It plays a Segment, Pause.sgt, that is
a controlling Segment with a low groove level accompanied by break
and fill embellishments. These alter the theme music by causing it to
track to the low groove level and play the break and fill embellish-
ments, while continuing with its own chord progression. Pause also
plays a few drone-like notes and eerie sounds to telegraph the expe-
rience of having a head bursting full of profound, karmic thoughts.
But, again, with a groove change, we have a situation where the
effect is delayed until the next measure boundary. We need to hear
something sooner! Profound thoughts like mine can’t wait. The
Idea.sgt secondary Segment fills the breach with a brief chorus of
angelic voices.
' When it's time to stop and think, call Thinking.
sub Thinking
' Play a small motif to carry us over since
' the controlling Segment is aligned to a measure.
Idea.play IsSecondary+AtBeat
' Now, the controlling Segment, which plays some
' embellishments and drone sounds while altering
' the groove level.
Pause.play IsControl+AtMeasure
end sub
Scripting n 283
Enough Already!
When it’s clear that the music itself is causing the hysteria, you can
opt out by calling the EndLevel routine. This takes advantage of
DirectMusic composition technology, which dynamically writes a
transition Segment and plays it to lead into the final Segment,
ScaNSevenEnd.sgt. It’s worth listening to this a few times to see
how it is able to pick chord progressions that wrap up the music II
quite handily, regardless of where the theme was at the moment you
decided to leave.
Unit
' EndLevel is called when we are done.
sub EndLevel
' Play the finish Segment but precede it with a dynamically
' composed transition with a fill and chords to modulate into it.
SkanSevenEnd.Play AtMeasure+PlayFill+PlayModulate
end sub
þ Note If you are previewing in Jones, you will notice that the visual
display temporarily shows nothing when the composed transition
Segment plays. Magic? I like to think everything that comes out of the
composition technology is magic, but there’s a more grounded reason.
Jones inserts a special display track in each Segment when it loads it.
This special Track gives Jones the ability to follow the Segments as they
play. The transition Segment is created on the fly within DirectMusic, so
there is no opportunity for Jones to intercept it. But, if you want to
believe it’s magic…
If your project involves music and audio design that are done by
somebody other than the programmer (read: YOU!), it pays to use
scripting. It frees the participants to work more efficiently and faster,
and there’s no question that you will end up with a product that is
284 n Chapter 12
Sound Effects
285
286 n Chapter 13
Unit
Here is the code to create a 3D AudioPath with four pchannels.
IDirectMusicAudioPath *pPath = NULL;
m_pPerformance->CreateStandardAudioPath(
DMUS_APATH_DYNAMIC_3D, // Create a 3D path
4, // Give it four pchannels
true, // Activate it for immediate use.
&pPath); // Returned path.
Later, when done with the AudioPath, just use Release() to get rid of
it:
pPath->Release();
To play the Segment on the AudioPath, pass both the Segment and
AudioPath to the Performance’s PlaySegmentEx() method:
IDirectMusicSegmentState8 *pSegState;
hr = m_pPerformance->PlaySegmentEx(
pSegment, // The Segment
NULL,NULL, // Ignore these
DMUS_SEGF_SECONDARY, // Play as a secondary Segment
0, // No time stamp.
&pSegState, // Optionally, get a SegState.
NULL, // No prior Segment to stop.
pPath); // Use AudioPath, if supplied.
Remember that there is not a limit to how many Segments can play
on how many AudioPaths. One Segment can play on multiple
AudioPaths, and multiple Segments can play on one AudioPath.
If the Segment needs to be stopped for some reason, there are
three ways to do it:
n Stopping the Segment State: This stops just the one instance
of the playing Segment, so all other Segments playing on the
AudioPath continue. Use this to terminate one sound on an
object without affecting others.
n Stopping the Segment: This stops all instances of the Seg-
ment, no matter which AudioPaths they are playing on. This is
the simplest as well as the least useful option.
n Stopping everything in the AudioPath: This stops all Seg-
ments currently playing on the AudioPath. This is very useful for
sound effects work.
Sound Effects n 289
In all three cases, call the Performance’s StopEx() method and pass
the Segment, Segment State, or AudioPath as the first parameter.
StopEx() figures out which parameter it has received and terminates
the appropriate sounds. In these two examples, we first pass the
Segment State and then the AudioPath:
// Stop just the one instance of a playing Segment.
m_pPerformance->StopEx(pSegState,NULL,0);
// Stop everything on the AudioPath.
m_pPerformance->StopEx(pPath,NULL,0);
Unit
tial cost. It is a good idea to deactivate AudioPaths that are currently
not in use. Deactivation does not release the AudioPath resources. It
keeps them around so the AudioPath is ready to go the next time you
need it, but it does disconnect the CPU and I/O overhead. Given that
3D buffers can be somewhat expensive on some hardware configura-
tions, deactivation of the AudioPath is a good feature to use.
One method on IDirectMusicAudioPath, Activate(), handles both
activation and deactivation. Here is example code for first deactivat-
ing and then activating an AudioPath:
// Deactivate the AudioPath
pPath->Activate(false);
// Activate the AudioPath
pPath->Activate(true);
interest are the commands to set the position and velocity. You
should be acquainted with all of the features of the 3D Buffer, how-
ever. These include:
n Position: Position is the single most important feature. Without
it, you cannot possibly claim to have 3D sound in your applica-
tion. Get and set the position of the object in 3D space.
n Velocity: Every object can have a velocity, which is used to cal-
culate Doppler shift. DirectSound does not calculate velocity for
you, which it theoretically could by simply measuring the change
in distance over time because that would cause a delay in any
velocity change. Therefore, you must calculate it directly and set
it for each Buffer if you want to hear the Doppler effect.
n Max distance and min distance: These set the range of dis-
tance from the listener at which sounds are progressively attenu-
ated. Sounds closer than min distance cease to increase in
volume as they get closer. Sounds farther than max distance
cease to get quieter as they get farther away.
n Cone angle, orientation, and outside volume: A very sophis-
ticated feature is the ability to define how an object projects its
sound. You can specify a cone of sound that emits from an object.
Cone orientation sets the direction in 3D space. Angle sets the
width of an inner and outer cone. The inner cone wraps the
space that plays at full volume. The outer cone marks the bound-
ary where sound plays at outside volume. The area between the
inner and outer cones gradually attenuates from full volume to
outside volume.
n Mode: You can also specify whether the sound position is rela-
tive to the listener (more on that in a second) or absolute space
or centered inside the listener’s head. I’m not particularly fond of
voices inside my head, so I avoid that last choice.
The DirectX SDK has excellent documentation on working with the
DirectSound 3D Buffer parameters, so I am not going to spend a lot
of time on this subject beyond the important position and velocity.
Here is some example code that sets the 3D position of an AudioPath
(more on setting velocity in our application example later in this
chapter):
Sound Effects n 291
}
p3DBuffer->Release(); II
Unit
Likewise, you can control any of the other 3D parameters on the
Buffer by using the IDirectSound3DBuffer8 interface. Remember
that you can adjust any of the regular DirectSound Buffer parame-
ters, like frequency, pan, and volume, in a similar way with the
IDirectSoundBuffer8 interface.
example gets the Listener via an AudioPath and, for grins, adjusts
the Doppler Factor.
IDirectSound3DListener8 *pListener = NULL;
pPath->GetObjectInPath(0,
DMUS_PATH_PRIMARY_BUFFER, // Retrieve from primary Buffer.
0,GUID_All_Objects,0, // Ignore object type.
IID_IDirectSound3DListener8, // Request the listener interface.
(void **)&pListener);
if (pListener)
{
DS3DLISTENER Data;
Data.dwSize = sizeof(Data);
// Read all of the listener parameters.
pListener->GetAllParameters(&Data);
// Now change something for the sake of this example.
Data.flDopplerFactor = 10; // Really exagerate the Doppler. II
pListener->SetAllParameters(&Data,DS3D_IMMEDIATE);
pListener->Release();
Unit
}
Getting Serious
Okay, we have covered everything you need to know to get DirectX
Audio making sound. As you start to work with this, though, you will
find that there are some big picture issues that need to be sorted out
for optimal performance.
n Minimize latency: By default, the time between when you start
playing a sound and when you hear it can be too long for sudden
sound effects. Clearly, that needs fixing.
n Manage dynamic 3D resources: What do you do when you
have 100 objects flying in space and ten hardware AudioPaths to
share among them all?
n Keep music and sound effects separate: How to avoid clash-
ing volume, tempo, groove level, and more.
Minimize Latency
Latency is the number one concern for sound effects. For music, it
has not been as critical, since the responsiveness of a musical score
can be measured in beats and sometimes measures. Although the
intention was to provide as low latency as possible for both music
294 n Chapter 13
Keep in mind, however, that there is the caveat that there’s an eso-
teric bad driver out there that could prove the exception.
The two commands set the write period and write latency and
are implemented via the IKsControl mechanism. IKsControl is a
general-purpose mechanism for talking to kernel modules and
low-level drivers. The two commands are represented by GUIDs:
n GUID_DMUS_PROP_WritePeriod: This sets how frequently
(in ms) the rendering process should wake up. By default, this is
10 milliseconds. The only cost in lowering this is increased CPU
overhead. Dropping to 5ms, though, is still very reasonable. The
latency contribution of the write period is, on average, half the
write period. So, dropping to 5ms is an average latency increase
of 2.5ms — worst case 5ms. II
n GUID_DMUS_PROP_WriteLatency: This sets the latency, in
Unit
milliseconds, to add to the sound card driver’s latency. For exam-
ple, if the sound card has a latency of 5ms and this is set to 5ms,
the real write latency ends up being 10ms.
So, total latency ends up being WritePeriod/2 + WriteLatency +
DriverLatency. Here’s the code to use this. This example sets the
write latency to 5ms and the write period to 5ms.
IDirectMusicAudioPath *pPath;
if (SUCCEEDED(m_pPerformance->GetDefaultAudioPath(&pPath))
{
IKsControl *pControl;
pPath->GetObjectInPath(0,
DMUS_PATH_PORT,0, // Talk to the synth
GUID_All_Objects,0, // Any type of synth
IID_IKsControl, // IKsControl interface
(void **)&pControl);
if (pControl)
{
KSPROPERTY ksp;
DWORD dwData;
ULONG cb;
dwData = 5; // Set the write period to 5ms.
ksp.Set = GUID_DMUS_PROP_WritePeriod ; // The command
ksp.Id = 0;
ksp.Flags = KSPROPERTY_TYPE_SET;
pControl->KsProperty(&ksp, sizeof(ksp),
&dwData, sizeof(dwData), &cb);
dwData = 5; // Now set the latency to 5ms.
ksp.Set = GUID_DMUS_PROP_WriteLatency ;
296 n Chapter 13
ksp.Id = 0;
ksp.Flags = KSPROPERTY_TYPE_SET;
pControl->KsProperty(&ksp, sizeof(ksp),
&dwData, sizeof(dwData), &cb);
pControl->Release();
}
pPath->Release();
}
Before you put these calls into your code, remember that it needs to
be running on DX9. If not, latency requests this low will definitely
cause glitches in the sound. Since you can ship your application with
a DX9 installer, this should be a moot point. But if your app needs to
be distributed in a lightweight way (i.e., via the web), then you might
not include the install. If so, you need to first verify on which version
you are running.
Unfortunately, there is no simple way to find out which version
of DirectX is installed. There was some religious reason for not
exposing such an obvious API call, but I can’t remember for the life
of me what it was. Fortunately, there is a sample piece of code,
GetDXVersion, that ships with the SDK. GetDXVersion sniffs around
making calls into the various DirectX APIs, looking for specific
features that would indicate the version. Mingle includes the Get-
DXVersion code, so it will run properly on DX8 as well as DX9.
Beware — this code won’t compile under DX8, so I’ve included a
separate project file, MingleDX8.dsp, that you should compile with if
you still have the DX8 SDK.
Unit
because you can completely define the algorithm to suit your needs,
and you have the extra flexibility of being able to restart with Seg-
ments or call into script routines or whatever is most appropriate for
your application. This does mean quite a bit of work. With that in
mind, the major thrust of the Mingle application deals with this exact
issue. Mingle includes an AudioPath management library that you
can rip out, revise, and replace in your app as you see fit. How does
it work? Let’s start with an overview of the design here, and then
let’s investigate in depth later when we look at the Mingle code.
There are two sets of items that we need to track:
n Things that emit sound: Each “thing” represents an independ-
ently movable object in the world that makes a noise and so
needs to be playing via one instance of an AudioPath. Each thing
is assigned a priority, which is a criteria for figuring out which
things can be heard, should there not be enough AudioPaths to
go around. There is no limit to how large the set of things can be.
There is no restriction to how priority should be calculated,
though distance from the listener tends to be a good one.
n 3D AudioPaths: This set is limited to the largest amount of
AudioPaths that can be running at one time. Each AudioPath is
assigned to one thing that plays through the AudioPath.
Since we have a finite set of AudioPaths, we need to match these up
with the things that have the highest priorities. But priorities can
298 n Chapter 13
Thing
Pri 1
3D AudioPath
Thing
Pri 3
3D AudioPath
Thing
3D AudioPath Pri 6
Thing
Pri 5
Figure 13-1: Three AudioPaths manage sounds for four things, sorted by priority.
hand, clearly has nothing to do with reality and is added on top of the
story to manipulate our perception of it. This is all fine and good, but
if you have the same system generating both the music and the
sound effects, you can easily fall into situations where they are at
cross-purposes. For example, adjusting the tempo or intensity of the
music should not unintentionally alter the same for sound effects. In
a system as sophisticated as DirectX Audio, this conflict can happen
quite frequently and result in serious head scratching. Typical prob-
lems include:
n Groove level interference: Groove level is a great way to set
intensity for both music and sound effects, but you might want to
have separate intensity controls for music and sound effects. You
can accomplish this by having the groove levels on separate II
group IDs. But there has to be an easier way…
Unit
n Invalidation interference: When a primary or controlling Seg-
ment starts, it can automatically cause an invalidation of all play-
ing Segments. This is necessary for music because some
Segments may be relying on the control information and need to
regenerate with the new parameters. But sound effects couldn’t
care less if the underlying music chords changed. Worse, if a
wave is invalidated, it simply stops playing. You can avoid this
problem by playing controlling or primary Segments with the
DMUS_SEGF_INVALIDATE_PRI or DMUS_SEGF_AFTER-
PREPARETIME flags set. But there has to be an easier way…
n Volume interference: Games often offer separate music and
sound effects volume controls to the user. Global control of the
volume is most easily managed using the global volume control
on the Performance (GUID_PerfMasterVolume). That affects
everything, so it can’t be used. One solution is to call Set-
Volume() on every single AudioPath. Again, there has to be an
easier way…
Okay, I get the hint. Indeed, there is an easier way. It’s really quite
simple. Create two separate Performance objects, one for music and
one for sound effects. Suddenly, all the interference issues go out the
window. The only downside is that you end up with a little extra
work if the two worlds interconnect with each other, but even
scripting supports the concept of more than one Performance, so
that can be made to work if there is a need.
300 n Chapter 13
Mingle
Okay, let’s have some programming fun. Imagine a cocktail party
with a decent complement of, uh, interesting people wandering
around: the snob with the running commentary, the hungry boor
noshing on every hors d’oeuvre in sight, the very irritating mosquito
person, and the guy who just can’t stop laughing. Gentle music plays
in the background, barely above the crowd noise. To experience all
this and more without inhaling any secondhand smoke, run Mingle
by selecting it from the Start menu (if you installed the CD). Other-
wise, double-click on its icon in the Unit II\Bin directory or compile
and run it from the 13_SoundEffects source directory. See Figure
13-2.
Taking up most of the Mingle window is a large square box with
a plus sign in the middle. The box is a large room, and you are the
plus marker in the middle. When Mingle first starts, there is general
party ambience, and some music starts playing quietly. Click in the
box. Ooops, you bumped into a partygoer, and he is letting you know
how he feels about that. Try again, but be more careful. Ouch!
Sound Effects n 301
II
Unit
Figure 13-2: Mingle.
þ Note If you are running under DX8, you should hear significant
buzzing or breakup of the sound. Mingle is really only meant to work
under DX9. Either install DX9 or rebuild Mingle with the low latency
code disabled (more on that in a bit).
Now let’s have some more serious fun. There are four boxes down
the left side, each representing a different person.
302 n Chapter 13
Click on the Talk button. This creates an instance of the person and
plops it in the party. You should immediately hear the person gabbing
and wandering around the room. Click the Talk button several more
times and additional instances of the person enter the room. The lit-
tle number box on the bottom right shows how many instances of
the particular person are yacking it up at the party. Click on the Shut
Up! button to remove a partygoer. Click on other participants, and
note that they are displayed with different colors.
Below the four boxes is an edit field that shows how many 3D
AudioPaths are allocated:
This displays how many AudioPaths are currently allocated for use
by all of the sound effects. This includes one AudioPath allocated for
the sounds that happen when you click the mouse. Mingle dynami-
cally reroutes the 3D AudioPaths to the people who are closest to
the center of the box (where the listener is). Continue to click on the
Talk buttons and create more people than there are AudioPaths.
Notice how the colored boxes turn white as they get farther from the
center. This indicates that a person is no longer being played through
an AudioPath.
II
Unit
Drop the number of sounds to a reasonable number, so it’s easier to
track an individual sound, and then experiment with these sliders. If
you can handle it, Mosquito is particularly good for testing because it
emits a steady drone. Drag Distance Factor to the right. This
increases the distances that the people are moving. Notice that the
Doppler becomes more pronounced. That’s because the velocities
are much higher, since the distance traveled is greater. Drag Doppler
to the right. This increases the Doppler effect. Drag Rolloff to the
right. Notice how people get much quieter as they leave the center
area.
Finally, take a look at the two volume sliders:
These control the volumes for sound effects and music. Drag them
up or down to change the balance. Ah, much better. Drag them all
the way down and you no longer want to mangle Mingle.
That completes the tour. Mingle demonstrates several useful
things:
n Separate sound effects and music environments
304 n Chapter 13
{
// If a Loader was provided by the caller, use it.
HRESULT hr = S_OK;
if (pLoader)
{
m_pLoader = pLoader;
pLoader->AddRef();
}
// If not, call COM to create a new one.
else
{
hr = CoCreateInstance(
CLSID_DirectMusicLoader,
NULL,
Sound Effects n 305
CLSCTX_INPROC,
IID_IDirectMusicLoader8,
(void**)&m_pLoader);
}
// Then, create the Performance.
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(
CLSID_DirectMusicPerformance,
NULL,
CLSCTX_INPROC,
IID_IDirectMusicPerformance8,
(void**)&m_pPerformance);
}
if (SUCCEEDED(hr))
{
// Once the Performance is created, initialize it.
// Optionally, create a default AudioPath, as defined by
II
// dwDefaultPath, and give it 128 pchannels.
Unit
// Set the sample rate to the value passed in dwSampleRate.
// Also, get back the IDirectSound interface and store
// that in m_pDirectSound. This may come in handy later.
DMUS_AUDIOPARAMS Params;
Params.dwValidData = DMUS_AUDIOPARAMS_VOICES |
DMUS_AUDIOPARAMS_SAMPLERATE;
Params.dwSize = sizeof(Params);
Params.fInitNow = true;
Params.dwVoices = 100;
Params.dwSampleRate = dwSampleRate;
hr = m_pPerformance->InitAudio(NULL,&m_pDirectSound,NULL,
dwDefaultPath, // Default AudioPath type.
128,DMUS_AUDIOF_ALL,&Params);
}
Then, it’s trivial to add the sliders to the Mingle UI and connect
them to this method on the two instances of CAudio.
Sound Effects n 307
CAudioPath
First, we need to add some fields to CAudioPath so it can track the
specific object (or “thing”) that it is rendering in 3D. These include a
pointer to the object as well as the object’s priority and whether a
change of object is pending. We also add a method for directly setting
the 3D position. Fields and flags are added for managing the priority
II
and status of a pending swap of objects, and an IDirectSound3D-
Unit
Buffer interface is added to provide a direct connect to the 3D
controls.
// Set the 3D position of the AudioPath. Optionally, the velocity.
bool Set3DPosition(D3DVECTOR *pvPosition,
D3DVECTOR *pvVelocity,DWORD dwApply);
// Get and set the 3D object attached to this AudioPath.
void * Get3DObject() { return m_p3DObject; };
void Set3DObject(void *p3DObject) { m_p3DObject = p3DObject; };
// Get and set the priority of the 3D object.
float GetPriority() { return m_flPriority; };
void SetPriority(float flPriority) { m_flPriority = flPriority; };
// Variables added for 3D object management.
void * m_p3DObject; // 3D tracking of an object in space.
float m_flPriority; // Priority of 3D object in space.
D3DVECTOR m_vLastPosition; // Store the last position set.
D3DVECTOR m_vLastVelocity; // And the last velocity.
IDirectSound3DBuffer *m_p3DBuffer; // Pointer to 3D Buffer interface.
CAudioPath::CAudioPath
The constructor has grown significantly to support all these new
parameters. In particular, it calls GetObjectInPath() to create a
pointer shortcut to the 3D Buffer interface at the end of the
AudioPath. This will be used to directly change the coordinates
every time the AudioPath moves.
CAudioPath::CAudioPath(IDirectMusicAudioPath *pAudioPath,WCHAR *pzwName)
{
m_pAudioPath = pAudioPath;
pAudioPath->AddRef();
wcstombs(m_szName,pzwName,sizeof(m_szName));
m_lVolume = 0;
// 3D AudioPath fields follow...
m_vLastPosition.x = 0;
m_vLastPosition.y = 0;
m_vLastPosition.z = 0;
m_vLastVelocity.x = 0;
m_vLastVelocity.y = 0;
m_vLastVelocity.z = 0;
m_flPriority = FLT_MIN;
m_p3DObject = NULL;
m_p3DBuffer = NULL;
// Try to get a 3D Buffer interface, if it exists.
pAudioPath->GetObjectInPath(
0,DMUS_PATH_BUFFER, // The DirectSound Buffer.
0,GUID_All_Objects,0, // Any Buffer (should only be one).
IID_IDirectSound3DBuffer,
(void **)&m_p3DBuffer);
}
CAudioPath::Set3DPosition()
Set3DPosition() is intended primarily for 3D AudioPaths in the 3D
pool. It is called on a regular basis (typically once per frame) to
update the 3D position of the AudioPath.
Set3DPosition() sports two optimizations. First, it stores the pre-
vious position and velocity of the 3D object in the m_vLastPosition
and m_vLastVelocity fields. It compares to see if either the position
or velocity has changed. If the 3D object’s position or velocity have
Sound Effects n 309
Unit
// Position hasn't changed. What about velocity?
if (pvVelocity)
{
if (!memcmp(&m_vLastVelocity,pvVelocity,sizeof(D3DVECTOR)))
{
// No change to velocity. No need to do anything.
return true;
}
}
else return true;
}
// We'll be using the IDirectSound3DBuffer interface that
// we created in the constructor.
if (m_p3DBuffer)
{
// Okay, we have the 3D Buffer. Control it.
m_p3DBuffer->SetPosition(pvPosition->x,pvPosition->y,pvPosition->z,dwApply);
m_vLastPosition = *pvPosition;
// Velocity is optional.
if (pvVelocity)
{
m_p3DBuffer->SetVelocity(pvVelocity->x,pvVelocity->y,pvVelocity->z,dwApply);
m_vLastVelocity = *pvVelocity;
}
return true;
}
return false;
}
310 n Chapter 13
CAudio
CAudio needs to maintain a pool of AudioPaths. CAudio already has a
general-purpose list of AudioPaths, which we explored in full in
Chapter 11. However, the 3D pool needs to be separate since its
usage is significantly different. It carries a set of identical 3D
AudioPaths, intended specifically for swapping back and forth, as we
render. So, we create a second list.
CAudioPathList m_3DAudioPathList; // Pool of 3D AudioPaths.
DWORD m_dw3DPoolSize; // Size of 3D pool.
We provide a routine for setting the size of the pool, a routine for
allocating a 3D AudioPath, a routine for releasing one when done
with it, and a routine for finding the AudioPath with the lowest
priority.
// Methods for managing a pool of 3D AudioPaths.
DWORD Set3DPoolSize(DWORD dwPoolSize); // Set size of pool.
CAudioPath *Alloc3DPath(); // Allocate a 3D AudioPath.
void Release3DPath(CAudioPath *pPath); // Return a 3D AudioPath.
CAudioPath *GetLowestPriorityPath(); // Get lowest priority 3D AudioPath.
CAudio::Set3DPoolSize()
CAudio::Set3DPoolSize() sets the maximum size that the 3D pool is
allowed to grow to. Notice that it doesn’t actually allocate any
AudioPaths because they should still only be created when needed.
Optionally, the caller can pass POOLSIZE_USE_ALL_HARDWARE
instead of a pool size. This sets the pool size to the total number of
available hardware 3D buffers. This option allows the application to
automatically use the optimal number of 3D buffers. Set3DPoolSize()
accomplishes this by calling DirectSound’s GetCaps() method and
using the value stored in Caps.dwFreeHw3DAllBuffers.
DWORD CAudio::Set3DPoolSize(DWORD dwPoolSize)
{
// If the constant POOLSIZE_USE_ALL_HARDWARE was passed,
// call DirectSound's GetCaps method and get the total
// number of currently free 3D Buffers. Then, set that as
// the maximum.
if (dwPoolSize == POOLSIZE_USE_ALL_HARDWARE)
{
if (m_pDirectSound)
Sound Effects n 311
{
DSCAPS DSCaps;
DSCaps.dwSize = sizeof(DSCAPS);
m_pDirectSound->GetCaps(&DSCaps);
m_dw3DPoolSize = DSCaps.dwFreeHw3DAllBuffers;
}
}
// Otherwise, use the passed value.
else
{
m_dw3DPoolSize = dwPoolSize;
}
// Return the PoolSize so the caller can know how many were
// allocated in the case of POOLSIZE_USE_ALL_HARDWARE.
return m_dw3DPoolSize;
}
II
CAudio::Alloc3DPath()
Unit
When the application does need a 3D AudioPath, it calls CAudio::
Alloc3DPath(). Alloc3DPath() first scans the list of 3D AudioPaths
already in the pool. Alloc3DPath() cannot take any paths that are
currently being used, which it tests by checking to see if the Audio-
Path’s Get3DObject() method returns anything. If there are no free
AudioPaths in the pool, Alloc3DPath() creates a new AudioPath.
CAudioPath *CAudio::Alloc3DPath()
{
DWORD dwCount = 0;
CAudioPath *pPath = NULL;
for (pPath = m_3DAudioPathList.GetHead();pPath;pPath = pPath->GetNext())
{
dwCount++;
// Get3DObject() returns whatever object this path is currently
// rendering. If NULL, the path is inactive, so take it.
if (!pPath->Get3DObject())
{
// Start the path running again and return it.
pPath->GetAudioPath()->Activate(true);
return pPath;
}
}
// Okay, no luck. Have we reached the pool size limit?
if (dwCount < m_dw3DPoolSize)
{
// No, so create a new AudioPath.
IDirectMusicAudioPath *pIPath = NULL;
m_pPerformance->CreateStandardAudioPath(
DMUS_APATH_DYNAMIC_3D, // Standard 3D AudioPath.
312 n Chapter 13
CAudio::Release3DPath()
Conversely, CAudio::Release3DPath() takes an AudioPath that is cur-
rently being used to render something and stops it, freeing it up to
be used again by a different object (or thing).
void CAudio::Release3DPath(CAudioPath *pPath)
{
// Stop everything that is currently playing on this AudioPath.
m_pPerformance->StopEx(pPath->GetAudioPath(),0,0);
// Clear its object pointer.
pPath->Set3DObject(NULL);
// If we had more than we should (pool size was reduced), remove and delete.
if (m_3DAudioPathList.GetCount() > m_dw3DPoolSize)
{
m_3DAudioPathList.Remove(pPath);
// The CAudioPath destructor will take care of releasing
// the IDirectMusicAudioPath.
delete pPath;
}
// Otherwise, just deactivate so it won't eat resources.
else
{
pPath->GetAudioPath()->Activate(false);
}
}
CAudio::GetLowestPriorityPath()
GetLowestPriorityPath() scans through the list of AudioPaths and
finds the one with the lowest priority. This is typically done to find
Sound Effects n 313
the AudioPath that would be the best candidate for swapping with an
object that has come into view and might be a higher priority. In
Mingle, GetLowestPriorityPath() is called by CThingManager when
it is reprioritizing AudioPaths.
þ Note Keep in mind that the highest priority possible is the lowest
number, or zero, not the other way around.
CAudioPath * CAudio::GetLowestPriorityPath()
{
float flPriority = 0.0; // Start with highest possible priority.
CAudioPath *pBest = NULL; // Haven't found anything yet.
CAudioPath *pPath = m_3DAudioPathList.GetHead();
for (;pPath;pPath = pPath->GetNext())
{
// Does this have a priority that is lower than best so far? II
if (pPath->GetPriority() > flPriority)
Unit
{
// Yes, so stick with it from now on.
flPriority = pPath->GetPriority();
pBest = pPath;
}
}
return pBest;
}
CThing
CThing manages a sound-emitting object in Mingle. It stores its cur-
rent position and velocity and updates these every frame. It also
maintains a pointer to the 3D AudioPath that it renders through and
uses that pointer to directly reposition the 3D coordinates of the
AudioPath.
For dynamic routing, CThing also stores a priority number. The
priority algorithm is simply the distance from the listener. The
shorter the distance, the higher the priority (lower the number). For
mathematical simplicity, the priority is calculated as the added
squares of the x and y coordinates.
The reassignment algorithm works in two stages. First, it marks
the things that need to be reassigned and forces them to stop play-
ing. Once all AudioPath reassignments have been made, it runs
through the list and starts the new things running. So, there is state
information that needs to be placed in CThing. The variables
314 n Chapter 13
CThing::Start()
CThing::Start() is called when a thing needs to start making sound.
This could occur when the thing is first invoked, or it could occur
when it has regained access to an AudioPath. First, Start() makes
sure that it indeed has an AudioPath. Once it does, it uses its 3D
position to set the 3D position on the AudioPath. It then sets the
AudioPath as the default AudioPath in the Performance. Next, Start()
calls a script routine to start playback. Although it is possible to hand
the AudioPath directly to the script and let it use it explicitly for
playback, it’s a little easier to just set it as the default, and then
there’s less work for the script to do.
þ Note There’s a more cynical reason for not passing the AudioPath as
a parameter. When an AudioPath is stored as a variable in the script, it
II
seems to cause an extra reference on the script, so the script never
Unit
completely goes away. This is a bug in DX9 that hopefully will get fixed
in the future.
bool CThing::Start()
{
if (m_pAudio)
{
if (!m_pPath)
{
m_pPath = m_pAudio->Alloc3DPath();
}
if (m_pPath )
{
m_pPath->Set3DObject(this);
m_pPath->Set3DPosition(&m_vPosition,&m_vVelocity,DS3D_IMMEDIATE);
if (m_pScript)
{
// Just set the AudioPath as the default path. Then,
// anything that gets played by
// the script will automatically play on this path.
m_pAudio->GetPerformance()->SetDefaultAudioPath(
m_pPath->GetAudioPath());
// Tell the script which of the four people
// (or the shouts) it should play.
m_pScript->GetScript()->SetVariableNumber(
L"PersonType",m_dwType,NULL);
// Then, call the StartTalking Routine, which
// will start something playing on the AudioPath.
m_pScript->GetScript()->CallRoutine(
L"StartTalking",NULL);
// No longer not running.
316 n Chapter 13
m_fWasRunning = false;
}
return true;
}
}
return false;
}
CThing::Stop()
CThing::Stop() is called when a thing should stop making sound.
Since it’s going to be quiet, there’s no need to hang on to an
AudioPath. So, Stop() calls CAudio::Release3DPath(), which kills the
sound and marks the AudioPath as free for the next taker.
bool CThing::Stop()
{
if (m_pAudio && m_pPath)
{
// Release3DPath() stops all audio on the path.
m_pAudio->Release3DPath(m_pPath);
// Don't point to the path any more cause it has moved on.
m_pPath = NULL;
return true;
}
return false;
}
CThing::CalcNewPosition()
CThing::CalcNewPosition() is called every frame to update the posi-
tion of the Thing. In order to update velocity, it’s necessary to
understand how much time has elapsed because velocity is really the
rate of distance changed over time. Since CThing doesn’t have its
own internal clock, it receives a time-elapsed parameter, dwMils,
which indicates how many milliseconds have elapsed since the last
call. It can then calculate a new position by using the velocity and
time elapsed. CalcNewPosition() also checks to see if the Thing
bumped into the edge of the box, in which case it reverses velocity,
causing the Thing to bounce back. When done setting the position
and velocity, CalcNewPosition() uses the new position to generate a
fresh new priority.
void CThing::CalcNewPosition(DWORD dwMils)
{
if (m_dwType < THING_SHOUT)
{
Sound Effects n 317
Unit
}
// We actually track the square of the distance,
// since that's all we need for priority.
m_flPriority = m_vPosition.x * m_vPosition.x + m_vPosition.y * m_vPosition.y;
}
if (m_pPath)
{
m_pPath->SetPriority(m_flPriority);
}
}
CThing::Move()
Move() is called every frame. It simply transfers its own 3D position
to the 3D AudioPath. For efficiency, it uses the DS3D_DEFERRED
flag, indicating that the new 3D position command should be batched
up with all the other requests. Since CThingManager moves all of
the things at one time, it can make a call to the listener to commit all
the changes once it has moved all of the things.
bool CThing::Move()
{
// Are we currently active for sound?
if (m_pPath && m_pAudio)
{
m_pPath->Set3DPosition(&m_vPosition,&m_vVelocity,DS3D_DEFERRED);
return true;
318 n Chapter 13
}
return false;
}
CThingManager
CThingManager maintains the set of CThings that are milling around
the room. In addition to managing the list of CThings, it has routines
to create CThings, position them, and keep the closest CThings
mapped to active AudioPaths.
class CThingManager : public CMyList
{
public:
CThingManager() { m_pAudio = NULL; m_pScript = NULL; };
void Init(CAudio *pAudio,CScript *pScript,DWORD dwLimit);
CThing * GetHead() { return (CThing *) CMyList::GetHead(); };
CThing *RemoveHead() { return (CThing *) CMyList::RemoveHead(); };
void Clear();
CThing * CreateThing(DWORD dwType);
CThing * GetTypeThing(DWORD dwType); // Access first CThing of requested type.
DWORD GetTypeCount(DWORD dwType); // How many CThings of requested type?
void CalcNewPositions(DWORD dwMils); // Calculate new positions for all.
void MoveThings(); // Move CThings.
void ReassignAudioPaths(); // Maintain optimal AudioPath pairings.
void SetAudioPathLimit(DWORD dwLimit);
void EnforceAudioPathLimit();
private:
void StartAssignedThings();
CAudio * m_pAudio; // Keep pointer to CAudio for convenience.
CScript * m_pScript; // Script
DWORD m_dwAudioPathLimit; // Max number of AudioPaths.
};
CThingManager::CreateThing()
CreateThing() allocates a CThing class of the requested type and
installs the current CAudio and CScript in it. CreateThing() doesn’t
actually start the CThing playing, nor does it connect the CThing to
an AudioPath.
CThing *CThingManager::CreateThing(DWORD dwType)
{
CThing *pThing = new CThing(m_pAudio,dwType,m_pScript);
if (pThing)
{
Sound Effects n 319
AddHead(pThing);
}
return pThing;
}
CThingManager::ReassignAudioPaths()
ReassignAudioPaths() is the heart of the dynamic resource system.
It makes sure that the highest priority things are matched up with
AudioPaths. To do so, it scans through the list of things, looking for
each thing that has no AudioPath but has attained a higher priority
than the lowest priority thing currently assigned to an AudioPath.
When it finds such a match, it steals the AudioPath from the lower
priority thing and reassigns the AudioPath to the higher priority
thing. At this point, it stops the old thing from making any noise by
killing all sounds on the AudioPath, but it doesn’t immediately start
II
the new sound playing. Instead, it sets a flag on the new thing, indi-
Unit
cating that it has just been assigned an AudioPath, and waits until the
next time around to start it. This is done to give the currently play-
ing sound a little time to clear out before the new one starts.
void CThingManager::ReassignAudioPaths()
{
// First, start the things that were set up by the
// previous call to ReassignAudioPaths().
StartAssignedThings();
CThingManager::StartAssignedThings()
After canceling ReassignAudioPaths(), there may be one or more
things that have been assigned a new AudioPath but have not started
making any noise. This is intentional; it gives the AudioPath a
chance to drain whatever sound was in it before the thing newly
assigned to it starts making noise. So, StartAssignedThings() is
called on the next pass, and its job is simply to cause each newly
assigned thing to start making noise.
void CThingManager::StartAssignedThings()
{
// Scan through all of the CThings...
CThing *pThing = GetHead();
for(; pThing; pThing = pThing->GetNext())
{
// If this thing was assigned a new AudioPath, then start it.
if (pThing->IsAssigned())
{
// Get the newly assigned AudioPath.
CAudioPath *pPath;
Sound Effects n 321
if (pPath = pThing->GetAudioPath())
{
// Was this previously making sound?
// It could have been an AudioPath that was assigned
// but was not played yet, in which case we do nothing.
if (pThing->WasRunning())
{
pThing->Start();
}
}
// Clear flag so we don't start it again.
pThing->ClearAssigned();
}
}
}
CThingManager::EnforceAudioPathLimit() II
EnforceAudioPathLimit() ensures that the number of AudioPaths
Unit
matches the number set in SetAudioPathLimit(). If the number went
down, it kills off low-priority AudioPaths to get back to where we
should be. EnforceAudioPathLimit() makes sure that any available
things that could be playing are given AudioPaths and turned on if
the number of AudioPaths increased.
void CThingManager::EnforceAudioPathLimit()
{
// First, count how many AudioPaths are currently assigned
// to things. This lets us know how many are currently in use.
DWORD dwCount = 0;
CThing *pThing = GetHead();
for(; pThing; pThing = pThing->GetNext())
{
if (pThing->GetAudioPath())
{
dwCount++;
}
}
// Do we have more AudioPaths in use than the limit? If so,
// we need to remove some of them.
if (dwCount > m_dwAudioPathLimit)
{
// Make sure we don't have any things or AudioPaths
// in a halfway state.
StartAssignedThings();
dwCount -= m_dwAudioPathLimit;
// dwCount now holds the number of AudioPaths that
// we can no longer use.
while (dwCount)
322 n Chapter 13
{
// Always knock off the lowest priority AudioPaths.
CAudioPath *pLowestPath = m_pAudio->GetLowestPriorityPath();
if (pLowestPath)
{
// Find the thing associated with the AudioPath.
pThing = (CThing *) pLowestPath->Get3DObject();
if (pThing)
{
// Disconnect the thing.
pThing->SetAudioPath(NULL);
}
// Release the AudioPath. This stops all playback
// on the AudioPath. Then, because the number of
// AudioPaths is above the limit, it removes the AudioPath
// from the pool.
m_pAudio->Release3DPath(pLowestPath);
}
dwCount--;
}
}
// On the other hand, if we have fewer AudioPaths in use than
// we are allowed, see if we can turn some more on.
else if (dwCount < m_dwAudioPathLimit)
{
// Do we have some turned off that could be turned on?
if (GetCount() > dwCount)
{
// If the total is under the limit,
// just allocate enough for all things.
if (GetCount() < m_dwAudioPathLimit)
{
dwCount = GetCount() - dwCount;
}
// Otherwise, allocate enough to meet the limit.
else
{
dwCount = m_dwAudioPathLimit - dwCount;
}
// Now, dwCount holds the amount of AudioPaths to turn on.
while (dwCount)
{
// Get the highest priority thing that is currently off.
pThing = GetHead();
float flPriority = FLT_MAX;
CThing *pHighest = NULL;
for (;pThing;pThing = pThing->GetNext())
{
// No AudioPath and higher priority?
if (!pThing->GetAudioPath() &&
Sound Effects n 323
}
} II
Unit
CThingManager::CalcNewPositions()
CalcNewPositions() scans through the list of things and has each cal-
culate its position. This should be called prior to MoveThings(),
which sets the new 3D positions on the AudioPaths, and Reassign-
AudioPaths(), which uses the new positions to ensure that the
closest things are assigned to AudioPaths. CalcNewPositions()
receives the time elapsed since the last call as its only parameter.
This is used to calculate the velocity for each thing.
void CThingManager::CalcNewPositions(DWORD dwMils)
{
CThing *pThing = GetHead();
for (;pThing;pThing = pThing->GetNext())
{
pThing->CalcNewPosition(dwMils);
}
}
CThingManager::SetAudioPathLimit()
SetAudioPathLimit() is called whenever the maximum number of 3D
AudioPaths changes. In Mingle, this occurs when the user changes
the number of 3D AudioPaths with the edit box. First, SetAudioPath-
Limit() sets the internal parameter, m_dwAudioPathLimit, to the
new value. Then, it tells CAudio to do the same. Finally, it calls
EnforceAudioPathLimit(), which does the hard work of culling or
adding and activating 3D AudioPaths.
324 n Chapter 13
CThingManager::MoveThings()
MoveThings() scans through the list of things and has each one
assign its new position to the AudioPath. Once all things have been
moved, MoveThings() calls the Listener’s CommitDeferredSet-
tings() method to cause the new 3D positions to take hold.
void CThingManager::MoveThings()
{
CThing *pThing = GetHead();
for (;pThing;pThing = pThing->GetNext())
{
pThing->Move();
}
m_pAudio->Get3DListener()->CommitDeferredSettings();
}
Mingle Implementation
The Mingle implementation of CThingManager is quite simple. You
can look at the source code in MingleDlg.cpp. Initialization occurs in
CMingleDlg::OnInitDialog(). In it, we load a script, which has rou-
tines that will be called by things when they start and stop playback.
m_pEffects points to the CAudio instance that manages sound
effects.
m_pScript = m_pEffects->LoadScript(L"..\\Media\\Mingle.spt");
Unit
if (pThing)
{
// Stop it. This will stop all sound on the
// AudioPath and release it.
pThing->Stop();
// Kill the thing!
m_ThingManager.Remove(pThing);
delete pThing;
// There might be another thing that now has the
// priority to be heard, so check and see.
m_ThingManager.EnforceAudioPathLimit();
UpdateCounts();
}
If at any point during the play the 3D AudioPath limit changes, one
call to SetAudioPathLimit() straightens things out.
m_ThingManager.SetAudioPathLimit(m_dwMax3DPaths);
m_pMouseThing->SetPosition(&Vector);
// Start a sound playing immediately.
m_pMouseThing->Start();
Background Ambience
It’s not enough to have a handful of individuals wandering around us
mumbling, whining, munching, or laughing. To fill in the gaps and
make the party feel a lot larger, we’d like to play some background
ambience. The ambience track is a short Segment with a set of ste-
reo wave recordings set up to play as random variations. The
Segment is actually played by the script’s StartParty routine, so
there’s no special code in Mingle itself for playing background ambi-
ence other than the creation of a stereo dynamic AudioPath for the
initial default AudioPath, which StartParty uses to play the Segment
II
on. The Segment is set to loop forever, so nothing more needs to be
Unit
done.
{
// Just checking to be safe...
if (NULL == pPMSG->pGraph)
{
return DMUS_S_FREE;
}
// Point to the next Tool after this one.
// Otherwise, it will just loop back here forever
// and lock up.
if (FAILED(pPMSG->pGraph->StampPMsg(pPMSG)))
{
return DMUS_S_FREE;
}
// This should always be a wave since we only allow
// that media type, but check anyway to be certain.
if( pPMSG->dwType == DMUS_PMSGT_WAVE)
{
// Okay, now the miracle code.
II
// Multiply the duration by ten to reach beyond the
Unit
// wildest pitch bend.
DMUS_WAVE_PMSG *pWave = (DMUS_WAVE_PMSG *) pPMSG;
pWave->rtDuration *= 10;
}
return DMUS_S_REQUEUE;
}
{
IDirectMusicGraph *pGraph = NULL;
pPath->GetObjectInPath(0,
DMUS_PATH_PERFORMANCE_GRAPH, // The Performance Tool Graph.
0,
GUID_All_Objects,0, // Only one object type.
IID_IDirectMusicGraph, // The Graph interface.
(void **)&pGraph);
if (pGraph)
{
// Create a CWaveTool
CWaveTool *pWaveTool = new CWaveTool;
if (pWaveTool)
{
// Insert it in the Graph. It will process all wave pmsgs that
// are played on this Performance.
pGraph->InsertTool(static_cast<IDirectMusicTool*>(pWaveTool),
NULL,0,0);
}
pGraph->Release();
}
pPath->Release();
}
That’s it. The Tool will stay in the Performance until the
Performance is shut down and released. At that point, the references
on the Tool will drop to zero, and its destructor will free the memory.
þ Note This technique has only one downside: If you actually do want
to set a duration shorter than the wavelength, it undoes your work. In
that scenario, the best thing to do is be more specific about where you
do this. You can control which pchannels the Tool modifies, or you can
place it in the Segments or AudioPaths where you want it to work, rather
than the entire Performance.
IDirectSound3DListener8 *CAudio::Get3DListener()
{
IDirectSound3DListener8 *pListener = NULL;
IDirectMusicAudioPath *pPath;
// Any AudioPath will do because the listener hangs off the primary buffer.
if (m_pPerformance && SUCCEEDED(m_pPerformance->GetDefaultAudioPath(&pPath)))
{
pPath->GetObjectInPath(0,
DMUS_PATH_PRIMARY_BUFFER,0, // Access via the primary Buffer.
GUID_All_Objects,0,
IID_IDirectSound3DListener8,// Query for listener interface.
(void **)&pListener);
pPath->Release();
}
return pListener;
}
To facilitate the use of the Listener, the Mingle dialog code keeps a
II
copy of the Listener interface and the DS3DLISTENER data struc-
Unit
ture that can be filled with all of the Listener properties and set at
once.
DS3DLISTENER m_ListenerData; // Store current Listener params.
IDirectSound3DListener8 *m_pListener; // Use to update the Listener.
In the initialization of the Mingle window, the code gets access to the
Listener and then reads the initial parameters into m_ListenerData.
From then on, the window code can tweak any parameter and then
bulk-update the Listener with the changes.
m_pListener = m_pEffects->Get3DListener();
m_ListenerData.dwSize = sizeof(m_ListenerData);
if (m_pListener)
{
m_pListener->GetAllParameters(&m_ListenerData);
}
sub StartTalking
if (PersonType = 1) then
Snob.play IsSecondary
elseif (PersonType = 2) then
Munch.play IsSecondary
elseif (PersonType = 3) then
Mosquito.play IsSecondary
elseif (PersonType = 4) then
HaHa.play IsSecondary
elseif (PersonType = 5) then
Shout.play IsSecondary
end if
end sub
Sound Effects n 333
sub StartParty
Crowd.play IsSecondary
PersonType = 1
Person1Name = "Snob"
Person2Name = "HorsDeOoovers"
Person3Name = "Mosquito Person"
Person4Name = "Laughing Fool"
end sub
sub EndParty
Crowd.Stop
HaHa.Stop
Mosquito.Stop
Munch.Stop
Shout.Stop
Snob.Stop
end sub II
Here’s how the script is called at startup. Right after loading the
Unit
script, call StartParty to initialize the Person name variable and allow
the script to do anything else it might have to. Then, read the names.
Since these are strings, the only way we can read them is via
variants. Fortunately, there are some convenience routines that take
the pain out of working with variants. You must call VariantInit() to
initialize the variant. Then, use the variant to retrieve the string
from the script. Since the memory for a variant string is allocated
dynamically, it must be freed. So, call VariantClear(), and that cleans
it up appropriately.
// Call the initialization routine. This should set the Person
// name variables as well as start any background ambience.
if (SUCCEEDED(m_pScript->GetScript()->CallRoutine(L"StartParty",NULL)))
{
// We will use a variant to retrieve the name of each Person.
VARIANT Variant;
char szName[40];
VariantInit(&Variant);
if (SUCCEEDED(m_pScript->GetScript()->GetVariableVariant(
L"Person1Name",&Variant,NULL)))
{
// Copy the name to ASCII.
wcstombs(szName,Variant.bstrVal,40);
// Use it to set the box title.
SetDlgItemText(IDC_PERSON1_NAME,szName);
// Clear the variant to release the string memory.
VariantClear(&Variant);
}
// Do the same for the other three names...
334 n Chapter 13
CSegment *CAudio::ComposeSegment(
WCHAR *pwzStyleName, // File name of the Style.
WCHAR *pwzChordMapName, // File name of the ChordMap.
WORD wNumMeasures, // How many measures long?
WORD wShape, // Which shape?
WORD wActivity, // How busy are the chord changes?
BOOL fIntro, BOOL fEnd) // Do we want beginning and/or ending?
{
// Create a Composer object to build the Segment.
CSegment *pSegment = NULL;
if (!m_pComposer)
{
CoCreateInstance(
CLSID_DirectMusicComposer,
NULL,
CLSCTX_INPROC,
IID_IDirectMusicComposer,
(void**)&m_pComposer);
II
}
Unit
if (m_pComposer)
{
// First, load the Style.
IDirectMusicStyle *pStyle = NULL;
IDirectMusicChordMap *pChordMap = NULL;
if (SUCCEEDED(m_pLoader->LoadObjectFromFile(
CLSID_DirectMusicStyle,
IID_IDirectMusicStyle,
pwzStyleName,
(void **) &pStyle)))
{
// We have the Style, so load the ChordMap.
if (SUCCEEDED(m_pLoader->LoadObjectFromFile(
CLSID_DirectMusicChordMap,
IID_IDirectMusicChordMap,
pwzChordMapName,
(void **) &pChordMap)))
{
// Hooray, we have what we need. Call the Composition
// Engine and have it write a Segment.
IDirectMusicSegment8 *pISegment;
if (SUCCEEDED(m_pComposer->ComposeSegmentFromShape(
pStyle, wNumMeasures, wShape,
wActivity, fIntro, fEnd,
pChordMap,
(IDirectMusicSegment **) &pISegment)))
{
// Create a CSegment object to manage playback.
pSegment = new CSegment;
if (pSegment)
{
336 n Chapter 13
// Initialize
pSegment->Init(pISegment,m_pPerformance,SEGMENT_FILE);
m_SegmentList.AddTail(pSegment);
}
pISegment->Release();
}
pChordMap->Release();
}
pStyle->Release();
}
}
return pSegment;
}
Using this is simple. When Mingle first opens up, call Compose-
Segment() and have it write something long. Set it to repeat forever.
Play it and forget about it.
CSegment *pSegment = m_pMusic->ComposeSegment(
L"Layback.sty", // Layback style.
L"PDPTDIA.cdm", // Pop diatonic ChordMap.
100, // 100 measures.
DMUS_SHAPET_SONG, // Song shape.
2, // Relatively high chord activity,
true,true); // Need an intro and an ending.
if (pSegment)
{
// Play it over and over again.
pSegment->GetSegment()->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
m_pMusic->PlaySegment(pSegment,NULL,NULL);
}
You might have noticed that this is the only sound-producing code in
Mingle that is not managed by the script. Unfortunately, scripting
does not support ComposeSegmentFromShape(), so we have to call
it directly in the program. However, one could have the script man-
age the selection of Style and ChordMap so that the music choice
could be iteratively changed without a recompile of the program.
DirectX Audio has the potential to create truly awesome sound envi-
ronments. Many of the techniques that apply well to music, like
scripting, variations, and even groove level, lend themselves to cre-
ating audio environments that are equally rich. The beauty is the
vast majority of the work is done on the authoring side with a mini-
mum amount of programming.
Sound Effects n 337
II
Unit
This page intentionally left blank.
Chapter 14
Notifications
339
340 n Chapter 14
Performance Notifications
The standard mechanism for managing notifications is pretty com-
prehensive. DirectMusic’s Performance Engine provides different
categories of notifications, which you can turn on or off as needed.
These range from notifications that tell the application when differ-
ent sounds and music clips start and stop to notifications that
indicate when musical events, such as changes in time signature or
groove level, occur. The notifications pass through the Performance
in pmsg form, much like note or wave messages. However, instead
of sending these messages to the synthesizer, DirectMusic delivers
them to the application.
The application can even choose how to receive the notifications;
it can set up an event and be signaled when one arrives, or it can
simply poll for the notification messages.
Notification Categories
The notification pmsgs are broken into categories, defined via
GUIDs. Many of the categories have subtypes, which are in turn
defined by a constant DWORD. The categories include:
Notifications n 341
Unit
occur on the fly. This is primarily used by authoring tools (in
other words, DirectMusic Producer).
n GUID_NOTIFICATION_SEGMENT: This notification indi-
cates a change in the playback status of a Segment, including
starting, looping, and stopping.
Of all of these, the Segment commands are by far the most useful.
These indicate when a Segment starts, stops, or loops and even
when it is stopped prematurely. The notification mechanism also
hands the application a pointer to the IDirectMusicSegmentState
interface associated with the playing Segment.
rtTime and mtTime carry the specific time that the notification
occurs. punkUser carries a pointer to a COM interface, allowing the
notification to drag along a pointer to an associated object, if appro-
priate. For example, the GUID_NOTIFICATION_SEGMENT
command uses punkUser to carry a pointer to the IDirectMusic-
SegmentState associated with the playing Segment.
Enabling a Notification
By default, DirectMusic does not generate any notifications. To
activate a notification, you must call the performance’s AddNotifica-
tionType() method:
// Enable Segment notifications.
pPerformance->AddNotificationType(GUID_NOTIFICATION_SEGMENT);
Receiving a Notification
There are several ways to receive the notification pmsg. You can poll
for it. You can set up an event and wait to be signaled. Or you can
stick a Tool in the Performance and use the Tool to intercept it.
Unit
// Do something here with the data from the notification, and then free it.
pPerformance->FreePMsg((DMUS_PMSG*)pPmsg);
}
Waiting on an Event
If you would prefer, DirectMusic can signal your application exactly
when a notification is ready. This option uses the standard event sig-
naling Windows APIs.
First, create an event handle with a call to the Windows Create-
Event() function.
HANDLE hNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
After creating the event, hand it to the Performance via its SetNotifi-
cationHandle() method. This tells the Performance to signal the
event whenever a notification is due.
344 n Chapter 14
pPerformance->SetNotificationHandle(hNotify, 4000);
In this code, the caller waits for a notification for up to one second.
Once the notification arrives, the caller retrieves the notification via
a call to GetNotificationPMsg().
slightly early (but still with the correct time stamp) results in a
response that might feel more accurate.
n You control where the notifications are sent by placing the Tool
in the appropriate object. You can insert Tools in Segments,
AudioPaths, and the Performance. A notification tool placed in a
Segment only receives notifications generated by that Segment,
which makes it very easy to understand the context of the
notification.
n If you plan to also use lyric notifications (we discuss that next),
you need to write a Tool anyway, and so you might as well use
the same mechanism for both and avoid the extra code.
For the last reason, we are using a Tool to capture notifications in
Jones.
II
Setting up a Tool is not as hard as it may seem. We learned this
Unit
in the last chapter when we created a Tool to lengthen wave pmsgs.
Define a C++ class based on the IDirectMusicTool interface. Set it
up to only receive DMUS_PMSGT_NOTIFICATION messages and
receive them at DMUS_PMSGF_TOOL_QUEUE time (which
means they arrive about 50ms or roughly a frame early). This chap-
ter’s Jones implementation covers installing such a Tool in detail, so
we can skip the code for defining and installing the Tool for now.
The active part of the Tool is the method ProcessPMsg(), which
receives the message. For processing notifications, it verifies that
the pmsg is of type DMUS_PMSGT_NOTIFICATION, reads the
parameters from the notification pmsg, does whatever it needs to do,
and then returns.
HRESULT CNotifyTool::ProcessPMsg(IDirectMusicPerformance* pPerf,
DMUS_PMSG* pPMSG)
{
if (pPMSG->dwType == DMUS_PMSGT_NOTIFICATION)
{
// Read the notification and do something.
}
// No need to send this on down the line.
return DMUS_S_FREE;
}
Lyric Notifications
Lyrics are a little simpler to talk about for several reasons:
n There’s only one way you can receive lyrics, and that is via a
Tool.
n There’s no enabling or disabling of lyrics. Regular notifications
need this feature because there are all kinds of messages that
you may not care about. With lyrics, the messages are always
Notifications n 347
Receiving a Lyric
There is exactly one way to receive a lyric — intercept it with a Tool.
II
Going this route does mean a little more work, but in truth we’re
Unit
talking an extra hour of programming that can pay off easily in the
long term.
Create a Tool with an IDirectMusicTool interface, set it up to
handle the DMUS_PMSGT_LYRIC message type, and place it in the
Performance. (We will provide example code later in this chapter
when we add notifications to Jones.) When a lyric comes down the
pike, DirectMusic calls the Tool’s ProcessPMsg() method, which can
then notify the application in whatever way it wants to.
HRESULT CLyricTool::ProcessPMsg(IDirectMusicPerformance* pPerf,
DMUS_PMSG* pPMSG)
{
if (pPMSG->dwType == DMUS_PMSGT_LYRIC)
{
// Read the lyric and do something based on what it says.
}
// No need to send this on down the line.
return DMUS_S_FREE;
}
{
DMUS_LYRIC_PMSG *pLyric = (DMUS_LYRIC_PMSG *) pMsg;
// It's a lyric!
}
That’s it. Since this code is probably called from within the Tool’s
ProcessPMsg() method, you do not even need to free the pmsg. Just
return DMUS_S_FREE and the Performance does it for you.
Unit
{
DMUS_LYRIC_PMSG *pLyric = (DMUS_LYRIC_PMSG *) pMsg;
// Read the lyric and use to change game state, which will be
// picked up asynchronously by the main engine.
if (!wcscmp(pLyric->wszString,L"Agony Over"))
{
// Soul rendering theme is finished. Begonia can
// stop her tears now and be a little more upbeat.
g_dwBegoniaMood = BM_UPBEAT;
}
else if (!wcscmp(pLyric->wszString,L"Shout"))
{
// Someone shouts her name, Begonia reacts.
g_dwBegoniaMood = BM_WHO_ME;
}
}
else if (pMsg->dwType == DMUS_PMSGT_NOTIFICATION)
{
DMUS_NOTIFICATION_PMSG *pNotify = (DMUS_NOTIFICATION_PMSG *) pMsg;
DWORD dwNotificationOption = pNotify->dwNotificationOption;
if (pNotify->guidNotificationType == GUID_NOTIFICATION_PERFORMANCE)
{
if (dwNotificationOption == DMUS_NOTIFICATION_MUSICALMOSTEND)
{
// Uh oh, primary music Segment is about to end.
// Let main loop know to go ahead and schedule a new Segment.
g_dwMusicState = MS_EXPIRING;
}
}
else if (pNotify->guidNotificationType == GUID_NOTIFICATION_SEGMENT)
{
350 n Chapter 14
if (dwNotificationOption == DMUS_NOTIFICATION_SEGSTART)
{
// Is this the start of the dance music?
// It was scheduled to start on a measure, so we
// needed to wait and start once it was ready.
if (pMsg->punkUser == g_pDanceMusic)
{
// Yes! Begonia can jump up and start dancing.
g_dwBegoniaMove = BD_START;
}
}
}
else if (pNotify->guidNotificationType ==
GUID_NOTIFICATION_MEASUREANDBEAT)
{
// Begonia has rhythm. She moves in time with the music.
// Read the beat field to decide what to do.
// Each time g_dwBegoniaMove changes, the game engine
// sets her on a new path and returns g_dwBegoniaMove to BD_NEXT.
if (pNotify->dwField1 == 2)
{
// Begonia jumps on beat 3.
g_dwBegoniaMove = BD_JUMP;
}
else
{
// On any other beat, Begonia wiggles.
g_dwBegoniaMove = BD_WIGGLE;
}
}
}
// Return the pmsg to the Performance.
}
Depending on how you acquired the pmsg, you may need to free it
now:
// Return the pmsg to the Performance.
pPerf->FreePMsg(pMsg);
þ Note If this exercise has inspired you to name your first daughter
Begonia, please reconsider.
Notifications n 351
Script Routines
Using scripting for notification is very different from performance
notifications or lyrics. Instead of writing C++ code to catch notifica-
tion or lyric messages, you write script routines that actively do
something at scheduled times in Segments.
Depending on what you intend to do with the notification, there
are different approaches:
n If the intention of the notification is to turn around and call a
script routine, then it clearly makes more sense to just call the
script in the first place! Likewise, if the intention is to play a
Segment, then it’s easy to write a script routine to do just that.
The beauty of these solutions is they never touch the C++ II
code.
Unit
n If the intention of the notification is to update a state variable,
then it’s just slightly more indirect. The script routine updates a
global variable in the script. Then, the main loop polls the vari-
able to read its value. This is actually very similar to the mecha-
nism we explored while choreographing Begonia’s performance.
Notifications in Jones
Let’s try the notification implementation in Jones and then see how
it’s done. If you installed the CD, run NotifyJones by selecting it
from the Start menu. Otherwise, double-click on its icon in the Unit
II\Bin directory or compile and run it from the 14_Notification
source directory.
Notice that a new display appears at the bottom-right corner.
Seg Start indicates that a Segment started playing. Perf Start indi-
cates that it is also the very first Segment.
Double-click on Wasp. This triggers a call to Wasp() in the
script. Wasp() plays a secondary Segment, which also has a Lyric
Track.
Wasp() kicks off a flurry of notification activity. First, Seg Start indi-
cates that the new Segment has started. Then, Bzzzzz, Piff!, and
finally Zow! appear, as the Segment delivers each of these lyrics.
After a while, Seg Near End indicates that the Segment is almost fin-
ished, and finally Seg End appears as the Segment completes (if you
Notifications n 353
look to the graphic display, you should see its rectangle moved com-
pletely to the left at this very moment).
Experiment a little more by turning on and off different notifica-
tions and playing the different script routines.
Jones Implementation
Jones doesn’t really use notifications in the same way that you would
for a game or other interactive application. Normally, you would want
to use notifications to trigger the application to run some code or set
a state variable. Instead, Jones just displays the text. But it’s impor-
tant that the notification system built into Jones as part of CAudio be
something that you can easily rip out and put to good use in a more II
useful way. So, the design focuses on making it easy to control, cap-
Unit
ture, and retrieve notifications. What you do with the notification
information is up to you.
We introduce the CNotificationManager class to control notifica-
tions. It handles all the busywork so that the tasks of enabling and
processing notifications are as simple as possible. CNotification-
Manager can:
n Enable and disable specific classes of notifications, including
lyrics
n Capture the notifications and store their information in an easy-
to-read-and-use format
n Provide easy retrieval of the notifications so the application can
act on them with a minimum of additional code
Let’s walk through the design.
Enabling Notifications
DirectMusic’s mechanism for classifying the notification categories
relies on GUIDs, which are a bit unwieldy. In order to avoid that
complexity throughout our code and also integrate lyric support in a
clean way, we represent each notification category with a bit flag. We
add one last bit flag to indicate a lyric. This allows us to have one
DWORD represent the on/off state of all notification categories,
including lyrics.
354 n Chapter 14
Unit
CNotificationManager has a method, TranslateType(), that reads a
pmsg and returns a NOTIFICATION_TYPE constant, which can
then be used to track the notification in a more friendly way.
NOTIFICATION_TYPE CNotificationManager::TranslateType(DMUS_PMSG *pMsg)
{
if (pMsg->dwType == DMUS_PMSGT_LYRIC)
{
return NOTIFICATION_LYRIC;
}
else if (pMsg->dwType == DMUS_PMSGT_NOTIFICATION)
{
DMUS_NOTIFICATION_PMSG *pNotify = (DMUS_NOTIFICATION_PMSG *) pMsg;
DWORD dwNotificationOption = pNotify->dwNotificationOption;
if (pNotify->guidNotificationType == GUID_NOTIFICATION_PERFORMANCE)
{
if (dwNotificationOption == DMUS_NOTIFICATION_MUSICSTARTED)
{
return NOTIFICATION_PERFSTARTED;
}
else if (dwNotificationOption == DMUS_NOTIFICATION_MUSICALMOSTEND)
{
return NOTIFICATION_PERFALMOSTEND;
}
else if (dwNotificationOption == DMUS_NOTIFICATION_MUSICSTOPPED)
{
return NOTIFICATION_PERFSTOPPED;
}
}
else if (pNotify->guidNotificationType == GUID_NOTIFICATION_SEGMENT)
{
356 n Chapter 14
if (dwNotificationOption == DMUS_NOTIFICATION_SEGSTART)
{
return NOTIFICATION_SEGSTART;
}
else if (dwNotificationOption == DMUS_NOTIFICATION_SEGALMOSTEND)
{
return NOTIFICATION_SEGALMOSTEND;
}
else if (dwNotificationOption == DMUS_NOTIFICATION_SEGEND)
{
return NOTIFICATION_SEGEND;
}
else if (dwNotificationOption == DMUS_NOTIFICATION_SEGABORT)
{
return NOTIFICATION_SEGABORT;
}
else if (dwNotificationOption == DMUS_NOTIFICATION_SEGLOOP)
{
return NOTIFICATION_SEGLOOP;
}
}
else if (pNotify->guidNotificationType ==
GUID_NOTIFICATION_MEASUREANDBEAT)
{
return NOTIFICATION_MEASUREBEAT;
}
else if (pNotify->guidNotificationType == GUID_NOTIFICATION_CHORD)
{
return NOTIFICATION_CHORD;
}
else if (pNotify->guidNotificationType == GUID_NOTIFICATION_COMMAND)
{
if (dwNotificationOption == DMUS_NOTIFICATION_GROOVE)
{
return NOTIFICATION_GROOVE;
}
else if (dwNotificationOption == DMUS_NOTIFICATION_EMBELLISHMENT)
{
return NOTIFICATION_EMBELLISHMENT;
}
}
else if (pNotify->guidNotificationType == GUID_NOTIFICATION_RECOMPOSE)
{
return NOTIFICATION_RECOMPOSE;
}
}
return NOTIFICATION_NONE;
}
Notifications n 357
Capturing Notifications
To capture both notifications and lyrics, CNotificationManager imple-
ments an IDirectMusicTool interface so it can insert itself directly in
the Performance and intercept DMUS_NOTIFICATION_PMSG and
DMUS_LYRIC_PMSG messages. To make this possible, we base the
CNotificationManager class on the IDirectMusicTool interface.
CNotificationManager then provides its own implementation of all
the IDirectMusicTool methods.
class CNotificationManager : public CMyList, public IDirectMusicTool
{
public:
// IUnknown methods.
STDMETHODIMP QueryInterface(const IID &iid, void **ppv); II
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
Unit
// IDirectMusicTool methods.
STDMETHODIMP Init(IDirectMusicGraph* pGraph) ;
STDMETHODIMP GetMsgDeliveryType(DWORD* pdwDeliveryType ) ;
STDMETHODIMP GetMediaTypeArraySize(DWORD* pdwNumElements ) ;
STDMETHODIMP GetMediaTypes(DWORD** padwMediaTypes,
DWORD dwNumElements) ;
STDMETHODIMP ProcessPMsg(IDirectMusicPerformance* pPerf,
DMUS_PMSG* pPMSG) ;
STDMETHODIMP Flush(IDirectMusicPerformance* pPerf,
DMUS_PMSG* pPMSG,
REFERENCE_TIME rtTime) ;
Most of the methods handle the process of setting up the Tool. When
it is installing the Tool, DirectMusic calls Init(), GetMsgDelivery-
Type(), GetMediaTypeArraySize(), and GetMediaTypes(). Depending
on what the Tool needs to do, it can implement these as needed.
Init() allows the Tool to initialize its internal structures at the
time DirectMusic places the Tool in a Graph. For our purposes, there
is nothing to do here, so the method simply returns S_OK.
HRESULT CNotificationManager::Init(IDirectMusicGraph* pGraph)
{
// Don't need to do anything at initialization.
return S_OK;
}
Unit
loop. It takes the pmsg and uses it to create a CNotification object,
which it places in its queue. Since the queue itself can be accessed
either from the Tool processing thread or the main loop thread, this
code must be protected with a critical section.
void CNotificationManager::InsertNotification(DMUS_PMSG *pMsg)
{
// TranslateType reads the pmsg and figures out the
// appropriate NOTATION_TYPE for it.
NOTIFICATION_TYPE ntType = TranslateType(pMsg);
// If this is a lyric, it still might not be enabled
// for capture, so check first.
if ((ntType != NOTIFICATION_LYRIC) ||
(m_dwEnabledTypes & NOTIFYENABLE_LYRIC))
{
// Allocate a CNotation structure to store the
// notification information.
CNotification *pNotification = new CNotification;
if (pNotification)
{
// The Init() routine fills the CNotification with
// name, type, and optional object pointer.
pNotification->Init(pMsg,ntType);
// Since this can be called from a different thread,
// we need to be safe with a critical section.
EnterCriticalSection(&m_CriticalSection);
AddTail(pNotification);
LeaveCriticalSection(&m_CriticalSection);
360 n Chapter 14
}
}
}
Retrieving Notifications
As the notifications are being captured by the CNotificationManager,
it stores them in its internal list. The application can then call
CNotificationManager::GetNextNotification() to retrieve the oldest
CNotification in the queue. When done with a retrieved
CNotification, the application must delete it.
CNotification *pNotification = pManager->GetNextNotification();
if (pNotification)
{ II
// Do something.
delete pNotification;
Unit
}
CAudio Integration
For notification support, CAudio adds a pointer to a CNotification-
Manager and two methods to access it:
public:
// Methods for managing notifications.
CNotification *GetNextNotification(); // Returns next notification in queue.
void EnableNotifications(DWORD dwType,bool fEnable);
private:
// CNotificationManager captures and queues notifications.
CNotificationManager * m_pNotifications;
m_pNotifications->DisableNotifications(dwType);
}
}
}
Unit
if (m_pNotifications)
{
m_pNotifications->Release();
m_pNotifications = NULL;
}
g_dwMusicState = MS_EXPIRING;
break;
case NOTIFICATION_SEGSTART :
if (pNotif->GetObject() == g_pDanceMusic)
{
g_dwBegoniaMove = BD_START;
}
break;
case NOTIFICATION_MEASUREBEAT :
if (pNotif->GetData() == 2)
{
g_dwBegoniaMove = BD_JUMP;
}
else
{
g_dwBegoniaMove = BD_WIGGLE;
}
break;
II
}
Unit
// Done with the notification.
delete pNotif;
}
}
DirectX Audio
Case Studies
Chapter 15: A Direct Music Case Study for Russian Squares for
Windows XP Plus Pack
Chapter 16: A DirectMusic Case Study for No One Lives Forever
Chapter 17: A DirectSound Case Study for Halo
Chapter 18: A DirectMusic Case Study for Interactive Music on
the Web
Chapter 19: A DirectMusic Case Study for Worms Blast
Chapter 20: A DirectMusic Case Study for Asheron’s Call 2:
The Fallen Kings
Chapter 21: Beyond Games: Bringing DirectMusic into the
Living Room
367
This page intentionally left blank.
Chapter 15
369
370 n Chapter 15
Adaptive Elements
The adaptive elements of a game determine the form of an adaptive
score. An important first step is identifying potential adaptive ele-
ments that tie gameplay to the music. Start by asking questions
about the gameplay. What is the core gameplay element? How does
it function? What are secondary gameplay elements? In what ways
can I link the music to this gameplay?
Music Cells
The core gameplay of Russian Squares is the elimination and addi-
tion of rows of squares. We made this game design element the core
of the music functionality. As the game adds rows or the player elimi-
nates them, the music responds with a subtle change; the adaptive
music system adds or subtracts an instrument, the harmony or
rhythm changes, etc. The music follows the overall pace of the
player. To accomplish this, there are about 50 music cells per compo-
sition, which correlate to different groove levels. As the player
completes rows, the music incrementally transitions to the next
groove level. Logical transition boundaries make those transitions
musical and seamless. I used mainly measure boundaries for transi-
tions in Russian Squares.
I used music intros, ends, and breaks to kick off and end
gameplay. The music transitions to an ambient break section when
the player pauses the game. The adaptive music system plays the
appropriate intros and ends based on the current music cell.
Variation
Russian Squares uses instrument-level variation to keep the individ-
ual cells from getting monotonous. Each groove level is anywhere
from two to eight measures in length and repeats as the player
works on a given row. Within DirectMusic, each instrument can have
up to 32 variations. When combined with other instruments, these
variations increase the amount of variation logarithmically. Most
often, one to three instruments per cell use variation, and that gives
the music an organic, spontaneous feel and prevents that all-too-
familiar loopy feeling. Too much variation, however, can unglue the
music’s cohesion.
A DirectMusic Case Study for Russian Squares for Windows XP Plus Pack n 371
Music Layers
Layering short musical gestures over the currently playing groove
level accents many gameplay elements, such as the game clock, the
clearing and adding of rows, and blockers. I use DirectMusic motifs
and secondary Segments to accomplish this. These elements line up
rhythmically with the music (i.e., the tempo of the clocks synchro-
nizes with quarter note values of the music).
Sounds
DLS-2 banks allowed for maximum flexibility and variation in this
project. Some of the instrument variations control instrument filters
(i.e., low pass filter sweeps) in addition to notes, which are crucial
for the genre of music. The sounds used are a combination of Sonic
Implants (www.sonicimplants.com) and custom banks.
Cooperative Composing
Three other composers (Erik Aho, Mike Min, and Bryan Robbins)
assisted in creating the three arrangements. Working with nonlinear
cells made it easy to work simultaneously. Generally, we assigned a
range of cells to each composer. Often, someone would start an idea
and the next composer would elaborate on it or add variations. This
method of working made sharing musical ideas easy and kept every-
thing musically coherent.
III
Creating the Adaptive Audio Design
Part
Our first objective for the adaptive score was to mirror the progres-
sion of game levels. This approach involved creating a score that
increased in intensity from the start to the end of each level (micro-
adaptability), while each successive level began at a higher intensity
than the last (macro-adaptability). Within the game levels, we focus
on the primary gameplay element of clearing rows. A musical change
occurs each time the player clears a row, a reward that lets the
player know he is on the right track. In addition, the change indicates
the growing intensity as the rows clear. Running out of time and hav-
ing a row added is the negative counterpart to clearing a row. The
score reflects this state as well.
A DirectMusic Case Study for Russian Squares for Windows XP Plus Pack n 375
Inherent Obstacles
The biggest trick was creating a score that could gradually increase
or decrease in intensity at any time while creating a satisfying musi-
cal experience. How could we put such a score together? How would
the layers function? How would transitions operate? The overall
objective seemed simple enough, but just thinking about execution
for a few moments brought up all kinds of tough questions. More-
over, digging into the production of the score unearthed many more!
The music in Russian Squares needs to change quickly. Consider
that it may take the player a minute or two to clear a row at the start
376 n Chapter 15
of the game, yet toward the end of any level, the player can clear a
row or the game can add one every few seconds. This poses a curi-
ous problem. If music changes every minute or so, you should use
longer sections to better hold the listener’s interest and avoid repeti-
tion. However, when the music changes every few seconds, it must
not disrupt the aesthetic flow of the phrases. A successful adaptive
score strikes a balance between the length of phrases, their transi-
tion boundaries, and the music’s responsiveness to game changes.
In the modern electronica genre, nothing can make the music
cheesier than wimpy sounds. Electronica lives and dies by its
grooves, fat synthesizer patches, and filter sweeps. It depends
heavily on timbres. This demand for great sounds creates a challenge
for the audio producer facing severe memory constraints.
The last challenge we needed to overcome was repetition. Any
game composer understands the challenge of avoiding undue repeti-
tion in game scores. Russian Squares was no exception. A player can
easily spend a lot of time solving a given level. As the difficulty rises,
rows are cleared, added, cleared, and added again, triggering the
same music cells. Luckily, DirectMusic offered us many ways to deal
with this and the rest of the challenges we faced during production of
our adaptive score.
changes and longer, less repetitive phrases. However, our first few
tests showed some glaring problems with this approach. It worked
well when the music changed every minute or so, but when the
music changed more often, it sounded a bit stilted or unnatural. In
addition, the programmers told us that primary Segments would not
respond as quickly or efficiently in the game as groove levels.
Next, I tried layering secondary Segments but quickly learned
that this method was a logistical nightmare, both technically and
musically. Getting more than three or four secondary Segments to
line up musically and respond quickly to game calls was not practical.
I also experimented with a muting/unmuting system using control-
ling secondary Segments to get the same effect, but again it did not
turn out to be practical for this design.
We use Styles and groove levels in this application, as they
respond quickly. Each groove level associates with a row. If the
player clears a row, the game increases the groove level by one, and
if the game adds a row, it decreases the groove level by one. There-
fore, the game engine slides the set of groove levels up and down as
the game progresses. Each groove level consists of a musical phrase
that is two to 12 measures long and transitions easily to the next
groove level. We use measure boundaries as transition points
between groove levels, as they allow for relatively quick transitions
while remaining musical. There are 45 groove levels for each
composition.
Using the Style/groove level system, we take full advantage of III
DirectMusic’s intros, ends, and breaks. Anytime a new game or new Part
level begins, DirectMusic plays an appropriate intro leading into the
appropriate groove level. When the game ends or the player com-
pletes a level, the music seamlessly plays an ending. When the game
pauses, a DirectMusic break plays until the player resumes the
game. All of this gives the game score a polished and professional
feel.
We use secondary Segments and layer them over the groove lev-
els for the musical accents that emphasize specific game events: row
cleared, row added, blockers, and the clock running out of time. Even
though the groove level changes when the player clears a row or the
game adds one, we want a consistent underscore that occurs simul-
taneously with the row change. Another secondary Segment plays
every time a blocker appears in the game. These secondary
378 n Chapter 15
Segments play on the grid boundary (in this case, the next sixteenth
note) of the underlying groove level so that they sound virtually
instantaneous, yet rhythmically line up with the music. As time is
running out on the clock (ten seconds to go), a looping secondary
Segment plays until time runs out or the player clears the row. This
Segment begins on the beat boundary so that the “tick tock” sound
lines up with the beat of the groove level. Each composition uses a
different sound set for these secondary Segments. This way, all the
sounds are integral to each composition and add coherence. For
example, the clock for Spin Cycle is a buzzer sound; for Bubble Mat-
ter, it’s a wooden “tick tock”; Gravity Ride uses the clank of metal
spikes to mark time.
Harmonic Considerations
For this game, I chose not to take advantage of DirectMusic’s adap-
tive harmonic abilities, such as ChordMaps or Chord Tracks. The
simple harmonic structure of the music did not merit the extra pro-
duction time that it would have taken to make it function well within
the design. Frequent or complex harmonic and chordal shifts did not
fit the aesthetic of electronica music. I created the music’s harmonic
or chordal changes directly in the pattern editor. Harmonic changes
occur when moving from one groove level to the next, which corre-
lates to a row being cleared or added.
Setting Up Styles
We set up each of the three compositions identically in DirectMusic
Producer. One style held all of the groove levels, the motifs, and the
Band for a given piece. We set up the Band (within the Style) with
the pchannels assigned to the custom DLS instrument patches after
we created the Style. We then set the instrument’s levels and pan-
ning for a rough mix, tweaking them during the composition process,
and created our patterns.
Continuous Controllers
Motion within instrument parts adds depth of emotion to an other-
wise static composition. Controllers 10 (pan), 11 (expression), and 74
(filter cutoff) breathe dynamic life into the Russian Squares music.
We manually entered control curves or points into Pattern Part Con-
tinuous Controller tracks. Performing CC data in real time using a
MIDI device was impractical and messy in DirectX 8. Performance
latency rendered the data inaccurate. In addition, thinning the CC
data after a Performance required tedious selecting and deleting.
Continuous controller curves, while painstaking to insert and adjust,
were much easier to edit than a multitude of individual CC data
points (which is the result of any CC Performance).
A DirectMusic Case Study for Russian Squares for Windows XP Plus Pack n 383
Segments
Each piece of music utilizes only one primary Segment. That Seg-
ment contains five tracks: Tempo, Time Signature, Style, Groove,
and Band. The Tempo Track is fixed, as are the Time Signature,
Style, and Band Tracks. The Style is inserted at beat one measure
one of the Style Track. When you are done with this, DirectMusic
automatically inserts that Style’s Band into the Band Track.
The game controls the groove level of the Segment Groove
Track by incrementing or decrementing the groove level when “clear
row” or “add row” occurs. The primary Segment plays continually as
its groove level moves up or down, thereby changing the Style/pat-
tern currently playing. For example, as the game starts, the Segment
plays groove level “Intro 20” and then moves to groove level 20. The
groove level increases to 21 as the player clears a row and moves to
22 when the player clears the next row. Then groove level “Break
22” plays if the player pauses the game. The player ends the game,
triggering groove level “End 22.”
the player. The clear row motif rewards the player’s progress, while
the add row gesture indicates the player’s regression. Each composi-
tion enlists a different set of sounds for these motifs. Synthesized
pitch bend gestures indicate clear row (upward pitch bend) and add
row (downward pitch bend) in Spin Cycle. Harp glissandos signal
clear row and add row in Bubble Matter, while Gravity Ride uses a
shaker and claves for those motifs. The blocker motif accents each
theme aggressively with single staccato gestures. As with the other
motifs, the instrument timbres mesh with the respective music
themes.
The clock motifs are the only looping motifs. They begin as the
game clock is running out of time (it turns red) and end when a row
is cleared or the clock runs out of time. This means that the clock
motif always ends as a clear row or add row motif plays, which cre-
ates a nice bookend. Each composition uses a unique instrument for
the sound of the clock — buzzer sound, wooden tick tock, and metal
clanks, respectively. The high score motif rewards the player when a
high score game is completed. These phrases are two or three mea-
sures long and overlap a composition’s end pattern. High score
motifs are simple one-instrument arpeggiated patterns that highlight
the player’s victory.
Motifs are inserted into secondary Segments for playback by the
game. (Motifs can be called independently of secondary Segments,
but the programmer requested they be called via secondary Seg-
ments due to the calls available in DirectX Audio Scripting.) Each of III
these secondary Segments contains a Time Signature Track and a Part
Segment Trigger Track. An insertion is made in the Segment Trigger
Track, and the properties window allows for selection of the
Style/motif. The game calls the secondary Segments, and they in
turn play the motifs.
387
388 n Chapter 16
film scores have more of the lighthearted feel that we were after.
The Barbarella soundtrack was also required listening.
I began the pre-production process by writing five or six themes
and prototyping them. These themes became the backbone of the
adaptive score. The adaptive scoring techniques for NOLF came out
of the concepts and technology implemented for three previous
Monolith games — Shogo: Mobile Armor Division, Blood II: The
Chosen, and Sanity. Shogo: Mobile Armor Division was the first
game I scored that broke the music down into separate music states,
which matched the game action. Blood II: The Chosen and Sanity
each added to those concepts, creatively and technically.
NOLF built upon my adaptive scoring foundation, improving on
many aspects of my technique. This white paper describes the adap-
tive scoring concepts, the production process, and implementation
process used to create this game score. In this case study I describe
my intentions and the actual outcome, what worked and what didn’t.
I also describe what I’d like to achieve with future action scores.
necessary until another music state is called. I could also define the
number of repeats. The music engine supported automatic transi-
tions from one music state to another or even to silence. This
prevents the music from repeating ad nauseum for moments when
player interaction is limited.
Music Sets
Each musical theme in NOLF is arranged using six basic music
states that make up a single music set. At the start of a level, one
music set is loaded along with the rest of the level assets. The six
standard music states are:
n Silence
n Super ambient
n Ambient
n Suspense/sneak
n Action 1
n Action 2
The key to composing music for any given music state is to give the
music enough musical ebb and flow to keep things interesting while
staying within the intensity range prescribed by the music state. For
example, the “ambient” music state may rise and fall a bit in musical
density but should not feel like it has moved up or down too dramati- III
cally. The goal is to maintain the musical state while holding interest.
One way that the music sets in NOLF achieve this level of sustained
Part
The Matrix
First conceived for the Shogo score, a transition matrix filled the
need to keep track of the myriad of possible movements between
music states. By defining the matrix in a simple script (more on the
script later), I was able to assign short sections of music to act as
specific transitions between states. When the game calls for a
change of music state, it knows which music state is currently play-
ing and which one it needs to move to and plays the appropriate
transition between them.
The transition matrix acts as a look-up chart for the music/game
engine. With six music states, there are 30 possible transitions.
Needless to say, I didn’t labor over 30 individual sections of music for
each theme. Many transitions did not need transition Segments, as
they sounded good cutting in on an appropriate boundary. Also, I
found that some transition Segments could be used for multiple tran-
sitions between music states. Transitions were generally divided
into two types to help clarify my thinking: transitions that move to a
higher or more intense music state and transitions that move to a
lower or less intense music state. Categorizing transitions in this
way made reusing transition material easier (i.e., transitioning from
music state three to music state two may be similar to “3 to 1,”
while “3 to 4” may be similar to “3 to 5” — but not always!).
A DirectMusic Case Study for No One Lives Forever n 391
Performance Boundaries
Key to making the transitions work musically were performance
boundaries. Performance boundaries defined the points along a
music state where transitions could take place. Boundary types
included Immediate, Grid, Beat, Measure, and Segment. Each of
these boundary types proved useful for different situations in NOLF.
When a music state was rhythmically ambiguous, Immediate or Grid
worked fine, allowing for the quickest transitions. Beat and Measure
boundaries came in handy when the rhythmic pulse needed to stay
constant, and Segment boundaries allowed the currently playing
melodic phrase or harmony to resolve before transitioning.
Maintaining a balance between coherent musical transitions and
the need to move quickly between states challenged us as arrangers.
As you can hear if you play the game, some transitions work better
than others. When a new state is called, there is an acceptable win-
dow of time to move to the new state. We used a window of zero to
six seconds (eight seconds tops). This meant that at a tempo of
120 BPM, a four-measure phrase (in 4/4) was the absolute maximum
that a current music state could finish prior to transitioning to the
new state. One typical solution was to use two measure boundaries
(for quicker transitions) for most of a music state and four measure
boundaries in spots that called for them aesthetically.
Composing and arranging convincing musical transitions in an
adaptive score twists your brain and forces you to think nonlinearly. III
The interactive score used in NOLF only scratches the surface in Part
this regard; there is plenty of room for future innovation. I can say
for certain that having written a number of nonlinear scores, I’ll
never think about music the same way again. In a way, it’s freed my
thinking about how music is put together. Even when listening to lin-
ear music, I sometimes think “Hmmm… this piece could just as
easily start with this section instead of that one” or “I bet they
could’ve started that transition two measures earlier!,” etc. Music is
malleable and only frozen when we record it.
392 n Chapter 16
Stinger Motifs
Two or three of the music sets used in NOLF employed motifs. The
motifs were applied as short musical accents, or stingers, that played
over the top of the currently playing music state. Performance
boundaries were set so that the motifs would be in sync with the
underlying music, and Chord Tracks were used so that motifs would
play along with a functional harmony (this was the only use of
DirectMusic’s difficult-to-navigate harmonic features).
These motifs were composed of brass riffs, quick guitar licks,
and things that would easily fit over the music state Segments. More
flexibility would have been nice so that different motifs could be
assigned to specific music states (possible using DirectX 8 Audio
scripting). Five or six motifs were written for each music set. The
engine called a motif randomly when the player hit an enemy AI
square in the head (ouch!). A silent motif was employed to prevent a
motif from playing every single time.
Scripting
Monolith created a simple scripting method, which provided me with
some control over the music asset management and implementation.
Being a DirectX 7 game, we didn’t have the DirectX 8 Audio
Scripting that now comes with DirectMusic. In NOLF there is a
script for each music set that is called when a game level is loaded.
The script’s basic functions are:
394 n Chapter 16
create the game score for DieHard: Nakatomi Plaza, I had an exactly
mirrored set from DLS as the target format to GigaStudio as the pro-
duction format.
DLS Creation
The DLS Level 1 instruments for No One Lives Forever came from
two sources; first, we licensed many instrument collections from
Sonic Network (the sounds are called Sonic Implants — www.sonic-
implants.com). The other samples were homemade, including some
solo cello samples. Sounds are a composer’s palette, and having a
rich palette, despite memory constraints of the game, was key to
making the interactive score convincing.
396 n Chapter 16
the NOLF score had some instrument sets that were built spe-
cifically for that theme — the vocal “BaDeDum” sample for its
theme and the horn “blatts” for the Ambush theme, in addition
to the cello samples already mentioned for the H.A.R.M. theme.
Creating looping samples posed a big challenge given the short
length of the samples. For many samples, a Mac program called
Infinity was used to create internal loops. The program has tools,
such as crossfade looping, that help smooth out harmonic thumps
and clicks common to short loops. That said, short loops are never
perfect, and compromises are always made for the sake of memory
constraints.
Figure 16-1: The Producer setup for the Ambush theme, showing project
structure and Style/pattern associations. III
Part
Instrument-level Variation
As you know, each pattern part can have up to 32 variations. Each
time a pattern is played, one variation per part is randomly chosen.
Multiple variations do not have to be utilized; in fact, many pattern
parts in NOLF had one “hard-wired” variation to play. We created
this “hard-wired” variation by disabling all other possible variations
for a pattern. I found that four or five variations on two or three parts
was enough variety for most patterns.
The ambient and sub-ambient patterns tended to get a deeper
variation treatment, with most parts having variations. It may be that
the moody, atmospheric, and arrhythmic nature of these music states
lent themselves to a truly nonlinear treatment.
Variations in the suspense or action music states entailed two or
III
three pattern parts with variation. This allowed the foundation of
Part
measures three and five, while the flute variations are given mea-
sures two and six. This ensures that the parts won’t step on each
other, despite its nonlinear nature.
Continuous Controllers
The use of continuous controllers is essential to breathing life into
any MIDI score, and a DirectMusic score is no exception. In
DirectMusic, each pattern part can have CC tracks inserted beneath
its piano roll. In NOLF, controller 7 (volume) is used to change a
A DirectMusic Case Study for No One Lives Forever n 403
III
þ Note Curves that do not reset during a transition can cause CC info
to “stick” at an unwanted level. This can occur when the music moves Part
from one Segment to the next during a CC curve. To prevent this, we
place CC data points at the beginning of every pattern part for those
pchannels that use CC data. This technique initializes the CC data for
each pattern, avoiding unwanted CC levels.
Segment Creation
Segments bring together all of the disparate patterns in a project into
unified arrangements. The patterns act as building blocks for the
Segments. Sometimes the order and arrangements of the Segments
were planned prior to their creation. At other times, we would
experiment with different orderings of patterns within Segments.
This flexibility resulted in some interesting outcomes, and we often
got more mileage out of our patterns. Creating short phrase-length
404 n Chapter 16
creation of features, and features (such as the Marker Track) are not
created until there is a known need for them.
Motif Creation
Motifs are created within Styles. There is a folder within each Style
for motifs. Motifs are created and edited in the same manner as pat-
terns. The only difference is in functionality; motifs layer over
Segments at a Segment’s tempo, while patterns are triggered via
groove levels from within Segments. Motifs follow the harmonic
structure of a Segment (if set up to do so).
To keep things simple, NOLF motifs are short, simple phrases
that punctuate and accent the underlying music. As mentioned ear-
lier, NOLF motifs play when a direct hit is made on an enemy AI.
Often, one or two staccato brass chords accent the music, or a sus-
penseful string tremolo rings over the primary Segment. In all cases,
NOLF motifs are one measure or less in duration.
The instrument of a motif works best when it is different from
the instruments of the primary Segment. This prevents the motifs
from stepping on a part that is already playing. Using a different or
specific range also helps in this regard. For example, if the primary
Segment contains low- and mid-range brass instruments, using a
trumpet motif in the upper registers will fit well in the mix rather
than make it muddy. Even if the primary Segment contains a mid-
range trumpet, a solo trumpet motif will be heard because of its dis-
tinct timbre.
III
The performance boundary for motifs was always Grid or Beat,
Part
so they would play in sync with Segments, yet respond quickly to the
game. This means that a motif plays no more than a half second after
a direct hit and its gunshot sound. The timing coordinates perfectly
with the sound of the gun, responding to a perfect shot. A perfor-
mance boundary of Immediate might cause the motif to step on the
gun sound and would not play in sync with the primary Segment.
Performance boundaries of Measure or Segment would cause the
motif to respond too late, losing the desired timing. NOLF motifs uti-
lized DirectMusic’s Chord Tracks so they perform in harmony with
the underlying music. Motifs are created in a neutral key (C), and the
primary Segments’ Chord Track transposes the motifs according to
its chord symbols. The chord symbols inserted in the Chord Track
reflect the key or harmony of the primary Segment’s music. In most
406 n Chapter 16
and the intro brings the music into the next state. More often, this
end/intro transition is the basis for that transition, and further editing
and composing is done to make it work well.
Many transition Segments function well in multiple transitions,
and this saves production time. For example, the transition Segment
built for music states 2 to 3 may also work between music states 2
and 4, etc. I compose one music state’s transitions at a time. Thus,
when I’m working on music state 2, I sequentially create transitions
between states 2 to 1, 2 to 3, 2 to 4, 2 to 5, and 2 to 6. Again, this is
because there are bound to be similarities among these transitions
that I can reuse.
Simple transitions are often the most effective. If the music
needs to stop or transition quickly, a large percussive accent brings
the music to a halt. It’s as if the music hits a wall, blunt and jarring,
and that frequently works well within the game. There are also cases
when no transition Segment is needed between music states. In
these cases, the music flows directly from one music state to the
next, and the release times of the DLS instruments create a natural
blending or crossfade between music states.
Sometimes, one transition Segment is not adequate for a particu-
lar transition. If a music state contains a variety of musical sections,
more than one transition Segment may be needed. For instance,
music state 4 has an A section that is 16 measures in length and a B
section also 16 measures long. If the instruments used in each sec-
tion are different or the harmony and tempo vary between them, III
then a single end Segment may not work from both section A and Part
section B. In a case such as this, two transition Segments are cre-
ated, one for transitioning from the A section and a second for
transitioning from the B section. During gameplay, the transition
matrix calls the appropriate transition Segment, depending on the
current playback point of the music state (more on this in the section
titled “The Transition Matrix”).
When creating music states and their transitions, keep their har-
monic content in mind. Music states with disparate key centers
cause difficult-to-execute transitions because the drastic harmonic
modulation of the transition may sound unnatural or awkward. I rec-
ommend using key centers that are closely related (i.e., C major to G
major) to create convincing transitions. I also use chromatic modula-
tion in NOLF (i.e., B major to C major), and many transitions simply
408 n Chapter 16
stay in the same key. It is easy to back yourself into a corner har-
monically when creating an adaptive score. Be aware of the tonal
centers of the music states as you create them, and try to think
ahead about how they will transition harmonically.
Conclusion
The score for No One Lives Forever was a challenge to produce but
was also very rewarding. The first challenge was convincing the pro-
ducers at Fox Interactive that an adaptive score could have high
standards of production quality. The Monolith team helped make the
case by presenting demos of previous scores, such as Sanity. Also,
my prototype themes helped convince them of my abilities as a com-
poser. Putting together convincing DLS banks within tight memory
constraints also posed a big challenge. The optimization process was
time consuming and tedious but key to the sound of the game. Final
mixing and editing called for great attention to detail by going though
all the patterns (all their variations), motifs, and Segments, making
sure volume levels, panning, and instrumentation meshed well
together. NOLF’s game state/music state integration gave me the
greatest reward. It was fantastic to simply drop music into the game III
and hear the interactivity immediately. It was also gratifying to col- Part
laborate with a strong team of arrangers/composers. Having the help
of three other musicians produced more content for the game, and
sharing compositional ideas and techniques made us all better musi-
cians. Finally, spy music was just plain fun to compose. The game’s
sense of humor made it a delight to create its music.
Overall, the adaptive design functioned as planned or better. The
transitions reacted quickly and smoothly to the game calls, and the
mood of each music state matched the on-screen action very well.
The instrument variation, music state variation, and use of silence
alleviated the repetitiveness common to many games, and the motifs
made direct hits more satisfying to the player. At its best, the adap-
tive score draws the player deeper into the game experience. My
biggest criticism is that sometimes the game states change faster
414 n Chapter 16
than the music was intended to react. This makes the music seesaw
between music states unnaturally. Many of these instances are only
noticeable to me, but some are more obvious. I will be thinking of
solutions to this type of dilemma for my next adaptive score. Also,
the sonic quality of the music is limited due to the 22 kHz DLS
banks. A combination of Wave Tracks and DLS banks would have
allowed for longer samples, phrases, and premixed sections, which
can increase the overall fidelity of a score while maintaining
adaptability.
Each adaptive game score that I produce gives me ideas and con-
cepts for the next. The biggest lesson learned from NOLF is that
global music integration is hugely important to a successful score.
Good integration creates the logical lines of communication between
the music system and the game engine. If these lines are weak or
nonexistent, the music will not respond well to gameplay, no matter
how well the music functions out of context. Context is everything.
III
Part
This page intentionally left blank.
Chapter 17
A DirectSound
Case Study for Halo
Introduction
Audio the Bungie Way
T he evolution of the audio in Halo began with the first two install-
ments of the Myth series of games developed by Bungie in 1997
and 1998. A lot of time was spent laying the groundwork for the
audio engine that we developed further during the production of Oni
and utilized in Halo. Functionality was developed to allow for multi-
ple permutations, nonrepetitive loops, detail sounds, dynamic dialog,
and impacts reactive to velocity and distance, as well as cascading
sound effects. While Halo was rewritten from the ground up for the
Xbox (after Bungie was acquired by Microsoft), it is important to
note that many of the high-level sound design concepts and their
implementation into the Halo audio engine are the result of years of
development and refinement with Jason Jones and the Bungie pro-
gramming team, in particular Halo’s audio programmer Matt Segur.
We share a philosophy here at Bungie that we like to call “Audio
the Bungie Way,” which details fundamental audio design for any of
our games. At the core of this philosophy is the understanding that
repetitive audio is not only annoying but also unrealistic. This is why
we allow for so many permutations in every sound call, why music
does not play constantly throughout a gaming session, and why we
insist on the many customizations to our audio engines.
417
418 n Chapter 17
This core idea means that we spend a huge amount of our efforts
in the implementation stage. There are many games released every
year that have audio assets of the highest quality, but due to lacklus-
ter implementation, they fall short of having the maximum impact.
We believe firmly that half of what makes a game sound good is in
the implementation. This process begins with the technical design
and continues through pre-production, production, and post-pro-
duction. The process requires the full effort and support of the
production and programming teams.
Production
Music
We utilized DirectSound for all of the music in Halo to provide a
dynamic musical score that is both constantly changing and reactive
to gameplay elements. Each piece of music during gameplay was
started and stopped in the scripting at various moments within each
level. When the gameplay design for a level was nearly completed,
we’d sit down with the designer and “spot” how music would play
during that level. When a piece was started, it began with the “in”
soundtag and continued into the main looping soundtag. This main
looping soundtag contained multiple permutations, which play back
randomly to create a constantly changing musical experience.
If you sit in one place in Halo listening to a piece of music, you
will notice that it never plays back exactly the same way twice due to III
the randomization of the permutations. The music was edited so that
the “in” soundfile plays seamlessly into any of the main loop permu-
Part
tations, and the main loop permutations can play seamlessly into the
“out” soundfile when the piece is stopped by the script. These main
loop permutations contain unique elements, so as they are randomly
played back, the piece takes on a new flavor each time you hear it.
The script can call upon an alternate track, which is used in reac-
tion to something going on during gameplay. This alternate track
(which can have its own unique “out”) might have a more or less
intense score, depending on the needs of that section of gameplay.
For instance, if the player is about to enter an encounter with a num-
ber of enemies, the script starts a piece of music beginning with the
“in” and proceeds to the “main” looping section. If during the course
of battle, the designer throws another onslaught of enemies at the
player, the script could trigger an alternate track that, in this case,
422 n Chapter 17
might be a more intense mix of the same piece of music. Then when
all enemies have been vanquished, the script could either stop the
piece of music (playing the “out” soundtag) or return to the “main”
looping section, indicating to the player that the battle was over.
This system means that the player gets a constantly changing musi-
cal score that reacts to gameplay elements as they appear.
Another example of this would be scripting an ambient piece of
music and then scripting a more rhythmic “alt” track that crossfades
when the player runs into enemies. For instance, we might begin a
piece of music when the player enters a new area. This music is
ambient in nature, without a clearly identifiable rhythm. When the
level designer decides to ambush the player with enemies, the alter-
nate track is triggered, which could either begin playing at the end of
the current loop or crossfade at the moment it is triggered. In this
case, the alternate track could be a more rhythmic version of the
ambient piece or depart from the ambient piece entirely. As when
the enemies have been vanquished, we can instruct the level
designer to script the tag to play the “alt out” or return to the
“main” loop to continue playing the ambient music.
Other elements that are useful, especially during ambient pieces
of music, are details and multiple tracks. Details are soundtags full of
soundfiles that could be triggered anytime during the playback of a
piece of music. We can set which group of files to use and a time
range in which to randomly trigger them. Additionally, we could layer
a second track on top of the first one, and having both “main” loops
playing back with multiple permutations of varying lengths would
provide an additional layer of variability. These techniques allow a lot
of flexibility and give the player variation and dynamic playback
while utilizing a traditional form of playback (streaming prerecorded
audio files). All music soundfiles were ADPCM compressed 44.1
kHz stereo AIFF files and loaded in 128K chunks from the hard drive
(sort of a faux streaming).
SFX
The background stereo ambiences in Halo were created in a similar
manner to the music. All ambient soundlooping tags were assembled
identically to the music files described above. We had multiple tracks
playing in many cases (such as an outdoor ambience plus a wind
track), each with multiple permutations in the “main” loop tag, as
A DirectSound Case Study for Halo n 423
Surround Sound
One built-in feature to the Xbox chip that is absolutely amazing is its
ability to generate real-time 5.1 Dolby Digital output. We encode and
decode Dolby Surround on the fly from dynamic game audio so that a
player gets a full surround audio experience throughout the game.
This is a first in console gaming — and a great first at that!
There are many advantages to implementing 5.1 audio, from
alerting the player to the presence of enemies to surrounding the
player with ambience and music. While we did not premix the music
or ambience in 5.1, we were able to surround the player with sound.
Stereo music and ambience were played 100 percent out of the front
speakers and 50 percent out of the rear speakers and also sent to the
LFE. Non-positional dialog was sent 100 percent to the center
speaker and 50 percent to each of the front speakers. All 3D posi-
tioned sound was played back in its appropriate position among the
four surround speakers and also sent to the LFE. The Halo audio
engine queries the Xbox at startup to see whether it is set to stereo
or Dolby Digital and plays back the audio appropriately to avoid dou-
bling up audio signals in the stereo fold down.
The scale can modify pitch, gain, and skip fraction, depending on the
requirements for the sound effect.
Dialog
The dialog in Halo was one of the areas that helped to give Halo a
unique flavor. There are two types of dialog: cinematic dialog that is a
traditional linear script and dynamic dialog. As you play through
Halo, your Marines will dynamically alert you to what is going on
around you, warn each other and you of danger, chastise, apologize
for allies getting shot or killed by friendly fire, and generally amuse
you. This aspect of Halo’s audio could not have succeeded without
A DirectSound Case Study for Halo n 429
plot to the player. There is about one hour and 15 minutes of cut
scenes in Halo, and that puts this part of Halo’s production on par
with a short feature film.
were not a sure thing until late in the summer of 2001, mere months
before shipping. The Xbox audio team did a great job delivering what
they promised, making it a great platform on which to develop.
there are quite a few good samples out there, they are not a replace-
ment for live musicians. For future titles, we will build time into our
schedule to record live musicians where appropriate.
Another time constraint issue was facial animation. We only had
the ability to open or close a character’s mouth in reaction to the
amplitude of the speech sample. For future games, we will be adapt-
ing a more robust facial animation system, which will make the
characters appear much more realistic when they are speaking.
There were several content areas in which we were constrained
to mono sound effects, such as first-person animations and all 3D
positional audio. Some 3D positional audio, such as the jeep engine,
should be stereo. There is work being done both at Bungie and
throughout Microsoft that will allow for stereo sound effects to be
432 n Chapter 17
3D positioned and will bring more life to sounds such as vehicles and
weapons.
Occlusion is another area we would like to refine. Our engine did
not allow for movable objects, such as doors, to occlude. For upcom-
ing games, we will be working to make sure that anything that
should occlude will occlude.
Our use of DSP was limited and is an area that has a lot of poten-
tial. We will be working with our programming team to allow us full
use of real-time EQ, LFO, and plug-ins such as limiters. We will also
be looking at the use of better data compression for high-quality,
low-memory audio in certain areas of the game.
A DirectMusic Case
Study for Interactive
Music on the Web
Overview
Background
add to the possibilities. The same amount of sample data required for
a single prerecorded drum pattern as part of a stream is therefore
able to produce countless variations. A sound designer can manipu-
late and reuse samples in new instruments without adding additional
material to the download. Furthermore, the sound designer can com-
press the samples on a case-by-case basis, allowing for the best
compromise between size and quality. In addition, since the user
downloads the actual DLS instrument as web content, the music
sounds consistent across the full range of playback hardware. The
ability to use real-time effects such as reverb, chorus, delay, and dis-
tortion further enhances the potential to make the most out of a few
samples, as well as adding depth to the final mix.
DirectMusic scripting adds the vital ingredient of interactivity.
DirectMusic content creators feed button clicks, mouse movements,
and other triggers into the script and use them to shape the music in
accordance with the user’s interactions with the site. It is also possi-
ble to give the user direct control over aspects of the music. This
control can range from something as simple as a volume control to
much more abstract control, say, over subtle harmonic changes; the
possibilities are limitless.
DirectMusic Styles and patterns allow for a great deal of control
over variation, making it perfect for background music that needs to
remain interesting over a long period of time (for instance, while the
user is reading the contents of the site). Within a pattern, you can
create up to 32 variations of each pattern part and control the play- III
back of these in a variety of ways. Parts, and even individual notes, Part
have parameters that can apply a degree of randomness to playback.
It is possible, with these features, to create self-varying musical
accompaniments guaranteed not to repeat in a predictable way.
DirectMusic’s efficiency, variability, and interactivity make it
uniquely suited to use on the web. When I first investigated the pos-
sibility of using it in this way, however, there was no available means
of communicating user interactions to a DirectMusic script. There-
fore, it became necessary to create an ActiveX control to do this.
436 n Chapter 18
Functionality
The potential uses of scripted music for a web site are almost limit-
less, but here are a few of the key techniques and components that I
find useful when designing interactive music for a web site:
n Play Segment: Play a specific piece of music for a content area,
or start a new piece when clicking through to a different part of
the site.
n Transitions: Script changes to the primary Segment in such a
way that they provide a seamless transition in order to maintain
musical coherence and flow.
A DirectMusic Case Study for Interactive Music on the Web n 437
Our web site is very simple, so there are only a small number of
hooks. Sites that are more complex may require more complex
music implementations. DirectMusic can certainly accommodate the
most demanding scripting requirements.
Once you have determined the hooks you will be using to drive
the music, you can begin to think about creating musical content to
match the site structure and interactive behavior.
page on the site? Are there elements of the site that the user inter-
acts with quite often? How can the musical content aid navigation
and usability? Which recurring elements require consistency
throughout the site for clarity? Understanding how the site functions
helps in designing appropriate interactive audio.
Content Creation
While a detailed discussion of content creation techniques in
DirectMusic Producer is beyond the scope of this case study, there
are some techniques worth considering here. Download size and the
unpredictable nature of user interactions are particularly important
442 n Chapter 18
issues when designing interactive music for a web site. Unlike most
games, web sites can potentially be viewed for long periods with
very little interaction. Alternatively, the interactions may come thick
and fast, and the music must be able to respond without becoming
incoherent. Consider these issues carefully when creating content.
Make sure that DLS instruments make the most of a small amount
of sample data. Build variety into your patterns to maintain interest
when no script routines are being triggered. Create Segments that
can transition smoothly at any point.
DLS Instrumentation
Long loading times are a big turnoff for web site users, and although
our DirectMusic content loads separately from the rest of the site, it
is still important to keep the size of the download to a minimum. You
should pay particular attention to the DLS instrument sample data,
as this is the bulk of downloaded material.
It is important to minimize sample data, while maintaining the
highest possible quality. The target download size for this site is less
than 100KB. To meet this requirement, we choose instruments that
do not require large samples. Long multi-sampled orchestral instru-
ments require many more samples than anyone has the patience to
download. Use short looping source samples that spread across the
keyboard without suffering undesirable effects. For example, use
samples of analog style waveforms, such as sawtooth waves, as the
basis for DLS instruments. Only a few cycles are required and a wide
variety of sounds can be created from the same source. For drums
and percussion, create a small number of source hits instead of long
loops. Also, reuse samples at various pitches and with various enve-
lopes as the basis for different instruments to get the most mileage
out of them. Be creative and generate many rich, multi-layered
sounds all from the same source samples.
Instrument Choices
III
Considering what we know at this point, we decided that our instru-
mentation should lean toward analog style pads, basses and synths
Part
Look in the Waves folder in Flash.dlp, and observe that the final ver-
sion uses nine samples amounting to 207KB of storage space,
uncompressed. Of these, seven responded well to ADPCM compres-
sion, and the final run-time DLS file weighs in at 89KB — within our
target range even before we create the final downloadable archive, at
which point we can expect some further file compression.
By making the right instrument choices, reusing samples, and
selectively using compression, it is possible to create a good quality
sound palette and keep the download to an acceptable size.
Pattern Structure
III
All the patterns in the Style share more or less the same structure.
They are all eight bars long, and they all share the same chord
Part
sequence (as defined in the Chords for Composition track) and draw
on a main pool of pattern parts contained in “All” and “All2.” These
patterns serve as a source of linked Pattern Parts for the other pat-
terns and have a groove level of 100 so that they themselves never
actually play. Linking pattern parts reduces duplication of content
and further reduces the size of the final run-time files.
Our patterns fall into two groups. Patterns 101 to 106 are
derived from All, while 201 to 204 are derived from All2. Patterns
101 to 104 provide a progression and some variation for the main
page of the site, and Patterns 105 and 106 add a guitar motif with
varying accompaniment for half of the content pages. Patterns 201 to
204 provide the music for the remaining content pages, based on a
sparser arrangement of the same basic elements.
446 n Chapter 18
Groove Levels
See the groove levels used by all the Patterns in Main.stp below.
Figure 18-6: The Style Designer window showing the Groove Range settings for
each Pattern.
Patterns 101 to 106 use groove levels one to seven roughly in order
of intensity. Groove level one is the most sparse, used only by the
Segment Intro.sgp when the site loads. The Segment Front.sgp, fea-
tured on the main page of the site, uses groove range two to four,
thus selecting from Patterns 102 to 104. Guitar.sgp uses groove
range five to seven or Patterns 105 and 106.
We assigned Patterns 201 to 204 to the groove range 11 to 16;
Minimal.sgp uses this groove range. The assignment of groove
A DirectMusic Case Study for Interactive Music on the Web n 447
Once you create your script, you can test the functionality of motifs
(and the script routines that drive them) further, but in the early
stages the Secondary Segment toolbar is a great help.
A DirectMusic Case Study for Interactive Music on the Web n 451
At this point, we have created and tested all of the required content,
as defined in the planning stage. In summary, the project consists of
one Style, one DLS Collection, four primary Segments, seven sec-
ondary Segments, and one AudioPath. We are now ready to script the
project.
A DirectMusic Case Study for Interactive Music on the Web n 453
Scripting
Scripting is the key to creating interactivity with DirectMusic. The
script drives the music in response to calls to its routines from the
DMX control. You have to create script routines for all of the hooks
defined previously. You may find “under-the-hood” routines useful in
controlling automated functions and in testing conditions for other
routines.
Getting the script right is vital for properly functioning music.
The slightest error can cause a routine, or even the whole script, to
fail. Having said this, AudioVBScript is a very simple scripting lan-
guage. Microsoft designed an easy-to-learn system for people with-
out a programming background. I learned how to use AudioVBScript
quickly, with no programming experience. This section provides
some useful pointers in relation to scripting a web site project.
like to go through the site, writing down the names of the routines I
will use for each interaction as I come across it.
You must make the content files available to the script before it
can use them. Do this by dragging the files from the project tree into
either the Embed Runtime or Reference Runtime containers in the
Script object. Each approach has advantages and disadvantages.
Dragging a Segment into the Embed Runtime container embeds the
Segment into the run-time version of the script file. In other words,
you do not need to include the run-time Segment file when you
deliver the final content files. This method is attractive, as it reduces
the number of files required for delivery. On the other hand, embed-
ding run-time files often involves complicated dependencies of
embedded files, especially where you use multiple scripts.
454 n Chapter 18
The AtImmediate flag ensures that the Segments stop right away,
regardless of their boundary settings.
Any routines in the script appear in the list to the right of the
script (see Figure 18-14). Double-clicking on a routine in this list
executes that routine. By entering one routine at a time, you build
the script up gradually until all the required functionality is present.
A DirectMusic Case Study for Interactive Music on the Web n 455
Create an AudioPath object when the script starts and specify that III
object each time a Segment plays. This ensures that the run-time
version uses the correct custom AudioPath.
Part
sub CreateAudioPath
if AudioPathLoaded <> 1 then
AudioPathLoaded = 1
FlashPath.load
set AudioPath1 = FlashPath.create
end if
end sub
The routine above, when run for the first time, loads the AudioPath
FlashPath and defines the variable AudioPath1 as FlashPath.create.
Adding the parameter AudioPath1 to a play command now ensures
that the Segment plays back on the correct AudioPath.
456 n Chapter 18
sub Start
CreateAudioPath
Intro.play 0, AudioPath1
end sub
This version of the start routine initializes the AudioPath and plays
Intro back on the correct AudioPath by running CreateAudioPath.
sub VolDown
Volume.Down
end sub
Variables can also be set and retrieved between scripts in the same
way.
Achieving desired functionality often requires complex interac-
tions between multiple routines and variables, especially in situa-
tions where the result of a particular routine differs depending on the
currently playing Segments. For example, when a transition occurs,
you may want to lock out certain motifs or prevent another transition
from occurring until the first one finishes. In these situations, you
may need to use several variables to track exactly what happens at
any given moment. You may also need to use conditional statements
to determine whether to start the new transition or motif or initiate
a queuing or locking mechanism. Our little web site happily lacks
such complexities, but DirectMusic scripting can cope with most
things that you care to throw at it if necessary!
Even with a simple project, keep scripts clear and tidy. There are
likely to be logical groupings of routines, and maintaining clarity
makes editing, testing, and bug fixing considerably quicker and eas-
ier. Any text that comes after an apostrophe is a comment (see
below). Comments are useful for explanatory notes or disabling bits
of script while testing.
sub Lozenge1
Loz1.play IsSecondary, AudioPath1' This is a comment
III
end sub Part
Delivery
Once everything checks out in DirectMusic Producer, test the sys-
tem as part of the web site. First, create the run-time versions of the
DirectMusic files.
You can save all the run-time files at once from the File menu (see
Figure 18-16). The default destination is a folder called RuntimeFiles
created in the Project folder. If you embed the content files rather
than reference them, it may not be necessary to save run-time ver-
sions of every file. Avoid duplicating files or including obsolete files
that increase the total size of the download.
Implementation
The DMX control works with HTML, Flash, and other web technol-
ogies. You can also use it in other applications that support ActiveX
controls, such as MS PowerPoint and Word. To use the DMX control
with HTML, you need a few lines of JavaScript to direct the routine
calls from the HTML to the DMX control, which is located in a dif-
ferent frame (refer to the discussion on framesets in the “Putting It
All Together” section) and identified as “Ctrl.” The JavaScript
audio.js is located in the Includes directory in the root of the site.
Reference audio.js in the head of each HTML document containing
routine calls as follows:
<!-- JavaScript to call into DMX Control -->
These calls are usually, but not exclusively, added to anchor tags.
460 n Chapter 18
<body bgcolor=#ffffff>
</body>
</html>
The various <param> tags define the appearance of the control, the
music source file, and the name of the Start routine that automati-
cally triggers when the content loads. When control.htm loads, the
DMX control either installs or launches, and loads the file defined in
the URL parameter. This URL must be absolute; relative URLs will
not work. The bottom half of the control displays text output describ-
ing its current state, such as Loading Script or Running. Right-click-
ing on the control and clicking on the Output tab displays all of the
output generated thus far, including details of routine calls. This out-
put is helpful in diagnosing scripting errors.
class="text_small">
<a href="#" OnClick="Call('VolUp')">Vol +</a> |
<a href="#" OnClick="Call('VolDown')">Vol -</a> |
<a href="#" OnClick="Call('VolOff')">Music Off</a> |
<a href="#" OnClick="Call('VolOn')">Music On</a>
</div>
Each anchor tag defines the text (e.g., Vol +) as a dummy link
(href=”#”) with the appropriate routine call added using the
OnClick trigger.
Multiple triggers are usable in the same tag, as in the following
example:
<a href=”whois.htm” OnMouseOver=”Call(‘Lozenge1’)” OnClick=”Call(‘Section1’)”>
462 n Chapter 18
This navigation button has one action for a rollover (the tuned single
note) and another for a click (‘Section1’), which triggers two routines
— one starting a new primary Segment at the next measure and the
other playing the motif WipeUp.sgt.
Going through the HTML and adding the relevant triggers
where necessary completes the music implementation. This is sim-
ple once the routine calls pass successfully into the DMX control,
and you test each piece of functionality as you add them.
A DirectMusic Case
Study for Worms Blast
Game Overview
463
464 n Chapter 19
Worms Blast looks simple at first but reveals its depths and surpris-
ing longevity as you play it, much in the same way as Tetris or the
predecessor of Worms Blast, Worms. The game features two play-
ers, each placed in a boat floating on water. The water has waves and
is difficult to move around in. The water level rises if objects fall in
the water. Overhead is a slowly descending landscape of colored
blocks, and between the two players is a wall. Occasionally, a window
opens in the wall between the two players, allowing the players to
fire directly at each other with a selection of wacky comedic weap-
ons. The landscape overhead moves slowly down toward the player.
By using a selection of weapons, the player can shoot color combina-
tions of the blocks that make up the overhead landscape, causing
them to disappear.
The game has several different modes, each with various goals,
such as outlasting your opponent, collecting certain combinations of
crates and colors, hitting certain targets, and so on. Worms Blast has
a large number of hidden weapons and comedy elements. The game
features characters already established in the long-running and
highly successful series of Worms games, and it has a fun, bright, and
colorful appearance. All in all, it is a game that looks very simple at
first, but the longer you play it, the more you discover its depth and
lasting playability.
Music Preparation
In the early stages of trying out music content for this game, I used a
custom-made music system featuring a fairly straightforward back-
ground music track along with short sampled phrases of various
instruments, each belonging to one of the different characters in the
game. So if the Calvin character scored points or picked up a weapon,
his instrument would play a motif at the next possible entry point,
marked in the background music file with markers. The system
worked well enough, but because all phrases had to fit anywhere in
the background music, the music had to be very uniform, and it just
didn’t sound very inspiring or heartfelt. The music turned out rather
bland in comparison to what I had hoped for. I scratched my head for
a while and decided to give DirectMusic a go.
I had witnessed demonstrations of DirectMusic at a couple of
game development seminars. I was impressed with the technology
but less so with the resulting music. The concept of having a piece of
music written to a set of chords and then overlaying a new set of
chords to that music scared me. This was probably because the
examples I was hearing, while interesting from a technical point of
view, just didn’t sound very good. Based on this impression, I
decided early on that I would not use any of DirectMusic’s chord fea-
tures in this game. I was adamant that if I wrote a melody, I wanted
that melody played back exactly how I wrote it. III
I had never used DirectMusic Producer before, and as I loaded
up the application for the first time, I admit I was a little intimidated
Part
instruments tends to get that “General MIDI” sound that gives off a
whiff of low budgets and shareware distribution. So I custom made
every sample in every instrument used in the game.
One of the most time-consuming tasks involved in this project
was the breaking down of premade drum loops. I split up each loop
into several very short loops, which I then rebuilt in DirectMusic
Producer. I would take a one-bar drum loop and slice it up into six-
teenths. This resulted in 16 stereo samples. Then I would import
these 16 stereo samples into DirectMusic Producer and design an
instrument with 32 regions — 16 left and 16 right. At the time I was
using DirectX 8.0, and DirectMusic Producer treated stereo samples
as two separate samples, so two separate regions had to be created
in the instrument designer. Luckily in version 8.1, this can be done
faster and easier, since the instrument designer now recognizes ste-
reo samples and automatically creates the two separate regions.
A useful tool in the above process is Square Circle Software’s
WaveSurgeon (www.wavesurgeon.com), which slices loops and saves
the individual short phrases as separate wave files and also exports a
MIDI “timing template.” This small MIDI file can be imported into
DirectMusic Producer as a pattern and, when played with the instru-
ment created from the individual loop slices, gives a reproduction of
the drum loop, which can be played back in different tempos. So if
the music increases or decreases in tempo, or the game experiences
a momentary system halt or slowdown, the drum loop will still be
locked to the rest of the music. III
Part
Looking back at it now, I’ve come to realize that perhaps it was for
the best after all. By separating the in-game music into four separate
projects, plus a fifth project for the front-end menu system, I had
less of a temptation to reuse the same instruments in too many of
the patterns. Since I was creating separate DLS instrument collec-
tions for each of the five music sets, I felt that I might as well make
all the instruments unique to that set. This gave the music and
instruments plenty of variety, an important issue for those wanting
to play the game for hours.
Creating five separate projects kept the number of Styles, Seg-
ments, and Patterns to a manageable level. Each of my five projects
contained about 60 different Patterns, 30 Segments, ten motifs, but
only one Band. This helped reduce the level of confusion that can
sometimes arise from the fact that, in a DirectMusic project, there
are different Bands all over the place that can affect the instruments
in unexpected ways. This isn’t helped by the fact that Bands some-
times have to be copied from a Style to a Segment or to a Script. If
you go and update one of those Bands, the other places where the
same Band exists do not get updated, leaving you with several copies
of a single Band with the same name, which you thought were all the
same but are in fact different from each other. The fact that I had
only one Band for each of my five different projects — even though
that band had to be copied to about 30 different Segments, and in
doing so were left independent of each other — helped ease the
chances of getting into a mess with the Bands.
helped make this two-bar break fit into the soundtrack as a whole
and sound like it was meant to be there.
At the end of each two-bar “special event” pattern, I put a drum
fill-in/build-up to create a natural-sounding transition back to the reg-
ular background music.
Conclusion
To sum up the Worms Blast music design, the game features the
following:
n Four different “sets” of background music
n About eight different musical parts in each set
n Five different intensity levels in each part
n Eighteen different two-bar patterns for major game events
n Nine different small motifs for minor game events
n Tempo changes to intensity
n Separate DirectMusic content for the front-end menu system,
the credits screen, and the world map screen
All in all, the game contains 338 musical patterns, most of which are
eight bars in length (some of which are two bars long), and 36 motifs.
It also has approximately 150 custom-made instruments, most of
which contain multiple regions.
Perhaps equally interesting is what this implementation doesn’t
have. First of all, it has no chords. Of course, the music has chords,
but as far as the DirectMusic engine is concerned, everything is in
the default chord of C major. It also doesn’t have ChordMaps, a con-
cept I admit I still haven’t really looked into. The game doesn’t use
any DirectMusic scripts. Instead, most of the interactive features are
implemented by the use of groove levels.
Chapter 20
Introduction
473
474 n Chapter 20
be the social aspects of the game that keep many of your users sub-
scribed to your service.
Given this, it is necessary to look at the activities that friends
enjoy together, as well as those popular and universal throughout the
world’s culture. It is also important to look at the tools people use to
identify themselves as individuals while identifying themselves with
various groups within society. An MMP that successfully provides
these activities and tools will be one with a compelling social base
and therefore have a longer subscriber life.
In my experience, music has the potential to provide all of this
and more. Music is fundamentally a social experience. Whether you
are going to a concert or playing in a band, it is a social experience at
heart. Outside of downloading pornography, chat and music down-
loading are also the two largest activities on the web. This makes
some type of musical activity the perfect fit for an MMP, which is
why we decided to add some form of player-created music into the
game.
Project Goals
Before we move into the details of the music system, let’s define a
common set of terms, review the problems, and discuss potential
solutions to those problems:
Repetition III
This is probably the number one reason I turn the music off on so Part
many games. I simply get sick of hearing the same thing repeatedly.
It is important that the game’s music vary each time that it plays.
The wider the variance, the longer the ear can withstand the same
basic song playing.
Adaptive
When many people talk about “interactive” or “dynamic” music sys-
tems, they are often talking about the music system being able to
adapt to changes in gameplay. Much like a good film score, the music
should highlight key moments in the gameplay, making things more
exciting and supporting the mood.
476 n Chapter 20
Interactive
By my definitions, an interactive music system is one that the user
can directly control in some way. To accomplish this goal, AC2 allows
players to play music with each other using a simplified musical
interface. Additionally, the Tumeroks, a type of playable character (or
race, to be more accurate) in the game, use drums to cast magic
spells, which also work with the music system.
Informative
My final goal was that the music system be informative. It is my
belief that a passive music system does not hold the listener as long
as a system that is providing information interesting to the listener.
We spend an amazing amount of time making the visuals of a game
clue in players about their surroundings, and so should the music.
Many of these clues can be subtle, leaving the player with subcon-
scious information about situations and surroundings. For AC2, the
scenery around the player determines the background music, while
monsters bring in individual melodies that warn the player of their
presence.
Code Budget
We knew from the beginning of the project that our programming
time and support would be limited. The music system was very
much my pet project for AC2. Others treated it with less priority
than it deserved. The team passed it off from one programmer to
another, and it accrued bugs along the way. Given this, we tried to
design things to “just work” with as little custom code as possible.
We also knew that eventually we would work on other projects, so
others might not follow any rules that we established about how to
place music in the world in the future.
Our mechanism for triggering music was the same as triggering
sound. A music hook looked just like a sound hook to the system, so
anywhere that we could call a sound sample we could call a Segment.
This was useful because it meant we could attach music hooks to
character animations or spell effects through our “fx system” or
environments through our ambient sound system. Once triggered,
DirectMusic handles everything else.
A DirectMusic Case Study for Asheron’s Call 2: The Fallen Kings n 477
Project Overview
I hope that this section gives you a good idea of how we structured
the entire project, from both a technical and musical perspective.
Time Feel
Like many other projects, what we set out to do was quite a bit
smaller than what we ended up creating. Our original thinking cen-
tered on getting the Tumeroks to be able to cast spells using drum
rhythms and allowing them to play together in an interactive drum
circle over the background score. Our first step in this was to get a
series of bar-long rhythms working together with one-beat fills that
would sound pleasing while played during any beat of the measure.
Each part would have to be satisfactory by itself, yet add to the feel-
ing of the group.
One of the experiences that we really wanted to emulate was the
push and pull of time using polyrhythms. By incorporating the tradi-
tions of certain world music, such as African and Latin, we were able
to transition and modulate between the feel of multiple time signa-
tures while remaining in a single time signature. For instance,
playing in 6/8 using a rolling six feel, then playing four dotted eighth
notes against that to create the polyrhythm of “four over six,” and
then using those dotted eighth notes as the primary rhythmic pulse
creates the feeling of playing in 4/4 while remaining in 6/8. Using
polyrhythm like this can obscure the bar line and allude the ear as to
the placement of one. This allowed our Tumerok drummers to fire
off spells at any beat without disrupting the flow of the music.
A DirectMusic Case Study for Asheron’s Call 2: The Fallen Kings n 479
Figure 20-1: Tumeroks are deeply in tune with the spirits of the
world around them. They channel magical energy through
drums to attack their foe.
The Stack
Using these fundamentals as a base to work from, Geoff Scott, the
composer on the project, went into Fruity Loops and began to com-
pose the initial drum patterns. Using the sounds of a Djembe, Dun
Dun, and Todotzi, each drum would incorporate a different time feel.
As we created each pattern, we referenced it against the previous
patterns, creating a large “stack” of rhythms. Writing this way
ensured that everything worked against everything else and pro-
vided insight into the music’s architecture as a whole. It was
important to balance the timbres of the drums, spreading the thin
480 n Chapter 20
and thick or high- and low-pitched notes across the measure so that
no one beat became overly accented. We then reviewed each rhythm
to make sure it was satisfactory on its own as well as in the context
of the entire score.
Once we were happy with this, we moved the data into Direct-
Music. We then composed a series of small fills on each drum that
would represent the different types of spells in the game. We
grouped these spells into similar categories, such as attacks,
enchantments, heals, summons, buffs, and debuffs. We gave each
category its own rhythmic figure. When the user casts a spell using
a drum, the rhythmic phrase creates an accent. A call and response
event occurs when several players cast spells.
Background Score
Our goal for the background score was for it to sound almost as part
of the environment’s sound design itself. We wanted to keep things
relatively simple and ambient and avoid ear-catching resolutions and
stabs. We also knew there would potentially be a very large number
of melodic parts playing over a given background score and that
those melodies would be the primary force adding tension and reso-
lution to the composition.
We chose modal pads with ambiguous harmonic movement —
sweeps and swells of color that provide mood over traditional mel-
ody. Like our rhythmic scheme, the lack of harmonic resolution
would not tire the ear. Similar to the concept used in Indian music,
we stated the given scale as a raga (the tonic droned and then played
upon for an entire afternoon), creating a meditative space without
the repetition and destination of a cycling chord progression.
To give the music a sense of global awareness to the player’s
current predicament, we used various modes to color the music
accordingly. A player standing alone in an area would hear the back-
ground score in the Lydian mode, but as creatures fill the area, the
music transitioned to Phrygian, giving a darker tonality. If monsters
continue to outnumber the player, the music might transition to a
diminished mode while a quickening drum groove begins to pulse.
A DirectMusic Case Study for Asheron’s Call 2: The Fallen Kings n 481
Monster Melodies
After we had some basic background music working, we wrote a few
melodies, which we tied to individual monsters in the game. When
one of these monsters is in the area, the melody warns the player of
its presence. We wrote these melodies as if they were part of the
background score, but designed them so that they could play on any
measure in the score. We tried to capture the feel of the creature in
the sound of its DLS patch and used information about where differ-
ent creatures would appear to better mix the parts with each other.
While we could not rely on this information as being correct, it gave
us a good point to test and design from, as in most cases we knew
what creatures were likely to appear together.
Player Melodies
With a basic background score and our initial stack of rhythms estab-
lished, we started to create the player melodies for the game. These
would be two-bar melodies, which the player could trigger anywhere
in the game and would start playing on the next marker placed every
two bars in the music. When multiple users play multiple melodies,
they get the feeling of playing music together.
We started by composing against our rhythmic stack, using a
bass and lute sound. We created ten melodies for each instrument
and pulled from multiple time feels contained in the existing drum III
rhythms. Creating this stack was probably the most difficult aspect of
this project. Geoff and I went home with intense headaches on more
Part
than one occasion, as wrapping our heads around all of the data was
quite a task.
Geoff’s approach to the initial instrument, the lute, was to write
using counterpoint and harmony, while keeping the melodic range as
tight as possible to leave room for the other instruments. The initial
lute parts used two scalar melodies with three-part harmony coun-
tered against each other, while pulling rhythmically from the four and
six feels. We based the harmony on one chord structure and concen-
trated on the color of that chord’s chord tones and tensions, while
being aware that the melodies would transpose to new chords and
through chord progressions.
482 n Chapter 20
Once again, I must stress the evolution of this project as a key moti-
vator in how we did things. Once every basic component was
working, we embarked on a long series of tweaks to make the overall
project more interesting and discovered ways in which we could
have done things better the first time. However, each of the restric-
tions that we placed on the project eventually became a motivator in
some new way to expand the project.
One such restriction was writing the background melodic score
in a single mode. While this made it much easier to write over and
provided the feel that we wanted, the player music suffered from the
484 n Chapter 20
What we define here is how a bass part and a melodic part move
through the chord progressions. We map bass parts to chord track
three and follow root motion up to the Eb. Melodic parts, however,
choose the closest acceptable notes to what they originally play,
much like a guitar player going through changes on a solo.
This technique opened up a wealth of emergent behavior in the
player music. The player music follows chord changes that are not
present in the background score, yet work perfectly with the back-
ground score behind them. A user busy chatting while they play no
longer sounds repetitive but instead actively follows chord changes.
As we take the liberty of hiding interesting changes in parts of the
world that may not even have a background score, there is an invisi-
ble landscape of musical changes waiting for the players to discover.
Groove Level
In our first pass, we went through each background score and set
various styles to respond to different groove levels. Our default
groove level is 30; nearby monsters add two to the groove level,
while players subtract one. As monsters outnumber the player, the
groove level rises, and the system introduces new parts into the mix
to intensify the music.
This only provided a scope of density and did not create the
change in color and mood we desired. For this, we added additional
code to change the primary Segment based on the current groove III
level. As the player goes up in groove levels, the chords chosen for
the music become darker. For instance, an area in a base mode of C
Part
Technical Considerations
Project Organization
Project organization, while not an exciting topic, is important none-
theless. Throughout the creation of our project, I took the time to
reorganize and rename any new data into standard naming conven-
tions, while cleaning up any old data that did not fit our paradigm. In
the end, solid organizational techniques will save you time and make
finding problems later easier.
Segment Trigger Tracks become a very useful organizational
tool, as I created a hierarchy of inherited data. A background score in
my project looks something like this:
n Master Segment
• Tempo/time
• Chords or ChordMap
• Markers
• Default Groove Level
• Band Segment
o Band
• Arrangement Segment
o Drum Segment
m Style Track for drums
o Strings Segment
m Style Track for strings
o Choir Segment
m Style Track for choir
One of the main reasons that I organize data in this fashion is to
speed up the process of creating variations, while keeping every-
thing neat and consistent. My master Segment contains all the
high-level controls, such as chords and tempo, while secondary Seg-
ments within the master Segment call Band data or group the
arrangement of multiple styles together. Making new arrangements,
mapping arrangements to new chord structures, or changing the
A DirectMusic Case Study for Asheron’s Call 2: The Fallen Kings n 487
Bands for an entire area are all very easy to do because I have signif-
icantly less data to track in each Segment. Also of note is that I
always call Bands by creating a Segment for them and using a Seg-
ment Trigger Track. This saves a ton of time and frustration, as you
only have to update your Band in a single place when you want to
change it, instead of having to find all the Segments associated with
that instrument and edit their Band settings.
As a general practice, we created all note data in Styles, encapsu-
lated in a Segment, and called from a Segment Trigger Track or as a
secondary Segment. I never place note data directly into Segments
because I often find myself in the position of eventually needing
some function of styles and having to convert many Segments into
styles.
accrued one extra Band change for every time the master Segment
looped. This essentially means that DirectMusic would upload five
instruments the first time through, ten the next, 15, then 20, and
then 25. At around 50 patch changes, the pauses and sync problems
start to become very noticeable until the music begins losing time.
Additionally, because the player music Segments must initialize a
Band with the 3D AudioPath, the player music Segment calls another
Band for each new melody that a user plays. Note that even when
resetting the same Band (such as on a loop), DirectMusic still
repatches that Band setting.
My first pass was to optimize the background score. I broke my
master Band into a series of Band Segments that contained the mini-
mum amount of Band data that each score needed. I gave each player
488 n Chapter 20
Note Scattering
On many percussion instruments, such as a thumb piano, I use a
technique I call note scattering to give them a more realistic sound.
Thumb pianos have individual metal bars for each note, and each of
these tend to have unique overtones, rattles, and other “signature
noises” associated with them. Instead of using six samples over a
three-octave range, spreading each sample over half an octave, I use
six samples and randomly alternate them on notes as I go up the
scale. This greatly reduces the chances of someone picking out the
repeating signature noises as a run plays up the scale.
Evolving Patches
Many of today’s synthesizers have very long patches that evolve and
change as they play. Such a patch would produce a very large sample
file, often unsuitable for the memory restrictions of game develop-
ment. However, it is quite possible to get this same effect using
several smaller, looping samples using the various parameters avail-
able in DirectMusic. To make a patch evolve, place the first sample
(say, a violin section) on layer one and set attack and delay times as
you normally would. Place another sample, perhaps a choir of voices,
on the second layer, and set the sample to ramp in very slowly over
time. When you trigger a note, you get the attack of the violins
slowly ramping into the voices. You can even use this trick with a III
single sample and play with its filter and modulation settings instead
or use its pan settings to move the sample from left to right.
Part
Custom Code
While we did not have support for full scripting, we did do some cus-
tom code in our interface to DirectMusic. The primary thing we
added was the concept of exclusivity, which we used to prevent mon-
ster melodies from playing over themselves. This concept would be
very useful to add to the core functionality of DirectMusic, as it
would allow users to weed out redundant Band changes by making
the Band Segment exclusive.
490 n Chapter 20
DirectMusic Script
We did the rest of our custom code in DirectMusic script and trig-
gered through script calls embedded in Segments. We used this code
primarily to handle the transition and selection of the primary Seg-
ment. The script intercepts calls to play new primary Segments,
places them in a queue, and triggers the new primary Segment when
the old primary Segment is about to end. The script also selects
which version of the primary Segment is going to play based on the
current groove level. We use this to change chord mappings based on
the groove level; higher groove levels get a more dangerous-
sounding mode, while lower ones get a more serene-sounding mode.
The entire script would be much simpler if DirectMusic had an
AtLoop flag for transitioning Segments, but currently that choice is
not available. If you set the transition to happen at the end of the
Segment and the Segment is set to loop indefinitely, you never reach
the end of the Segment and your transition never happens. Thus, we
needed custom code to handle this. A simplified version of this code
is listed below with an explanation of what each routine does.
First, we need to initialize the variables used in the script.
Dim finish ‘ used for determining flags to send with play command
Dim nextPlaying ‘ number of Segments in the queue
Dim nextSeg ‘ object of Segment in the queue
Dim groovereport ‘ current groove level
Dim segA ‘ used for storing temporary Segment object
Dim segB ‘ used for storing temporary Segment object
Dim segC ‘ used for storing temporary Segment object
Dim seg1 ‘ Primary Segment object name for groove offset 0-4
Dim seg1a ‘ Primary Segment object name for groove offset 5-9
Dim seg1b ‘ Primary Segment object name for groove offset 10+
For each area in the game, there is a queue Segment that calls the
appropriate queue routine. This routine calls Init to initialize the
variables and then checks to see if the nextPlaying variable is set. By
default, DirectMusic initializes all variables to zero, so if nextPlaying
is zero, we know the music has not started. Once the script sets the
nextPlaying and finish flags, it calls the routine ChooseHuman1.
Sub Que_01
Init
If nextPlaying = 0 Then
finish = 0
Else
finish = 1
End If
nextPlaying = 1
ChooseHuman1
End Sub
groovereport = GetMasterGrooveLevel()
If groovereport < 5 Then
Set nextSeg = segA
End If
If groovereport > 4 Then
Set nextSeg = segB
End If
If groovereport > 9 Then
Set nextSeg = segC
492 n Chapter 20
End If
TriggerPlay
End Sub
This routine checks the finish variable and triggers the Segment to
play as either AtMeasure.sgt or AtFinish.sgt. Playing the Segment
AtFinish.sgt essentially throws out the play command. If the primary
Segment is currently playing and set to loop indefinitely, it will never
reach AtFinish.sgt. This is necessary in case the primary Segment
does not start or stops for some unexplained reason. If this happens,
the new primary Segment will start playing immediately because no
primary Segment is currently playing. If the primary Segment is
already playing, then nothing happens until its Script Track calls the
next routine.
Sub TriggerPlay
If finish = 0 Then
nextSeg.play(AtMeasure)
else
finish = 0
nextSeg.play(AtFinish)
End If
End Sub
Figure 20-3: Our master Segment with its script call in the Script Track.
A DirectMusic Case Study for Asheron’s Call 2: The Fallen Kings n 493
All primary Segments call the final Segment just before they end. A
script call is placed in a Script Track on the second beat of the last
bar in the Segment. Once called, it checks to see which Segment
number nextPlaying is set to and calls the selection routine for that
primary Segment. In this example, there is only one choice, so it
calls the ChooseHuman1 routine explained above. By placing this on
the second beat of the last bar in the Segment, the routine triggers
the next primary Segment to play on the next measure, and the pri-
mary Segment transition happens at the end of the primary
Segment.
Sub PlayNext
Init
If nextPlaying = 1 Then
ChooseHuman1
End If
TriggerPlay
End Sub
The result is that the primary Segments only transition at their ends
while still being set to loop. Additionally, the control code allows us
to select new primary Segments based on the groove level, which
we use to recompose the music in this example from Dorian to Phry-
gian to a whole half-tone scale based on the current groove level.
This gives us a more reactive score, where darker tonalities are III
associated with being outnumbered, and lighter tonalities are associ- Part
ated with outnumbering the creatures around you.
Postmortem
Although we plan to expand the system that we created here as the
game evolves, it is always a good idea to go back and look at the vari-
ous things that went right and wrong in a project.
494 n Chapter 20
What Worked
Player Music as a Purely Social Game
If someone asked me what I am most proud of working on in
Asheron’s Call 2, it would be the addition of music as a purely social
game. No one has ever created a dynamic music system quite like it,
and we have already witnessed people getting to know each other
because of the music system. These types of systems are what build
friendships long after the core gameplay has become stale and pro-
vide people with new outlets for their creativity. I can only hope that
the music played in-game inspires someone to pick up an instrument
in real life, as music is something that fundamentally makes people’s
lives better.
Figure 20-4: During the last four hours of the beta test, users
gathered on top of the Deru Tree to play music together. Part of
this jam session is included as an MP3 track on the CD.
Layered Approach
I cannot express enough the importance of starting with something
simple and expanding it. Revision and the willingness to throw
things away is part of what makes a project work well in the end. At
any given stage in the project, we had something interesting work-
ing. As we added new layers to the mix, things just kept getting
A DirectMusic Case Study for Asheron’s Call 2: The Fallen Kings n 495
Music Is Informative
The monster melodies and use of groove levels in the game provide
a kind of musical information that I believe positively enhance the
user’s experience of the game. They layer another kind of musical
tension into the mix based on the user’s situation and give arrange-
ments of monsters a unique musical sound. One of our designers
even realized that the game engine was not creating monsters in the
right locations just from hearing the music.
DirectMusic III
DirectMusic was truly the only music system that could accomplish Part
this type of holistic musical vision, and while the system is far from
perfect, it does some things amazingly well. The ability to adapt a
section of music to multiple key signatures allowed us to get a range
of moods from a single piece of music, while the synchronization and
small data rates of the file format allowed us to work within the net-
work-based parameters that an online game such as ours requires.
496 n Chapter 20
Lack of Support
Most sound designers fight with a lack of support in their endeavors.
In many companies, it is hard to get the proper resources for sound,
as the industry tends to focus heavily on graphics. In addition, many
people have no idea how we create sound and music, let alone how
they affect the experience. The original coder assigned to the music
system worked on the user interface and localization of the game.
Both the music and UI require two huge systems. In addition, she
did not know much about sound and music, so explaining things
often took an exorbitant amount of time. Moreover, once someone
found a bug, verifying bug fixes always required Geoff or me to be
present. Additionally, the code proved very unstable at times, so
A DirectMusic Case Study for Asheron’s Call 2: The Fallen Kings n 497
Future Work
In the future, I am interested in prototyping the two main concepts
of the system separate from each other before integrating them
together again. If we created a system of pure player music, we could
implement a host of new controls for users, allowing them to control
things like tempo, chord and color choices, effect properties, and
more. We are also very interested in pursuing a system that allows
users to create their own melodies through a very simple interface,
which would give them a much greater degree of musical control,
while still protecting them from bad note choices. I would like to see
how far we could push this system by itself, as there is potentially a
lot more room for control within the mix.
I would also like to spend some time prototyping a purely film
score-type approach, where the speed at which the music adapts to
the current situation is stressed. I would also like to try integrating
traditional Redbook audio tracks into the mix to increase the sonic
quality while still maintaining the interactivity. III
Once we have gained experience at pushing the boundaries of
these systems separately, I would like to take that knowledge and
Part
see what parts of the two systems can be combined without limiting
either system. Can we create a truly adaptive score, one that adapts
to any situation, with the sound quality of Redbook audio, while still
maintaining an interesting player music game? How much more con-
trol can we give to the users before they strangle their ears with it?
I also think that we need to spend some time learning other
areas of DirectMusic and exploring what other features lay hidden
under the hood. For instance, we did not make use of ChordMaps for
variation in the project, and we barely touched on the things that
scripting can provide. We also did not use chord changes to write our
background score and instead used them for player music and mood
changes. How would the system differ if our background score used
the more traditional style of writing in DirectMusic? Could we solve
498 n Chapter 20
On the CD
On the CD included with this book, you will find a folder with a
greatly simplified, but quite functional, version of my project. Due to
licensing restrictions, I had to rewrite the music and use new DLS
banks for the music in this version, but the fundamental principals
used to create this project are identical to the ones used to create
the original score and should serve as a great utility to understanding
the techniques presented in this chapter.
In the project, you will find three folders containing the back-
ground score elements, monster melody elements, and player music
elements of the project. If you load the Demo bookmark in the pro-
ject, the Secondary Segment window will contain the necessary
Segments to preview all of the music in the project as well.
At the bottom of the Secondary Segment window, you will find
two queue Segments that start the background music through script-
ing or transition from one background score to the next. Above them
are three utility Segments used to raise, lower, or reset the groove
level of the project. You may see the current groove offset value by
looking at the groovereport variable in the Script window. The
remaining Segments in the Secondary Segment window are monster
melodies and player music, which you should trigger once the pri-
mary Segment starts to simulate what might be happening in the
game.
1. Start the primary Segment by playing the Que_Hum_C_
Dorian.sgt Segment in the Secondary Segment window. The
music may take a few seconds to begin.
2. Play several of the monster melodies (mn_drudge1.sgt, for
instance) to simulate the presence of monsters in the area.
3. Play several of the player music Segments, and notice how
they follow chord changes not originally heard in the back-
ground score, yet they still work with the background score.
4. Raise the groove level by playing the GrooveUp.sgt utility
Segment several times until you hear the sound of the drums
come in. After a few seconds, the key should change, and the
player music should adjust accordingly.
5. Raise the groove level several more times. The drums will
become louder, and the key will change again to become
even darker.
III
Part
This page intentionally left blank.
Chapter 21
What people are going to be selling more of in the future is not pieces
of music but systems by which people can customize listening experi-
ences for themselves… In that sense, musicians would be offering
unfinished pieces of music — pieces of raw material, but highly
evolved raw material, that has a strong flavor to it already. I can
also feel something evolving on the cusp between “music,” “game,”
and “demonstration”…. Such an experience falls in a nice new
place — between art and science and playing. This is where I expect
artists to be working more and more in the future.
Brian Eno in WIRED issue 3.05 — http://www.wired.com/wired/
3.05/eno.html?pg=4&topic=
501
502 n Chapter 21
of what was a more than a ten-minute linear piece with three distinct
movements.
As playing ProjeKctX.sgt demonstrates, the sound of Direct-
Music can be as full and clear as Redbook audio or better if the
composer wishes to use higher bandwidth source material. From the
user’s point of view a stand-alone nonlinear Segment appears to be
just another sound file that, with a registered player app installed,
only needs to be double-clicked to start. On first listen, it sounds like
most any other music file. However, on a second, third, fourth, and
even hundredth listen, the real difference between the DirectMusic
Segment and a linear file becomes more and more apparent — not in
sound quality of course, but in the actual experience of listening. In
fact, one could say that there never really is more than “one listen”
of a robust nonlinear music file because it can render a unique rendi-
tion with every play if the composer so desires. This is the essential
difference between linear and nonlinear playback: A robust nonlinear
music file continually rewards the engaged and active listener with
every listen.
Some people on hearing or reading about nonlinear music for the
first time form unrealistic assumptions and expect behavior along
the lines of “Mary Had a Little Lamb” somehow magically playing
itself as “Twinkle, Twinkle, Little Star.” A little listening experience
easily dispels such notions as most unlikely (not to mention undesir-
able). What generally happens with nonlinear playback is more like
what happens when listening to different performances of the same III
piece of music. The nonlinear performance can even sound like Part
different groups of musicians playing the same piece with genre-
appropriate stylistic variations if the composer provides enough
material. Even if the composer designs a huge piece with wildly
varying elements, the identity of any real musical composition stays
recognizable in the same way that we can recognize Coltrane’s ren-
dition of “My Favorite Things” as the “same tune” that Julie
Andrews sings in The Sound of Music. Simply put, a composition that
has no identifiable elements that can translate across different set-
tings and renditions is likely not a composition in the traditional
sense. Conversely, if a composition is recognized by an intelligent
listener regardless of arrangement or rendition, then the identity of
the piece remains intact as some sort of metadata, whether the for-
mat is linear or nonlinear. For instance, it will not take more than one
508 n Chapter 21
grasp at first, but I’ve found that they become more useful the more
experienced I become.
First, in brief:
Your role in the project is the first key factor to consider. How
much freedom do you have to alter the material? If you are the sole,
original composer, the answer is perhaps easy. If you are working as
remixer, obviously a different approach is required. One role that is
unique to nonlinear music production is something like that of a
translator — bringing linear material written by another composer
(or yourself) into the nonlinear world while conveying the spirit of
the original material, which of course brings its own set of require-
ments and restrictions.
Next, what functionality do you need to design for, as in what
functionality does your player app expose to the end user? Will your
material play in a “start and stop” player like the ProjeKct X player?
Or are you required to expose more parameters, such as the ability
to change the mix by loading different AudioPaths or different Band
files to mute certain tracks, or allow control of purely musical param-
eters such as changing the tempo or harmonic character of the
music. Material based heavily on large linear audio tracks simply
won’t allow purely musical parameter changes, since these parame-
ters are, in effect, “baked in.” This leads naturally to the next
concept.
The most critical concept is what I call the granularity of the
material that you are working with. It is also the hardest concept to III
convey in the absence of nonlinear composition experience, since it Part
is unique to the field. Is the piece a Bach fugue for a keyboard instru-
ment, a jazz standard with vocals, an ultra-polished mega-hit pop
song, or a musically ambient sound bed for an art installment? Each
of these will have a different granularity, which will dictate your
approach and how much DirectMusic functionality you can use or
expose.
Now we can look at these concepts a little more in depth.
Role
Your role in the process is hopefully self-evident but still worth artic-
ulating to identify the boundaries within which you must work. Are
you the sole composer of the piece, working entirely from scratch
with the freedom to follow the piece wherever it leads? Are you
510 n Chapter 21
working with music from the classical canon in the public domain,
with a jazz standard, or with a traditional score composer and taking
his or her linear ideas and converting them to DirectMusic files (as
often happens in game music)? Or are you working with someone
else’s finished audio multitracks, using as much or as little as you
like in combination with your own material the way a remix artist
might? Or are you limiting yourself to recreating a variable playback
version of the original piece (“translating” from linear to nonlinear)?
The answers to these questions will help define your role.
Since the roles are not discrete stations, separate and isolated
from each other, we can arrange the various options on an axis or
continuum. For instance, remixers nearly always add their own
material to a remix, often using only a vocal track and nothing else
from the original. If working as translator, one might choose between
adhering to the exact form of the material, allowing variations within
the different parts, or one might make editorial decisions and
attempt to capture the spirit of the original while taking much liberty
with the original form. On a number line from 1 to 12, the continuum
might look something like this:
1 2 3 4 5 6 7 8 9 10 11 12
Composer----------------------------------------Remixer----------------------------------------Translator
format. I also changed the mix quite a bit from the original and
brought in musical material from markedly different sections of the
same improvisational suite so that this small Segment of material
could stand a little better on its own as a demo. It is just one arrange-
ment of several that I did with the material, the most formally literal
with regard to the original. Had my role been further to the left of
the axis above, I might have added quite a bit of my own material to
act as setting for the ProjeKct X material or even just used samples
here and there in my own composition.
Functionality
Of course, game audio is currently the most common application to
compose nonlinear music for, but as the title of this article indicates,
the application for consideration here is a stand-alone player applica-
tion. Currently, the few stand-alone player apps available function
primarily as simple start and stop devices, but the APIs are there to
add specific DirectMusic functionality to allow a custom player to
expose values for different parameters to user input. The following
list of parameters is in the order of increasing granularity, as well as
increasing restriction on the type of source material that is compati-
ble with such functionality:
n Standard playlist functionality to play multiple Segment files in a
desired order
n Choice of different AudioPaths and different Band files within a
III
Segment to allow for different mixes Part
1 2 3 4 5 6 7 8 9 10 11 12
Simple play/stop-----------------------Game engine-----------------------User-alterable parameters
512 n Chapter 21
Granularity
Anyone who has worked much with DirectMusic probably already
has an intuitive understanding of what I mean by “granularity.” It is
useful to think of it in the literal sense; sand is quite granular and
conforms naturally to the shape of most any vessel. Gravel is less
granular but still conforms to a mold, within rough limits. Bricks or
blocks are much less malleable, but in return maintain their own
shape and stability, which means we can use them to build a stable
vessel or container into which we can pour gravel and sand if we
wish. The analogy is simple: MIDI tracks in combination with DLS
instruments are like sand, phrase samples are like gravel, and Wave
Tracks are like bricks.
1 2 3 4 5 6 7 8 9 10 11 12
MIDI+DLS---------------------------------Phrase Samples--------------------------------Wave Tracks
tempo, key, harmonic sequence, and phrasing. The right side is less
concerned with musical parameters and more concerned with audio
parameters. It is less musically malleable (in terms of key, tempo,
etc.) but as such retains much more of the critical performance char-
acteristics so crucial to popular music (i.e., the singer’s voice or an
instrumentalist’s tone). As such, its stability (or invariability) will
likely also determine the overall form of the Segment.
It is critical that a composer become familiar with how the basic
architectural units in DirectMusic relate to one another in order to
understand the concept of granularity. Roughly, from smallest to larg-
est, these units are notes, variations, patterns, Styles, and Segments.
These units tend to fit one inside the other, sort of like a series of
nested Russian boxes. Once you grasp the internal relationships of
the DirectMusic elements, the most critical step in designing a non-
linear Segment becomes somewhat easier — defining the smallest
possible unit you can reduce the source material to without losing
the essential quality you wish to convey, almost like discovering a
“musical molecule.” This analogy, of course, cannot be taken too
literally since your definition of the granularity will likely be deter-
mined somewhat by your role and not by some objective musical
quality.
For example, if you “translate” a hit song by a well-known artist
and you want your Segment to maintain a traditional song form, the
critical element is almost certainly the singer’s vocal performance
and the lyrics. The granularity in such a case is possibly as large as III
an entire verse and certainly no smaller than a full vocal/lyrical Part
phrase. This would tend to dictate a design based around Direct-
Music Wave Tracks, and the variations would likely be more in the
post-production elements, such as altering the mix via track levels
and differing DSP treatments, using alternate instrumental tracks, or
altering the song’s form. If you’re working closely with the artist,
you might be able to include alternate vocal takes. If, however, you
design for a dance remix of the same song and are not concerned
with maintaining pop song form, you can make the vocal line as gran-
ular as you want to take it. In such a case, cutting the vocal line into
phrase samples and making a DLS instrument with them might be
the best option, depending on the rest of your material.
In the case of instrumental music, the level of granularity may
also be as large as a verse or a chorus, especially if the critical
514 n Chapter 21
Conclusion
In conclusion, I want to make clear that I view DirectMusic as the
basis for a nonlinear musical format, not as a finished and definitive
format. DirectMusic has the advantage of being part of the DirectX
APIs, which means there are many potential DirectMusic players (in
hardware terms) already in listeners’ homes right now, today. With
the resources in this book, anyone with enough interest could make
their own DirectMusic Segments and a simple player, make them
available via download or file sharing, and have millions of potential
listeners. Its disadvantage is that the authoring tools have never
benefited from the rigorous design/dev/user-test feedback cycle
required of commercial software in the marketplace. They have
Beyond Games: Bringing DirectMusic into the Living Room n 515
always shipped free as part of the DirectX SDK, and it shows. How-
ever, dedicated users have proven that they can master the
challenges of the tools and turn the incredible complexity of the
architecture to great musical advantage in award-winning games.
Nonlinear music can become the next step in music production tech-
nology if music lovers and the listening public have access to quality
examples of nonlinear music, not only in games but also on their
home multimedia stations. If that happens and the demand for
“more” occurs, better tools and improvements in the architecture
will not be far behind. Then — who knows? — by the turn of the next
century, our current linear formats may seem as quaint and outdated
as one of Edison’s wax cylinders does now.
III
Part
This page intentionally left blank.
About the Authors
517
About the Authors
518
About the Authors
519
About the Authors
520
About the Authors
Ciaran Walsh began his musical career in his teens, writing songs
and playing bass in a number of bands and performing in and around
London, England. Fellow musicians introduced Ciaran to the world of
MIDI, sequencing, and sampling, and he soon bought his first com-
puter, an Amiga 500. Within two years Ciaran signed his first record
deal as a solo artist and between 1994 and 2000 he produced three
albums, ten singles, and a number of remixes and co-productions.
Under the name Deviant Electronics, he developed a reputation for
hard-hitting live shows that took him all over the world.
A long-standing interest in video games, and a belief that more
could be done with music to heighten the experience, led Ciaran to
begin working with Wide Games, an independent UK console games
developer. Together they set up Wide Sounds to specialize in interac-
tive audio for games and other media. Ciaran designed and scripted
the interactive music system for Prisoner of War, and continues to
work with DirectMusic on new titles. He has also created music and
interactive audio for television commercials, multimedia content, and
corporate branding for clients including Mercedes, Adidas, and Pepsi.
521
About the Authors
522
Index
523
524 n Index
H SetNotificationHandle(), 343
Halo, 417 SetPrepareTime(), 192, 211
dialog, 428 Stop(), 187
history of, 418 StopEx(), 184, 187
music production, 421 IDirectMusicScript, 262
sound effects production, 423 CallRoutine(), 263, 276
surround sound, 426 EnumRoutine(), 267, 276
Hollywood Edge, 380 EnumVariable(), 268, 277
HRESULT, 140 GetVariableNumber(), 265, 277
HRTF, 425 GetVariableObject(), 265
HTML+Time, 436 GetVariableVariant(), 265
Hubbard, Craig, 394 Init(), 275
Hysteria, setting, 279 SetVariableNumber(), 265
SetVariableObject(), 265
I SetVariableVariant(), 265
IActiveScript, 271 IDirectMusicSegment, 142, 177
IDirectMusicAudioPath, 232, 242 Download(), 144, 232
Activate(), 238, 289, 319 GetParam(), 181
ConvertPChannel(), 245 Unload(), 144, 232
CreateStandardAudioPath(), 231 IDirectMusicSegmentState, 186, 187, 214
GetObjectInPath(), 146, 235, 253 IDirectMusicSong, 185
Release(), 237 IDirectMusicTool, 328, 357, 361
SetVolume(), 237 Flush(), 329
IDirectMusicChordMap, 335 GetMediaTypeArraySize(), 328, 358
IDirectMusicComposer, 335 GetMediaTypes(), 328, 358
ComposeSegmentFromShape(), 336 GetMsgDeliveryType(), 328, 357
IDirectMusicGraph, 330 Init(), 328, 357
IDirectMusicLoader, 142 ProcessPMsg(), 328, 329, 345, 347, 359
ClearCache(), 157, 168 IDirectMusicTrack, 179
CollectGarbage(), 158 GetParam(), 182
EnableCache(), 157, 161, 164, 168 IDirectSound3DBuffer, 146, 289
EnumObject(), 157, 166 IKsControl, 295
GetObject(), 154, 171, 172 IMediaParams, 258
LoadObjectFromFile(), 144, 153, 288 informative music, 476
ReleaseObject(), 158, 167 intensity
ReleaseObjectByUnknown(), 158 in Asheron’s Call, 478
ScanDirectory(), 156, 161, 169 in Russian Squares, 374
SetObject(), 152, 155, 171 interaction “hooks,” 440
SetSearchDirectory(), 152 interactive audio, 3
IDirectMusicObject, 243 differences from adaptive audio, 6
IDirectMusicPerformance, 142 interactivity, 3
CloseDown(), 145 in Asheron’s Call, 476
CreateAudioPath(), 230, 243 on the web, 435
CreateStandardAudioPath(), 287 Intro Embellishment, 59
GetDefaultAudioPath(), 146 Invalidation, 211
GetGraph(), 329 inversion, 101
GetNotificationPMsg(), 343 IPersistStream, 150, 258
GetParam(), 181 Load(), 151
InitAudio(), 143, 145, 164 ISpecifyPropertyPages, 258
PlaySegmentEx(), 144, 146, 184, 187, 233 IUnknown, 140
SetBumperLength(), 192 AddRef(), 140
528 n Index
QueryInterface, 141 M
Release(), 140, 147 macro-adaptability, 374
marker, 205
J Marker Track, 37
Jarett, Keith, 514 massively multiplayer games, 473
Jones, 188, 272, 351 music for, 474
Mastelotto, Pat, 502
K McLachlan, Sarah, 506
Kelly, Kevin, 502 memory footprint, 4
King Crimson, 502 memory use in DirectMusic, 13
Message Window, 120
L MFC, 135, 161
latency, 138, 293 micro-adaptability, 374
sound effects, 326 MIDI, 226
Latency Time, 191, 207 DirectMusic and, 8, 9, 14, 15
layering, 19, 68 Channel, 180
in NOLF, 396 control over wave playback, 20
DLS instruments, 27 Controllers, 15, 16
in Asheron’s Call, 494 MIDI file, 8, 160
in Russian Squares, 371, 374 reading into Producer, 76
in Wave Tracks, 20 Min, Mike, 371
in Wide Sounds, 437 Mingle, 286, 297
LFO, 30 implementation, 324
linear music vs. nonlinear music, 505 Mix-in Buffer, 228
Listener, 291 Moby, 506
accessing, 292 MOD files, 8
Doppler, 292, 303 Monolith, 387
manipulating, 330 mood, 7
Orientation, 291 Mother of All Windows, 52, 104
Position, 291, 303 motif, 56, 197
Rolloff Factor, 292, 331 in NOLF, 392, 405
Velocity, 292 in Russian Squares, 384
LithTech DirectMusic player, 396, 410 in Wide Sounds, 437
LithTech engine, 393 in Worms Blast, 466, 470
LithTech Player/Tester, 408 looping, 384
Loader, 138, 139, 149 munchkin, 301
garbage collection, 158 music cells, 370, 377
LoaderView sample application, 159 music layers, 371
loading, 129 music sets, 389
Lock ID, 48 music states, 388, 391
loop-slicing, 380 instrument level variation, 403
looping DLS sounds, 24 MUSIC_TIME, 188
low-latency sound effects, 326 musical form, 372, 373
low-frequency oscillator (LFO), 30 musical space, 482
lyric, 340, 346 Mute Track, 181
interpreting, 347
notification, 346 N
Lyric Track, 340 No One Lives Forever (NOLF), 389
nonlinear music, 501, 505
Index n 529
T Lyric, 340
tempo, 3, 10, 217, 219 Mute, 181
DirectMusic restrictions, 10 Pattern, 49, 77, 179
in Asheron’s Call, 478 Script, 128, 158, 270, 271, 340
in Worms Blast, 467 Segment Trigger, 158
jitter, 299 Sequence, 21, 77, 178
Tempo Track, 21, 62, 76 SignPost, 93
Test Player, 394 Style, 61
Time, 191 Tempo, 21, 62, 76
Current, 191 Time Signature, 21, 62, 178
Latency, 191, 207 types of, 17
Prepare, 192, 207 Wave, 17, 178
Queue, 192, 207 transition, 36, 208
Time Signature, 217 boundary options, 37
Time Signature Track, 21, 62, 178 in Asheron’s Call, 493
Tool, 327 in NOLF, 390, 406
accessing in AudioPath, 246, 257 in Wide Sounds, 436
adjust timing, 191 matrix, 410
AudioPath, 225, 226, 345 Transition Segment, 66, 116
AudioPath Configuration, 229 transpose, 34
AudioPath Stage, 236 triggers, 461
capturing notifications, 343, 344 tutorial, 75
creating, 330
CWaveTool, 328, 329 U
delivery time, 344 Unload(), 232
Immediate Time, 344 unloading, 129
IPersistStream support, 258
Lyric, 345, 347 V
name, 251 variability, 4, 176
notification, 357 variable (script), 122, 125, 262
Performance, 226, 345 variable playback, 41
Prepare Time, 192 variant, 265, 333
property page, 258 VariantClear(), 333
Queue Time, 344, 358 variation, 4, 41, 42
Segment, 226, 345 auditioning, 44
setting up, 345 bar, 82
thread, 346 editing, 44
Wave processing, 327 empty, 43
ToolGraph, 226, 329 enable and disable, 43, 83
AudioPath, 329 in Halo, 423
creating, 329 in NOLF, 389, 401
inserting, 330 in ProjecKct X, 507, 510
Performance, 225, 330, 361 in Russian Squares, 370
trace statement, 121 in Wide Sounds, 437
Track, 17, 19, 177, 178 locking, 48
Band, 22, 62, 178, 181 on the web, 435
Chord, 21, 76, 87, 181 properties, 46
ChordMap, 186 Variation Choices window, 52, 104
Control, 181, 184 Variation Switch Points, 50, 446
Groove, 61, 181 VBScript, 112, 271
Group, 19 Velocity, 290
532 n Index
Hundreds of music, audio, and interactive entertainment industry professionals are joined
together as members of the Game Audio Network Guild (G.A.N.G.). G.A.N.G. is an inter-
national, professional, non-profit organization dedicated to raising the bar for audio
development in interactive media. The individuals who make up G.A.N.G understand the
power of audio and its importance in making interactive media a gratifying and provoca-
tive experience. G.A.N.G. is determined to better the state of audio in interactive media.
As a guild, we change the way people experience interactive media. As a network, we
help each other achieve new successes in our own professional endeavors.
G.A.N.G. Members
G.A.N.G. boasts over 400 members in 22 different countries. Professionals, associates,
apprentices, and students alike make up the body of the organization. Members commu-
nicate via G.A.N.G.’s extensive online message board community, annual game industry
events, and through local chapter meetings. Members enjoy a slew of benefits and dis-
counts for belonging to G.A.N.G. but more important than all that is belonging to the
most prestigious organization in interactive audio!
G.A.N.G. Sponsors
G.A.N.G. sponsors support our initiative to promote excellence in audio for interactive
media:
n Audio Products International Corporation (www.miragespeakers.com
www.energyspeakers.com and www.athenaspeakers.com)
n Dolby Laboratories, Inc. (www.dolby.com)
n DTS (www.dtsonline.com)
n Native Instruments (www.nativeinstruments.com)
n Sound Ideas (www.sound-ideas.com)
Accomplishments and Long Term Goals
n GANG is working with the Recording Musicians Association to craft a new union
agreement specifically for videogames that allows game composers to use the fin-
est musicians in Los Angeles and other union cities
n GANG’s recently formed Grammy Committee is taking over the effort to create a
new Grammy category specifically for game music
n GANG is putting together a special committee to advise speaker manufacturer
Audio Products International Corporation in their endeavor to create the world’s
finest 5.1 speaker system specifically for gamers
n The 2003 Annual GANG Awards show hosted over 600 top industry profession-
als, presented 33 awards and 2 prizes for student and professional competitions,
and featured 4 live performances by GANG members!!
6 Warning By opening the CD package, you accept the terms and conditions
of the CD/Source Code Usage License Agreement.
Additionally, opening the CD package makes this book nonreturnable.