The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

MIDI::RtMidi::FFI::Device - OO interface for MIDI::RtMidi::FFI

VERSION

version 0.06

SYNOPSIS

    use MIDI::RtMidi::FFI::Device;
    
    # Create a new device instance
    my $device = RtMidiOut->new;
    
    # Open a "virtual port" - this is a virtual MIDI device which may be
    # connected to directly from external synths and software.
    # This is unsupported on Windows.
    $device->open_virtual_port( 'foo' );
    
    # An alternative to opening a virtual port is connecting to an available
    # MIDI device on your system, such as a loopback device, or virtual or
    # hardware synth. Your device must be connected to some sort of synth to
    # make noise.
    $device->open_port_by_name( qr/wavetable|loopmidi|timidity|fluid/i );
    
    # Now that a port is open we can start to send MIDI messages, such as
    # this annoying sequence
    while ( 1 ) {
        # Send Middle C (0x3C) to channel 0, strong velocity (0x7A)
        $device->note_on( 0x00, 0x3C, 0x7A );
        
        # Send a random control change value to Channel 0, CC 1
        $device->cc( 0x00, 0x01, int rand( 128 ) );
        
        sleep 1;
        
        # Stop playing Middle C on channel 0
        $device->note_off( 0x00, 0x40 );
        
        sleep 1;
    }

DESCRIPTION

MIDI::RtMidi::FFI::Device is an OO interface for MIDI::RtMidi::FFI to help you manage devices, ports and MIDI events.

METHODS

new

    my $device = MIDI::RtMidi::FFI::Device->new( %options );
    my $midiin = RtMidiIn->new( %options );
    my $midiout = RtMidiOut->new( %options );

Returns a new MIDI::RtMidi::FFI::Device object. RtMidiIn and RtMidiOut are provided as shorthand to instantiate devices of type 'in' and 'out' respectively. Valid attributes:

  • type - Device type : 'in' or 'out' (defaults to 'out')

    Ignored if instantiating RtMidiIn or RtMidiOut.

  • api - MIDI API to use. This should be a RtMidiApi constant. By default the device should use the first compiled API available. See search order notes in Using Simultaneous Multiple APIs on the RtMidi website.

  • api_name - MIDI API to use by name. One of 'alsa', 'jack', 'core', 'winmm' or 'dummy'.

  • name - Device name

  • queue_size_limit - (Type 'in' only) The buffer size to allocate for queueing incoming messages (defaults to 1024)

  • 14bit_mode - Sets 14-bit control change behaviour. One of 'midi', 'pair', 'await', 'backwards', 'doubleback' or 'bassack'. More information on these options can be found in "14-bit Control Change Modes"

  • rpn_14bit_mode - Sets 14-bit behaviour for RPN control change messages on CC6. One of 'midi', 'pair', 'await', 'backwards', 'doubleback' or 'bassack'. More information on these options can be found in "14-bit Control Change Modes".

    The value 'midi' is recommended.

    This is for type 'out' only - RPN decoding is not currently implemented.

  • nrpn_14bit_mode - Sets 14-bit behaviour for NRPN control change messages on CC6. One of 'midi', 'pair', 'await', 'backwards', 'doubleback' or 'bassack'. More information on these options can be found in "14-bit Control Change Modes"

    The value 'midi' is recommended.

    This is for type 'out' only - NRPN decoding is not currently implemented.

  • bufsize - (Type 'in' only) An alias for queue_size_limit.

  • ignore_sysex - (Type 'in' only) Ignore incoming SysEx messages (defaults to true)

  • ignore_timing - (Type 'in' only) Ignore incoming timing messages (defaults to true)

  • ignore_sensing - (Type 'in' only) Ignore incoming active sensing messages (defaults to true)

ok, msg, data, ptr

    warn $device->msg unless $device->ok;

Getters for RtMidiWrapper device struct members

open_virtual_port

    $device->open_virtual_port( $name );

Open a virtual device port. A virtual device may be connected to other MIDI software, just as with a hardware device. The name is an arbitrary name of your choosing, though it is perhaps safest if you stick to plain ASCII for this.

This method will not work on Windows. See "Virtual Devices and Windows" for details and possible workarounds.

open_port

    $device->open_port( $port, $name );

Open a (numeric) port on a device, with a name of your choosing.

See "open_port_by_name" for a potentially more flexible option.

get_ports_by_name

    $device->get_ports_by_name( $name );
    $device->get_ports_by_name( qr/name/ );

Returns a list of port numbers matching the supplied name criteria.

open_port_by_name

    $device->open_port_by_name( $name );
    $device->open_port_by_name( qr/name/ );
    $device->open_port_by_name( [ $name, $othername, qr/anothername/ ] );

Opens the first port found matching the supplied name criteria.

get_all_port_nums

    $device->get_all_port_nums();

Return a hashref of available devices the form { port number => port name }

get_all_port_names

    $device->get_all_port_names();

Return a hashref of available devices of the form { port name => port number }

close_port

    $device->close_port();

Closes the currently open port

get_port_count

    $device->get_port_count();

Return the number of available MIDI ports to connect to.

get_port_name

    $self->get_port_name( $port );

Returns the corresponding device name for the supplied port number.

get_current_api

    $device->get_current_api();

Returns the MIDI API in use for the device.

This is a RtMidiApi constant.

set_callback

    $device->set_callback( sub {
        my ( $ts, $msg ) = @_;
        # handle $msg here
    } );

Type 'in' only. Sets a callback to be executed when an incoming MIDI message is received. Your callback receives the time which has elapsed since the previous event in seconds, alongside the MIDI message.

NB As a callback may occur at any point in your program's flow, the program should probably not be doing much when it occurs. That is, programs handling RtMidi callbacks should be asleep or awaiting user input when the callback is triggered.

For the sake of compatibility with previous versions, some data may be passed which is passed to the callback for each event. This data parameter exists in the librtmidi interface to work around the lack of closures in C. It is less useful in Perl, though you are free to use it.

The data is not stored by librtmidi, so may be any Perl data structure you like.

    $device->set_callback( sub {
        my ( $ts, $msg, $data ) = @_;
        # handle $msg here
    }, $data );

See the examples included with this dist for some ideas on how to incorporate callbacks into your program.

set_callback_decoded

    $device->set_callback_decoded( sub {
        my ( $ts, $msg, $event ) = @_;
        # handle $msg / $event here
    } );

Same as "set_callback", though also attempts to decode the message, and pass that to the callback as an array ref. The original message is also sent in case this fails.

cancel_callback

    $device->cancel_callback();

Type 'in' only. Removes the callback from your device.

ignore_types

    $device->ignore_types( $ignore_sysex, $ignore_timing, $ignore_sensing );
    $device->ignore_types( (1)x3 );

Type 'in' only. Set message types to ignore.

ignore_sysex

    $device->ignore_sysex( 1 );
    $device->ignore_sysex( 0 );

Type 'in' only. Set whether or not to ignore sysex messages.

ignore_timing

    $device->ignore_timing( 1 );
    $device->ignore_timing( 0 );

Type 'in' only. Set whether or not to ignore timing messages.

ignore_sensing

    $device->ignore_sensing( 1 );
    $device->ignore_sensing( 0 );

Type 'in' only. Set whether or not to ignore active sensing messages.

get_message

    $device->get_message();

Type 'in' only. Gets the next message from the queue, if available.

get_event

    $device->get_message_decoded();

Type 'in' only. Gets the next message from the queue, if available, decoded as an event. See "decode_message" for what to expect from incoming events.

get_event

Alias for "get_message_decoded", for backwards compatibility.

NB Previous versions of this call spliced out the channel portion of the message. This is no longer the case.

decode_message

    $device->decode_message( $msg );

Decodes the passed MIDI byte string. Some messages, such as clock control, may be decoded by this module. The most common mesage types are passed through to Midi::Event.

The most common message types are:

('note_off', channel, note, velocity)
('note_on', channel, note, velocity)
('key_after_touch', channel, note, velocity)
('control_change', channel, controller(0-127), value(0-127))
('patch_change', channel, patch)
('channel_after_touch', channel, velocity)
('pitch_wheel_change', channel, pitch_wheel)
('sysex_f0', raw)
('sysex_f7', raw)

Additional message types handled by this module are:

('timecode', rate, hour, minute, second, frame )
('clock')
('start')
('continue')
('stop')
('active_sensing')
('system_reset')

See Midi::Event documentation for details on other events handled by that module, though keep in mind that a realtime message will not have the dtime parameter.

send_message

    $device->send_message( $msg );

Type 'out' only. Sends a message on the device's open port.

encode_message

    my $msg = $device->encode_message( note_on => 0x00, 0x40, 0x5a )
    $device->send_message( $msg );

Attempts to encode the passed message with MIDI::Event or message handling within this module. See "decode_message" for some common supported message types.

The event name 'sysex' is an alias for 'sysex_f0'.

send_message_encoded

    $device->send_message_encoded( @event );
    # Event, channel, note, velocity
    $device->send_message_encoded( note_on => 0x00, 0x40, 0x5A );
    $device->send_message_encoded( control_change => 0x01, 0x1F, 0x3F7F );
    $device->send_message_encoded( sysex => "Hello, computer?" );

Type 'out' only. Sends a MIDI::Event encoded message to the open port.

send_message_encoded_cb

    # Within callback ...
    $device->send_message_encoded_cb( @event );

Type 'out' only. A variant of send_message_encoded for use within user-defined callbacks handling 14 bit CC, as callbacks are invoked by send_message_encoded.

send_event

Alias for "send_message_encoded", for backwards compatibility.

NB Previous versions of this module erroneously stripped channel data from messages. This is no longer the case - channel should be provided where necessary.

panic

    $device->panic( $channel );
    $device->panic( 0x00 );

Send an "All MIDI notes off" (CC 123) message to the specified channel. If no channel is specified, the message is sent to all channels.

PANIC

    $device->PANIC( $channel );
    $device->PANIC( 0x00 );

Send 'note_off' to all 128 notes on the specified channel. If no channel is specified, the message is sent to all channels.

Warning: This method has the potential to flood buffers! It should be a recourse of last resort - consider "panic", it'll probably work.

get_14bit_mode

    $device->get_14bit_mode;

Get the currently in-use 14 bit mode. See "14-bit Control Change Modes".

set_14bit_mode

    $device->set_14bit_mode( 'await' );
    $device->set_14bit_mode( $callback );
    $device->set_14bit_mode( $callback, 'no purge' );

Sets the 14 bit mode. See "14-bit Control Change Modes". Pass a true value to the second parameter to skip purging the last-modified cache of control change values.

This method is intended to help find the most compatible 14 bit CC encoding or decoding mode - it probably shouldn't be used mid performance or playback unless you seek odd side effects.

If a RPN or NRPN is active, this 14 bit mode will not have an effect on CC6. See </set_set_rpn_14bit_mode> and </set_nrpn_14bit_mode>

disable_14bit_mode

    $device->disable_14bit_mode;
    $device->disable_14bit_mode( 'no purge' );

Disables 14 bit mode. See "14-bit Control Change Modes".

resolve_cc_encoder

    $device->resolve_cc_encoder( 'await' );

Return the callback associated with the given 14 bit CC encoder method.

resolve_cc_decoder

    $device->resolve_cc_decoder( 'midi' );

Return the callback associated with the given 14 bit CC decoder method.

get_timestamp

    $device->get_timestamp;

Returns the time since the first MIDI message was processed

set_last_event

    $device->set_last_event( control_change => 2, 6, 127 );

Set the last event explicitly. This event should represent a single 7-bit MIDI message, not a composite value such as 14 bit CC.

set_last

An alias for set_last_event

get_last_event

    my $last_event = $device->get_last_event( control_change => $channel, $cc );
    # ... Do something with $last_event->{ val } and $last_event->{ ts }

Returns a hashref containing details on the last event matching the specified parameters, if it exists. Hashref keys include the value (val) and timestamp (ts).

get_last

An alias for get_last_event

purge_last_events

    $device->purge_last_events( 'control_change' );

Delete all cached events for the event type.

open_rpn

    $device->open_rpn( $channel, $msb, $lsb );
    $device->open_rpn( 1, 0, 1 );

