![]() |
Multimedia »
Audio and Video »
Audio
Intermediate
License: The MIT License
Wrapper Library for Windows MIDI APIBy Leslie SanfordA small library encapsulating the Windows MIDI API |
VC6, MFC, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
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 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:
CMIDIInDevice and CMIDIReceiver classes CMIDIOutDevice class CShortMsg class CLongMsg class Let's look at each part of the library.
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.
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:
std::exception, so catching this exception will catch any exception thrown. DWORD data types. They must be unpacked before you can do anything interesting with them. You can do this yourself, or you can allow the CShortMsg to take care of this for you. If you decide to pack and unpack short messages, you can use class methods in the CMIDIInDevice class and the CMIDIOutDevice class for unpacking and packing short messages respectively. CMIDIReceiver for receiving system exclusive messages. 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:
LPSTR buffer or create a CLongMsg object and initialize it with a valid system exclusive message. CMIDIOutDevice doesn't pay any attention to time stamps. In order to send messages at a certain time, you'll have to create a timing scheme of some sort. 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!
GetDevID method to both the CMIDIInDevice class and the CMIDIOutDevice class. Added CMIDIMsg, CShortMsg, and CLongMsg classes to the library. Redid demo app from scratch. The keyboard display is derived from my new class CPianoCtrl. All source code is included in the demo project (no need to email any longer for the source!). CShortMsg class can now send messages without the status byte. I also changed the way threads are created from using CreateThread to using AfxBeginThread and changed the way thread termination is handled so that it is done more gracefully. Several other changes to the library here and there as well as fixing some bugs in the demo application.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 28 Jan 2008 Editor: Genevieve Sovereign |
Copyright 2002 by Leslie Sanford Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |