RtMidi

RtMidi enables ffgac, ffedit, and fflive to use MIDI controllers as input devices.

The global constructors RtMidiIn() and RtMidiOut() are built-in to the quickjs engine. This is a simple wrapper around RtMidi functionality.

This page is mostly based on RtMidi documentation itself.

NOTE: Currently, you need to explicitly poll for the events. I plan on moving this to using async functions in the future.

NOTE2: There is no support for RtMidi’s setCallback(), in case you are reading RtMidi’s documentation and wondering why it’s not here on this page.


RtMidiIn Constructor

The new RtMidiIn() constructor is used to create a new RtMidiIn object.

Syntax

new RtMidiIn()

Return value

The new RtMidiIn object.

Examples

const midiin = new RtMidiIn();

RtMidiIn.prototype.getVersion()

A static function to determine the current RtMidi version.

Syntax

getVersion()

Return value

A string with the current RtMidi version.

Examples

const midiin = new RtMidiIn();
print(midiin.getVersion());

RtMidiIn.prototype.getCompiledApi()

A static function to determine the available compiled MIDI APIs.

Syntax

getCompiledApi()

Return value

An Array with the IDs of the compiled MIDI APIs.

Examples

const midiin = new RtMidiIn();
const apis = midiin.getCompiledApi();
print(apis);

RtMidiIn.prototype.getApiName()

Return the name of a specified compiled MIDI API.

Syntax

getApiName(api)

Parameters

api is the specified MIDI API.

Return value

A string with the name of the specified MIDI API.

Examples

const midiin = new RtMidiIn();
const apis = midiin.getCompiledApi();
const first_api_name = midiin.getApiName(apis[0]);
print(first_api_name);

RtMidiIn.prototype.getApiDisplayName()

Return the display name of a specified compiled MIDI API.

Syntax

getApiDisplayName(api)

Parameters

api is the specified MIDI API.

Return value

A string with the display name of the specified MIDI API.

Examples

const midiin = new RtMidiIn();
const apis = midiin.getCompiledApi();
const first_api_display_name = midiin.getApiDisplayName(apis[0]);
print(first_api_display_name);

RtMidiIn.prototype.getCompiledApiByName()

Return the compiled MIDI API having the given name.

Syntax

getCompiledApiByName(name)

Parameters

name is the specified MIDI API name.

Return value

A number with the specified MIDI API.

Examples

const midiin = new RtMidiIn();
const apis = midiin.getCompiledApi();
const first_api_name = midiin.getApiName(apis[0]);
const first_api = midiin.getCompiledApiByName(first_api_name);
// first_api should be the same value as apis[0]

RtMidiIn.prototype.setClientName()

This function sets the client name (I don’t really know what this means).

Syntax

setClientName(name)

Parameters

name is the name for the client.

Examples

const midiin = new RtMidiIn();
midiin.setClientName("client");

RtMidiIn.prototype.setPortName()

This function sets the port name (I don’t really know what this means).

Syntax

setPortName(name)

Parameters

name is the name for the port.

Examples

const midiin = new RtMidiIn();
midiin.setPortName("port");

RtMidiIn.prototype.getCurrentApi()

Returns the MIDI API specifier for the current instance of RtMidiIn.

Syntax

getCurrentApi()

Return value

A number with the current MIDI API specifier for the current instance of RtMidiIn.

Examples

const midiin = new RtMidiIn();
const current_api = midiin.getCurrentApi();
print(current_api);

RtMidiIn.prototype.getPortCount()

Return the number of available MIDI input ports.

Syntax

getPortCount()

Return value

A number with the number of available MIDI input ports.

Examples

const midiin = new RtMidiIn();
const port_count = midiin.getPortCount();
print(`port_count ${port_count}`);

RtMidiIn.prototype.getPortName()

Return a string identifier for the specified MIDI input port number.

Syntax

getPortName(portNumber)

Parameters

portNumber is an optional port number greater than 0 can be specified.

Return value

A string identifier for the specified MIDI input port number.

Examples

const midiin = new RtMidiIn();
const port_count = midiin.getPortCount();
print(`port_count ${port_count}`);
for ( let i = 0; i < port_count; i++ )
{
  const port_name = midiin.getPortName(i);
  print(`[${i}] ${port_name}`);
}

RtMidiIn.prototype.openPort()

Open a MIDI input connection given by enumeration number.

Syntax

openPort(portNumber, portName)

Parameters

  • portNumber is an optional port number greater than 0 can be specified. Otherwise, the default or first port found is opened.
  • portName is an optional name for the application port that is used to connect to portId can be specified.

Return value

true on success, null otherwise.

Examples

const midiin = new RtMidiIn();
midiin.openPort();

RtMidiIn.prototype.openVirtualPort()

Create a virtual input port, with optional name, to allow software connections.

Syntax

openVirtualPort(portName)

Parameters

portName is an optional name for the application port that is used to connect to portId can be specified.

Return value

true on success, null otherwise.

Examples

const midiin = new RtMidiIn();
midiin.openVirtualPort();

RtMidiIn.prototype.closePort()

Close an open MIDI connection (if one exists).

Syntax

closePort()

Examples

const midiin = new RtMidiIn();
midiin.openPort();
midiin.closePort();

RtMidiIn.prototype.isPortOpen()

Returns true if a port is open and false if not.

Syntax

isPortOpen()

Return value

A boolean with true if a port is open and false if not.

Examples

const midiin = new RtMidiIn();
midiin.openPort();
const is_port_open = midiin.isPortOpen();
print(`is_port_open ${is_port_open}`);
midiin.closePort();

RtMidiIn.prototype.ignoreTypes()

Specify whether certain MIDI message types should be queued or ignored during input.

Syntax

ignoreTypes(midiSysex, midiTime, midiSense)

Parameters

  • midiSysex
  • midiTime
  • midiSense

Examples

const midiin = new RtMidiIn();
midiin.openPort();
midiin.ignoreTypes(false, false, false);

RtMidiIn.prototype.getMessage()

You should call this function repeatedly and process the resulting message until the function returns an empty Array (msg.length === 0).

NOTE: The MIDI message format varies from one device to another, and is outside the scope of this documentation. You can find more information on google, or pages like this one.

Syntax

const midiin = new RtMidiIn();
midiin.openPort();
midiin.ignoreTypes(false, false, false);
while ( true )
{
  const msg = midiin.getMessage();
  if ( msg.length === 0 )
    break;
  print(JSON.stringify(msg));
}
midiin.closePort();

Return value

An Array with the bytes from one message received from the MIDI device.

Examples

const midiin = new RtMidiIn();

RtMidiOut Constructor

The new RtMidiOut() constructor is used to create a new RtMidiOut object.

NOTE: RtMidiOut is used to send messages to MIDI devices. If you want to receive and send messages from a device, you will have to open both an RtMidiIn and an RtMidiOut.

Syntax

new RtMidiOut()

Return value

The new RtMidiOut object.

Examples

const rtmidiout = new RtMidiOut();

RtMidiOut.prototype.getCurrentApi()

Returns the MIDI API specifier for the current instance of RtMidiOut.

Syntax

getCurrentApi()

Return value

A number with the current MIDI API specifier for the current instance of RtMidiOut.

Examples

const midiout = new RtMidiOut();
const current_api = midiout.getCurrentApi();
print(current_api);

RtMidiOut.prototype.getPortCount()

Return the number of available MIDI output ports.

Syntax

getPortCount()

Return value

A number with the number of available MIDI output ports.

Examples

const midiout = new RtMidiOut();
const port_count = midiout.getPortCount();
print(`port_count ${port_count}`);

RtMidiOut.prototype.getPortName()

Return a string identifier for the specified MIDI output port number.

Syntax

getPortName(portNumber)

Parameters

portNumber is an optional port number greater than 0 can be specified.

Return value

A string identifier for the specified MIDI output port number.