Open a Registered Parameter Number (RPN) for setting with later control change messages for CC6. This method will also close any open RPN or NRPN.

open_nrpn

    $device->open_rpn( $channel, $msb, $lsb );
    $device->open_rpn( 1, 0, 1 );

Open a Non-Registered Parameter Number (NRPN) for setting with later control change messages for CC6. This method will also close any open RPN or NRPN.

close_rpn

    $device->close_rpn( $channel );

Close any open RPN on the given channel.

close_nrpn

    $device->close_rpn( $channel );

Close any open NRPN on the given channel.

get_rpn

    $device->get_nrpn( $channel );

Get the currently open RPN for the given channel.

get_nrpn

    $device->get_rpn( $channel );

Get the currently open RPN for the given channel.

send_rpn

    $device->send_rpn( $channel, $msb, $lsb, $value );

Send a single value for the given RPN. This method is suitable for individual settings accessed via RPN. It will open the RPN, send the passed value to CC6 on the passed channel, then close the RPN.

A 14 bit value is expected if rpn_14bit_mode is set.

rpn

    $device->rpn( $channel, $msb, $lsb, $value );

An alias for "send_rpn".

send_nrpn

    $device->send_nrpn( $channel, $msb, $lsb, $value );

Send a single value for the given NRPN. This method is suitable for single setting values accessed via NRPN. It will open the NRPN, send the passed value to CC6 on the passed channel, then close the NRPN.

A 14 bit value is expected if nrpn_14bit_mode is set.

If sending modulation to a NRPN, calling "open_nrpn" and sending a stream of control change messages separately is recommended:

    $device->open_nrpn( $channel, 1, 1 );
    $device->cc( $channel, 6, $value )
    # ...more cc() calls here
    $device->close_nrpn( $channel );

nrpn

    $device->nrpn( $channel, $msb, $lsb, $value );

An alias for "send_nrpn".

get_rpn_14bit_mode

    $self->get_rpn_14bit_mode;

Get the currently in-use RPN 14 bit mode.

get_nrpn_14bit_mode

    $self->get_nrpn_14bit_mode;

Get the currently in-use NRPN 14 bit mode.

set_rpn_14bit_mode

    $device->set_rpn_14bit_mode( 'await' );
    $device->set_rpn_14bit_mode( $callback );
    $device->set_rpn_14bit_mode( $callback, 'no purge' );

Sets the RPN 14 bit mode. See "14-bit Control Change Modes", similar to "set_14bit_mode".

set_nrpn_14bit_mode

    $device->set_nrpn_14bit_mode( 'await' );
    $device->set_nrpn_14bit_mode( $callback );
    $device->set_nrpn_14bit_mode( $callback, 'no purge' );

Sets the NRPN 14 bit mode. See "14-bit Control Change Modes", similar to "set_14bit_mode".

disable_rpn_14bit_mode

    $device->disable_rpn_14bit_mode;
    $device->disable_rpn_14bit_mode( 'no purge' );

Disables the RPN 14 bit mode. See "14-bit Control Change Modes".

disable_nrpn_14bit_mode

    $device->disable_nrpn_14bit_mode;
    $device->disable_nrpn_14bit_mode( 'no purge' );

Disables the NRPN 14 bit mode. See "14-bit Control Change Modes".

note_off, note_on, control_change, patch_change, key_after_touch, channel_after_touch, pitch_wheel_change, sysex_f0, sysex_f7, sysex

Wrapper methods for "send_message_encoded", e.g.

    $device->note_on( 0x00, 0x40, 0x5a );

is equivalent to:

    $device->send_message_encoded( note_on => 0x00, 0x40, 0x5a );

cc

An alias for control_change.

14 bit Control Change Modes

14 bit Control Change messages are achieved by sending a pair of 7-bit messages. Only CCs 0-31 can send / receive 14-bit messages. The most significant byte (or MSB or coarse control) is sent on the desired CC. The least significant byte (or LSB or fine control) is sent on that CC + 32. 14 bit allows for a control value between 0 and 16,383.

