The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

MIDI::Music - Perl interface to /dev/music.

SYNOPSIS

    use MIDI::Music;

    my $mm = new MIDI::Music;

    # Play a MIDI file through the
    # first available device
    $mm->playmidifile('foo.mid') || die $mm->errstr;

or:

    use MIDI::Music;
    use Fcntl;
    my $mm = new MIDI::Music;

    # Initialize device for writing
    $mm->init('mode'     => O_WRONLY,
              'timebase' => 96,
              'tempo'    => 60,
              'timesig'  => [2, 2, 24, 8],
              ) || die $mm->errstr;

    # Play a C-major chord
    $mm->playevents([['patch_change', 0, 0, 49],
                     ['note_on', 0, 0, 60, 64],
                     ['note_on', 0, 0, 64, 64],
                     ['note_on', 0, 0, 67, 64],
                     ['note_on', 0, 0, 72, 64],
                     ['note_off', 144, 0, 60, 64],
                     ['note_off', 0, 0, 64, 64],
                     ['note_off', 0, 0, 67, 64],
                     ['note_off', 0, 0, 72, 64],
                    ]) || die $mm->errstr;
    $mm->dumpbuf;
    $mm->close;

or:

    use MIDI::Music;
    use MIDI;
    use Fcntl;

    my $opus  = MIDI::Opus->new();
    my $track = MIDI::Track->new();

    my $mm = new MIDI::Music('tempo'    => 120, # These parameters
                             'realtime' => 1,   # can be passed to
                             );                 # the constructor

    # Record some MIDI data from
    # an external device..
    $mm->init('mode' => O_RDONLY) || die $mm->errstr;

    for (;;) {

        << break condition here... >>

        my $event_struct = $mm->readevents;

        push(@{ $track->events_r }, @$event_struct)
            if (defined $event_struct);
    }

    $mm->close;

    $opus->tracks($track);
    $opus->write_to_file('bar.mid');

DESCRIPTION

MIDI::Music is a high-level interface to /dev/music, and is designed to function on any *NIX system supported by Open Sound System v.3.8 or higher.

Playback through internal and external MIDI devices is supported, as is the "recording" of events from an external device. Additional goals in designing MIDI::Music were:

  1. to provide an API with as few methods necessary to satisfy 99% of MIDI programmers' purposes.

  2. to provide easy integration with Sean M. Burke's MIDI-Perl suite by means of a common event specification.

There are, at present, essentially three things you can do with MIDI::Music:

  1. Play a MIDI file.

  2. Play a series of events defined in an event structure, which is a LoL as described in the MIDI::Event documentation.

  3. Read a series events from an external device. These events are returned as the same type of event structure as in [2].

It is important to remember that MIDI::Music is not a "realtime" synthesizer interface in the strictest, unbuffered sense of the term. :) Rather, a series of events are written to an internal buffer (in playback-related methods, anyway) which is flushed periodically. The "playevents" function may have, for example, long since returned, while the user continues to hear notes being played.

FWIW: The readevents() method is fast to be sure, but the time involved in the interpretation of data from the external synthesizer should be taken into account. This time will of course depend on how many messages are being processed at any given read, the speed of the machine doing the processing, etc.

INITIALIZATION PARAMETERS

These parameters are common to the constructor and the init() method. A parameter supplied to the init() method will override the same parameter supplied to new().

General

'device' => $device

where $device is the number identifying which synth device to use. The default value is 0 (first available device).

Recording

'readbuf' => $buffer_size

where $buffer_size is the length in bytes of each read operation (if you are using MIDI::Music for recording), each event being read (at the underlying level) as an 8-byte string. The default "read buffer" size is 4096 (512 events per read, maximum).

'actsense' => $bool

Enable active sensing, if the device is to be opened for reading. Default is 1 (true). =pod

'realtime' => $bool

Enable incoming realtime messages, if the device is to be opened for reading. Setting this to 1 allows the delta-times of recorded events to be calculated from "timing-clock" messages sent from the external device (otherwise, the delta-time of any recorded event will be 0). Default is 0 (false).

'timing' => $bool

Enable incoming timing messages, if the device is to be opened for reading. The 'realtime' option must be set to true for this to be useful. Default is 1 (true). =pod

Patch caching

Specifying these parameters may speed playback on hardware synths (if, for example, a large number of pattern-change messages are being processed over a short period of time (?)), though they have not been tested.

'gminstr' => \@instruments

where \@instruments is a reference to an array containing the program numbers of instruments to cache on initialization. For example, passing 'gminstr' the value:

    [ 0, 19, 56 ]

will result in the piano, the church organ, and the trumpet voices (respectively) being cached.

'gmdrum' => \@drum_kits

where \@drum_kits is a reference to an array containing the numbers identifying drum kits to cache on initialization.

Timing

'tempo' => $bpm

Initial tempo, where $bpm is number of beats per minute. Default is 120.

'timebase' => $timebase

Initial number of ticks per quarter-note. Default is 96.

'timesig' => \@timesig

Initial time signature, where \@timesig is a reference to an array containing the time signature values:

    [ nn, dd, cc, bb ]

As described in the MIDI::Filespec documentation.

CONSTRUCTOR

new(%init_parameters)

Return a new MIDI::Music. See section "INITIALIZATION PARAMETERS" for a description of options.

METHODS

playmidifile($path)

Play MIDI file supplied in $path. You must have Sean M. Burke's MIDI-Perl modules installed in order to use this method.

If init() has not already been called, initialization will be handled automatically. The dumpbuf() method is also called automatically to clear the play-buffer at the end of the "song," so there is no need to bother with it.

Returns true on success, false on error (the last recorded error string being available through the errstr() method).

Note: certain initialization parameters supplied to the constructor, such as timebase, may be overridden should they be specified differently in the MIDI file header.

init(%init_param)

Opens and initializes the device according to parameters supplied. Returns true on success, false on failure (in which case the error can be obtained by calling the errstr() method).

In addition to the options described in the "INITIALIZATION PARAMETERS" section, init() accepts the 'mode' parameter, which value is an open-flag (O_RDWR, O_WRONLY, O_RDONLY, etc.). Default is O_RDWR.

Open-flag constants are provided by Fcntl.pm.

close()

Close the device. This is called automatically along with the destructor, but you may need it in order to (for example) initialize a different device (only one device/instance at a time is supported at present).

playevents(\@event_structure)

Play the events contained in the supplied reference to an event structure. You almost always should call dumpbuf() after this in order to flush any events remain in the play-buffer.

Returns true on success, false on error (the last recorded error string being available through the errstr() method).

The device must be initialized for writing before a call to playevents().

readevents()

Read some events and return them as a reference to an event structure. There will be a maximum of ($readbuf / 8) events contained in the structure, where $readbuf is value of the initialization parameters of the same name (default is 4096).

(In the author's experience, there are rarely more than two or three non-timing events per read, and usually there is only one.)

If the 'realtime' initialization parameter was set to 1, delta-times will be computed to reflect the number of timing clocks that have occured from the time the device was initialized (the running version of OSS must have realtime support for this to work). Otherwise, all delta-times will be zero.

The readevents() method returns undef if nothing but realtime messages are read.

The device must be initialized for reading before a call to readevents().

dumpbuf()

Dump any events remaining in the play-buffer.

errstr()

Return last recorded error message.

NOTES

  • To my knowledge, the "realtime" initialization option, allowing timing-clocks to determine the delta-times of recorded events, will work only with the commercial release of OSS. This is not an issue unless you are interested in doing realtime recording (e.g. recording events while playing your keyboard/sequencer as opposed to "recording" in some sort of stepwise fashion). The commercial version of OSS may be obtained from the 4Front Technologies website:

        http://www.opensound.com/

    It is worth the (resonable) price, and of course has the cross-platform advantage over ALSA.

  • MIDI::Music was developed and tested on an i686 running SuSE 6.4, with OSS version 3.9.6 installed. A mid-1990s Yamaha QY22 sequencer was used in testing the recording-related functions.

TO DO

  • Add methods for obtaining synthesizer information (number, types of available devices).

  • At present, MIDI::Music supports the interface with only one open device at a time. Future versions will allow for simultaneous instances of initialized devices, if possible.

  • At present, system-exclusive events (produced by bulk dumps, etc.) are not included in the event structures returned by readevents(). This should be fixed in the next release.

  • Streamline the playmidifile() code for greater memory-efficiency, if possible.

AUTHOR

Seth David Johnson, seth@pdamusic.com

SEE ALSO

The Open Sound System homepage (4Front Technologies):

    http://www.opensound.com/

The OSS Programming Guide (PDF), describing in some detail the /dev/music API on which MIDI::Music is based:

    http://www.opensound.com/pguide/oss.pdf

Sean M. Burke's MIDI-Perl extensions provide methods for dealing with MIDI files; you will need to have them installed if you wish to use the playmidifile() method. The documentation for MIDI::Events provides a description of the "event structures" common to MIDI::Music and MIDI-Perl.

Alex McLean's experimental MIDI::Realtime is an earlier attempt to provide a synthesizer interface to Perl. MIDI::Realtime takes an entirely different approach both in terms of interface and in terms of implementation, and may be better suited than MIDI::Music to specific purposes.

The aforementioned extensions can be obtained from the CPAN:

    http://www.cpan.org/

perl(1).