Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

DirectXAudio: Music Playback
Author: Jack Hoxley
Written: 19th October 2001
Contact: [EMail]
Download: AUD_02.Zip (20kb)


Contents of this lesson
1. Introduction
2. Initialising DirectMusic8
3. Messages
4. Playing / Stopping


1. Introduction

Welcome back for part 2 of the DirectXAudio series. In the first part of the series we learnt about basic sound effects playback - just about enough to write a simple game engine; in this part we're going to learn the basics of music playback. Once you've finished this lesson you should be pretty much sorted for audio playback - you could easily write a module for your game that plays background music and sound effects. If that's all you need for your game then that's fine, but for those of you who want to play around with some of the more advanced topics, then I'll cover them in later lessons.


2. Initialising DirectMusic8

Before we can do any work with a DirectX module we must initialise it - this should be fairly obvious by now, even if you dont have much DirectX experience. Luckily this is a fairly trivial excercise, and you could probably memorise it if you really wanted...

Option Explicit
Implements DirectXEvent8 'creates the "DirectXEvent8_DXCallback" function.

'DirectMusic Objects:
Private oDX As DirectX8 'the root of all DX8 applications
Private oDMPerf As DirectMusicPerformance8 'the master performance
Private oDMLoader As DirectMusicLoader8 'helps load music into buffers
Private oDMSeg As DirectMusicSegment8 'actually stores the music to be played


Above are the declarations that you'll need. The second line ("Implements...") will be discussed a little later on, for now you dont really need to pay any attention to it. This next piece of code will completely initialise DirectMusic8:

'//0. Any Variables
Dim dmParams As DMUS_AUDIOPARAMS

'//1. Create the objects
Set oDX = New DirectX8
'we must have a root DX8 object, it is used to control/create
'many other important objects. As shown in the next two calls:
Set oDMPerf = oDX.DirectMusicPerformanceCreate
Set oDMLoader = oDX.DirectMusicLoaderCreate

'You can play around with these settings to see what happens...
oDMPerf.InitAudio frmMain.hWnd, DMUS_AUDIOF_ALL, dmParams, Nothing, DMUS_APATH_DYNAMIC_STEREO, 128
oDMPerf.SetMasterAutoDownload True


The above code need-only be executed when the program first boots up, and as long as the objects dont get terminated then they will remain initialised until terminated. There are various settings to play with the InitAudio( ) function, but the ones shown are fairly straightforward - and will work for almost all situations.

At this point in time we have an initialised DirectMusic8 instance, but before we get onto the loading/playing of music I'm going to cover the termination of DirectMusic8 - whilst VB/Windows/DirectX will tend to clean up for you if you forget, it is good practise to explicitly destroy the interfaces you create. This is how:

If ObjPtr(oDMSeg)Then Set oDMSeg = Nothing
If ObjPtr(oDMLoader)Then Set oDMLoader = Nothing
If Not (oDMPerf Is Nothing) Then
    oDMPerf.CloseDown
    Set oDMPerf = Nothing
End If
If ObjPtr(oDX) Then Set oDX = Nothing


3. Messages

Before we get onto the actual playing of music I want to cover DirectX8 messaging. In several components (Input, Sound, Music, Play) your application can set up a messaging system such that DirectX will tell you when certain events occur. This is particularly useful when playing music as it can tell you when music has finished, so that you can then schedule/load in the next piece of music and begin playback straight away. Alternatively you could (on a regular basis) keep asking DirectMusic if playback has finished - but that's lots of extra work (at 30fps, you could ask DirectMusic 3500 times and it'll tell you the same thing); if you let DirectMusic tell you, then you can sit back and relax till it tells you that something needs to happen.

Messages are done using a callback system - if you've been programming for a while, or have done much C/C++ programming then this will probably be a fairly familiar method to you. For those of you who have no idea what a callback function is... Normally your program will make calls to a program/library - initialise this, initialise that, play this, play that; whilst it may return data it is essentially a one-way communication. A callback allows the other program/library execute parts of your program. In the simplest case this basically involves the other program raising an event, and the code you place in that procedure is executed. It's still one-way, but in the other direction!

This following code will go in the InitDMusic( ) function, after the main DirectMusic8 initialisation:

oDMPerf.AddNotificationType DMUS_NOTIFY_ON_SEGMENT 'relay messages about the segment
hEvent = oDX.CreateEvent(Me)
oDMPerf.SetNotificationHandle hEvent 'used to identify the messages


The first line tells the DirectMusic performance what type of messages we want it to send to our program, there are several different types:
DMUS_NOTIFY_ON_SEGMENT = information on the current piece of music (started playing, finished playing etc...)
DMUS_NOTIFY_ON_CHORD = information for when the chord of the music changes
DMUS_NOTIFY_ON_COMMAND = when a command event is invoked
DMUS_NOTIFY_ON_MEASUREANDBEAT = information about the beat/measure of the current music
DMUS_NOTIFY_ON_PERFORMANCE = A performance level event.
DMUS_NOTIFY_ON_RECOMPOSE = A recomposition event

