Direct Music Tutorial

Category:
Multimedia/Games
Type:
Snippets
Difficulty:
Advanced

Author: Jack Hoxley

Version Compatibility:

Instructions: Copy the declarations and code below and paste directly into your VB project.

The Theory, and Getting Started

How DirectMusic works, and other things you need to know...

The Variables

The Variables you will need...

Making The Interface

Making the form, adding controls and giving them the right names

Form_Load: Where it all begins

What code to put in the Form_Load event

The Open Code

Loading the MIDI file

The Playing Code

Getting some output from your app.

The Pause Code

Getting the Program to pause the music.

The Stop Code

Making your program STOP!

Changing The Volume

Make your program break the speakers.......

The Progress Bar

This gives the user a guide as to how far through the file they are.

 

 


THEORY AND GETTING STARTED

DirectMusic is fairly new in directX history, it only appeared in version 6 and 7, It is a good component though, and if you have the resources, a lot better than some other formats.
DirectMusic loads a .MID file into a buffer (similiar to DirectSound/Draw) and then modifies it and plays it back. There is another file format that can be used: Downloadable sounds, I might cover this later on, but for the casual game developer it's a bit over complicated.

For this tutorial you will need a .MID file, there are several supplied with Windows, but they are all rubbish. If you haven't got any, there is one supplied with the DirectMusic Project from my download page.


THE VARIABLES

These variables go into the (Declarations) section of your Main Form:

Option Explicit
Dim dx As New DirectX7
'the root object, as seen in every DirectX application
Dim perf As DirectMusicPerformance

Dim perf2 As DirectMusicPerformance
'These 2 variables are the buffers, "perf" is the master, "perf2" is used to gather 'information about the MIDI file
Dim seg As DirectMusicSegment

Dim segstate As DirectMusicSegmentState
'Segments, these hold segments of the music!
Dim loader As DirectMusicLoader
'This is an object that helps load the files into the buffers.
Public GetStartTime As Long

Public Offset As Long

Public mtTime As Long

Public mtLength As Double

Public dTempo As Double

Dim timesig As DMUS_TIMESIGNATURE
'This'll hold the time signiture that the MIDI is in.
Dim IsPlayingCheck As Boolean
'A simple true/false statement as to whether or not it's playing.
Dim msg As String
'Used in the error handler sub.
Dim time As Double

Dim fIsPaused As Boolean
Dim ISITPAUSED As Boolean '--TIME-- These settings are sued in the progress bar section.
Dim Total_Time As Double

Dim Current_Time As Double

Dim Percent_Time As Double


The next bit of code is the error handler, a simple, almost pointless wrapper sub for generating error messages:

Sub localerror(ErrorNum As Long, ErrorDesc As String) 'Nothing much to explain here...
msg = ErrorDesc

msg = "(" & ErrorNum & ") - " & msg

MsgBox msg

End Sub

 


MAKING THE INTERFACE
This section needs to be done before anything else. Refer to this picture for the basic layout:

 

Note, there is a timer on the form, and a Shape control hidden in the White PictureBox. (This picture was taken at run-time).

Just to make it simple for me, use this list as a reference for all the objects:

Form1
.Caption="DirectMusic MIDI Player" - or anything similiar

.ScaleMode=3 (Pixel)

.StartUpPosition=2 (CenterScreen)

cmdOpen -Top Command Button

.Caption=" Open "Tester.Mid" "

cmdPlay

.Caption=" Play "Tester.Mid" "

cmdPause

.Caption=" Pause "Tester.Mid" "

cmdStop

.Caption=" Stop "Tester.Mid" "

Edit_Volume -The Label that shows the volume

.Caption=100

.Height=33

.Width=48

.Font=MS Sans Serif, Bold, size 18

UpDown_Volume - This can be found in the "Microsoft Common Controls 2" library

.Max=100

.Min=100

.Value=75

Get_Time_Timer -Timer Control

.Enabled=False

.Interval=50

Picture1

.Height=204

.Width=45

.ScaleMode=3 (Pixel)

Shape1 -Place This inside Picture1

.FillStyle=0 (Solid)

.FillColor=Blue (Or any colour of your choice)

.Height=200

.Width=41

.Top=200

.Left=0

lblPercentage -The Small Label in the middle of the Progress Bar

-Right click on it, and select "Bring To Front"

.Caption="%"

.BackStyle=0 (Transparent)

.Alignment=2 (Center)

.Font = MS Sans Serif, Bold, Size 10

.Height=13

.Width=41

.Left=0

.Top=104

lblLength

.AutoSize=true

.Caption="Length: "

lblTimeSig

.AutoResize=true

.Caption="Time Sig: "

lblTempo

.AutoResize=true

.Caption="Tempo: "


Now that's out of the way, we can get onto some code. If you find any problems with the above descriptions, download the working example from my "Downloads Page"



