NiCMidi 1.1.0
A MIDI library derived from J.D.Koftinoff jdksmidi
How NiCMidi plays MIDI

If we want to play the contents of a MIDIMultiTrack we must send them to an hardware MIDI port with accurate timing.

As said in previous sections, this can easily be done by the AdvancedSequencer object. This section is a more in-depth examination of how this is done by mean of some library classes.

The MIDITimer class

The MIDITimer is a static class that provides MIDI timing. This is accomplished by starting a background thread that calls a user-defined callback function at a regular rate. The timing is provided by the C++ <std::chrono> classes, so you need to compile the library according to (at least) the C++ 0x11 standard; the default temporal resolution is 10 msecs, but you can change it with the MIDITimer::SetResolution() method.

When playing MIDI, the timer is usually controlled by the MIDIManager class (described later), so it is rarely necessary to use its methods directly. You may find useful its MIDITimer::Wait() method (which waits a certain number of milliseconds) or the MIDITimer::GetSysTimeMs() method (which returns the absolute time in milliseconds from the class instantiation).

The MIDIOutDriver and MIDIInDriver classes

The MIDIOutDriver and MIDIInDriver classes are objects which communicate between the library software and the hardware MIDI ports regardless the underlying OS; NiCMidi uses the RTMidi library of Gary Scavone (see http://www.music.mcgill.ca/~gary/rtmidi/) to have a common interface for all OS. Every port has an Id number and a readable name (given by the OS).

If you want to send (or receive) MIDI messages to (from) a port you must open it with the MIDIOutDriver::OpenPort() or MIDIInDriver::OpenPort() methods. The MIDIOutDriver has a method MIDIOutDriver::OutputMessage() which sends a MIDITimedMessage to the port; the MIDIInDriver is a bit more complicated, because it manages a queue for incoming messages, and you have various methods for inspecting them (see the class documentation for details).

However, when using the high-level objects of the library (such as AdvancedSequencer, MIDISequencer, MIDIRecorder, etc.), all the work (opening and closing ports, sending/receiving messages ...) is done by these classes, so even in this case the user rarely needs to use the methods of this class directly.

The MIDIManager class

The MIDIManager is a static class that handles the temporized communications between the software and the hardware MIDI ports.

This modular design allows the user to stack many components which all share the same timing (for example a sequencer, a metronome, a recorder ...) and encourages him to develop its own objects.

The MIDITickComponent class

The core of MIDI timed playback is the pure virtual MIDITickComponent class: this is the prototype for all objects that have a callback procedure to be called at every tick of the MIDITimer; the AdvancedSequencer, MIDISequencer, MIDIThru, Metronome and MIDIRecorder classes all inherit from it. Let us analyze its most important methods:

Due to the difficulty of calling member functions as callbacks in C++ this class actually needs two methods (one static and one member) to implement the callback mechanism:

  • The static MIDITickComponent::StaticTickProc(tMsecs sys_time, void* p) is called by the MIDIManager, getting as parameters the absolute system time and the this pointer of the class instance as a void. This does nothing in the base class, and you must redefine it in your subclass. It only should cast the pointer to point to the derived class and then call the (virtual) member callback. For example:
    void MyClass::StaticTickProc(tMsecs sys_time, void* p) {
    MyClass* my_pt = static_cast<MyClass*>(p);
    my_pt->TickProc(sys_time);
    }
    unsigned long long tMsecs
    The type of a variable which can hold the elapsed time in milliseconds.
    Definition: timer.h:44
  • The member MIDITickComponent::TickProc(tMsecs sys_time) is pure virtual in the base class: you must implement it and do all your work here. You can know the number of msecs elapsed from the Start() call with sys_time - sys_time_offset and use it to perform time calculations and send, receive or manipulate MIDI messages with accurate timing.

This is an example of the usage of the Metronome:

#include "metronome.h"
#include "manager.h"
int main() {
Metronome metro; // creates the metronome and adds it to the
// MIDIManager queue
metro.SetTempo(100); // sets the musical tempo
metro.Start();
MIDITimer::Wait(10000); // waits 10 seconds
metro.Stop();
}
static void Wait(unsigned int msecs)
Stops the calling thread for the given number of milliseconds.
Definition: timer.h:105
A MIDITickComponent implementing a metronome.
Definition: metronome.h:46
virtual void Stop()
Stops the metronome.
virtual void Start()
Starts the metronome.
bool SetTempo(float t)
Sets the musical tempo.
Contains the definitions of the class Metronome.

In the AdvancedSequencer class the Start() method is aliased with Play(), following the ordinary naming conventions in a sequencer.

More detailed examples of the usage of the derived classes are in the following files: