Bitwig Scripting: Some Notes about NoteInputs

19 Sep 2016

I've written a few Bitwig controller scripts and have gradually been developing a deeper understanding of Bitwig's scripting API.

One of the things that can be tricky, despite the generally very good API documentation provided by Bitwig, is understanding how note input filter masks affect which MIDI messages will be sent to a script's onMidi callback function.  This post looks at how filter masks work and demonstrates how they can be used to support a variety of scripting use cases.

Before we look at some code, let's review what Bitwig's API documentation says about the MidiIn.createNoteInput method:

NoteInput createNoteInput (String name, String... masks) throws ControlSurfaceException

Creates a note input that appears in the track input choosers in Bitwig Studio. This method must be called within the init() function of the script. The messages matching the given mask parameter will be fed directly to the application, and are not processed by the script.

Parameters
name - the name of the note input as it appears in the track input choosers in Bitwig Studio
masks - a filter string formatted as hexadecimal value with ? as wildcard. For example 80???? would match note-off on channel 1 (0). When this parameter is {}, a standard filter will be used to forward note-related messages on channel 1 (0).

And now let's take a look at a short example script.

This script creates a NoteInput with an empty filter mask and results in Bitwig using a standard filter that forwards note-related messages (note on/off, pitchbend, aftertouch, etc.) directly to Bitwig's audio engine (provided it's using the note input associated with the script, of course).  This means that note messages will bypass the rest of the application, including the script's onMidi callback handler.  So the documentation snippet above that says, "The messages matching the given mask parameter will be fed directly to the application, and are not processed by the script." could be more precisely written as something like "The messages matching the given mask parameter will be fed directly to the audio engine, and are not available to the GUI or script."

Usually this is what you want for note messages but not always.  For example, what if you want to use note on messages to sometimes trigger clips and other times play instruments?  Let's look at another script example and see how that could work.

In the above script, the default filter mask is used again, and there is a call right after the note input is created to setShouldConsumeEvents(false).  This enables our callback to "see" note messages.  When a given CC (25 in the example) is great than 0 we turn on a "shift mode" which can use note messages to control clip launching.  To suppress the audio engine you can make a call to the setKeyTranslationTable method on the input, passing it an array of length 128 where each value is a -1.  When shift mode is disabled another call is made to setKeyTranslationTable, setting the table back to it's default values.  This technique gives us the ability to send note related messages to instruments or use them to control other aspects of application behavior such as clip launching.  (Thanks to the comment from Tom Helzle pointing out that the setKeyTranslationTable can be used to keep the audio engine from processing note messages. My initial impl handled note messages directly in the script, which is not recommended due to performance limitations of JavaScript.)

Another common use case for filter masks is to receive note messages on a MIDI channel other than 1.  Here's a script that shows how you could do that.

This script also features a call to setShouldConsumeEvents(false) on the input.  This is required because we're using a catch-all filter mask that matches all messages sent to channel 16; setting setShouldConsumeEvents to false prevents the NoteInput from "swallowing" non-note MIDI messages.  Without this call we couldn't map CC messages in the GUI, which would be highly suboptimal!

An alternate approach, as demonstrated by the following script, is to filter only note-related messages on channel 16 and omit the call to setShouldConsumeEvents(false).  Either approach works fine so it largely comes down to a matter of personal preference.

Hopefully this has improved your understanding of note inputs and filter masks in Bitwig's scripting API.  For simple scripts this isn't something you need to worry about, but for more advanced use cases knowing how to use filter masks to control how note messages are routed can be very helpful.

For more information on this topic take a look at Thomas Helzle's excellent blog and this KVR forum post has some good info, too.