MidiMan Object Agent Library

MidiMan Object Agent Library

by Bob Hood




Table Of Contents

1.0 Overview
2.0 Constructor
3.0 Methods
3.1 Reading MIDI Events
3.2 A Note About Pitch Wheel Events
3.3 Generating MIDI Events
3.4 Resetting MIDI Channels
3.4.1 Input
3.4.2 Output
3.5 Sample Generic Script
4.0 Data Members



1.0 Overview

MidiMan is an LScript Object Agent that provides an interface to a MIDI device under Microsoft Windows 9x/2000. MIDI events can be read from the device, as well as generated and sent to the device using MidiMan. The Object Agent only supports simple MIDI messages (those requiring at most only one or two bytes of data). Complex message types, like System Exclusive, are not supported.

MidiMan has an internal event buffer capable of caching up to 50,000 events before older events begin to be overwritten.

MidiMan is only available for Windows platforms:

Platform Library
Intel mmoa_x86.zip




2.0 Constructor

The MidiMan constructor accepts up to two device names to open. If two are provided, one should be designated for input while the other should be designated for output. To designated a valid MIDI device name for input, you precede the device name with a less-than character ('<'). Opening a MIDI device for output would require using the greater-than character ('>') immediately before the name.

The device name you use should correspond exactly to that used by Windows to identify MIDI devices. Case does not matter, correct spelling does. For instance, suppose you had identified the device name "SB Live! MIDI UART" as being the device channel with which you wish to interact. In order to open that device for input, you would invoke the MidiMan constructor as follows:

Example 2.1: Opening A MIDI Device For Input

    . . .
    midi_in = MidiMan("<SB Live! MIDI UART");
    . . .

Opening a device for output would be similar:

Example 2.2: Opening A MIDI Device For Outpust

    . . .
    midi_out = MidiMan(">SB Live! MIDI UART");
    . . .

If you plan to do input and output at the same time, it is more efficient to let a single instance of MidiMan manage both channels than to generate a separate instance of MidiMan for each direction:

Example 2.3: Opening Both Input And Output In The Same Instance

    . . .
    midi = MidiMan("<SB Live! MIDI UART",">SB Live! MIDI UART");
    . . .

Some pre-defined constant values have been supplied to better help you identify or designate specific MIDI events.

  • NOTEOFF

  • NOTEON

  • AFTERTOUCH

  • CTLCHANGE

  • PRGCHANGE

  • CHNLPRESS

  • PITCHWHEEL

  • SYSCOMMON



Note: It is important to note that, even if the MIDI input channel is opened successfully, events generated on that channel will not be processed by MidiMan until the channel is enabled using the startInput() method (see this entry later in this document).




3.0 Methods


3.1 Reading MIDI Events

The readEvent() method will query MidiMan's internal event stack, and will return the oldest event contained upon it. This method returns, in order, the MIDI command, MIDI channel number, data element #1, data element #2, and the timestamp (in milliseconds) of the event. It may be the case that the combination of the command and channel values did not generate any associated data elements. Please refer to the MIDI v1.0 specification for more info.

If no arguments are passed to readEvent(), then the oldest MIDI event in the cache is returned. If no events are available, readEvent() will return 'nil'.

Example 3.1: Reading An Event

    . . .
    midi_in = MidiMan("<SB Live! MIDI UART");
    . . .
    (cmd,chn,dat1,dat2,ts) = midi_in.readEvent();

    if(cmd != nil)
    {
        . . .
    }
    . . .

You can also pass one or more event designators to readEvent() which will be used to filter events in the cache. Only those events matching the specified event values will be returned. Intervening events in the buffer not in the filter list will be discarded.

Example 3.2: Filtering Events

    . . .
    midi_in = MidiMan("<SB Live! MIDI UART");
    . . .
    (cmd,chn,dat1,dat2,ts) = midi_in.readEvent(NOTEON,NOTEOFF);

    if(cmd != nil)
    {
        . . .
    }
    . . .

readEvent() is only useful for reading a MIDI device that has been opened for input. If no such state exists within the Object Agent instance, then readEvent() will always return 'nil'.


3.2 A Note About Pitch Wheel Events

The MIDI definition specifies that the Pitch Wheel has an at-rest value of 0x2000 (8192 decimal). MidiMan simplifies the process if handling Pitch Wheel events by normalizing the data value. This means that MidiMan will return an at-rest value of 0 for the Pitch Wheel, and all data events will be relative offsets from that value. If you need to use the actual specification-defined values for the Pitch Wheel, simply add 8192 to any value generated by that event.

The values return by readEvent() for the Pitch Wheel include the actual generated value in the first data element. The second data element is unused, and is set to zero (0).


3.3 Generating MIDI Events

Using sendEvent(), you can send simple MIDI events to any device you have successfully opened for output. The argument list for sendEvent() exactly corresponds to the return list for readEvent(). The event command is provided first, followed by the MIDI channel number to receive the event. This can be a number from 1 to 16.