FORM LOAD: WHERE IT ALL STARTS

This is the first code that your program executes, so place this code in the "Form_Load":

Private Sub Form_Load()
On Error GoTo LocalErrors


Set loader = dx.DirectMusicLoaderCreate()
'This is the DMusic Main Component

'Creating a Perf2 so that we can get all the segment information without having to play 'the segment
Set perf2 = dx.DirectMusicPerformanceCreate()
'Create the Second buffer.
Call perf2.Init(Nothing, 0)

perf2.SetPort -1, 80

Call perf2.GetMasterAutoDownload

Set perf = dx.DirectMusicPerformanceCreate()'create the first Buffer.
Call perf.Init(Nothing, 0)

perf.SetPort -1, 80

Call perf.SetMasterAutoDownload(True)

perf.SetMasterVolume (Edit_Volume.Caption * 42 - 3000)
'A Simple equation to send the 'correct volume to DirectX, you'll need to remember this.
Edit_Volume.Caption = UpDown_Volume.Value
'The volume is set depending on what
'edit_Volume's caption is. This line transfers the value from the up/down control

'to the label


Exit Sub

LocalErrors:

Call localerror(Err.Number, Err.Description)
'We've already written this sub.
End Sub


Most of this code is straight forward, and is explained in the Comments. Now we can move on


THE OPEN CODE

This code is where you prepare the buffer's to accept data, then load the Music into them. Copy the following code into the "cmdOpen" Command Button:

Private Sub cmdOpen_Click()
Dim Minutes As Integer

Dim a As Integer

Dim length As Integer

Dim length2 As Integer

Dim FileName As String

On Error GoTo LocalErrors

If Not seg Is Nothing And Not segstate Is Nothing Then
' There is a Segment and a SegmentState
If perf.IsPlaying(seg, segstate) = True Then
' Segment currently playing, so exit
MsgBox "Please Stop currently playing music before selecting a new segment"

Exit Sub

ElseIf ISITPAUSED = True Then

MsgBox "Please Stop currently playing music before selecting a new segment"
'A simple error catcher
Exit Sub

End If

End If


Set loader = Nothing

Set loader = dx.DirectMusicLoaderCreate

FileName = App.path & "\Tester.mid"
'If this were a program, you'd set the music file here using the
'common dialog control

Set seg = loader.LoadSegment(FileName)

cmdPlay.Enabled = True


' Set the search directory based on the placement of the .mid file that was loaded

length = Len(FileName)

length2 = length

Dim path As String

Do While path <> "\"

path = Mid(FileName, length, 1)

length = length - 1

Loop

Dim SearchDir As String

SearchDir = Left(FileName, length)

loader.SetSearchDirectory (Left(FileName, length + 1))

perf2.SetMasterAutoDownload True


'Set all the Captions to empty

lblTempo.Caption = vbNullString

lblTimeSig.Caption = vbNullString

lblLength.Caption = vbNullString


'Play the segment just long enough to get the info

mtTime = perf2.GetMusicTime()

Call perf2.PlaySegment(seg, 0, mtTime + 2000)


'GetTempo

dTempo = perf2.GetTempo(mtTime + 2000, 0)

lblTempo.Caption = "Tempo: " & Format(dTempo, "00.00")


'GetTimeSig

Call perf2.GetTimeSig(mtTime + 2000, 0, timesig)

lblTimeSig.Caption = "Time Sig: " & timesig.beatsPerMeasure & "/" & timesig.beat


'GetLength

mtLength = (((seg.GetLength() / 768) * 60) / dTempo)
'This is an important set of numbers, this'll result in the total
'number of seconds the file lasts.

Total_Time = mtLength
'a variable copy, "Total_Time" is used later on in the project
' Put the length in a time that we can relate to

Minutes = 0

a = mtLength - 60

Do While a > 0

Minutes = Minutes + 1

a = a - 60

Loop

lblLength.Caption = "Length: " & Format(Minutes, "00") & ":" & Format((mtLength - (Minutes * 60)), "00.0")


' Now that we retreived all the segment info, we'll stop playing the segment, the user wont here any music being played 'here, as it does it so quickly...

Call perf2.Stop(seg, Nothing, 0, 0)


seg.SetStandardMidiFile

Exit Sub

LocalErrors:

If Not seg Is Nothing Then

Call perf2.Stop(seg, Nothing, 0, 0)
'If there was an error, stop playing the music
End If

MsgBox ("There was a problem loading the requested file. No file has been loaded")
'output result to user
FileName = vbNullString

End Sub


Now, when you run the application, and click on the load button, it should put some information into the labels. Now we need to get it to do something!



THE PLAY CODE

Now you'll get some output. Assuming that everything is working fine, this code should play the contents of the "perf" buffer. Dont try running it until you have the "Stop" code written in, as you may cause it to lock up if it isn't stopped.
Copy the following code into the "Play" command button:

Private Sub cmdPlay_Click()
If seg Is Nothing Then
'The user hasn't clicked "Open" yet.
MsgBox ("Please open a segment or MIDI file before playing")

Exit Sub

End If


If fIsPaused Then
'If it is paused, start the file from where it already is.
Offset = mtTime - GetStartTime + Offset + 1

Call seg.SetStartPoint(Offset)

Set segstate = perf.PlaySegment(seg, 0, 0)

cmdPause.BackColor = &H8000000F
'gray - used as a flag for a later argument
Else
'Reset it back to the beggining, then start playing it.
Offset = 0

If perf.IsPlaying(seg, segstate) = True Then

Call perf.Stop(seg, segstate, 0, 0)

End If

seg.SetStartPoint (0)

Set segstate = perf.PlaySegment(seg, 0, 0)

cmdPause.BackColor = &H8000000F
'gray -another flag
'SETUP TIME BAR

get_Time_Timer.Enabled = True
'enable the progress bar.
Exit Sub

End If

fIsPaused = False

End Sub

 

 


THE PAUSE CODE

Next up, the pausing code. All three (Play/pause/stop) link together, so dont test the application until you have added all the bits of code.
As above, place this code in the "Pause" Command button:

Private Sub cmdPause_Click()
On Error GoTo LocalErrors

If seg Is Nothing Then 'If it hasn't been loaded yet, get out of here
Exit Sub

End If

IsPlayingCheck = perf.IsPlaying(seg, segstate)
If IsPlayingCheck = True Then 'music is playing

fIsPaused = True

' pause music and button down

mtTime = perf.GetMusicTime()

GetStartTime = segstate.GetStartTime()

Call perf.Stop(seg, Nothing, 0, 0)

cmdPause.BackColor = &HFFFFC0
'blue This flag is used below:
Else

If cmdPause.BackColor = &HFFFFC0 Then
'button is blue, therfore it is paused
'unpause

fIsPaused = False

Offset = mtTime - GetStartTime + Offset + 1

Call seg.SetStartPoint(Offset)

Set segstate = perf.PlaySegment(seg, 0, 0)

cmdPause.BackColor = &H8000000F 'gray

End If

End If

Exit Sub

LocalErrors:

Call localerror(Err.Number, Err.Description)
'The Error handler shown earlier
End Sub


THE STOP CODE

And finally, the stop code. Once this code is complete you can listen to the MIDI at your hearts content. (Should you want to)
This code goes in the "Stop" button:

Private Sub cmdStop_Click()
If seg Is Nothing Then
'nothing has been loaded yet...
Exit Sub

End If

fIsPaused = False
'internal flag to tell it that it isn't paused.
cmdPause.BackColor = &H8000000F
'grey
Call perf.Stop(seg, segstate, 0, 0)

cmdPlay.Enabled = True

time = 0

get_Time_Timer.Enabled = False
'Stop the timer from counting
End Sub

 


CHANGING THE VOLUME

The rest of the code from here is about optimizing, and getting more out of your current application. The above code is necessary in every application that uses DirectMusic; the following stuff is probably useful though. Changing the volume is very easy, paste these lines in the appropriate place:

Private Sub UpDown_Volume_Change()
Edit_Volume.Caption = UpDown_Volume.Value
'This copies the value to the label, and
'at the same time triggers the following line:

End Sub

Private Sub Edit_Volume_Change()
perf.SetMasterVolume (Edit_Volume.Caption * 42 - 3000)

End Sub


Experiment with the volume, it can be used during a game to signify key points (getting louder) or just to fade out at the end of a level.

 



THE PROGRESS BAR

This is the final piece to be added to your project. Although it isn't implemented in this project, this is how you can make the music loop. This code iutputs a percentage of the way through the music - you could modify this code so that, at 100% (finished) it stops the music, resets it back to the begginning, then starts playing it all over again.

Put the following code in the timer control:

If perf.IsPlaying(Nothing, segstate) = True Then 'Only update the display if there is music playing,,,
Current_Time = ((((perf.GetMusicTime() - (segstate.GetStartTime() - Offset)) / 768) * 60) / dTempo)
'get the current time
Percent_Time = Int((Current_Time / Total_Time) * 100)
'work out the percentage - you remember these?
'These bits are irrelevant - I left these lines in the code when i compressed it up

'so's not to confuse people, I left them here:

'Me.Caption = Percent_Time & "% {Playing}"

'Update User-Interface

'shape1 moves up the picture box as the MIDI file plays on....

'2 Pixels=1%

'END useless code!

Shape1.Top = 200 - (Percent_Time * 2)
'Because the shape starts at the bottom, it needs to work out from the bottom:
'Hence the 200-

lblPercentage = Percent_Time & "%"
'copy the result to the label
Else

'Me.Caption = "{Stopped}" another useless line.

End If