Introduction

The Gig Performer scripting language, which we have rather unimaginatively named GP Script, is a special purpose language designed to add programmable and responsive functionality to Gig Performer. For example, you can write scripts with code that can respond to incoming MIDI note events and then transpose them, make chords out of them constrained to a scale, or use them to change widget values or to directly control parameters of a plugin (keyboard tracking, anyone?). Script code can be triggered in response to your moving a knob, slider or button. There are also time generators that can be used as LFOs (with varying shapes) or as ADSRs.

Events and Callbacks

Generally, GP Script is event driven. It doesn’t do anything until something “happens” such as your playing a key, turning a knob, switching to another rackspace or to a different variation. A certain amount of time passing can also count as something “happening”. All of these examples are known as events and when you write code that can respond to an event, that code is called a callback. GP Script can currently handle the following events:

  • Initialization

  • Rack activation

  • Rack deactivation

  • Variation change

  • MIDI events (notes, CC values, Aftertouch, etc.)

  • Widget changes

  • Plugin parameter changes

  • LFOs (also known as Time Generators)

  • OSC Messages (coming soon)

A simple example

var pi : double

Initialization
   pi = 3.14159
   Print(pi)
End

So, what’s going on here? Well, first we declared a variable called pi and whose value must be a floating-point number (Double). In GP Script all variables must be declared, and they must have a type. The type of a variable defines the kinds of values that the variable can represent and also determines what kinds of operations can be applied to it. So called strongly typed languages make it easier to prevent certain kinds of common bugs.

The keyword Initialization defines the beginning of a callback that will be executed just once when a rackspace is initially loaded. In this example we simply assign a value to the variable and “print” its value. In the context of Gig Performer, printing means to show the item in a special window called the Script Logger. The keyword End completes the definition of this callback.

A more interesting example

// Declare a global variable
var A800 : MidiInBlock  // A800 is a named block in Gig Performer

// Callback when a note event (on or off) is received
On NoteEvent (m : NoteMessage) From A800
   SendNow(A800, m)  // Send the note to the A800 MidiIn block
   SendNow(A800, Transpose(m, 5)) // Also send a transposed note
End

Like before, the first thing we’re doing is declaring a variable. But unlike the example above that just used a floating-point number, the variable A800 is declared with the type MidiInBlock. That type represents a Gig Performer MIDI In plugin block. Consequently, the name of the variable, in this case, A800, is significant. For this script to work there must be a MIDI In block in your rackspace and its scripting name, more properly known as a handle, must be A800. So in this example, we are binding a variable name to an entity in Gig Performer.

screenshot of a *MIDI In* block with the GP Script handle ``A800``

This block has been assigned the handle A800. Note the handle name on the upper left and the {S} on the upper right, the latter indicating that the handle is available for use in GP Script.

Next, we define a callback. A callback is a block of code that will run automatically when the event associated with that callback occurs. The callback above responds to MIDI note events (both NoteOn and NoteOff events) that arrive at the MIDI In block named A800. There are other callbacks that respond to other MIDI events such as Control Change events or Aftertouch. The actual message itself is accessed through the incoming parameter argument m which has the type NoteMessage.

Once you define a callback for a MIDI event you have full responsibility for that event. GP Script will not automatically send the MIDI event back to the MIDI In block which means that the MIDI In block will not forward the event to whatever synth plugins are connected to it. So if you want the incoming note to be played, you have to explicitly send it out, which we do with the statement SendNow(A800, m). Immediately afterwards, we send the note again, after transposing it by 5 semitones. Voilà, instant chords.

Basic concepts

GP Script is a programming language designed specifically for use with Gig Performer. Rather than integrating an existing language such as Lua, Python or ChaiScript and living with some compromises, we felt it worthwhile to have an essentially seamless environment that would immediately make sense. The clean syntax reflects that integration. GP Script is influenced more by Algol/Pascal. While we will assume for now that the reader has at least some basic understanding of programming there will be plenty of samples to help users to get up to speed quickly.

As mentioned above, GP Script is a strongly typed language; every variable must be declared along with a type. However, along with the usual types such as integers, strings, doubles and so forth, GPScript has built-in types that represent plugins, widgets, various kinds of LFOs and some special purpose types such as NoteTracker and ChordRecognizer.

So as easily as you can write

var i : Integer

to represent an integer value and perform operations such as addition and subtraction, you can write

var A800 : MidiInBlock

Which represents a named MidiInBlock, presumably associated with an A800 MIDI keyboard controller. Now you can do operations such as manipulate incoming MIDI messages before sending them on to whatever audio plugin is connected to it.

GP Script includes the usual looping and conditional statements and of course you can define your own functions. There is also an extensive collection of efficient built-in functions you can leverage.

A list of built-in functions is provided in the included reference.

Scripts belong to rackspaces, meaning that you can have a completely separate script running in each of your rackspaces.

Tip

The top of the GP Script editor window has two buttons that provide you with templates to help you get started as well as system function code completion.

../../_images/script-editor-with-left-buttons-highlighted.png

Statements

Assignment

Syntax:

<Identifier> ':=' <Expression>

Assign an expression to a variable. For example:

a := 42
b := a + 1

// You can also write an assignment using just the '=' character
str = "I don't like C syntax"

For

Syntax:

For <Assignment> ';' <BoolExpression> ';' <Assignment> Do
   <Statements>
End

The for loop lets you iterate a statement block until the Boolean expression evaluates to false. It is often used to process an array of values. For Example:

For i = 0; i < 10; i = i + 1 Do
   //statements here
End

While

Syntax:

Syntax: While <BoolExpression> Do
   <Statements>
End

The while statement lets you loop though a block of statements until the Boolean expression evaluates to false. The important difference between a while loop and a For loop is that the former does not have an explicit clause to update the expression to be tested. An example for the while loop:

Done = false
While !Done Do
   // Statements, one of which ultimately sets Done to true
End

Caution

Be very careful with both these looping constructs. If the condition does not ultimately evaluate to false, the loop will never end and Gig Performer will hang.

If

Syntax:

If <BoolExpression> Then
   <Statements>
[ [ Elsif <BoolExpression> Then
   <Statements>]
Else
   <Statements> ]
End

This is the usual conditional test. The requirement for End prevents bugs due to the dangling else problem. The Elsif clause (as many as you need) is optional as is the Else clause.

Todo

give an example

Select

Syntax:

Select
   [<BoolExpression> Do
      <Statements>]
End

The Select statement is a better option than the If statement when you need to test for many conditions. While similar in concept to the C switch statement or the Pascal case statement, the Select statement doesn’t require each case to be a constant value.

Select
   a > b Do
      Print("A is greater than B")

   TimeNow() - previousTime > 1000 Do
      Print("At least one second has passed")
      previousTime = TimeNow()

   true Do
      Print("This executes if none of the other expressions were true")
End

Function calls

GP Script includes a large and growing collection of system functions for a variety of purposes such as math functions (rounding or scaling numbers), creation and modification of MIDI messages, management of function generators. A list of functions that are built into GP Script is provided in the included reference.

User functions

You can also write your own functions in GP Script.

Syntax:

Function <name> ([<identifier> : <type> [, <identifier> : <type>]]) [Autotype] [Returns <type>]
   <Local variable declarations>

   <Statements>
End

Functions must have a unique name. Zero or more parameters can be defined and parameters are always passed in by value. Note that types such as Widgets, Plugins and dynamic arrays are objects and so what’s really being passed by value is the reference (a pointer) to the object.

Functions can optionally return a value in which case the Returns clause must be present. If the Returns clause is present, then a variable called result exists in the function scope and whatever is assigned to that variable will be the return value. There is no mechanism to return from the middle of a function.

Examples:

Function SumNumbers(a,b,c : integer) Returns Integer
   result = a + b + c
End
Function PlayMajorChord(keyboard : MidiInBlock, root : NoteMessage)
   SendNow(keyboard, root)               // Original note
   SendNow(keyboard, Transpose(root, 4)) // The third
   SendNow(keyboard, Transpose(root, 7)) // The fifth
End

Operators

GP Script has the usual comparison operators (<, >, <=, >=, ==, !=) as well as boolean operators (And, Or, Not). Precedence rules are such that you very rarely require parentheses. GP Script also has a ternary operator following the style of Algol and, more recently, Haskell:

Result = If widgetValue > 0.5 Then 1 Else 0 End

Note that the Else is not optional in the ternary operator.