For example, to manually set CC 6 on channel 0 to the value 1,337 you would do something like:

    my $value = 1_337;
    my $msb = $value >> 7 & 0x7F;
    my $lsb = $value & 0x7F;
    $sevice->cc( 0, 6, $msb );
    $sevice->cc( 0, 38, $lsb );

If receving 14 bit Control Change, you would need to cache the MSB value for the geven CC and channel, then combine it later with the matching LSB, something like:

    $device->set_callback_decoded( sub {
        my ( $ts, $msg, $event ) = @_;
        state $last_msb;

        if ( $event->[0] eq 'control_change' ) {
            my $cc_value;
            my ( $channel, $cc, $value ) = @{ $event }[ 1..3 ];
            if ( $channel < 32 ) {
                # Cache MSB
                $last_msb->[ $channel ]->[ $cc ] = $value;
            }
            elsif ( $channel < 64 ) {
                my $msb = $last_msb->[ $channel ]->[ $cc ];
                $cc_value = $msb << 7 | $value & 0x7F;
            }
            else {
                $cc_value = $value;
            }
            if ( defined $cc_value ) {
                # ... do something with $cc_value here
            }
        }
        # ... process other events here
    } );

Some problems emerge with this approach. The first is MIDI standards - deficiencies in, and deviation from.

For example, the MIDI 1.0 Detailed Specification states:

"If both the MSB and LSB are sent initially, a subsequent fine adjustment only requires the sending of the LSB. The MSB does not have to be retransmitted. If a subsequent major adjustment is necessary the MSB must be transmitted again. When an MSB is received, the receiver should set its concept of the LSB to zero."

Let's break this down. "If 128 steps of resolution is sufficient the second byte (LSB) of the data requires the sending of the LSB. The MSB does not have to be retransmitted.". The decoding callback above should cater for this, as the cached MSB will persist for multiple LSB transmissions. So far, so OK.

"If a subsequent major adjustment is necessary the MSB must be transmitted again." - again, this is fine - it fits in with expectations so far.

"When an MSB is received, the receiver should set its concept of the LSB to zero". This, to me, is ambiguous. Should our CC now be set to ( $msb << 7 ) + 0? Or is it an instruction to forget any existing LSB value and await the transmission of a fresh one before constructing a CC value?

With the former approach you could imagine a descending control passing a MSB threshold, then jumping to a value aligned with the floor value of the new lower MSB, before jumping back up when the next LSB is received. The latter approach seems to make more sense to me as it would avoid such jumps.

Some implementations skip transmission of the MSB where it would be zero. That is for values < 128, no MSB is sent. If the controller starts at zero, no MSB value would be cached. If the cached MSB happens to be invalid when small values are sent (that is, the device *never* sends MSB for values < 128), then we must resort to heuristic detection for crossing of this MSB threshold (a large jump in LSB).

Some implementations send LSB first, MSB second. If a LSB/MSB pair is sent each time, this is easily handled. If a pair is sent, then fine control is sent subsequently via LSB we have a problem. When we cross a MSB threshold, we need to wait for the new MSB value before we can construct the complete CC value. This means we need to somehow know when to stop performing fine control with new LSB values, and await a new MSB value - we are back to heuristic detection, looking for LSB jumps.

All to say, there are some ambiguities in how this is handled, and there are endless variations between different devices and implementations.

The second problem is needing to write explicit 14 bit message handling in each project individually. This module intends to obviate some of this by providing 14 bit message handling out of the box, with a number of compatibility options. Currently, these options are mostly derived from reading manuals and forum posts - testing and feedback appreciated!

For Output (Sending)

When sending 14 bit CC, multiple messages must potentially be constructed, then sent individually. A number of options on handling this are built into this module.

midi (recommended)

This implements the MIDI 1.0 specification. MSB values are only sent where they have changed. LSB values are always sent. Messages are in MSB/LSB order.

await

Equivalent to 'midi' when sending messages.

pair

Always sends a complete pair of messages for each controller change, in MSB/LSB order.

backwards

Sends a complete pair of messages for each controller change, in LSB/MSB order.

backwait

Sends messages in LSB/MSB order. MSB values are only sent when they have changed.

