Click here to Skip to main content
Click here to Skip to main content

Playing Midi Files with DirectMusic

By , 30 Dec 2010
 

Introduction

This article focuses on the use of CMidiMusic which allows an easy access to DirectX DirectMusic API. This class allows loading and playing general midi sequence files(.mid). The class is designed to perform midi playback in one segment and offers additional features like using any midi port installed in your system, 3D sound environment, sound effects, etc.

Direct Audio Specifications

The DirectX 8.0 Audio part offers improved integration between DirectSound and DirectMusic, including a great set of new features. Some of these are listed hereafter:

  • The last version of Direct Audio allows using hardware acceleration for sound synthesis.
  • With the new AudioPath model, the sound of a port does not go directly to a directsound buffer, instead, it goes to an audiopath which controls data flow from a performance to the final output. The audiopath allows controlling the 3D position of each sound and adding other effects.
  • The segments are modified independently and you can apply effects like pan, volume, individually.
  • It allows using DLS2 (Downloadable sound level 2 standard) which provides a great sound quality and unlimited use of instruments with the software synthesizer.
  • FX (Reverb, Chorus...) if it is available in the software synthesizer.
  • It overcomes the 16 midi channels limit, allowing the use of as many midi channels as the software is able to handle.
  • The playback can be controlled accurately in run time by selecting different sets of musical variations and changes in the chords progress.
  • 3D Positioning.

Main Interfaces of DirectMusic

  • IDirectMusic8: This interface allows managing buffers and ports. There should only exist one instance of this interface per application.
  • IDirectMusicPerformance8: This is the most important interface in playback management. It is used to add and remove ports, play segments, notify event reception, control music parameters and obtain timing information.
  • IDirectMusicPort8: This interface provides access to DirectMusicPorts objects like MPU-401 or the software synthesizer.
  • IDirectMusicSegment8: This interface represents a segment, musical piece made up of multiple tracks. It can contain a midi file, a wave, a segment.
  • IDirectMusicLoader8: Its main function is to find and load the different objects. These objects are to be stored in a segment.
  • IDirectMusicSegmentState8: The (playback) engine creates a SegmentState object which allows analyzing the state of the segment currently playing.
  • IDirectMusicAudioPath8: The IDirectMusicAudioPath8 interface represents the stages of data flow from the data file to the primary buffer.

DirectMusic Architecture

Once a resource has been loaded in a segment, the performance dispatches the messages defined by a tool of an application, such tools are grouped in toolgraphs which process specific segment messages. A tool can modify a message and pass it on, delete it, or send a new message.

Finally, the messages are delivered to the output tool, which converts the data to MIDI format before passing it to the synthesizer. Channel-specific MIDI messages are directed to the appropriate channel group on the synthesizer. The synthesizer creates sound waves and streams them to a device called a sink, which manages the distribution of data through buses to DirectSound buffers.

There are three kinds of buffers:

  • Sink-in buffers are DirectSound secondary buffers into which the sink streams data. Here are applied many effects like 3D, pan, volume, etc...The resulting waveform is passed either directly to the primary buffer or to one or more mix-in buffers.
  • Mix-in buffers receive data from other buffers, apply effects, and mix the resulting wave forms. These buffers can be used to apply global effects.
  • The primary buffer performs the final mixing on all data and passes it to the rendering device.

The following diagram shows the flow of data from files to the speakers:

Using CMidiMusic

In first sight, it is necessary to add in Visual C++ IDE this library: Go to Project -> Settings -> Object Library Modules and add dxguid.lib of DirectX8 SDK.

After this, it will be necessary to include in the project the header "dmusic.h" and "dmusic.cpp" file and finally instance an object of CMidiMusic class type as shown below:

void CPlayerDlg::OnButton_Start()     
{
    DWORD dwcount; // Counter variable to enumerate the midi ports 
    INFOPORT Info; // INFOPORT structure to store port information 
    BOOL bSelected;

    CMidiMusic *pMidi;     // Pointer to CMidiMusic object type
    pMidi=new CMidiMusic;     // Allocate it      
    pMidi->Initialize(FALSE);// Initialize without 3D positioning
    
    dwcount=0;
    bSelected=FALSE;
    
     // Port enumeration  phase 
     // It is necessary to supply a port counter 
    while (pMidi->PortEnumeration(dwcount,&Info)==S_OK)
    {
        // Ensure it is an output hardware device
        if (Info.dwClass==DMUS_PC_OUTPUTCLASS) 
        {
            if (!((Info.dwFlags & DMUS_PC_SOFTWARESYNTH) || bSelected))
            {
                // Select the enumerated port 
                pMidi->SelectPort(&Info);
                bSelected=TRUE;
            }
        }
    
    dwcount++;      
   }

     // Read the MIDI file 
    pMidi->LoadMidiFromFile("c:\\music\\song_004.mid");
     // Play the file
    pMidi->Play();
    AfxMessageBox("Playing...");
     // Stop it
    pMidi->Stop();

    //Important!: Delete the pointer to the object in order to call the 
    //destructor 
    //which call the DirectMusic releases interfaces
    delete pMidi;
}

CMidiMusic Capabilities

Synthesizer 3D Effects (Reverb,Chorus)
Microsoft Software Yes Only in not 3D mode
Hardware No No
External No No

More Information

For more information about the CMidiMusic class, read the attached file readme.txt included in the sources. You will be able to find more information in the online help of http://www.microsoft.com/directx and DirectX 8 SDK technical documentation.

Overview of the Demo Project

As you can see, this is not WinAmp, wish it was. ;) Nevertheless, all of CMidiMusic class features are made available.

History

  • 31 Jan 2002 - Updated source files
  • 12 May 2003 - Updated source files
  • 28 Dec 2010 - Updated article and source files

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

Carlos Jiménez de Parga
Software Developer
Spain Spain
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: Time position problem using MCImemberwanders1 Feb '05 - 10:36 
Thank for the quick reply.
 
I'm not using DirectMusic yet. The MCI_OPEN delay is a bug in Win XP/NT also described by AXELMUELLER on 'the code project' (http://www.codeproject.com/audio/mididemocp.asp) At the end of the article...
 
I'm currently testing the other option as he descibes MMSYSTEM, midiOutxxx() and midiStreamxxx() and it runs perfectly except for the problem setting the correct start position.
 
For example, I have several music parts in the midi file and the second part start at say, 3.38.000. When playing the file from the beginning all parts start where they should. If setting the start position directly to 3.38.000 nothing happens. If I set the start pos to 3.30.000 and play from there it's quiet at 3.38.000 and after. Even stranger, some other music part in the file plays correctly when setting their start pos. I've tried both with midi format 0 and 1.
 


 
wanders
GeneralRe: Time position problem using MCImemberCarlos Jiménez de Parga1 Feb '05 - 22:01 
It's a very strange problem. I don't know very much about MMSYSTEM midi commands so I can't be very helpful for you. Nevertheless, I suggest you to use the CMidiMusic class to discard any problem in the MIDI file or the program logic. Use the HRESULT CMIDIMusic::Seek(MUSIC_TIME mtMusicTime)method to check it. It should work fine.
 
cheers
Carlos.

GeneralMIDI messages to multiple portsmemberEddieLotter24 Oct '04 - 8:41 
The standard MIDI file specification has provision for multiple ports to overcome the 16 channel limitation of a single port.
 
I have several MIDI files that are encoded for multiport use, but they can only be played from within a MIDI seqeuncer that supports multiple ports.
 
I would very much like to write a MIDI file player that supports multiport MIDI files, however, DirectMusic loads the entire MIDI file into a single segment and does not allow one the opportunity to modify which track goes to which port.
 
Am I missing something? How would you go about implimenting this scenario?
 
Thanks for your time and effort.
GeneralRe: MIDI messages to multiple portsmemberCarlos Jiménez de Parga25 Oct '04 - 22:48 
Hi Eddie,
 
The solution is not easy in any case. First off, DirectMusic architecture doesn't support multiple ports in an "easy to code" way because it hasn't got any track information related to device or ports.
You might have two alternatives:
 
The first one is to develop a MIDI file reader and parser that extracts the ports assigned to MIDI channels and then, programming the player from the scratch using multiple directmidi::COutputPort objects.
 
The second alternative is also to develop a MIDI file reader and parser and then using directmidi::CPortPerformance::AssignPChannel to route a specific performance channel to another port. In this approach, you must manually download the instrument from the MIDI file to the performance channel in order to play in a different non-default port.
 

cheers
 

PS.
 
You can obtain a MIDI reader toolkit from the Stanford University at:
 
http://ccrma.stanford.edu/software/stk/[^]

GeneralRe: MIDI messages to multiple portsmemberEddieLotter26 Oct '04 - 13:36 
Thanks for the response Carlos.
 
I am familiar with parsing MIDI files and I am familiar with port handling in DirectMusic, it's getting MIDI messages into segments programmatically that has me stumped.
 
Thanks again.
 
Cheers
Eddie
GeneralCarlos, please contact mememberTitchener1 Sep '04 - 14:26 
Carlos-
 
I'm interested in discussing with you the possibility of having you develop some audio software for us on a contract basis. Could you please contact me at:
pt@NO_SPAMpower-t.com .
 
Thanks,
 
Paul T.
Generalgetting the length in (milli)secondsmemberRüpel25 Aug '04 - 23:43 
does someone know how to retrieve the length of a midi-file in seconds or milliseconds? for some midi-files it's totalticks/768, for others it's totalticks/768/x, where x is some obscure factor.
 
GetGlobalParam(GUID_PerfMasterTempo,... doesn't work because it's not that x (despite the fact, that you have to call SetGlobalParam() once to be able to user GetGlobalParam()).
 
any ideas?
 
*Update*
 
I've found the following VB Code elsewhere on the Net, but I can't find similar Functions in the C++ Interfaces of my DirectMusic Documentation. Is it possible to translate that into C++?
 
'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.

 
:wq
GeneralRe: getting the length in (milli)secondsmemberCarlos Jiménez de Parga30 Aug '04 - 0:18 
Hi Rüpel,
 
Those formulas are right. If you analyze the last one, you can take notice that seg.GetLength() is the number of ticks your segment lasts, understanding that the ticks are incremented DMUS_PPQ times for each quarter-note where DMUS_PPQ is defined as 768 ticks/(1 beat = 1 quarter note).
 
Therefore, if your MIDI sequence returns 301558 ticks in the count, for example, you'll have 301558/768 = 392.65 quarter notes in your sequence.
 
Then, you will have to obtain the tempo of your sequence (remember that your sequence can have several tempo changes). If you observe the tempo changes in a sequencer, you can obtain the total time in minutes if you divide the number of quarter notes by the BPM's or beats per minute. Therefore, if you have 392.65 beats in your MIDI and 98 BPM, the total minutes will be 392.65/98 = 4 minutes x 60 = 240 seconds.
 
If you decide to apply this formula you will have to obtain the tempo in each MUSIC_TIME (ticks) of yor sequence. To perform it in C++, you will have to retrieve the GUID_TempoParam parameter using either IDirectMusicSegment8::GetParam or IDirectMusicPerformance8::GetParam and providing the current MUSIC_TIME of your sequence to achieve it.
 
Regards.

GeneralRe: getting the length in (milli)secondsmembersolace12126 Nov '06 - 3:29 

Hello, I have pieces of midi music which change tempo very frequently. What is the best way to obtain the length inside a program? Is there a better way than calling GetParam hundreds of thousands of times? How does something like Windows Media Player do it? If I load up a midi, it knows right away how long the pieces is...
 
Thanks.

GeneralRe: getting the length in (milli)seconds [modified]memberCarlos Jiménez de Parga26 Nov '06 - 4:13 
Personally, I've never tried to call GetParam thousands of times to get tempo changes, it would be a madness, besides it's against the system performance. If I were you, I would read part of the MIDI file to obtain the tempo track and finally read the tempo marks inside it to use them for your time seeking calculation.
 
This URL may help you:
 
http://www.borg.com/~jglatt/tech/midifile.htm[^]
 

-- modified at 10:20 Sunday 26th November, 2006

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 30 Dec 2010
Article Copyright 2001 by Carlos Jiménez de Parga
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid