ORchestra Language Reference¶
This document covers the syntax, built-in functions, and usage patterns of the ORchestra scripting language. It is meant to be read once you have ORchestra built or installed as a plugin.
Table of Contents¶
- Overview
- Reserved Keywords
- Operators
- Variables
- Data Sequences
- The Global Step
- MIDI Output
- Note Division Literals
- Substeps / Sub-divisions
- Musical Note Names
- Built-in Functions
- User-Defined Functions
- Function Arrays
- Compile Error Reference
- Examples
Overview¶
I know text-based programming can seem a bit daunting at first if you are not a programmer, but it provides the most compact way of expressing data and functionality instead of using nodes. I will provide a bunch of examples to get you started.
The ORchestra scripting language is evaluated from top to bottom. This means you have to declare variables before using them later in the script.
Best Practice: Define functions first, then data, then use these and finish off with note or cc functions.
The only functionality we care about in the language for ORchestra is functions, integer values, variables, and arrays of constants, variables, or functions. There are no for or while loops, no classes or structs, and no if/else control flow.
All values are integers; there are no floating-point values. Numeric literals are clamped to 0-255, but expressions can produce negative values at runtime (e.g. via - or unary minus). When sending MIDI out, only values between 0 and 127 are allowed, as traditional MIDI dictates.
General Rules¶
- Each new line is a new instruction (uses
\nas delimiter) - All whitespace within a line is ignored
- Use
//for single-line comments - Numeric literals must be between 0 and 255; MIDI output is clamped between 0 and 127
Reserved Keywords¶
The following words are true language keywords and cannot be used as variable names:
note- Creates a MIDI note trackcc- Creates a MIDI control change trackeuc- Euclidean sequence generator functionfn- Defines a user functionend- Ends a function definitionreturn- Returns a value from a functionprint- Outputs a value to the consolen1n2n4n8n16n32n64- Note division literalsCDEFGAB- Musical note letters (when followed by#,b, or an octave)
The symbol $ is also reserved for the global count; see The Global Step.
Built-in function names: ran, bpm, beat, transpose, swing are built-in functions, not keywords. They can be shadowed by variable names, but doing so is confusing and not recommended.
Note: The test keyword is only available in debug builds.
Operators¶
Arithmetic Operators
+ - * / % operate on numbers with standard mathematical precedence. Parentheses can be used to override precedence, just like in regular programming. All arithmetic operators take precedence over logical and comparison operators.
Logical Operators
| ^ & evaluate logic operations on triggers (values greater than 0 are treated as true). These operators always return 0 (false) or 1 (true). You can look these up if you are unsure what they do in traditional programming.
Comparison Operators
> < >= <= == != compare two values and return 0 (false) or 1 (true).
Variables¶
The = operator declares and assigns a variable. We use variables to set values once and reference them in other places, or to give them names for easier use.
Declaration Syntax:
- Variable names can be any length
- Must start with an alphabetic character or _, which means it cannot start with a number
Assignment Examples:
Simple value:
Expressions:
Variable reference:
References another variable.
Data sequence:
See Data Sequences for more details.If you are not sure what a variable is at a given point, you can always output the variable or any value using print().
Data Sequences¶
Data sequences are like arrays in traditional programming: a series of values of the same type, such as numbers or functions.
Key Points:
- Values can be used anywhere; they are always just numbers.
- Sequences are defined using C-style array syntax with [] and comma separators
- Each index represents a value to be used in a step
Example:
You can access a specific index of a data sequence by specifying the variable name followed by [] to indicate that you want to access the value at a specific position in the sequence. The first index of a data sequence is 0, then 1 for the second, and so on. We borrow this idea from how most programming languages represent array access.
a = [64, 65, 66]
c = a[0] // c is now the same as the 1st element of a, which is 64
d = a[1] // d is here now the same as the 2nd element of a, which is 65
You can likewise change a specific element of an array by setting the index.
Like below, we first define a data sequence a and then we change the 1st element of a to 100 instead of 64.
If you would like to access an element of an array with a variable, you can do this too. Simply put the variable in place of the constant number from the examples above.
index = 1
a = [64, 65, 66]
c = a[index] // c is now being set to the 2nd element of a because we use index, which is 1
You do not need to worry about selecting a value that is out of the range of a data sequence. If you pass a value that is bigger than the length, it will wrap around. If you are familiar with modulo %, this is exactly what is happening.
a = [64, 65, 66]
c = a[3] // c is 64 here, since the length is 3 and we specify 3, we go to the front again (3 % 3 = 0)
With these basics in mind, we need to go over one concept that is key to understanding how to use values in ORchestra before we can send some MIDI out.
The Global Step¶
ORchestra has a global count which is received from either a DAW or the internal clock when running in standalone. This is based on tempo and beat division. So effectively this is the beat of ORchestra and how far we are in the sequence. The global step changes on every tick at which the script is evaluated.
Just using a sequence variable without any index will use the global step. This is how we use our data sequences as sequences for MIDI. Everything is wrapped around the length of the sequence, meaning that even if the global count is at 5 and your sequence is 4 long, it will wrap around and be at position 0.
Example:
For Global Step 0 we have a trigger which is 1, and a note of 64 with a velocity of 100 and MIDI channel 1 (default). For Global Step 1 we have a trigger that is 0, so this will not output any note. For Global Step 2 we have a trigger again, and a note value of 65, with the same velocity. Global Step 3 will result in nothing played again, as we do not have a trigger.
Now when the Global Step becomes 4, the length of the trigger and note data sequences is only 4 and we will wrap around, effectively repeating the pattern again. This is part of the power of ORchestra, as we can define triggers and note data sequences of different lengths, having triggers on different notes as we loop around.
Example:
Here our note sequence would be as follows for each global step:
The possibilities get quite complex when combining trigger sequences of different lengths with logical operators, because this phasing functionality of the global step access can create quite long variations from simple patterns.
Global count variable ($):
The special variable $ provides access to the global count (tick number):
- During Prepare (preprocessing), $ evaluates to 0
- During Tick (runtime), $ evaluates to the current globalCount
This is useful for creating evolving patterns and time-based logic:
// Simple counter that increments with each tick
counter = $
// Create a cycling pattern (0, 1, 2, 3, 0, 1, 2, 3...)
pattern = $ % 4
// Conditional trigger based on tick count
trigger = $ > 10 // Becomes 1 after 10 ticks
// Velocity that increases over time
velocity = $ * 2 + 64
// Use in array indexing for sequential access
notes = [60, 62, 64, 65]
note_value = notes[$ % 4]
Now that you have a grasp of the basic ways of representing data in ORchestra, let us use it to send some MIDI.
MIDI Output¶
MIDI can be output with the built-in functions note() or cc().
Important:
- Just like any other functions, arguments can be values, expressions, or variables
- The trigger argument checks whether the value is greater than 0 to determine when to send MIDI. So a 2 is the same as a 127 in this case; only 0 does not output anything
- Even if a step in a data sequence has a value, it will only send a value if the trigger for the same step is non-zero
- The channel parameter is optional in both note and cc; if omitted it defaults to MIDI channel 1
Note Output Syntax:
note(trigger, note, velocity, duration) // channel defaults to 1
note(trigger, note, velocity, duration, channel) // explicit channel
durationsets how long the note is held. Use a note division literal (n8,n16, etc.) or a raw number (see Note Division Literals).
CC Output Syntax:
cc(trigger, controlNumber, controlValue) // channel defaults to 1
cc(trigger, controlNumber, controlValue, channel) // explicit channel
Example:
a = [1, 0, 1, 0] // Trigger pattern
b = [64, 64, 65, 67] // Note sequence
note(a, b, 100, n8) // Trigger: a, Notes: b, Velocity: 100, Duration: 8th note, Channel: 1 (default)
note(a, b, 100, n8, 2) // Same but on MIDI channel 2
Substeps / Sub-divisions¶
Substeps allow you to subdivide individual steps in a sequence, creating more complex rhythmic patterns within a single step. This is achieved using nested arrays. The length of the substep divides the step into equal-length portions.
Substeps work for all parameters of note() or cc(). However, just like normally, if a track is not triggered, it will not play subdivisions for notes or velocities.
Syntax:
A substep is defined by placing an array within the main sequence array:
Key Points:
- Each step in a data sequence can be either a single value or a substep array
- Substep arrays can contain up to 6 values / subdivisions (MAX_SUB_DIVISION_LENGTH)
- When a substep is encountered, each value within it is played in sequence before moving to the next step
- Substeps are useful for creating fills, rolls, or varying note patterns within a single beat
- When using substeps with note() or cc(), the trigger must also use a substep to activate individual sub-divisions
- If a trigger substep has more divisions than the note/velocity/CC value substeps, the system will map to the nearest equivalent value proportionally
Examples:
Basic substep:
// First step has 4 subdivisions, second and third are single values
// If the overall beat division is set to quarter notes, the first step plays 2 16th notes with a 16th note pause in between.
a = [[1, 0, 1, 0], 0, 1]
note(a, 60, 100, n8)
Mixed substeps with different lengths:
// First step subdivided into 3 notes, others are single values.
// Note that here it will only play the first note, as the trigger is not a subdivided one.
notes = [[60, 65, 70], 64, 67]
note(1, notes, 100, n8)
// If we instead define it like this we have triplets playing for the triggers
// and each note in the subdivisions has a trigger for it.
trigger = [[1, 1, 1], 1, 1]
notes = [[60, 65, 70], 64, 67]
note(trigger, notes, 100, n8)
Substep operations:
// Substeps can be used in operations
a = [[60, 65, 70], 0, 0]
b = a + 10 // Adds 10 to each value in the substep
note(1, b, 100, n8) // Plays [70, 75, 80] in the first step
Accessing substep elements:
// You can access individual substeps using array indexing
pattern = [[1, 1, 0], 0, 0]
firstStep = pattern[0] // Gets the entire substep [1, 1, 0]
Mapping substeps with different lengths:
// Trigger has 4 subdivisions, notes only has 2
trigger = [[1, 1, 1, 1], 0, 0]
notes = [[60, 64], 0, 0] // Maps: 60, 60, 64, 64 (nearest value)
note(trigger, notes, 100, n8)
// This allows fewer note values to span more trigger divisions
Note Division Literals¶
ORchestra provides note division literals as a convenient way to specify durations and beat divisions. These compile to their underlying numeric values and can be used anywhere a number is expected.
Literal to value mapping:
| Literal | Underlying value | Note type |
|---|---|---|
n1 |
1 | Whole note |
n2 |
2 | Half note |
n4 |
3 | Quarter note |
n8 |
4 | 8th note |
n16 |
5 | 16th note |
n32 |
6 | 32nd note |
n64 |
7 | 64th note |
Usage:
As the duration argument in note():
As the division argument in beat():
These are not special types — they are simply integer constants. You can use the raw numbers directly if you prefer (beat(4) is identical to beat(n8)). Because they compile to plain numbers, you can also use them in expressions: n8 + 1 evaluates to 5.
Musical Note Names¶
Note pitches can be represented in two ways:
1. Raw MIDI values (0-127):
2. Musical notation:
- Capital letter for the note (C, D, E, F, G, A, B)
- Optional # (sharp) or b (flat)
- Octave number (0-10)
Example:
When compiled, note names are converted to MIDI values, allowing them to be combined with other values and used in expressions. This means we can easily transpose or do other math with them:
Built-in Functions¶
Euclidean Sequence Generator¶
The euc(hits, length, shift) function generates Euclidean rhythm patterns.
Parameters:
- hits - Number of beats to distribute
- length - Total length of the sequence
- shift - (Optional) Number of steps to rotate the pattern. Positive values shift right, negative values shift left.
Returns: A data sequence containing 0s and 1s (designed for triggers)
Note: Can only be used for variable assignment, not as a direct parameter.
Example:
// Euclidean sequence with 4 hits divided across 8 steps
a = euc(4, 8)
note(a, 64, 100, n8) // Use the euclidean pattern as a trigger
// Shifted Euclidean sequence
b = euc(5, 8, 2) // 5 hits across 8 steps, shifted by 2
Random Number Generator¶
The ran(low, high) function generates random values at runtime.
Parameters:
- low - Minimum value (inclusive)
- high - Maximum value (inclusive)
Returns: A random integer between low and high
Note: Evaluated at every tick, providing new random values each time.
Example:
vel = ran(50, 100) // Random velocity between 50-100
note(1, 64, vel, n8) // Play C4 with random velocity
BPM (Tempo) Control¶
The bpm() function sets the tempo for the sequencer.
Note: - Sets the internal tempo instead of using the value from the DAW or UI - Limited to 0-255 range due to language constraints - Called during initialization, not every tick - Any variable or expression can be used to set this as well
When running in standalone this is the only way to set the tempo. When running in a DAW, if bpm() is used, it overrides the tempo information received from the DAW.
Example:
You can change tempo at runtime if you wish. It can behave unpredictably, but it is fun.
tempo = [120, 120, 140, 120]
bpm(tempo) // will set the bpm to 120 or 140 depending on the global count
Beat Division Control¶
The beat() function sets the note division (timing resolution) for the sequencer.
Parameters:
- division - Note division value. Use either a Note Division Literal or the underlying number.
Note:
- Sets the internal note division instead of using the value from the DAW or UI
- Determines how often the sequencer steps forward
- Called during initialization, not every tick
- The nX literals compile to the numbers shown above; either form can be used
Example:
beat(n8) // Set to 8th notes (same as beat(4))
bpm(120)
pattern = euc(3, 8)
note(pattern, C4, 100, n8) // Triggers on 8th notes at 120 BPM
Transpose¶
There is a built-in function for transposing all notes going to the MIDI out. Obviously you can define your own transposing function, but sometimes you want to be able to transpose globally for everything that is output.
Swing¶
There is a built-in function for adding swing to the sequencer timing. swing(amount) shifts every odd step early or late relative to the straight grid.
Parameters:
- amount - 0 to 100. 50 is no swing (straight timing). Values below 50 shift odd steps earlier; values above 50 shift them later.
Notes:
- Maximum shift is half a step duration in either direction.
- The value is clamped to the 0-100 range.
- Removing swing() from a script and recompiling resets swing back to 50.
beat(n16)
swing(65) // slight late swing on every odd 16th note
pattern = euc(4, 8)
note(pattern, C4, 100, n16)
Print¶
There is a built-in function for outputting any value to the console. print() takes one value or expression and outputs it. It is a great way to inspect a sequence of notes or a final value if you are not sure why a certain value is the way it is at the end of a script, for example. It only outputs raw values as numbers, so note values will be shown as MIDI numbers.
a = [0, 100]
print(a) // will alternate between 0 and 100 depending on the global count
// `print($)` is a great way to inspect what the global count is
User-Defined Functions¶
You can define reusable functions with the fn keyword. Functions are inlined at the call site (their body is copied into the instruction stream).
Syntax:
Calling functions:
Returning values:
Use the return keyword to leave a value on the stack so the function can be used inside expressions:
fn functionName(param)
return param * 2
end
a = functionName(5) // a == 10
b = functionName(3) + 1 // b == 7
Key Points:
- Functions must be defined before they are called
- Parameters become global variables; they persist after the call
- Functions can call other previously defined functions and built-in functions
- Nested function definitions are not allowed
- Function names cannot collide with built-in function names or variable names
- return leaves the result on the stack. Calling a returning function as a statement (not in an expression) will leak one stack value — prefer using the return value when a function has return
Using functions in expressions:
Functions that use return can appear anywhere an expression is valid — assignments, arrays, and as arguments to other functions:
fn scale(x)
return x * 2
end
a = scale(10) // a == 20
b = scale(5) + scale(3) // b == 16
c = [scale(1), scale(2)] // c == [2, 4]
note(1, scale(C4), 100, n8) // use return value as note pitch
Examples:
// Simple function with no parameters
a = [0]
fn setA()
a[0] = 99
end
setA()
// Function with parameters
fn playChord(root, vel)
note(1, root, vel, n8)
note(1, root + 4, vel, n8)
note(1, root + 7, vel, n8)
end
playChord(C4, 100)
playChord(E4, 80)
// Function that returns a value
fn trans(note, semitones)
return note + semitones
end
melody = [trans(C4, 0), trans(C4, 4), trans(C4, 7)]
note(1, melody, 100, n8) // plays C4, E4, G4 in sequence
Function Arrays¶
Function arrays let you group named functions and dispatch to one at runtime based on an index. This is useful for creating song sections, alternating grooves, or any scenario where you want to switch between predefined blocks of behavior.
How they differ from value arrays:
| Feature | Value Array | Function Array |
|---|---|---|
| Syntax | a = [1, 2, 3] |
a = [func1, func2] |
| Elements | Numbers, expressions, variables | Function names only |
| Access | a[$] reads a value |
a[$] executes a function |
| Return value | The stored value | Ignored (function runs for side effects) |
Creating a function array:
arrayName = [function1, function2, function3]
arrayName[index]() // executes the function at the index
The compiler detects a function array by checking whether the first element is the name of a previously defined function. If so, every element must be a valid function name.
Dispatching with an index:
arrayName[$]() // use global count as index
arrayName[$ % 4]() // any expression works
arrayName[someVar]() // variables work too
The index is wrapped using modulo, so arrayName[5] on a 2-element array executes function2 (index 1).
Key Points:
- Functions in a function array are executed for their side effects; any return value is discarded
- Functions must be defined before they are used in a function array
- Function arrays select which function to execute using index % array_length
- Normal value arrays and function arrays cannot be mixed in the same declaration
Example:
// Define two functions with different note sequences
fn verse()
note(1, C4, 100, n8)
note(1, E4, 80, n8)
end
fn chorus()
note(1, G4, 127, n8)
note(1, C5, 127, n8)
end
// Create function array -- alternates between verse and chorus
song = [verse, chorus]
song[$]() // verse on even ticks, chorus on odd ticks
Compile Error Reference¶
This section lists the error and warning messages ORchestra can produce while compiling a script, along with what causes them and how to fix them.
Scanner errors¶
These errors happen while the script is being turned into tokens, before compilation proper begins.
Unexpected Character '<char>'¶
The scanner found a character it does not recognize as part of the ORchestra language.
What to check:
- Make sure operators use the correct characters: + - * / % | ^ & = > < !.
- Ensure parentheses ( ) and square brackets [ ] are plain ASCII, not typographic equivalents.
- Note: { and } are not used in ORchestra scripts.
Syntax and expression errors¶
These errors happen when the compiler sees a token in a place it does not expect.
Unexpected Character '<char>'¶
The compiler encountered a token it did not expect while parsing an expression or statement.
What to check:
- Verify the order of tokens, e.g. note(1, 64, 100, n8) not note 1, 64, 100, n8.
- Make sure every opening bracket has a matching closing bracket.
- Check that operators have values on both sides, e.g. a + b not a +.
Unexpected Character '<char>'. Start a line with an variable identifier, or note() or cc()¶
A line began with something that is not a valid statement start.
What to check:
- Each line should start with a variable assignment (a = ...), a function call (myFn()), note(...), cc(...), fn ..., return ..., or end.
- Comments must start with //.
Missing a <token>¶
A required closing token was not found where expected.
What to check:
- Add the missing bracket, parenthesis, or similar token reported in the message.
- Common cases: missing ] for array access, missing ) for function calls, missing ] at the end of an array literal.
Unexpected end, you're missing a <token>¶
The compiler reached the end of a line or the end of the script while still expecting a closing token.
What to check: - Close all open parentheses and brackets before the end of the line. - Make sure multi-line function calls or arrays are not split incorrectly.
Function errors¶
Unexpected function or variable '<name>'¶
The compiler does not recognize the given name as a defined function or variable.
What to check: - Define variables before using them. - Define functions before calling them. - Check for typos in the name. - Make sure the name is not a reserved keyword.
Expected '<expected>' function parameters, but received <received>¶
A function call was given the wrong number of arguments.
What to check:
- Count the arguments in the call and compare them to the function definition.
- Remember that note() takes 4 or 5 arguments, cc() takes 3 or 4, euc() takes 2 or 3, etc.
- Do not use extra or missing commas.
Function '<name>' is already defined¶
A function with this name has already been declared.
What to check: - Use a unique name for each function. - Function names cannot shadow built-in functions either.
'<name>' is already used as a variable name¶
You are trying to define a function with a name that is already used as a variable.
What to check: - Choose a different function name, or rename the existing variable first.
Cannot define a function inside another function¶
A fn ... end block was found inside another fn ... end block.
What to check: - Move the nested function definition out to the top level. - ORchestra does not support nested functions.
return can only be used inside a function body¶
A return statement appeared outside of any function definition.
What to check:
- Only use return between fn and end.
function '<name>' has no return value and cannot be used in an expression¶
A function that does not use return was called inside an expression or assignment.
What to check: - Only use returning functions inside expressions. - Call non-returning functions as standalone statements.
Function array errors¶
function arrays can only contain functions¶
A function array contained something that is not a previously defined function name.
What to check:
- Function arrays must contain only function names, e.g. [verse, chorus].
- Do not put numbers, variables, or expressions in a function array.
'<name>' is not a defined function¶
A name in a function array does not match any defined function.
What to check: - Define the function before creating the function array. - Check for typos.
function '<name>' has <n> parameters, expected <expected>¶
All functions in a function array must accept the same number of parameters.
What to check: - Make every function in the array use the same parameter count.
function '<name>' must agree on return value with other functions in the array¶
All functions in a function array must either all return a value or all return nothing.
What to check:
- Make sure every function in the array uses return, or none of them do.
function array '<name>' has no return value and cannot be used in an expression¶
A function array whose functions do not return values was used inside an expression.
What to check:
- Use function arrays that return values only inside expressions.
- Otherwise call them as standalone statements, e.g. song[$ % 4]().
Array and data-sequence errors¶
Max Length for array exceeded. Can't be longer than <maxLength>¶
An array literal has more elements than allowed.
What to check:
- Top-level data sequences are limited to MAX_DATASEQUENCE_LENGTH (32) elements.
- Substep arrays are limited to MAX_SUB_DIVISION_LENGTH (6) elements.
Note and division errors¶
Notes need at least a capital letter for the note name, and an octave from 0-10. Optional is # or b¶
A note identifier could not be parsed because it is missing required parts.
What to check:
- Use the form C4, F#5, Db3, etc.
- The note letter must be uppercase A–G.
- The octave must be a number from 0 to 10.
Note Letters are reserved for notes, couldn't convert this to note¶
A token started with a note letter but the rest could not be parsed as a valid note.
What to check:
- Verify the format: uppercase letter, optional # or b, then octave number.
- Avoid using note names as variable names.
Unknown note division literal¶
A note division literal was not recognized.
What to check:
- Use only n1, n2, n4, n8, n16, n32, or n64.
Function-definition errors¶
Unexpected end, you're missing a end¶
A function definition reached the end of the script without finding end.
What to check:
- Add end at the end of every fn block.
- Make sure end is on its own line.
Warnings¶
Warnings do not stop compilation, but they indicate that a value was clamped or limited.
Constant value <value> exceeds 255, capping to 255¶
A numeric constant was larger than 255 and was capped.
Constant value <value> is negative, capping to 0¶
A numeric constant was negative and was capped to 0.
Note value can't be greater than 127, capping value¶
A note name resolved to a MIDI value above 127 and was capped.
Note value can't be smaller than 0, capping value¶
A note name resolved to a MIDI value below 0 and was capped.
Only 255 unique variables are supported¶
The script uses more than 255 unique variable/function-parameter names.
Examples¶
Basic Pattern¶
Euclidean Rhythm¶
Combining Sequences with Logic¶
// Create two patterns
a = euc(3, 8)
b = euc(5, 8)
// Combine with XOR - triggers when only one is active
combined = a ^ b
note(combined, D4, 100, n8)
Phasing Patterns¶
// Different length sequences phase over time
pattern1 = euc(3, 8)
pattern2 = euc(5, 13)
// Combine with AND - both must be active
both = pattern1 & pattern2
note(both, E4, 120, n8)
Random Velocity and Notes¶
// Random velocity for each triggered note
trigger = euc(4, 8)
velocity = ran(80, 127)
note(trigger, C4, velocity, n8)
// Random note selection
notes = [C4, D4, E4, G4, A4]
randomNote = notes[ran(0, 4)]
note(1, randomNote, 100, n8)
Using CC Messages¶
// Control filter cutoff with sequence
cutoff = [64, 80, 100, 120]
cc(1, 74, cutoff, 1) // Always trigger, CC#74 (filter cutoff)
Setting Tempo and Division from Script¶
// Override DAW tempo and use fast 16th notes
bpm(120)
beat(n16) // 16th notes
// Create rapid hi-hat pattern
hihat = euc(11, 16)
note(hihat, 42, 80, n8, 10)
// Kick and snare on quarter notes
kick = [1, 0, 0, 0]
snare = [0, 0, 0, 0, 1, 0, 0, 0]
note(kick, 36, 100, n8, 10)
note(snare, 38, 100, n8, 10)
Modifying Arrays with Index Assignment¶
// Create a melody and modify specific notes
melody = [C4, D4, E4, F4]
melody[1] = G4 // Change second note to G4
melody[3] = A4 // Change fourth note to A4
// Create dynamic patterns
kick = [1, 0, 0, 0]
kick[2] = 1 // Add extra kick on third beat
note(kick, 36, 100, n8, 10) // Modified kick pattern
note(1, melody, 100, n8) // Modified melody
Complex Rhythm¶
// Kick on 1 and 3
kick = [1, 0, 1, 0]
// Snare on 2 and 4
snare = [0, 1, 0, 1]
// Hi-hat euclidean pattern
hihat = euc(7, 8)
note(kick, 36, 100, n8, 10) // Kick on channel 10
note(snare, 38, 100, n8, 10) // Snare on channel 10
note(hihat, 42, 80, n8, 10) // Hi-hat on channel 10
Using Substeps for Drum Fills¶
// Create a pattern with a drum fill on the 4th step
trigger = [1, 0, 1, [1, 0, 1, 1]] // Fourth step has rapid hits
note(trigger, 38, 100, n8, 10) // Snare drum
// Alternating note pattern with substep variation
notes = [[60, 64, 67], 60, 62, 64] // First step plays notes in rapid sequence
note(1, notes, 100, n8)
// Modulo operation example with substeps
counter = [[0, 1, 2, 3], 4, 5, 6]
everyOther = counter % 2 // Creates pattern: [[0,1,0,1], 0, 1, 0]
note(everyOther, C4, 100, n8)
User-Defined Function Example¶
// Build a major chord from a root note
fn chord(root, vel)
note(1, root, vel, n8)
note(1, root + 4, vel, n8)
note(1, root + 7, vel, n8)
end
chord(C4, 100)
chord(F4, 80)
Function Array Example¶
// Alternate between two sections every tick
fn verse()
note(1, C4, 100, n8)
note(1, E4, 80, n8)
end
fn chorus()
note(1, G4, 127, n8)
note(1, C5, 127, n8)
end
song = [verse, chorus]
song[$]()
Using Global Count ($) for Evolving Patterns¶
// Create a cycling pattern that repeats every 4 ticks
phase = $ % 4
pattern = [1, 0, 1, 0]
trigger = pattern[phase]
note(trigger, C4, 100, n8)
// Gradually increase velocity over time
velocity = ($ * 2) % 127 + 20 // Starts at 20, increases by 2 each tick
kick = [1, 0, 0, 0]
note(kick, 36, velocity, n8, 10)
// Change MIDI CC value based on global count
filterValue = ($ * 4) % 127 // Sweeps filter from 0 to 127
cc(1, 74, filterValue, 1) // CC#74 (filter cutoff)
// Conditional pattern that activates after tick 16
lateEntry = $ > 16
snare = [0, 1, 0, 1]
trigger = snare & lateEntry // Only plays after tick 16
note(trigger, 38, 100, n8, 10)
// Create a sequence that changes every 8 ticks
octave = ($ / 8) % 3 // Switches between 0, 1, 2
baseNote = 60 + (octave * 12) // C4, C5, C6
note(1, baseNote, 100, n8)