Most of the above are fairly straightforward, If you need any additional information about the above, check out the SDK help file. You can also find details straight from the DirectX8 library through the object browser (hit F2 in the IDE). You can use some of the above flags to gain information about the audio data being played.

The final part of messaging is the actual function itself. As you saw earlier there was a declaration "Implements DirectXEvent8" and I said you could just ignore it. Well now we pay it some attention. Delete the line from your code and look at the (object) combobox in the code pain (the left-hand side) and you will see what you would expect to see. Now type the "Implements DirectXEvent8" line back in, and check the combo box again - all of a sudden a new entry "DirectXEvent8" has appeared. VB has produced the correct function prototype for our callback system. Any code we place in this procedure will be executed whenever a message is delivered to our application. It is crucial that you understand that simple fact.

The basic outline for a DirectXEvent8 callback function looks like this; the select case will switch between the messages, so insert any message-specific code in the correct section.

Private Sub DirectXEvent8_DXCallback(ByVal eventid As Long)
If eventid = hEvent Then
    'the message is for us
    Dim dmMSG As DMUS_NOTIFICATION_PMSG
    
    If Not oDMPerf.GetNotificationPMSG(dmMSG) Then
        'error!
    Else
        Select Case dmMSG.lNotificationOption
            Case DMUS_NOTIFICATION_SEGABORT
            Case DMUS_NOTIFICATION_SEGALMOSTEND
            Case DMUS_NOTIFICATION_SEGEND
            Case DMUS_NOTIFICATION_SEGLOOP
            Case DMUS_NOTIFICATION_SEGSTART
            Case Else
       End Select
    End If
End If
End Sub


4. Playing / Stopping

Finally - I've gotten to the point where I can actually show you how to play some music. But first we need to load some audio data into our program. This is done using the following code:

oDMLoader.SetSearchDirectory App.Path & "\"
Set oDMSeg = oDMLoader.LoadSegment(App.Path & FILENAME)
oDMSeg.SetStandardMidiFile


How trivial! I dont think DirectX data loading gets much simpler than the above 3 lines. The first line must point to the folder where the audio file is located - strictly its not necessary for our program, but it will be useful for more complex music engines. Once you've configured the search directory, all you need to is pass the actual filename (and not the full path) to any audio file you want to load. The main reason for this is that if internal files reference other files they will not know the complete path for the file - only it's name. The second line just creates the segment - a segment is basically our music buffer that we play back from. The third line is only required if you have just loaded a MIDI file.

DirectMusic only accepts 4 major audio formats: .WAV, .MID, .RMI and .SEG - and before you all email me asking this question - NO MP3 playback. If you require MP3 playback then look into DirectXShow (and this tutorial here).

Now that we've loaded the data into our buffer we need to know how to play it. The following code will play the music in either looping or non-looping mode:

If chkLoop.Value = 1 Then
   oDMSeg.SetRepeats -1                          
Else
   oDMSeg.SetRepeats 0                           
End If                                                           
oDMPerf.PlaySegmentEx oDMSeg, DMUS_SEGF_DEFAULT, 0

You can change the .SetRepeats value to any positive integer, with -1 being a special case; it is perfectly reasonable to set it to 8 (and get the piece played 8 times before it stops).

Now that we're able to play it would be a good idea to have the ability to stop the music as well! This code isn't too complicated either, and is shown here:

oDMPerf.StopEx oDMSeg, 0, DMUS_SEGF_DEFAULT


That was hard! You can play with these parameters a little to achieve a pause function if you really wanted one. The last two things that I'm going to cover in this tutorial are volume and tempo; the former is very useful, whilst the latter probably has some use - but the only one I've found is to make chipmunk-music :-)

Volume is easy to set, and is in the range +20 decibels to -200 decibels. Here's the function call that you need:

oDMPerf.SetMasterVolume scrlVol.Value


Again, not very complicated. Next we're going to change the tempo; normally tempo is measured as beats-per-minute; and to a certain extent it still is internally in DirectMusic. But we can only set a multiplier value for the tempo, not actually set the bpm. Normally the tempo will be 1.0, 0.5 would be 1/2 speed, 2.0 would be double speed... 0.0 will stop the music (another way of making a pause function). This is how we set the tempo:

oDMPerf.SetMasterTempo scrlTempo.Value / 100


The "/100" part in the above line is only required by the sample program because I'm using a standard scroll-bar, which cant do floating point values.

One final thing to note about the volume and tempo changing - they're not always applied instantaneously; you can sometimes hear/see a noticable delay between requesting a change and it actually happening. This is most noticable with a tempo change. If you find your game pausing when playing with music properties it is quite likely to be because of this...


And thats a wrap! You now have all the code to write your own, albeit simple, music playback engine. Hope you enjoy...

You can get the source code from the link above, or from the downloads page.

DirectX 4 VB © 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster