ORchestra is currently in alpha, pretty much feature complete but still working on small fixes and bugs.
ORchestra is a powerful MIDI sequencer plugin that generates and combines sequences of notes or midi CC messages. It features a custom scripting language for creating complex rhythmic patterns through logical operations and phasing of different lengths of data. The ORchestra language does not aim to be a complete programming language, and have been created to fit the need and specific vision for ORchestra. There's many other live coding tools out there, and it's also not trying to replace these. Rather, this is my brain child and idea of what a fun experimental midi scripting language inside a DAW could be, and invites for experimenting with phasing loops to create semi algorithmic compositions or patterns.
The original prototype that sparked the idea can be found here: https://github.com/Tronhjem/EuclidsCombinator
Disclaimer: AI has been used on this project to try out new features like github copilots agents on git, by implementing simple extensions of the lanugage, and writing unit tests etc. Majority of the code is still written by me and this is by no means a vibe coded project.
- Euclidean Rhythm Generation: Create rhythmic patterns using the euclidean algorithm
- Sequence Combination: Use logic operations (
&,^,|) to combine sequences - Phasing Patterns: Sequences of different lengths phase and evolve over time
- Custom Scripting Language: Powerful yet simple syntax for defining musical patterns
- MIDI Output: Generates MIDI notes and CC messages
- Mathematical Operations: Full arithmetic support with standard precedence
- Comparison Operators: Compare values and create conditional patterns
- Prerequisites
- Quick Start
- Build Instructions
- Running Tests
- Fuzzing
- Syntax and Language Reference
- Examples
- Troubleshooting
Before building ORchestra, ensure you have the following installed:
- CMake (version 3.22 or higher)
- C++ Compiler with C++17 support (GCC, Clang, or MSVC)
- Git (for cloning and managing submodules)
- JUCE Framework (automatically fetched as a git submodule)
You can run this with the Projucer, and open the ORChestra.jucer file, and generate and build the project, which is by far the easiest if you do not want to deal with CMake. You'll find the jucer file in /ORchestra/ORChestra.jucer. Follow documentation for JUCE if you get stuck on how to run the project with the Projucer.
Alternatively, you can get started using the provided setup script:
./setup.shThis will:
- Initialize and update the JUCE submodule
- Create a build directory
- Configure CMake with default settings
Then build the project:
cd build
cmake --build .Step 1: Clone and Initialize Submodules
git clone https://github.com/Tronhjem/ORchestra.git
cd ORchestra
git submodule update --init --recursiveStep 2: Create Build Directory
mkdir build
cd buildThe CMake build supports separate compilation of the plugin and tests for faster development:
Build tests only (no JUCE dependency, faster):
cmake -DBUILD_PLUGIN=OFF -DBUILD_TESTS=ON ..
cmake --build .
./UnitTests/ORchestraTestsBuild plugin only (requires JUCE):
cmake -DBUILD_PLUGIN=ON -DBUILD_TESTS=OFF ..
cmake --build .Build both (default):
cmake ..
cmake --build .Tests use the Catch2 framework and can be built independently from the JUCE plugin. This allows for quick test iterations without compiling the entire JUCE framework.
cd build
cmake -DBUILD_PLUGIN=OFF -DBUILD_TESTS=ON ..
cmake --build .
./UnitTests/ORchestraTestsFuzzing targets the ORchestra engine with random input to catch crashes, hangs, or assertion failures. The fuzzing suite is completely separate from unit tests and has no JUCE dependency, making it suitable for Windows builds and CI servers.
There are two fuzz targets:
- ORchestraFuzz — Scanner and Compiler only (parser robustness)
- ORchestraFuzzVM — End-to-end: scan, compile, VM::Prepare, and VM::Tick (runtime robustness)
The VM fuzzer includes a corpus of seed scripts (Fuzzing/corpus/) that are mutated rather than generating pure random bytes. This finds deeper bugs much faster.
# Scanner/Compiler fuzzer
./run-fuzzer.sh scanner # default: 100,000 iterations
./run-fuzzer.sh scanner 42 500000
# VM end-to-end fuzzer (uses corpus seeds)
./run-fuzzer.sh vm # default: 100,000 iterations
./run-fuzzer.sh vm 42 Fuzzing/corpuscmake -DBUILD_PLUGIN=OFF -DBUILD_TESTS=OFF -DBUILD_FUZZING=ON -B build
cmake --build build --target ORchestraFuzz --target ORchestraFuzzVM
# Scanner/Compiler fuzzer
./build/Fuzzing/ORchestraFuzz 42 500000
# VM fuzzer with corpus
./build/Fuzzing/ORchestraFuzzVM 42 Fuzzing/corpusThe VM fuzzer saves crash reproducers to build/Fuzzing/crashes/ and writes the last tested input to build/Fuzzing/_last_input.txt.
For coverage-guided fuzzing with AddressSanitizer, use upstream Clang (not AppleClang) and enable FUZZING_USE_LIBFUZZER:
cmake -DBUILD_FUZZING=ON -DFUZZING_USE_LIBFUZZER=ON -DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++ -B build
cmake --build build --target ORchestraFuzz --target ORchestraFuzzVM
./build/Fuzzing/ORchestraFuzzVM -max_total_time=300 Fuzzing/corpus/Common libFuzzer flags:
-max_total_time=N— run for N seconds-runs=N— run N total iterations
libFuzzer runs indefinitely by default until stopped or a crash is found.
The ORchestra scripting language is evaluated from top to bottom. You must declare variables before using them later in the script.
Best Practice: Define data sequences first, then create tracks that use them.
- Each new line is a new instruction (uses
\nas delimiter) - All whitespace within a line is ignored
- Use
//for single-line comments - All values are 8-bit integers, limited to 0-127 for MIDI compatibility
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 > 0 are true).
These operators always return 0 (false) or 1 (true).
Comparison Operators
> < >= <= == != compare two values and return 0 (false) or 1 (true).
The = operator declares and assigns a variable.
Declaration Syntax:
- Variable names can be any length
- Must start with an alphabetic character or
_ - Cannot start with a number
Assignment Examples:
Simple value:
a = 64Expressions:
a = 64 + 2
c = a | 1
z = a * (2 + 4)Data sequence:
a = [127, 0, 64]see for more details Data Sequences
Variable reference:
References another variable. If it's a sequence, it evaluates at the current global step.
c = [64, 64, 64]
a = c // References the sequence cArray indexing:
Access specific values in a data sequence (zero-indexed):
c = [64, 65, 70]
a = c[0] // value is 64
x = c[1] // value is 65Array index assignment:
Set specific values in a data sequence:
c = [64, 65, 70]
c[0] = 60 // Changes first element to 60
c[2] = 72 // Changes third element to 72
// Can use expressions
notes = [60, 62, 64]
notes[1] = notes[0] + 5 // Sets second element to 65Boolean operations:
Note that logical and comparison operators always return 0 or 1:
a = [64, 63]
c = a[0] > 0 // value is 1
z = [1, 0]
x = [0, 1]
y = z[0] & x[0] // value is 0 (AND operation: 1 & 0 = 0)The following words are reserved and cannot be used as variable names:
note- Creates a MIDI note trackcc- Creates a MIDI control change trackran- Random number generator functioneuc- Euclidean sequence generator functionbpm- Sets the BPM (tempo) for the sequencerbpmDiv- Sets the note division (timing resolution)fn- Defines a user functionend- Ends a function or pattern definitionptn- Defines a pattern for use in pattern arraysreturn- Returns a value from a function
Note: The keywords print and test are reserved for debugging purposes but are only available in debug builds.
Data sequences are arrays of numeric values used to create musical patterns.
Key Points:
- Values can be used anywhere - they're always just numbers
- For triggers (first argument of
noteorcc), values > 0 are treated as true - For notes, velocities, and CC values, the values are sent as defined
- Sequences are defined using C-style array syntax with
[]and comma separators - Each index represents a value to be used in a step
Example:
a = [64, 64, 65] // Simple 3-step sequenceTracks output MIDI messages and are created using the note or cc keywords.
These are used to send data from your Data Sequences to midi.
Important:
- Tracks are not variables - they execute immediately
- Arguments can be values, expressions, or variables
- The trigger argument checks if value > 0 to determine when to send MIDI
- Even if a step in a Data Sequence has a value, it will only send a value if the trigger of the same step is true.
- The
channelparameter is optional in bothnoteandcc- if omitted it defaults to MIDI channel 1
Note Track Syntax:
note(trigger, note, velocity, duration) // channel defaults to 1
note(trigger, note, velocity, duration, channel) // explicit channelCC Track Syntax:
cc(trigger, controlNumber, controlValue) // channel defaults to 1
cc(trigger, controlNumber, controlValue, channel) // explicit channelExample:
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 2ORchestra has a global count which is received either from a DAW, or calculated as the position since start, based on tempo and note division. This is used to determine which index of each Data Sequence it should pick from. Just setting a variable without any index will use the global step. This is how we use our Data Sequences with tracks as sequences for midi. Everything is wrapped around the length of the sequence, meaning even if the global count is at 5, and your sequence is 4 long, it will wrap around and be a position 0.
Example:
a = [1, 0, 1, 0]
b = [64, 65, 66, 67]
note(a, b, 100, n8)For Global Step 0 we have a trigger which is 1, and a note of 64 with a velocity of 100 and MIDI channel of 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, and 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 Sequence is only 3 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:
a = [1, 0, 1]
b = [64, 65, 66, 67]
note(a, b, 100, n8)Here our note sequence would be as following for each global step:
Step Trigger Note result
0 1 64
1 0 --
2 1 66
3 1 67
4 0 --
5 1 65
6 1 66
The possibilities get quite complex when combining trigger sequences of different lengths with logical operators as it can create quite long variations with simple patterns, because of this phasing functionality of the Global Step accessing.
Global count variable ($):
The special variable $ provides access to the global count (tick number):
- During
Prepare(preprocessing),$evaluates to0 - During
Tick(runtime),$evaluates to the currentglobalCount
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]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 equally 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 example on notes or velocities.
Syntax:
A substep is defined by placing an array within the main sequence array:
a = [[value1, value2, ...], normalValue, ...]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()orcc(), 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 note division is set to 4th notes, the first step is playing 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 have 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 stepAccessing 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 divisionsNotes can be represented in two ways:
1. Raw MIDI values (0-127):
a = [60, 62, 64] // C4, D4, E42. Musical notation:
- Capital letter for the note (C, D, E, F, G, A, B)
- Optional
#(sharp) orb(flat) - Octave number (0-10)
Example:
a = [C4, C#4, Db2]When compiled, note names are converted to MIDI values, allowing them to be combined with other values and used in expressions.
The euc(hits, length) function generates euclidean rhythm patterns.
Parameters:
hits- Number of beats to distributelength- Total length of the sequence
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 triggerThe 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 velocityThe bpm(tempo) function sets the tempo for the sequencer.
Parameters:
tempo- BPM value (0-127). Common values: 60 = slow, 80 = moderate, 120 = fast
Note:
- Sets the internal tempo instead of using the value from the DAW or UI
- Limited to 0-127 range due to language constraints
- Called during initialization, not every tick
Example:
bpm(120) // Set tempo to 120 BPM
pattern = euc(4, 8)
note(pattern, C4, 100, n8)The bpmDiv(division) function sets the note division (timing resolution) for the sequencer.
Parameters:
division- Note division value (1-7):1= Whole note2= Half note3= Quarter note (default)4= 8th note5= 16th note6= 32nd note7= 64th note
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
Example:
bpmDiv(4) // Set to 8th notes
bpm(120)
pattern = euc(3, 8)
note(pattern, C4, 100, n8) // Triggers on 8th notes at 120 BPMYou can define reusable functions with the fn keyword. Functions are inlined at the call site (their body is copied into the instruction stream).
Syntax:
fn functionName
// body
end
fn functionName(param1, param2)
// body using param1, param2
endCalling functions:
functionName()
functionName(arg1, arg2)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 == 7Key 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, variable names, or pattern names
returnleaves 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 hasreturn
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 pitchExamples:
// 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 transpose(note, semitones)
return note + semitones
end
melody = [transpose(C4, 0), transpose(C4, 4), transpose(C4, 7)]
note(1, melody, 100, n8) // plays C4, E4, G4 in sequencePatterns are code blocks defined with the ptn keyword. Unlike functions, patterns cannot be called directly -- they can only be grouped into pattern arrays for runtime dispatch based on an index.
Defining patterns:
ptn patternName
// body
endCreating a pattern array:
arrayName = [pattern1, pattern2, pattern3]Calling a pattern array:
arrayName($) // use global count as index
arrayName($ % 4) // any expression works
arrayName(someVar) // variables work tooKey Points:
- Patterns have no parameters and no parentheses in their definition
- Pattern arrays select which pattern to execute using
index % array_length - Patterns must be defined before being used in an array
- Only
ptndefinitions can be used in pattern arrays (notfnfunctions) - Pattern names cannot collide with function names, variable names, or other pattern names
Example:
// Define two patterns with different note sequences
ptn verse
note(1, C4, 100, n8)
note(1, E4, 80, n8)
end
ptn chorus
note(1, G4, 127, n8)
note(1, C5, 127, n8)
end
// Create pattern array -- alternates between verse and chorus
song = [verse, chorus]
song($) // verse on even ticks, chorus on odd ticks// Simple kick drum pattern
kick = [1, 0, 0, 0]
note(kick, 36, 100, n8) // C1 on MIDI channel 1// Create a euclidean pattern
pattern = euc(5, 8)
note(pattern, C4, 100, n8)// 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)// 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 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)// Control filter cutoff with sequence
cutoff = [64, 80, 100, 120]
cc(1, 74, cutoff, 1) // Always trigger, CC#74 (filter cutoff)// Override DAW tempo and use fast 16th notes
bpm(120)
bpmDiv(5) // 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)// 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// 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// 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)// Helper that builds a 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) // C major chord
chord(F4, 80) // F major chord
chord(G4, 90) // G major chord// Function that computes a value used in an expression
fn clampedVelocity(v)
return v % 64 + 64 // keep velocity between 64-127
end
trigger = euc(4, 8)
note(trigger, C4, clampedVelocity($), n8) // velocity changes each tick// Reusable melody transposer
fn up(n)
return n + 12
end
base = [C4, D4, E4, G4]
high = [up(C4), up(D4), up(E4), up(G4)] // one octave up
trigger = euc(4, 8)
note(trigger, base, 100, n8)
note(trigger, high, 70, n8, 2) // doubled an octave up on channel 2// Two patterns that alternate each tick
ptn verse
note(1, C4, 100, n8)
note(1, E4, 80, n8)
end
ptn chorus
note(1, G4, 127, n8)
note(1, C5, 127, n8)
note(1, E5, 110, n8)
end
song = [verse, chorus]
song($) // verse on even ticks, chorus on odd ticks// Four patterns cycling every 4 ticks
ptn intro
note(1, C4, 80, n8)
end
ptn build
note(1, C4, 100, n8)
note(1, G4, 90, n8)
end
ptn drop
note(1, C4, 127, n8)
note(1, E4, 127, n8)
note(1, G4, 127, n8)
end
ptn break
note(0, C4, 0, n8) // silence
end
arrangement = [intro, build, drop, break]
arrangement($ % 4)// Patterns using euclidean rhythms, switched by section
ptn sparse
trigger = euc(3, 8)
note(trigger, C4, 90, n8)
end
ptn dense
trigger = euc(7, 8)
note(trigger, C4, 100, n8)
end
groove = [sparse, dense]
groove($ / 8) // switch pattern every 8 ticks// 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)Problem: JUCE not found error
Solution: Ensure you've initialized the submodule:
git submodule update --init --recursive
Problem: CMake version too old
Solution: Install CMake 3.22 or higher:
- Ubuntu: sudo apt-get install cmake
- macOS: brew install cmake
Problem: Build fails with missing compiler
Solution: Install build essentials:
- Ubuntu: sudo apt-get install build-essential
- macOS: Install Xcode Command Line Tools
Problem: Reserved keyword error
Solution: Check that you're not using: note, cc, ran, euc, print, or test as variable names
Problem: Note name parsing error
Solution: Ensure note names follow the format: [A-G][#/b]?[0-10]
Examples: C4, F#5, Db3
Problem: Index out of bounds
Solution: Verify array indices are within range (0 to array length - 1)
- Open an issue on GitHub
- Check existing issues for similar problems
- Include your ORchestra script and error messages when reporting bugs
AGPLv3 - see LICENSE file for details.