Examples

const midiout = new RtMidiOut();
const port_count = midiout.getPortCount();
print(`port_count ${port_count}`);
for ( let i = 0; i < port_count; i++ )
{
  const port_name = midiout.getPortName(i);
  print(`[${i}] ${port_name}`);
}

RtMidiOut.prototype.openPort()

Open a MIDI output connection given by enumeration number.

Syntax

openPort(portNumber, portName)

Parameters

  • portNumber is an optional port number greater than 0 can be specified. Otherwise, the default or first port found is opened.
  • portName is an optional name for the application port that is used to connect to portId can be specified.

Return value

true on success, null otherwise.

Examples

const midiout = new RtMidiOut();
midiout.openPort();

RtMidiOut.prototype.openVirtualPort()

Create a virtual output port, with optional name, to allow software connections.

Syntax

openVirtualPort(portName)

Parameters

portName is an optional name for the application port that is used to connect to portId can be specified.

Return value

true on success, null otherwise.

Examples

const midiout = new RtMidiOut();
midiout.openVirtualPort();

RtMidiOut.prototype.closePort()

Close an open MIDI connection (if one exists).

Syntax

closePort()

Examples

const midiout = new RtMidiOut();
midiout.openPort();
midiout.closePort();

RtMidiOut.prototype.isPortOpen()

Returns true if a port is open and false if not.

Syntax

isPortOpen()

Return value

A boolean with true if a port is open and false if not.

Examples

const midiout = new RtMidiOut();
midiout.openPort();
const is_port_open = midiout.isPortOpen();
print(`is_port_open ${is_port_open}`);
midiout.closePort();

RtMidiOut.prototype.sendMessage()

Immediately send a single message out an open MIDI output port.

Syntax

sendMessage()

Return value

true on success, null otherwise.

Examples

const midiout = new RtMidiOut();
midiout.openPort();
midiout.sendMessage([ 0xF0, 0x00, 0xF7 ]);
midiout.closePort();

Full Example

This example will open the MIDI device specified by the script parameter to pixelsort the image.

The pixelsorting threshold will be set with the MIDI controller using the first 4 faders in pairs. The low threshold is fader[1] - fader[0] and the high threshold is fader[3] - fader[2].

NOTE: Most MIDI controllers only send messages when a control is changed. Therefore you will probably get no visible change in the script below before you start playing around with the faders.

let midiin = null;
let midi_threshold_low  = [ 0, 0 ];
let midi_threshold_high = [ 0, 0 ];

// options
const opt_pix_fmt    = "gbrp";          // supported pixel formats are "gbrp" and "yuv444p"
const opt_colorspace = "hsv";           // yuv444p: "yuv";
                                        // gbrp:    "rgb", "hsv", "hsl"
const opt_range_y    = [ 0, 1 ];        // [ [0 .. 1], [0 .. 1] ]
const opt_range_x    = [ 0, 1 ];        // [ [0 .. 1], [0 .. 1] ]
const opt_threshold  = [ 0.25, 0.75 ];  // [ [0 .. 1], [0 .. 1] ]
const opt_order      = "vertical";      // "vertical" or "horizontal"
const opt_reverse    = false;           // true or false
const opt_trigger_by = 2;               // 0, 1, or 2
const opt_sort_by    = 2;               // 0, 1, or 2

/*********************************************************************/
/* scales value from 'from' range to 'to' range */
function scaleValue(value, from_min, from_max, to_min, to_max)
{
  return (value - from_min) * (to_max - to_min) / (from_max - from_min) + to_min;
}

