You are here

Extracting MIDI Data | How To Create Scripts In NI Kontakt, Part 3

Learning how to extract MIDI data from Kontakt will allow our scripts to respond to different user actions in different ways, and even to control entire Kontakt Multis at once.

In the first two parts of this workshop, we've been creating a script to implement a simple harmoniser. Last month, we gave our harmoniser a basic user interface, and also added persistence, so that the state of certain values are remembered when an Instrument containing the script is saved. Now it's time to extend our harmoniser with some new functionality.

Considering A Transposition

Taking our existing script, which is shown in the screen belowThis is how we left our script in last month's tutorial.This is how we left our script in last month's tutorial., let's consider how we would add a transpose feature to our harmoniser. First, let's create another knob in the Performance View for a variable called $transpose, by adding the following instructions after the declaration and configuration of the $velocity knob.

declare ui_knob $transpose(‑12, 12, 1)set_knob_defval($transpose, 0)$transpose := 0make_persistent($transpose)

Click Apply, and you'll notice a third knob appears in the Performance View.

To make the transpose knob work, as you might already be anticipating, we need to add the value of $transpose to the pitch of the note that's played in the on note callback. After doing this, the play_note command should look something like this:

play_note($EVENT_NOTE + $interval + $transpose, $EVENT_VELOCITY + $velocity, 0, ‑1)

Note that, because we're just adding values together, the order in which the three variables are added doesn't matter. If you click Apply, you'll hear that the interval of the harmony note is now the sum of $interval and $transpose. So if $interval is set to ‑1 and $transpose is set to 4, the actual interval of the harmony note will be ‑1 + 4 = 3. Once again, a minor third!

This is all very well, but you might be thinking that our new transposition function behaves in a suspiciously similar fashion to our original interval knob. And you'd be right: at this point, the two knobs essentially do the same thing. In order to truly make our transpose knob behave like a transpose knob, we need to make it adjust both the pitch of the harmony note and the pitch of the original note that triggered the on note callback in the first place.

For the moment, let's disable the play_note() command. We could do this by simply deleting it from the Script, of course; but a better approach is to turn the instruction into a comment. Comments are areas of text in the Script that are ignored by KSP and are written between a pair of curly‑brace symbols like this:

{ I am a comment, KSP will ignore me. }

Try writing this comment into the Script anywhere you like. As soon as you enter the open‑curly‑brace symbol, notice that the Script Editor will display this character (along with all subsequent characters) in cyan. Once you enter a close‑curly‑brace symbol, the Script Editor will adjust this syntax colouring so that only the comment is displayed in cyan. Aside from making it easy to see where comments are in your Script, this syntax colouring is also extremely helpful from a debugging perspective. Forgetting to close a comment with the appropriate curly‑brace would lead to an error when clicking Apply. So if you see large areas of cyan‑coloured text, it can sometimes be an indication that a comment was not closed correctly.

Comments are often used for making notes (or, indeed, comments) about the behaviour of certain instructions. If you look at some of the example Scripts supplied with Kontakt, you'll notice that comments are often used to add user‑readable explanations to the beginning of Scripts. But comments can also be useful in our current situation, where we don't necessarily want to permanently remove an instruction from the Script, but we do want to temporarily prevent it from running.

So, put a pair of curly‑brace symbols around the play_note instruction ( { play_note(...) } ) and click Apply. If you play some notes, you'll now only hear the notes you play — the harmonisation has indeed been disabled because we commented out the play_note instruction. We're now going to implement the transposition for these notes that you play directly.

Transposing

As we already know, the on note callback is triggered when a note event is received. And we've looked at how the pitch and velocity parameters of that event can be accessed by using two built‑in variables: $EVENT_NOTE and $EVENT_VELOCITY. Another, more verbose way to access the value of these parameters would be to use the get_event_par() command, which you can read as 'get event parameter'. The get_event_par() command takes two arguments: firstly, the identification number of the event in question, and secondly, the type of parameter you want to be returned from that event, such as the pitch or velocity in the case of a note event.

Each event in a Script has an associated identification number so that it can be uniquely identified by KSP. So far we've been working with note events, which are produced either when you play a note on the keyboard or a new note is created within the Script with the play_note() command. However, KSP also supports other types of events for dealing with controller data, pitch‑bend, and so on. For this reason, having a unique identifier for each event becomes essential when you have Scripts that manipulate and create many different events of many different types.

To access the ID number of the event that triggers a callback, we can use the $EVENT_ID built‑in variable. So to get the pitch of the note event that triggers the on note callback using the get_event_par() command, we would write the following:

get_event_par($EVENT_ID, $EVENT_PAR_NOTE)

$EVENT_PAR_NOTE tells the get_event_par() command that the parameter we want to retrieve from the event with the id $EVENT_ID is the note pitch. If we wanted to retrieve the velocity, we would substitute $EVENT_PAR_NOTE for $EVENT_PAR_VELOCITY.

Newton's third law states that to every action there is always opposed an equal reaction. Similarly, in computer programming, it's often the case that where you have a command to get a value, there will also be an equivalent command to set that same value. And so, where the get_event_par() command is used to retrieve a value from an event, the set_event_par() command is provided so that we can change the value of an event. To understand how this command works, let's jump straight in by adding the following command to the on note callback (after the commented out play_note() instruction):

set_event_par($EVENT_ID, $EVENT_PAR_NOTE, 60)

When you click Apply, every note you play — no matter which note you play — will now be of the same pitch: middle 'C'.

The first two arguments for the set_event_par() command are the same as for get_event_par(). First, the ID of the event we want to manipulate is specified, followed by the type of parameter we want to change. The third argument is the new value we're providing for the given parameter of the specified event. So the instruction we just entered tells KSP to change the note pitch ($EVENT_PAR_NOTE) of the note event with the id $EVENT_ID to 60 (which is middle 'C').

Now, in a similar way to how we made the interval knob work in the first part of this workshop, we can make our transpose knob come to life by substituting the constant 60 with the following expression:

$EVENT_NOTE + $transpose

When you click Apply, the transpose knob will now transpose the notes you play by the specified number of semitones. And, if you remove the curly braces from the play_note() instruction, the harmonise functionality will be returned and should also transpose accordingly when the transpose knob is adjusted. Should you experience any errors when clicking Apply, check your listing against the one shown in the screen aboveThe Script Editor should look like this after adding the transpose knob and the set_event_par() instruction to the on note callback. Note that the first and last instructions in the script (on init and end on) have been clipped due to the limited size of the Script Editor.The Script Editor should look like this after adding the transpose knob and the set_event_par() instruction to the on note callback. Note that the first and last instructions in the script (on init and end on) have been clipped due to the limited size of the Script Editor..

Before moving on, it's worth mentioning that the order of the instructions in the on note callback is critical: the play_note() instruction must come before set_event_par(). Can you guess why?

The reason, of course, is that otherwise the harmony note would be transposed twice. Because the set_event_par() modifies the pitch of the note that triggered the callback, any subsequent references to the pitch of that note event will return the modified value — not the original value. And since we add the $transpose variable to the pitch of our harmony note in the play_note() instruction, it would have the effect of adding the $transpose variable twice because the value of $transpose has already been added to the pitch of the note referenced by the $EVENT_NOTE variable.

If this seems a little confusing, try swapping the order of the commands around and you'll see what I mean. The way to make it work if the set_event_par() instruction comes first would be to remove + $transpose from the expression that specifies the pitch of the harmony note in the play_note() command.

If I Think, Therefore I Am

What's handy about adding the transpose mode to our harmoniser is that we can now use this Script as either a harmonisation or transposition tool. If $interval is set to 0, we can use the transpose knob to transpose the input notes as desired without any harmonisation. However, there's one small issue here: our Script currently plays an additional note regardless of the value of $interval. So, if $interval is set to 0, even though there won't be any harmonisation, we'll end up with a duplicate note of the same pitch as the original note being triggered.

In order to solve this minor issue, we need to tell KSP that the play_note() instruction should only be executed if $interval does not equal 0. To do this, we'll use parts of the KSP dialect that are referred to as Control Statements, which are sometimes known as 'conditional statements' in the world of computer science. I'm going to stick with the latter description for this workshop, as I think it makes the nature of these instructions a little easier to understand.

A conditional statement is used when you want to specify one or more instructions that should only be executed if a certain condition is met. For example, in telling KSP to only use the play_note() instruction if $interval does not equal 0, '$interval does not equal 0' would be the condition. If the condition is true, KSP will execute the play_note() instruction. And if the condition is false, the play_note() instruction will be ignored. Here's how we would actually specify this in our script:

if($interval # 0) play_note($EVENT_NOTE + $interval + $transpose, $EVENT_VELOCITY + $velocity, 0, ‑1)end if

We tell KSP we're going to define a conditional statement by using the keyword if, followed by the condition itself in parentheses. The condition is specified using Boolean logic, so the outcome can either be true or false, and KSP provides many so‑called Boolean operators that let us express different conditions. In the above example, 10 is the KSP Boolean operator for 'is not equal to'. If we wanted to reverse the logic, so that our Script only plays an extra note when $interval is equal to 0, we could have used the = Boolean operator instead, which means 'is equal to.'

Alternatively, if we wanted to make only positive $interval values play an extra note, we could use the 'is greater than' operator: >. And similarly, if we wanted only negative $interval values to play an extra note, there's the 'is less than' operator: <.

After the conditional statement come the instructions we want to execute if the condition is true. In this case, that's the play_note() instruction. And finally, just as when defining a callback, we need to tell KSP that we've finished defining the instructions to be executed if the condition is true by adding the end if instruction.

In this brief example, I've only skimmed the surface of conditional statements (or Control Statements in KSP parlance). There are other types of Boolean operators and ways to control the flow of instructions in your Scripts, which are detailed in the KSP Reference Manual that's supplied as a PDF file with Kontakt. We'll be taking a closer look at some of these next month when, in the fourth and final part of this workshop, we'll explain how to create a basic step sequencer from scratch.  

Going Further: Multi Scripting

Here is a simple Multi Script that transposes all incoming MIDI notes. Note that the Multi Script Editor button is highlighted white in the top right of the Multi Rack header when the editor is open.Here is a simple Multi Script that transposes all incoming MIDI notes. Note that the Multi Script Editor button is highlighted white in the top right of the Multi Rack header when the editor is open.

So far, in our exploration of Kontakt scripting, we've been looking at scripts that are tied to individual instruments. In order to use our harmoniser script in different instruments, we would have to copy and paste the same script into those different instruments. This is fine for most scripts, of course, since most scripts are instrument‑specific. But with more generic functionality like transposition, wouldn't it be nice if this behaviour could affect all instruments in a Multi Rack? Well, indeed; and that's exactly the job of the Multi Script feature Native Instruments added to Kontakt in version 4.

Writing a Multi Script is similar to a conventional script, except that instead of working with pre‑packaged events like notes, you have to manipulate raw MIDI data. However, this is not nearly so scary as you might think.

To define a Multi Script, click the Multi Script Editor button to the right of the Multi Rack header (next to the 49‑64 button). You'll see the now‑familiar Script Editor, complete with five available slots. Click the Edit button if you can't see the text editor area, and we're ready to go.

To begin with, copy the complete on init callback from the regular harmoniser Script, and then delete the first two sections where the $interval and $velocity user‑interface knobs are declared and configured. If you click Apply, you should see the transpose knob appear in the Performance View. Now, because Multi Script works with MIDI events, rather than defining an on note callback to handle the transposition, we need to define the on midi_in callback instead.

In the same way on note is called every time a note is played, the on midi_in callback is called every time Kontakt receives a MIDI event. This means you have access to every MIDI event received by Kontakt, whether it's a note‑on, a note‑off, a controller, pitch‑bend, or whatever.

Let's define the on midi_in callback for our transpose Multi Script.

on midi_in

if($MIDI_COMMAND = $MIDI_COMMAND_NOTE_ON or $MIDI_COMMAND = $MIDI_COMMAND_NOTE_OFF)

set_event_par($EVENT_ID, $EVENT_PAR_MIDI_BYTE_1, $MIDI_BYTE_1 + $transpose)

end if

end on

At the heart of the callback is a conditional statement, like those discussed in the main text. The reason for this is that we only want to process note‑related MIDI events; we don't want to apply transposition to controller data, for example. However, this particular conditional statement comprises two conditions with the or Boolean operator. As you may know, MIDI uses two separate events for notes: on and off. A note‑on event is sent when a device is to begin playing a note, and a note‑off event is sent when a device is to stop playing a note. We need to process both note‑on and ‑off messages, because otherwise we could end up with stuck notes if the correct note wasn't told to stop playing. So the conditional statement tells KSP to only execute the following instruction if the type of MIDI event received (specified by the $MIDI_COMMAND built‑in variable) is either a note‑on ($MIDI_COMMAND_NOTE_ON) or a note‑off ($MIDI_COMMAND_NOTE_OFF). With the use of the or operator, the overall condition is true if either one of the sub‑conditions are true.

Next, we need to modify the received MIDI event, which we do via our old friend the set_event_par() command, discussed in the main text. The pitch of a MIDI note is held in the first data byte of the event, so in this case we tell set_event_par() that the type of the parameter we want to change is $EVENT_PAR_MIDI_BYTE_1. The built‑in variable $MIDI_BYTE_1 stores the value of the first data byte (which, in this case is the pitch), just as $EVENT_NOTE stored the pitch of a note event in a standard script. And so, our new pitch value is the sum of the original pitch and $transpose.

Lastly, we tell KSP we've finished defining the conditional statement and, indeed, the on midi_in callback. And when we click Apply: voila! The transpose knob will transpose all incoming MIDI notes to all instruments. Just don't adjust the knob while notes are playing, as the corresponding note‑off pitches will get scrambled. We could fix this by remembering the transposition difference for each note individually in an array, but that would be beyond the scope of this particular box. Answers on a postcard, however, are welcome from anyone who wants to show off!