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.
|