You are here

Creating A Step Sequencer | How To Create Scripts In NI Kontakt, Part 4

In the final part of this short series, we investigate a more advanced use of scripting to create a simple step sequencer.

Creating A Step Sequencer | How To Create Scripts In NI Kontakt, Part 4

Over the past three issues, we've been discussing the basics of using Kontakt Script Processor (KSP) to extend the functionality of sampled instruments in Kontakt. If you've followed this far, you should hopefully have a good understanding of how the basic building blocks — callbacks, variables, commands, and so on — can be used to manipulate incoming notes to affect playback. However, in this final part we'll be using our scripting knowledge to attempt something a little more ambitious. Rather than simply manipulate incoming notes, we're going to use them to trigger a sequenced playback by building a basic step sequencer.

The idea for this step sequencer is that when you hold down a key, an endless stream of pulsing notes will be triggered at a specified resolution. Each step in the sequence will represent the velocity of a repeated note, making it possible to create interesting rhythmic patterns. When the number of steps in the sequence has been reached, the sequencer will cycle back to the beginning, so as not to interrupt the flow of notes.

In creating this script, I'm going to use the somewhat appropriately named (especially for our purpose) 'Pimped Analog Saw' instrument, which you'll find in the Synth / Lead folder of Kontakt 4's factory library. As mentioned in the first part of this series, modifying the instruments in Kontakt's factory library can be a thorny proposition when it comes to scripting, since all of the instruments make fairly comprehensive use of scripting from the outset. So the first thing to do after loading the instrument is to find your way to the now‑familiar Script Editor in Instrument Edit Mode and disable the existing scripts. To do this, click the first, second and fifth Script slots (which are labelled Options, Instrument and Unisono — Portamento), and activate each script's bypass button in the top‑left of the Script Editor. Each bypass button will turn red when the corresponding script is disabled.

Now select the third Script slot, which should be labelled '<empty>', and enter a name for the script in the 'Title for this Script' field: say, 'Step Repeater'.

An Array Of Values

The first step (if you'll excuse the pun) in creating a step sequencer is to define the actual steps. Each step will hold a single value that represents the velocity of the note that will be played. So you might think it would be logical to declare a separate variable for each step. However, while this approach could be made to work, it would ultimately result in a verbose solution that was hard to understand.

Instead of declaring all the steps individually, a better idea is to store them together in a data structure called an array. If you think about a pile of books, rather than placing each one individually in its own location, a better approach is put all the books on a shelf. Now, when you're looking for a book, you can go straight to the shelf, rather than considering each book in its own different location. And, looking from left to right, you can easily find the second book on the shelf, or the ninth. In the world of computer programming, an array is basically like a shelf for a collection of values.

Our step sequencer is going to have 16 steps, so we need to create an array that can hold 16 values. To do this, we would write the following instruction in the on init callback:

declare %steps[16]

There are two important parts to note in this declaration. Firstly, unlike referencing a variable, where a dollar‑sign prefix is used, arrays are prefixed with a percent symbol. Secondly, the number of values you want to allocate for your array must be specified using square brackets after the name of the array. Much like buying a bookshelf, you can't change the length of an array after it has been declared.

You can access the values in an array in pretty much the same way you would access other variables. For example, to store the constant 64 into the third value of the array, you would write the following instruction:

%steps[2] := 64

You might be wondering why I wrote %steps[2], when I want to access the third value. The answer is that since computers tend to count from zero, %steps[0] would reference the first value and %steps[1] the second, so %steps[2] accesses the third value. This means that the 16th value of our array would be %steps[15] and not %steps[16], which would be illegal since our array only has 16 spaces and %steps[16] would refer to a 17th value that does not exist. Indeed, if you try writing the instruction %steps[16] := 64, Kontakt will give you a 'Script Warning' when you click Apply: "array index out of bounds,” and your Script will not function as planned.

Because we want the value of each step to be editable via an Instrument's Performance View, we need a user-interface control to represent our array. If you remember from the previous parts of this workshop, a knob can be used to represent a variable. So, rather than creating both a knob and a variable and trying to link the two, you can just create the knob and reference it in the same way you would a variable. Similarly, in the case of an array, Kontakt provides a user interface control called a table that can be used to represent an array. So let's declare a table as the first line of our sequencer's on init callback.

declare ui_table %steps[16](4,4,127)

If you click Apply, you should see the table appear in the Script Editor's preview area, as shown in the screen below.The Script Editor should look something like this after adding the table declaration.The Script Editor should look something like this after adding the table declaration.

In the same way we configured the behaviour of a knob by specifying various arguments in the declaration, you'll notice that the table declaration is followed by three arguments (in brackets, after the number of values has been specified). The first two values adjust the width and the height of the table's visual appearance in a grid‑based unit. If you make the first number smaller, each step will become narrower and the table will take up less space horizontally. If you adjust the second number, the height of each step will change, and thus the height of the entire table. The final number specifies the range for the steps; and, in this case, the range we want is the number of available velocities: from 0 to 127.

(If you wanted a table where negative values could be expressed, specifying the range as ‑127 would add a zero‑point to the table, making it possible for each step to be of a value between ‑127 and +127. However, since you can't have negative velocity values, we don't need to worry about this right now.)

When you declare an array or table, all of the values are set to zero by default. Given that a velocity of zero is the same as specifying a 'note off', this isn't a particularly useful starting point for our sequencer; so let's create a sequence of default values. To do this, we need to assign values to each variable in the array, adding instructions to set a value for each of the 16 steps. As an example, try setting %steps[0], %steps[4], %steps[8], and %steps[12] to 127, and all the other steps to 90. You should end up with something similar to the display shown in the screen at the top of the page.After assigning values to each element in the array, your Script Editor should closely resemble the table shown here. The full list of assignments in the script itself has been deliberately cropped here to save space.After assigning values to each element in the array, your Script Editor should closely resemble the table shown here. The full list of assignments in the script itself has been deliberately cropped here to save space.

After assigning values to the steps, we should make the table 'persistent' (so its values can be stored as part of the instrument) by adding the make_persistent(%steps) instruction.

Polyphony, Menus & More

As we discussed in the introduction, the idea of our step sequencer is for the steps to be triggered once a note is played on the keyboard, with the sequence being repeated after the last step has been played. In order to keep track of which step should be played, we need to create a step counter. For this, we'll simply create a variable called $counter.

declare $counter

Next, we want to create a way for the length of the steps to be specified. For example, do we want the steps to be played back as quarter notes, eighth notes, or some other length? (Readers in England will forgive me for using the American convention here, since this is also adopted by KSP.) We could use a knob to set the length of each step, but that might be a little confusing. If a user saw the length was set to '3', what would that tell them? A nicer way of allowing the user to choose a length would be to create a pop‑up menu, which we can do quite easily thanks to another KSP user interface control type: the menu.

We declare a menu in a similar way to other types of controls:

declare ui_menu $length

If you click Apply, you'll notice that a pop‑up menu button containing the word 'length' has appeared next to the table. However, if you click on the pop‑up menu, you'll notice that it's currently empty. To populate the menu with various length options, we'll use the add_menu_item() command to, well, add items to the menu. As an example, we could create a quarter‑note option like this:

add_menu_item($length, "1/4 Note”, $DURATION_QUARTER)

You might already be able to guess what the three arguments do in this instruction. Firstly, we tell KSP to which menu an item should be added: in this case, our $length menu. Next, we specify a text string for the label we want the menu item to have. And, finally, we associate a value with the menu item. This is the value that will be assigned to the $length variable if that particular item is chosen by the menu. So here, we're telling KSP to assign the value of the built‑in variable that specifies the duration of a quarter note if the quarter‑note item is chosen in the menu.

Continue populating the menu in the same way with the following lengths: $DURATION_EIGHTH, $DURATION_SIXTEENTH, $DURATION_QUARTER_TRIPLET, $DURATION_EIGHTH_TRIPLET, and $DURATION_SIXTEENTH_TRIPLET. If you get stuck, you can see how these instructions should be written in the final script shown on the last page of this article. We can set the default value for $length by simply assigning it to one of the values used in the menu items. So to get $length to default to an eighth note, we would add the following instruction.

$length := $DURATION_EIGHTH

And, once again, we will want to make $length persistent by adding the make_persistent($length) instruction.

We're almost done with our initialisation; but, before we finish, I just want to add a couple of extra touches. We'll add two knobs to our sequencer: one to control how many of the 16 steps are played in a sequence (making the sequencer useful for different time signatures), and another to gate the playback length of the steps. This means that while the steps will play back at the timing interval specified by the $length menu we just created, the actual length of the note triggered by a step can be made shorter, which is useful for creating a more staccato performance.

To declare the knob that defines the number of steps, add the following:

declare ui_knob $numSteps(1,16,1)$numSteps := 16make_persistent($numSteps)

Note that the minimum value of the knob is set to 1 and its maximum value is 16, preventing $numSteps from being set higher than the number of steps we have available in our array. The variable is set to 16 default and is also made persistent.

For the second knob, we'll create a variable called $divisor. This will be the value that the note length is divided by when a note a triggered by the sequencer. So if it's set to 1, the note will play for the whole length of a step, whereas if it's set to 2, the note will play for half the length of a step, and so on.

declare ui_knob $divisor(1,4,1)$divisor := 2make_persistent($divisor)

Here, the range of values is between 1 and 4, although you could make this maximum value higher than 4 if you wanted. By default, $divisor will be assigned a value of 2, and the variable is made persistent.

If you click Apply now, you should see a completed user interface as shown in the screen above.Once you've define the on init callback, the Script's user interface should look something like this.Once you've define the on init callback, the Script's user interface should look something like this.

Last but not least, the make_perfview instruction should be added so that the user interface appears in the instrument's Performance View. As discussed in the first part of this series, you might prefer to remove the background image for this instrument to make the interface a little cleaner.

It's Alive!

Now we've defined all the required variables with the appropriate user‑interface controls, it's time to bring the sequencer to life. And, since we want the sequencer to be triggered when a note is played, we will once again be defining our old friend the on note callback.

The first thing to do is to ignore the note that triggers the on note callback, with the ignore_event($EVENT_ID) instruction. The reason is that the played note will become the first step in the sequence, and although we could modify its velocity to make it the first note in the sequence, it's actually easier to just ignore it. Next, before we start playing any notes, it's important to set $counter to zero so that the sequence will always start playing from the first step when a note is triggered.

Now for the fun part. Because we want our sequence to keep playing until the note that triggered it is released, we need to employ a programming technique known as a 'while loop'. In other words, we need to tell KSP to loop through the instructions to play back the sequence while the note is held down.

In the same way that callbacks are defined between on and end on instructions, and conditional 'if' statements are defined between if and end if instructions, a while loop is defined between while and end while instructions. In fact, like the conditional 'if' statement we used last month, a while loop also has a conditional requirement, because we need to specify the condition under which the looping will take place. For our sequencer example, we want KSP to loop while a note is held down, which we can do like this:

while($NOTE_HELD = 1) {this is where our sequence playback instructions will go }end while

$NOTE_HELD is a built‑in variable that holds a value of 1 when the note that triggered the callback is held down. When the note is released, its value will be 0. So the condition ($NOTE_HEAD = 1) will only be true for as long as the note is held down.

The first instruction required in the playback loop is the instruction to play a note for a given step:

play_note($EVENT_NOTE,%steps[$counter],0,$length/$divisor)

Here we tell KSP to play a note of the triggered pitch ($EVENT_NOTE), with the velocity that's stored in %steps[$counter]. This is where you can see the real power of using an array to store our steps: by changing the value of $counter, we can select which velocity will be used from our series of steps. The remaining arguments specify that no sample offset (0) is to be used, and that the duration of the note should be the $length of a step divided by the $divisor.

After triggering a note, we need to tell KSP to wait for the duration of time specified by the $length variable before triggering the next note. This is done with the aptly named 'wait' command.

wait($length)

Finally, before the next note is played, we need to increment the playback counter so that KSP moves onto the next step in our sequence:

$counter := $counter + 1

However, if you think back to the beginning of the article, you'll remember that our array stores 16 steps, indexed between the numbers 0 and 15. This means that if we keep incrementing the value of $counter past 15, we'll quickly find ourselves in trouble. So to reset the counter once the maximum number of steps is reached, we need to add the following conditional statement:

if($counter >= $numSteps) $counter := 0end if

When $numSteps is set to its default value of 16, these instructions can be read like this: if $counter is greater than or equal to 16, set $counter to 0. By making the maximum number of steps a variable, rather than hard‑wiring the constant 16 into the code, we add a little more flexibility to our sequencer.

After $counter has been set to an appropriate value, KSP encounters the end while instruction; and, assuming the while condition is still true — that the note is still held — KSP will loop back to the first instruction in the loop, which, in this case, is play_note(). And that's it. Click Apply, try playing a note, and hopefully you'll feel the faintest urge to grin at least a little.

However, if you play more than one key at a time, you might notice that there is a small issue. Even though each key triggers its own sequence (via the on note callback), there's only one $counter variable per Instrument. This means that each sequence accesses the same $counter variable for playback, resulting in many sequences reading, incrementing and resetting the same value at the same time. In order for the sequencer to work correctly, each triggered sequence needs to have its own independent $counter variable.

Fortunately, Native Instruments foresaw such an issue and implemented a special type of variable called a polyphonic variable. As you might be able to guess, declaring a variable as polyphonic means that each triggered note can access its own version of the variable without stepping on the toes of other note. To make $counter a polyphonic variable, simply change its declaration, as follows:

declare polyphonic $counter

And that, as they say, really is that.The complete script for a simple sequencer, shown in Nils Liberg's KScript Editor (see 'An Alternative Editor' box).The complete script for a simple sequencer, shown in Nils Liberg's KScript Editor (see 'An Alternative Editor' box). Hopefully, this short series will have helped in unravelling some of the mysteries associated with scripting in Kontakt. Native Instruments continue to improve and refine KSP with each new version of the software; and, armed with a little knowledge, it's really quite amazing what you can achieve with it.  

An Alternative Editor

Kontakt's built‑in Script Editor is all well and good, but you may start to observe its somewhat cumbersome shortcomings as your scripts grow in complexity and, particularly, in length. This will be especially true if you've ever worked with other programming environments. However, help is at hand thanks to Nils Liberg's superb KScript Editor, a third‑party Script Editor for Kontakt available for both Windows and Mac OS X. KScript Editor can be downloaded from the author's homepage (www.nilsliberg.se/ksp); and, best of all, it's free, although the author asks for donations if you find the program useful.

In addition to providing a more fully featured text editor, with line numbers and more comprehensive syntax colouring, KScript Editor has many intelligent features. For example, it can display a list of the callbacks defined in your script, enabling you to easily navigate to different callback definitions with a single click. It even has its own compiler with support for basic error‑checking.

Once you're ready to test a script, simply press F5 to 'compile' the script, and, if all is well, the editor will copy the resulting script onto the clipboard. Now, in Kontakt, there's a special 'Apply from' pop‑up menu that's set to "... Editor / Patch” by default, telling Kontakt to use the built‑in Script Editor. However, if you select "... Clipboard” from this pop‑up menu, Kontakt will prevent editing in the built‑in editor and, instead, when you click Apply, will copy the script from the clipboard and use this instead. This makes it easy to use Kontakt and KScript Editor side‑by‑side; and, if you become hooked on writing your own scripts, you really will find this tool invaluable.