/*********************************************************************/
export function setup(args)
{
  // select pixel format
  args.pix_fmt = opt_pix_fmt;

  // initialize RtMidi and list ports
  midiin = new RtMidiIn();
  const portCount = midiin.getPortCount();
  console.log(`rtmidi port count: ${portCount}`);
  for ( let i = 0; i < portCount; i++ )
  {
    const name = midiin.getPortName(i);
    console.log(`[${i}] ${name}`);
  }

  // parse params (a number is expected)
  if ( !("params" in args) )
    throw new Error("A parameter is expected for the MIDI port number in the command line (use -sp <port number>).");
  const port = args.params;
  const ok = typeof port === 'number'
          && Number.isInteger(port)
          && port >= 0;
  if ( !ok )
    throw("MIDI port number expected as script parameter");

  // open the selected MIDI controller
  if ( midiin.openPort(port) === null )
    throw(`Error opening MIDI controller at port ${port}`);
  console.log(`MIDI controller at port ${port} opened`);
  midiin.ignoreTypes(false, false, false);
}

/*********************************************************************/
function parse_rtmidi_events()
{
  while ( true )
  {
    const msg = midiin.getMessage();
    const msglen = msg.length;
    if ( msglen == 0 )
      break;
    // Uncomment the following line to debug the message structure.
    // console.log(JSON.stringify(msg));
    if ( msglen == 3 )
    {
      if ( msg[0] == 176 )
      {
        switch ( msg[1] )
        {
        /* faders */
        case  0: midi_threshold_low [0] = msg[2]; break;
        case  1: midi_threshold_low [1] = msg[2]; break;
        case  2: midi_threshold_high[0] = msg[2]; break;
        case  3: midi_threshold_high[1] = msg[2]; break;
        }
      }
    }
  }
}

/*********************************************************************/
export function filter(args)
{
  // parse all RtMidi events
  parse_rtmidi_events();

  // input data
  const data = args["data"];
  const height = data[0].height;
  const width  = data[0].width;

  // range
  const y_begin = Math.lround(scaleValue(opt_range_y[0], 0, 1, 0, height));
  const y_end   = Math.lround(scaleValue(opt_range_y[1], 0, 1, 0, height));
  const x_begin = Math.lround(scaleValue(opt_range_x[0], 0, 1, 0, width));
  const x_end   = Math.lround(scaleValue(opt_range_x[1], 0, 1, 0, width));
  const range_y = [ y_begin, y_end ];
  const range_x = [ x_begin, x_end ];

  // colorspace: yuv444p: "yuv";
  //             gbrp:    "rgb", "hsv", "hsl"
  const colorspace = opt_colorspace;

  // trigger_by: yuv444p: 'y', 'u', 'v';
  //             gbrp:    'r', 'g', 'b',
  //                      'h', 's', 'v',
  //                      'h', 's', 'l'
  const trigger_by = opt_colorspace[opt_trigger_by];

  // sort_by: yuv444p: 'y', 'u', 'v';
  //          gbrp:    'r', 'g', 'b',
  //                   'h', 's', 'v',
  //                   'h', 's', 'l'
  const sort_by = opt_colorspace[opt_sort_by];

  // threshold
  const y_low  = midi_threshold_low [1] - midi_threshold_low [0];
  const y_high = midi_threshold_high[1] - midi_threshold_high[0];
  const threshold_low  = scaleValue(y_low,  -127, 127, opt_threshold[0], opt_threshold[1]);
  const threshold_high = scaleValue(y_high, -127, 127, opt_threshold[0], opt_threshold[1]);
  const threshold = [ threshold_low, threshold_high ];

  // options
  const options = {
    colorspace: colorspace,
    trigger_by: trigger_by,
    sort_by: sort_by,
    order: opt_order,
    mode: "threshold",
    reverse_sort: opt_reverse,
    threshold: threshold,                   // can be high low or low high
    clength: 0,
  };

  // call the internal pixelsort function
  // console.log(orig_frame_num, div_frame_num, JSON.stringify(options));
  ffgac.pixelsort(data, range_y, range_x, options);
}

Run it with (replace the <port_number> with an actual port number, from the list printed by the script when no parameters are specified):

$ fflive -i input.avi -vf script="script.js:<port number>"

NOTE2: You can also use RtMidi with all scripts available in FFglitch. This means normal bitstream transplication scripts, vf_script, pict_type_script, mb_type_script, and so on…