Two data elements can optionally be provided if the command being executed requires them. For example, the NOTEON command requires the note identifier (60 == Middle C), as well as the velocity at which the note should be played. The following example illustrates playing middle C through MIDI channel 3 on the output device:

Example 3.3: Playing Middle C

    . . .
    midi = MidiMan(">SB Live! MIDI UART");
    midi.sendEvent(NOTEON,3,60,64);
    sleep(100);
    midi.sendEvent(NOTEOFF,3,60,64);
    . . .

Some MIDI commands will not require extra data. For instance, System Common messages only utilize the MIDI channel value to accomplish their work:

Example 3.4: Events Without Data

    . . .
    midi = MidiMan(">SB Live! MIDI UART");
    midi.sendEvent(SYSCOMMON,6);   // tell all oscillators to tune themselves
    . . .

3.4 Resetting MIDI Channels


3.4.1 Input

The MIDI input stream can be reset using the resetInput() method. When the input stream is reset, all input on the stream is halted, and the internal event buffer is cleared. No further events will be processed on the input channel until the startInput() method is called.

Aside from discarding buffered data and ignoring subsequent events, resetting the input channel has the effect of setting the timestamp settings back to zero. When the channel is re-enabled using startInput(), the timestamp values will once again begin increasing from zero.


Note: MidiMan opens the MIDI input channel but does not enable it for events. The startInput() method must be called before events on the input channel are processed by MidiMan.


3.4.2 Output

The MIDI output stream is reset using the resetOutput() method. When called, the device(s) associated with the output stream will cease all current activity. For instance, a synthesizer will immediately cut off all notes that might be playing when this event is broadcast.


3.5 Sample Generic Script

The following complete Generic LScript will, in order, play the C-Major scale on the selected MIDI device, record all NOTE events until Middle-C is pressed, and finally, play back all events recorded:

Example 3.5: Exercising MidiMan

    @version 2.4
    @warnings
    @script generic

    @insert "MidiMan.inc"

    @define MIDIDEVICE  "SB Live! MIDI UART"

    // C-Major scale notes
    midi_notes = @60,62,64,65,67,69,71,72,71,69,67,65,64,62,60@;

    generic
    {
        // open the MIDI device for output, and play the C-Major scale on it

        midi = MidiMan(">" + MIDIDEVICE);
        for(x = 1;x <= 15;x++)
        {
            midi.sendEvent(NOTEON,1,midi_notes[x],64);
            sleep(150);
            midi.sendEvent(NOTEOFF,1,midi_notes[x],64);
        }

        // open the MIDI device for input, and record the NOTE events

        midi = MidiMan("<" + MIDIDEVICE);
        midi.startInput();

        while(true)
        {
            t = midi.readEvent(NOTEON,NOTEOFF);

            if(t)
            {
                midi_cmd += t[1];       // Command (NOTEON or NOTEOFF)
                midi_chn += t[2];       // MIDI channel
                midi_dt1 += t[3];       // Note
                midi_dt2 += t[4];       // Velocity
                midi_ts  += t[5];       // Timestamp (milliseconds)

                last if t[3] == 60;     // Middle-C terminates record
            }
            else
                sleep(50);
        }

        midi.resetInput();    // disregard further input events

        // open the MIDI device for output, and play back the recorded events

        midi = MidiMan(">" + MIDIDEVICE);

        for(x = 1;x <= midi_cmd.size();x++)
        {
            midi.sendEvent(midi_cmd[x],midi_chn[x],midi_dt1[x],midi_dt2[x]);
            if(x < midi_cmd.size())
            {
                how_long = midi_ts[x+1] - midi_ts[x];
                sleep(how_long);
            }
        }
    }



4.0 Data Members

MidiMan makes available two data members. Keep in mind that all data members are strictly read-only.

The first data member is called input and contains a Boolean flag that indicates the state of the input MIDI channel in MidiMan. If 'true', then the input channel is open and available. This flag could be checked after the Object Agent instance is generated to ensure that required channel was successfully initialized:

Example 4.1: Checking The Input Channel

    . . .
    midi = MidiMan("<SB Live! MIDI UART");
    if(!midi.input)
    {
        info("Could not initialize MIDI input channel!");
        midi = nil;    // release MidiMan
        return;
    }
    . . .

Called output, the second data member works in a fashion similar to input. It is used to test the state of the MIDI output channel in MidiMan.

Example 4.2: Checking The Output Channel

    . . .
    midi = MidiMan(">SB Live! MIDI UART");
    if(!midi.output)
    {
        info("Could not initialize MIDI output channel!");
        midi = nil;    // release MidiMan
        return;
    }
    . . .



Examples

Example 2.1 Opening A MIDI Device For Input
Example 2.2 Opening A MIDI Device For Outpust
Example 2.3 Opening Both Input And Output In The Same Instance
Example 3.1 Reading An Event
Example 3.2 Filtering Events
Example 3.3 Playing Middle C
Example 3.4 Events Without Data
Example 3.5 Exercising MidiMan
Example 4.1 Checking The Input Channel
Example 4.2 Checking The Output Channel

Generated 01/11/02 by Doc'ter