doubleback

"Double backwards" mode. Sends a complete pair of messages for each controller change, in MSB/LSB order, with the MSB value on the high controller number.

bassack

"Bass-ackwards" mode. Sends a complete pair of messages for each controller change, in LSB/MSB order, with the MSB value on the high controller number.

Callback

You may also provide your own callback to send 14 bit Control Change. This callback will receive the following parameters:

  • device - This instance of the device.

  • channel - The channel to send the message on, 0-15.

  • controller - The receiving controller, 0-31.

  • value - A 14 bit CC value, 0-16383.

To take a simple example, imagine we wanted a callback which implemented the MIDI standard:

    sub callback {
        my ( $device, $channel, $controller, $value ) = @_;
        my $msb = $value >> 7 & 0x7F;
        my $lsb = $value & 0x7F;
        my $last_msb = $device->get_last( control_change => $channel, $controller );
        if ( !defined $last_msb || $last_msb->{ val } != $msb ) {
            $device->send_message_encoded_cb( control_change => $channel, $controller, $msb )
        }
        $device->send_message_encoded_cb( control_change => $channel, $controller | 0x20, $lsb );
    }
    
    my $out = RtMidiOut->new( 14bit_callback => \&callback );
    
    # The sending of this message will be handled by your callback.
    $out->cc( 0x00, 0x06, 0x1337 );

Callbacks should not call the send_message_encoded, send_event, control_change or cc methods as these may invoke further 14 bit message handling, potentially causing an infinite loop. The </send_message_encoded_cb> method exists for sending messages within 14 bit CC callbacks.

For Input (Decoding)

When decoding 14 bit Control Change messages involves coalescing a pair of 7 bit messages which may not appear in a strict order. One value must be cached and combined with one or more values which arrive later.

The following decode modes are built in:

midi

This implements the strictest interpretation of the MIDI 1.0 specification. LSB messages are combined with the last sent MSB. If no MSB has yet been received, the value will be < 128. When a new MSB is received, LSB is reset to zero and a new value is returned.

await (recommended)

This is the same as 'midi' mode, but it always awaits a LSB message before returning a value.

This is likely the most compatible and reliable mode for decoding.

pair

Expects a pair of values in MSB/LSB order. This is equivalent to the 'await' mode, as that can adequately decode messages sent using this approach.

backwards

Expects a pair of values in LSB/MSB order. New values are only returned on receipt of the MSB.

backwait

Expects an initial pair in LSB/MSB order, with additional fine control sent as additional LSB messages. This uses a heuristic to guess when to wait for new MSB values.

doubleback

"Double backwards" mode. Expects messages in MSB/LSB ordered pairs with MSB on the high controller number. New values are returned on incoming LSB messages.

bassack

"Bass-ackwards" mode. Expects messages in LSB/MSB ordered pairs with MSB on the high controller number. New values are returned on incoming MSB messages.

Callback

You may also provide your own callback to send 14 bit Control Change. This callback will receive the following parameters:

  • device - This instance of the device.

  • channel - The channel the message was sent on, 0-15.

  • controller - The receiving controller, 0-63.

  • value - A 7 bit CC value, 0-127.

Imagine we have a device which is MIDI 1.0 compatible, but does not send a new MSB value of zero for values < 128. We need to somehow detect a large swings in LSB, then assume the MSB has been set to zero. For extra credit, let's only do this only when the controller has tended towards the low end of the scale.

Wrapping a built-in decoder is possible with the "resolve_cc_decoder" method.

    my $callback = sub {
        my ( $device, $channel, $controller, $value ) = @_;
        my $method = $device->resolve_cc_decoder( 'await' );
        
        # Pass MSB through;
        return $device->$method( $channel, $controller, $value ) if $controller < 32;
        
        my $last_msb = $device->get_last( control_change => $channel, $controller - 32 );
        # If we start low, we never get a MSB
        my $last_msb_value = $last_msb->{ val } // 0;
        
        # Pass LSB through if we are not at the low end of the dial
        return $device->$method( $channel, $controller, $value ) if $last_msb_value > 3; # magic number
        
        # Explicitly set a MSB of 0 if there has been a large jump in LSB
        my $last_lsb = $device->get_last( control_change => $channel, $controller );
        my $diff = abs( $last_lsb->{ val } - $value );
        $device->set_last( control_change => $channel, $controller - 32, 0 ) if $diff > 100;
        
        # Finally, process the value
        $device->$method( $channel, $controller, $value );
    };
    
    my $in = RtMidiIn->new( 14bit_callback => $callback );
    $in->set_callback_decoded ( sub {
        my ( $ts, $msg, $event ) = @_;
        # For 14 bit CC, $event will contain a message decoded by your callback
    } );

One issue with the above implementation is that the heuristic magic numbers are untuned - they would require some real world testing and tuning, and may even vary depending on play styles or input source. Another issue is that this scenario is (I think) likely rare and probably does not need specific handling.

Some MIDI Terms

There are terms specific to MIDI which are somewhat overloaded by this interface. I will try to disambiguate them here.

Device

A MIDI device, virtual or physical, is required to mediate MIDI messages. That is, you may not simply connect RtMidi to another piece of software without a virtual or loopback device in between, e.g. via the "open_virtual_port" method. RtMidi may talk to connected physical devices directly, without the use of a virtual device. The same is true of any software-defined virtual or loopback devices external to your software, RtMidi may connect directly to these.

"Virtual device" and "virtual port" are effectively interchangeable terms when using RtMidi - each MIDI::RtMidi::FFI::Device may represent a single input or output port, so any virtual device instantiated has a single virtual port.

See "Virtual Devices and Windows" for caveats and workarounds for virtual device support on that platform.

Port

Every MIDI device has at least one port for Input and/or Output. In hardware, connections between ports are usually 1:1. Some software implementations allow for multiple connections to a port.

There is a special Output port usually called "MIDI Thru" or "MIDI Through" which mirrors every message sent to a given Input port.

Channel

Each port has 16 channels, numbered 0-15, which are used to route messages to specifically configured instruments, modules or effects.

Channel must be specified in any message related to performance, such as "note on" or "control change".

Messages and Events

A MIDI message is a (usually) short series of bytes sent on a port, instructing an instrument on how to behave - which notes to play, when, how loudly, with which timbral variations & expression, and so on. They may also contain configuration info or some other sort of instruction.

In this module "events" usually refer to incoming message bytes decoded into a descriptive sequence of values, or a mechanism for turning these descriptive sequences into message bytes for ouput.

General MIDI and Soundfonts

General MIDI is a specification which standardises a single set of musical instruments, accessed via the "patch change" command. Any of 128 instruments may be assigned to any of 16 channels, with the exception of channel 10 (0x09) which is reserved for percussion.

Soundfonts are banks of sampled instruments which may be loaded by a General MIDI compatible softsynth. These can be quite large and complex, though they usually tend to be small and cheesy. If you remember 90s video game music or web pages playing .mid files, you're on the right track.

Some implementations also support DLS files, which are similar to soundfonts, though unlike soundfonts the specification is freely available.

Virtual Devices and Windows

Windows currently (as of June 2024) lacks built-in support for on-the-fly creation of virtual MIDI devices. While Windows MIDI Services will offer dynamic virtual loopback, alongside MIDI 2.0 support, it is a work in progress.

This situation has resulted in some confusion for MIDI users on Windows, and a number of solutions exist to work around the issue.

Loopback Devices

Virtual loopback drivers allow for the creation of external ports which may be connected to by each participant in the MIDI conversation.

Rather than create a virtual port, you connect your Perl code to a virtual loopback device, and connect your DAW or synth to the other side of the loopback device.

The best currently working virtual loopback drivers based on my research are:

loopMIDI by Tobias Erichsen

Sbvmidi by Springbeats

LoopBe by nerds.de

In my own experience loopMIDI is the simplest and most flexible option, allowing for arbitrary numbers of devices, with arbitrary names.

You should review the licensing terms of any software you choose to incorporate into your projects to ensure it is appropriate for your use case. Each of the above is free for personal, non-commercial use.

General MIDI

