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
GeneralRelease problem Pin
lipanje
4:30 6 Oct '09  
GeneralRe: Release problem Pin
lipanje
2:31 8 Oct '09  
Generalis there a way to make it work under Windows Mobile? Pin
glook
4:42 31 Jul '09  
GeneralLINK : fatal error LNK1104: cannot open file 'mfc42d.lib' Pin
gsdagsg
17:56 15 Jul '09  
GeneralProblem with releasing Pin
lespaulka001
4:32 17 Nov '08  
General[Message Removed] Pin
Katekortez
10:30 25 Oct '08  
GeneralMFC, Window Form Pin
EllenTCY
8:01 22 Mar '08  
QuestionWorking with offsets Pin
Danny Van Hoof
11:12 9 Feb '08  
GeneralRe: Working with offsets Pin
Leslie Sanford
11:16 9 Feb '08  
GeneralCompile Error [modified] Pin
JimD.9999
13:56 26 Jan '08  
GeneralRe: Compile Error Pin
Leslie Sanford
23:04 26 Jan '08  
GeneralRe: Compile Error Pin
JimD.9999
5:08 27 Jan '08  
GeneralRe: Compile Error Pin
Leslie Sanford
6:59 27 Jan '08  
GeneralRe: Compile Error Pin
JimD.9999
7:48 27 Jan '08  
GeneralLicense question Pin
yogbert42
13:46 22 Jan '08  
GeneralRe: License question Pin
Leslie Sanford
11:41 23 Jan '08  
GeneralRe: License question Pin
yogbert42
1:32 24 Jan '08  
QuestionSend to combinations of midi out ports? Pin
machine_run
6:31 16 Jan '08  
GeneralRe: Send to combinations of midi out ports? Pin
Leslie Sanford
22:04 19 Jan '08  
GeneralI love it Pin
Razvan C. Cojocariu
19:25 28 Nov '07  
GeneralVS 2005 Linking Pin
evanchri
9:29 9 Oct '07  
Generalcan u guide me i am new for mixer control Pin
rajneshmalik
3:15 30 Aug '07  
GeneralRe: can u guide me i am new for mixer control Pin
Leslie Sanford
8:13 1 Sep '07  
Generalmidi2wav conversion Pin
patelviral
10:44 23 Jun '07  
GeneralSending long Sysex Messages Pin
flanby7
11:05 20 May '07  


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