Technical Considerations


Project Organization

Project organization, while not an exciting topic, is important nonetheless. Throughout the creation of our project, I took the time to reorganize and rename any new data into standard naming conventions, 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:

  • Master Segment

    • Tempo/time

    • Chords or ChordMap

    • Markers

    • Default Groove Level

    • Band Segment

      • Band

    • Arrangement Segment

      • Drum Segment

        • Style Track for drums

      • Strings Segment

        • Style Track for strings

      • Choir Segment

        • 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 everything neat and consistent. My master Segment contains all the high-level controls, such as chords and tempo, while secondary Segments 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 Bands for an entire area are all very easy to do because I have significantly 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 Segment 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, encapsulated 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.

Optimizing Patch Changes

Because of the sheer number of potential Segments playing in our music system, it was important to pay attention to optimization techniques. In fact, at some point in the project, suboptimal data arrangements led to some strange behaviors. I found out the hard way that setting the Band is potentially a very expensive operation, which if not optimized can cause small pauses, sync errors, and other undesirable behaviors. Every time the engine called for a Band change, DirectMusic was not filtering out the redundant Band changes, which lead to hiccups since too many Bands were being set at once. My original Band Segment was mistakenly set to loop every bar, and because it called as part of a looping master Segment, it 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 minimum amount of Band data that each score needed. I gave each player or monster melody its own Band Segment as well. However, while this greatly optimized the background score, my two-bar looping player music Segments were causing a lot of individual patch changes, as each Segment would trigger a Band change on loop, and it was possible to have upwards of 60 people playing music in an area. My solution was to make each secondary Segment 999 bars long, with the Style looping inside of it. This meant that a player music Segment only called its Band when the Segment played initially, or when 1000 bars of music had passed by. While it is still possible that 60 people could trigger a new Segment at once, causing 60 patch changes to happen, it is highly unlikely, especially given Internet latencies.

Optimizing Voices and DLS Banks

It is also very important to optimize your voice counts and DLS banks. It's very easy to have a bloated voice count if you're not careful, thus slowing the performance of your entire game. The most common cause of high voice counts is DLS banks, which have very long release times. You want to trim the release times to be as short as possible, and if you use a long release time, make sure you do not create short patterns that repeat before that release time has passed. For instance, if your Segment lasts for about four seconds and you set your release time on the DLS bank to ten seconds, you end up with notes from the original loop playing two loops later.

You should also be aware of the memory that you use, as it directly accounts for much of the cost of changing bands. I do not go into general audio data optimization techniques here. However, there are things that you can do to make better-sounding DLS banks using less data.

Cheap Stereo Separation

When creating synthesizer or orchestral patches, it is often desirable to have a stereo effect. Ideally, your original samples are in stereo, but this obviously chews up more data. A quick-and-dirty way to achieve the same effect is to create two layers with different settings applied to the same wave and pan those layers left and right.

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 development. However, it is quite possible to get this same effect using several smaller, looping samples using the various parameters available 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 single sample and play with its filter and modulation settings instead or use its pan settings to move the sample from left to right.

Custom Code

While we did not have support for full scripting, we did do some custom code in our interface to DirectMusic. The primary thing we added was the concept of exclusivity, which we used to prevent monster 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.

DirectMusic Script

We did the rest of our custom code in DirectMusic script and triggered through script calls embedded in Segments. We used this code primarily to handle the transition and selection of the primary Segment. 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+ 

The Init routine initializes the Segment variables to the Segments in the DirectMusic project. I do this to make things easier to edit, as now the Segment name only needs to be stored in one place, and the variable can be used instead of hard coding the Segment names everywhere they are referenced.

 Sub Init    set seg1 = MI_Hum_C_Dorian    set seg1a = MI_Hum_C_Phrygian    set seg1b = MI_Hum_C_WholeHalf    End Sub 

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 

This routine sets my temporary variables (segA, segB, and segC) tob be the primary Segments that I wish to choose between based on groove level. It then calls the ChooseGroove3 routine.

 Sub ChooseHuman1    Set segA = seg1    Set segB = seg1a    Set segC = seg1b    ChooseGroove3 End Sub 

This routine checks the master groove level offset and sets the nextSeg object to be the correct primary Segment based on that offset. It finally calls the TriggerPlay routine to play the Segment.

 Sub ChooseGroove3    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    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 

click to expand
Figure 20-3: Our master Segment with its script call in the Script Track.

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 primary 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 Phrygian to a whole half-tone scale based on the current groove level. This gives us a more reactive score, where darker tonalities are associated with being outnumbered, and lighter tonalities are associated with outnumbering the creatures around you.




DirectX 9 Audio Exposed(c) Interactive Audio Development
DirectX 9 Audio Exposed: Interactive Audio Development
ISBN: 1556222882
EAN: 2147483647
Year: 2006
Pages: 170

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net