A General MIDI synth called "Microsoft GS Wavetable Synth" should be available for direct connection on Windows. While the sounds are basic, it can act as a useful device for testing. This should play a middle-C note on the default piano instrument:

    use MIDI::RtMidi::FFI::Device;
    my $device = RtMidiOut->new;
    $device->open_port_by_name( qr/gs\ wavetable/i );
    $device->note_on( 0x00, 0xc3, 0x7f );
    sleep( 1 );
    $device->note_off( 0x00, 0xc3 );

General MIDI on Linux

The days of consumer sound cards containing their own wavetable banks are behind us. These days, General MIDI is usually supported in software.

A commonly available General MIDI soft-synth is TiMidity++ - a version is likely packaged for your distro. This package may or may not install a timidity service (it may be packaged separately as timidity-daemon). If not, you can quickly make a timidity port available by running:

    $ timidity -iAD

You may also need to install and configure a soundfont for TiMidity++.

Another option is FluidSynth, which should also be packaged for any given distro. To run FluidSynth you'll need a SF2 or SF3 soundfont file. See Getting started with fluidsynth and Example Command Lines to start fluidsynth. FluidR3_GM.sf2 Professional is a high quality sound font with a complete set of General MIDI instruments.

A typical FluidSynth invocation on Linux might be:

    $ fluidsynth -a pulseaudio -m alsa_seq -g 1.0 your_soundfont.sf2

General MIDI on MacOS

An Audio Unit named DLSMusicDevice is available for use within GarageBand, Logic, and other Digital Audio Workstation (DAW) software on MacOS.

If you wish to use banks other than the default QuickTime set, place them in ~/Library/Audio/Sounds/Banks/. You may now create a new track within GarageBand or Logic with the DLSMusicDevice instrument, and select your Sound Bank within the settings for this instrument.

The next step is to open a virtual port, which should autoconnect within your DAW and be ready to send performance info to DLSMusicDevice:

    # Open virtual port with a name of your choosing
    $device->open_virtual_port('My Snazzy Port');
    # Send middle C
    $device->note_on( 0x00, 0xc3, 0x7f );
    sleep( 1 );
    $device->note_off( 0x00, 0xc3 );

The 'MUS 214: MIDI Composition' channel on YouTube has a Video on setting up DLSMusicDevice in Logic.

A potential alternative option is FluidSynth. This has more limited support for DLS banks but should load SF2/3 banks just fine. See "General MIDI on Linux" for links to get started using FluidSynth. A typical FluidSynth invocation on MacOS might be:

    % fluidsynth -a coreaudio -m coremidi your_soundfont.sf2

KNOWN ISSUES

Use of MIDI::Event is a bit of a hack for convenience, exploiting the similarity of realtime MIDI messages and MIDI song file messages. It may break in unexpected ways if used for large SysEx messages or other "non-music" events, though should be fine for encoding and decoding note, pitch, aftertouch and CC messages.

Test coverage, both automated testing and hands-on testing, is limited. Some elements of this module (especially around 14 bit CC and (N)RPN) are based on reading, and probably often misreading, MIDI specifications, device documentation and forum posts. Issues in the GitHub repo are more than welcome, even if just to ask questions. You may also find me in #perl-music on irc.perl.org - look for fuzzix.

This software has been fairly well exercised on Linux and Windows, but not so much on MacOS / CoreMIDI. I am interested in feedback on successes and failures on this platform.

NRPN and 14 bit CC have not been tested on real hardware, though they work well in the "virtual" domain - for controlling software-defined instruments.

Currently open MIDI::RtMidi::FFI issues on GitHub

SEE ALSO

RtMidi

MIDI CC & NRPN database

Phil Rees Music Tech page on NRPN/RPN

MIDI::RtMidi::FFI

MIDI::Event

CONTRIBUTING

https://github.com/jbarrett/MIDI-RtMidi-FFI

All comments and contributions welcome.

BUGS AND SUPPORT

Please direct all requests to https://github.com/jbarrett/MIDI-RtMidi-FFI/issues

CONTRIBUTORS

  • Gene Boggs <gene@cpan.org>

AUTHOR

John Barrett <john@jbrt.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2024 by John Barrett.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.