Click here to Skip to main content
Email Password   helpLost your password?
MIDIDeviceDemo Image

Introduction

Using the Windows low-level MIDI API is not necessarily easy. There are a lot of details to keep track of, and it can get a bit frustrating when you are trying to write a MIDI application. The goal of my small wrapper library is to elevate some of the grunt work that goes into using the Windows low-level MIDI API by encapsulating the functions into C++ classes. With these classes, most of the dirty work is handled behind the scenes. This leaves you free to concentrate on more important matters.

The Library

The MIDI library is encapsulated in the midi namespace. In order to use the library, you must link to the winmm.lib library. There are several parts to the library described in the following header files:

Let's look at each part of the library.

midi.h - Useful Constants

The midi.h header file contains many useful constants. For example, the midi::NOTE_OFF constant represents the value for turning off a MIDI note. These constants come in handy when creating and interpreting MIDI messages. You don't have to look up the values in a MIDI reference. Just plug in the constant you need, and you're all set.

MIDIInDevice.h - The CMIDIInDevice and CMIDIReceiver Classes

The CMIDIInDevice class represents MIDI input devices. A MIDI input device can be any hardware device installed on your computer, such as a soundcard, capable of receiving MIDI data. There can be several of these devices present, and the CMIDIInDevice class provides methods for determining the number and characteristics of these devices. The GetNumDevs() function returns the number of MIDI input devices on your system, and each number represents that device's identifier. The GetDevCaps() function returns information about a particular device by storing it in a MIDIINCAPS structure. This is a Windows structure that contains many attributes describing MIDI input devices, such as their names. A good first step before using the CMIDIInDevice class is to determine the number of MIDI input devices on your system and which one you want to use to receive MIDI data.

Before CMIDIInDevice objects can be used, they have to be opened. What do I mean by opened? Think of using the ifstream class from the standard library for reading the contents of a file. Before the file can be read, it has to be opened. The CMIDIInDevice class works in the same way - before it can "read" MIDI data, it has to be opened. You open CMIDIInDevice objects by passing a device identifier to either the constructor or the Open() function. The device identifier is a unique number representing a MIDI input device. You can retrieve this number by using the aforementioned GetNumDevs() and GetDevCaps() functions.

To begin receiving MIDI data, call the StartRecording() function after the MIDI input device has been opened. The device will continue receiving MIDI data until StopRecording() is called to stop the process. Where do the MIDI messages go once they are received? This leads us to the second major component in the input part of the library - the CMIDIReceiver class.

The CMIDIReceiver class is an abstract class representing objects that receive MIDI data from the CMIDIInDevice class. An object derived from the CMIDIReceiver class can register itself with an CMIDIInDevice object. The CMIDIInDevice will then pass on MIDI messages it receives by calling methods in the CMIDIReceiver derived object. The CMIDIReceiver has two methods for receiving short messages, and two methods for receiving system exclusive messages. In both cases, there is one method which is called when things have gone normally and a second method which is called if an error has occurred.

Short messages are represented by the DWORD data type. You don't have to do anything special to receive them. System exclusive messages, on the other hand, are represented by a LPSTR data buffer. In order to receive system exclusive messages, provide the CMIDIInDevice with buffers to store the messages. This is done with the AddSysExBuffer() function. Pass as many buffers as you like during the recording process. When the CMIDIInDevice receives a system exclusive message, it stores it in the buffer and passes it back through the CMIDIReceiver you registered.

Finally, once you are finished using the CMIDIInDevice, close the device simply by calling the Close() function.

To make short messages and system exclusive messages easier to work with, I've added three new classes: CMIDIMsg, CShortMsg, and CLongMsg. The CMIDIMsg class is the abstract base class for all MIDI messages. Both the CShortMsg and CLongMsg classes are derived from it. The CShortMsg class represents MIDI short messages such as note-on and note-off messages. The CLongMsg class represents system exclusive messages. It is essentially a buffer for the bytes that makes up an system exclusive message. It can be used for general purpose system exclusive messages, or you can derive a more specialized class from it.

A code snippet is worth a thousand words, so let's look at an example of using the CMIDIInDevice and CMIDIReceiver classes:

#include "StdAfx.h"
#include <iostream>
#include "MIDIInDevice.h"
#include "ShortMsg.h"

using midi::CMIDIInDevice;
using midi::CMIDIReceiver;

// My class for receiving MIDI messages
class MyReceiver : public CMIDIReceiver
{
public:
    // Receives short messages
    void ReceiveMsg(DWORD Msg, DWORD TimeStamp);

    // Receives long messages
    void ReceiveMsg(LPSTR Msg, DWORD BytesRecorded, DWORD TimeStamp)
    {}

    // Called when an invalid short message is received
    void OnError(DWORD Msg, DWORD TimeStamp)
    { std::cout << "Invalid short message received!\n"; }

    // Called when an invalid long message is received
    void OnError(LPSTR Msg, DWORD BytesRecorded, DWORD TimeStamp) {}
};

// Function called to receive short messages
void MyReceiver::ReceiveMsg(DWORD Msg, DWORD TimeStamp)
{
    midi::CShortMsg ShortMsg(Msg, TimeStamp);

    std::cout << "Command: " <<
static_cast<int>(ShortMsg.GetCommand());
    std::cout << "\nChannel: " <<
static_cast<int>(ShortMsg.GetChannel());
    std::cout << "\nDataByte1: " <<
static_cast<int>(ShortMsg.GetData1());
    std::cout << "\nDataByte2: " <<
static_cast<int>(ShortMsg.GetData2());
    std::cout << "\nTimeStamp: " <<
static_cast<int>(ShortMsg.GetTimeStamp());
    std::cout << "\n\n";
}

int main(void)
{
    try
    {
        // Make sure there is a MIDI input device present
        if(CMIDIInDevice::GetNumDevs() > 0)
        {
            MyReceiver Receiver;
            CMIDIInDevice InDevice(Receiver);

            // We'll use the first device - we're not picky here
            InDevice.Open(0);

            // Start recording
            InDevice.StartRecording();

            // Wait for some MIDI messages
            ::Sleep(10000);

            // Stop recording
            InDevice.StopRecording();

            // Close the device
            InDevice.Close();
        }
        else
        {
            std::cout << "No MIDI input devices present!\n";
        }
    }
    catch(const std::exception &Err)
    {
        std::cout << Err.what();
    }

    return 0;
}

Some notes about the code above:

MIDIOutDevice.h - The CMIDIOutDevice Class

The CMIDIOutDevice class represents MIDI output devices. Like the CMIDIInDevice class, it has methods for determining the number of devices present and for retrieving information about each device. Using an object of this class is straight forward. Simply open it, create a MIDI message, send the message, and close the device when you are done with it.

Here is another code example:

#include "StdAfx.h"
#include <iostream>
#include "midi.h"
#include "MIDIOutDevice.h"
#include "ShortMsg.h"

using midi::CMIDIOutDevice;
using midi::CShortMsg;

// Some useful constants
const unsigned char CHANNEL = 0;
const unsigned char NOTE_ID = 64;
const unsigned char VELOCITY = 127;

int main(void)
{
    try
    {
        // Make sure there is an output device present
        if(CMIDIOutDevice::GetNumDevs() > 0)
        {
            CMIDIOutDevice OutDevice;

            // Use the first device
            OutDevice.Open(0);

            // Create note-on message
            CShortMsg NoteOnMsg(midi::NOTE_ON, CHANNEL, NOTE_ID,
              VELOCITY, 0);

            // Turn note on
            NoteOnMsg.SendMsg(OutDevice);

            // Wait a bit
            ::Sleep(5000);

            // Create note-off message
            CShortMsg NoteOffMsg(midi::NOTE_OFF, CHANNEL, NOTE_ID, 0, 0);

            // Turn note off
            NoteOffMsg.SendMsg(OutDevice);

            // Close device
            OutDevice.Close();
        }
        else
        {
            std::cout << "No MIDI output devices present!\n";
        }
    }
    catch(const std::exception &Err)
    {
        std::cout << Err.what();
    }

    return 0;
}

Some comments:

The MIDIDeviceDemo Demonstration App

The demo application uses the small MIDI library I've created for input and output. The piano keyboard is interactive. Clicking on a key with the mouse triggers a note. You can also play the piano from the computer keyboard. The keys from 'Z' to '/' correspond to the piano. To change the range of the computer keyboard, press keys 1-5. The '1' key represents the lowest range, and the '5' key represents the highest range.

If a MIDI keyboard is connected to the computer, playing keys on the keyboard will cause the corresponding notes on the keyboard display to change colors. Incoming channel messages are displayed as well. The application has very little practical value, but it does show how the library works.

Hopefully, you will find this small library as useful as I have. I would appreciate any comments or suggestions for future revisions. Take care!

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionThe role of threads
Dukato
22:48 20 Jan '10  
Thanks for this excellent article and the very detailed explainations about messages and devices and all that stuff.
What I miss a bit (and would like to read about) is a part on the role of threads.

Particularly the question how to use threads to get stable timing for input and output of midi events would be a very interesting topic. So in case you are willing to update this article, my vote goes for this topic.

Or could you point me to a resource, where this problem is being explained (I could not find any)?

Thanks
GeneralRuntime problem when building with VS2008
Member 4532182
12:01 15 Jan '10  
I got the code working while using VS2005 but recently started using VS2008.
Now when I build my project in either debug or release mode on Windows 7 64 bit the pointer to the midihdr seems to be lost or wrong after receiving multiple sysex.

MIDIHDR *MidiHdr = reinterpret_cast<MIDIHDR *>(Param1);

Device->m_Receiver->ReceiveMsg(MidiHdr->lpData,
MidiHdr->dwBytesRecorded,
Param2);

When I build the same code on VS2005 again the problem is gone....

The problem seems to be caused by CRT 9.x but I'm not sure about that.

Any tips are welcome
GeneralRe: Runtime problem when building with VS2008
Leslie Sanford
12:31 15 Jan '10  
I haven't looked at my code in years, so I can't remember if I ever made this change or not, but the parameters Param1 and Param2 to the MIDIProc function should be of type INT_PTR, I believe. This should make them safe in a 64-bit environment.
GeneralRe: Runtime problem when building with VS2008
Ben F. Zonneveld
12:45 15 Jan '10  
So this:
void CALLBACK CMIDIInDevice::MidiInProc(HMIDIIN MidiIn, UINT Msg,
DWORD_PTR Instance, DWORD_PTR Param1,
DWORD_PTR Param2)

should be using INT_PTR?

By the way, i build for Win32
GeneralRe: Runtime problem when building with VS2008
Leslie Sanford
14:10 15 Jan '10  
Ben F. Zonneveld wrote:
should be using INT_PTR?


No, leave it DWORD_PTR, that's correct. Looks like I corrected the code some time back.

Well, that leaves us back where we started.
GeneralRelease problem
lipanje
4:30 6 Oct '09  
It works perfectly fine when using in DEBUG mode, but if I try to RELEASE it, I get this error:

MIDIOutDevice.h(74) : error C2664: 'midiOutGetErrorTextW' : cannot convert parameter 2 from 'char [128]' to 'LPWSTR'

Any idea why this is happening? Thanks a lot, Matevz
GeneralRe: Release problem
lipanje
2:31 8 Oct '09  
Solved!¨
Project properties differ for "DEBUG" and "RELEASE" configuration. Just make sure they are equal-compare them.
Generalis there a way to make it work under Windows Mobile?
glook
4:42 31 Jul '09  
great work, cool classes, thanx!
but i'd like to build mobile applications with MIDI support, is there a way to modify your classes to make 'em work under WM?
GeneralLINK : fatal error LNK1104: cannot open file 'mfc42d.lib'
gsdagsg
17:56 15 Jul '09  
LINK : fatal error LNK1104: cannot open file 'mfc42d.lib'

Only the 64 bit come with it, so basically this doesn't work with the express version of VC2005 because there is no MFC with it.
GeneralProblem with releasing
lespaulka001
4:32 17 Nov '08  
Hello!
I haven't got problem when debuggin, or running with my application, but when I try to release it writes error:

error LNK2019: unresolved external symbol "public: __thiscall midi::CMIDIOutDevice::CMIDIOutDevice(void)

It could be a linking problem, but I'm lined with winmmm.lib

Please helpUnsure
Thanks for your further answer!
General[Message Removed]
Katekortez
10:30 25 Oct '08  
Spam message removed
GeneralMFC, Window Form
EllenTCY
8:01 22 Mar '08  
Hey guys! I got a problem in using this library.
As I am developing a Window Form application, but I want to use this library which is MFC based. Do anyone know there are ways to use this library in my application?????

Please help, since I am a beginner in programming, I really got no idea in how to do conversion or else.
QuestionWorking with offsets
Danny Van Hoof
11:12 9 Feb '08  
I'm writing an application to merge midi files. Therefore I wonder whether the package supports

- reading a midi file into a sequence, starting at a specific offset instead of at the beginning of the sequence
- cutting part from a sequence (between specific offsets)
- moving part from a sequence to a new offset

Any suggestions?

Danny
GeneralRe: Working with offsets
Leslie Sanford
11:16 9 Feb '08  
Danny Van Hoof wrote:
I'm writing an application to merge midi files. Therefore I wonder whether the package supports

- reading a midi file into a sequence, starting at a specific offset instead of at the beginning of the sequence
- cutting part from a sequence (between specific offsets)
- moving part from a sequence to a new offset


The wrapper library only supports MIDI IO. Unfortunately, it doesn't provide any way to edit or playback sequences.

My C# MIDI Toolkit does some of this, though.
GeneralCompile Error [modified]
JimD.9999
13:56 26 Jan '08  
Did you try building this version?
DWORD_PIR instead of DWORD_PTR

Also, it it possible to read a *.mid file using MCI?
I want to send the data to a custom device.

modified on Saturday, January 26, 2008 7:13:20 PM

GeneralRe: Compile Error
Leslie Sanford
23:04 26 Jan '08  
JimD.9999 wrote:
Did you try building this version?
DWORD_PIR instead of DWORD_PTR


Oops. Thanks for the catch. I will submit a correction tomorrow.

JimD.9999 wrote:
Also, it it possible to read a *.mid file using MCI?
I want to send the data to a custom device.


I think so, but I haven't looked at MCI in a long time.
GeneralRe: Compile Error
JimD.9999
5:08 27 Jan '08  
No big deal, it was easy enough to fix, and hard to miss.

I used your keyboard control - thanks it worked great and was easy to add to my project.

If your not up to figuring out how to read a file, then ok.
I was hoping there was some easy way to that - that the file stream would
be just like a device stream - once you figure out how to open it.
Too bad they made the API so obscure.
GeneralRe: Compile Error
Leslie Sanford
6:59 27 Jan '08  
JimD.9999 wrote:
If your not up to figuring out how to read a file, then ok.
I was hoping there was some easy way to that - that the file stream would
be just like a device stream - once you figure out how to open it.
Too bad they made the API so obscure.


I took a quick look at Jeff Glatt's website and found this:

MCI Sequencer[^]

This may be what you're looking for. Jeff has a ton of information about the various Windows multimedia API's here[^]. Hope this helps.
GeneralRe: Compile Error
JimD.9999
7:48 27 Jan '08  
Thanks!
You know what, I almost tried that, figuring a file would be just like a device stream, and that you would just put the file name it there. Would have it been *that* hard for M$ to just say that in MSDN?
I'm busy this week, but I will look in to that.
I just glanced at that part of your code, but it should drop right it.
GeneralLicense question
yogbert42
13:46 22 Jan '08  
Hi Leslie,
Great work! These files seem to be licensed under the LGPL. This means for commercial usage the library would have to be compiled as a dll. Is that your intention, or would you allow static linking of this C++ Midi library? Did you consider changing the license to the MIT license, given that your newer C# Midi library is using the MIT license?
GeneralRe: License question
Leslie Sanford
11:41 23 Jan '08  
yogbert42 wrote:
Great work! These files seem to be licensed under the LGPL. This means for commercial usage the library would have to be compiled as a dll. Is that your intention, or would you allow static linking of this C++ Midi library? Did you consider changing the license to the MIT license, given that your newer C# Midi library is using the MIT license?


I need to change it over to the MIT license. This code was posted ages ago before I really understood the ramifications of the various license agreements. I'll try to post an update soon.
GeneralRe: License question
yogbert42
1:32 24 Jan '08  
Leslie Sanford wrote:
Did you consider changing the license to the MIT license, given that your newer C# Midi library is using the MIT license?


I need to change it over to the MIT license. This code was posted ages ago before I really understood the ramifications of the various license agreements. I'll try to post an update soon.


Thanks, that's good to know. Does this license change also apply to your C++ Piano Control (which also ships as pure source files, suggesting that they are meant to be statically linked to a project, but internally they say they are licensed under the LPGL)?
QuestionSend to combinations of midi out ports?
machine_run
6:31 16 Jan '08  
Is there some way to receive midi data, alter it, and send it to
different constantly changing combinations of midi out ports, hopefully
without causing latency from opening and closing ports?
GeneralRe: Send to combinations of midi out ports?
Leslie Sanford
22:04 19 Jan '08  
machine_run wrote:
Is there some way to receive midi data, alter it, and send it to
different constantly changing combinations of midi out ports, hopefully
without causing latency from opening and closing ports?


What I would do is open all of the MIDI output devices you need for the job and keep them open for as long as you need them. Then just send MIDI messages to whichever device you want based on whatever criteria you're using. In other words, don't open/close the devices for each message but rather keep them open for as long as you need them.
GeneralI love it
Razvan C. Cojocariu
19:25 28 Nov '07  
Good job dude!


Last Updated 28 Jan 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010