kumo
Published on: Mon Dec 15 2025
Overview
kumo is a command-line tool for generating MIDI events. The project is inspired by Iannis Xenakis theories and this paper.
Written in Go, the system combines probability distributions and number-theoretic filtering to produce pseudo-stochastic musical patterns.
System Architecture
kumo is organized into modular packages, each addressing a stage of the generative workflow.
The CLI
The cmd package defines the executable interface using the Cobra library.
kumo.go: Loads configuration via Viper from$HOME/.kumo/kumo.yaml. If the file does not exist, a default configuration is generated and saved.play.go: Runs the real-time generation loop and sends MIDI events to a MIDI port.rec.go: Generates a MIDI sequence and writes it to a.midfile.
This layer translates user commands into actions performed by the rest of the underlying logic.
Probabilistic Sampling
The stochastic package contains the probability distributions used to generate values such as pitch, duration, and velocity. It consists of:
-
The
Distributioninterface with aSample()andString()methods. -
The implementations of the above interface, for some of the most common probability distributions:
UniformDistGaussianDistPoissonDist
Sieve Parser
The sieve package implements a domain-specific language for defining musical sieves.
A sieve filters integers (e.g., MIDI pitches or rhythmic values) based on modular arithmetic conditions, enabling a degree of control over which values are sampled by the stochastic generator.
Design Approach
The sieve parser is implemented as a hand-written recursive-descent parser, which:
- Lexes the input string into tokens (integers, operators, parentheses).
- Parses the tokens into an abstract syntax tree.
- Evaluates the AST to determine if an integer satisfies the sieve conditions.
Syntax and Semantics
The sieve language supports:
- Modular arithmetic conditions (e.g.,
12@0forn % 12 == 0). - Logical operators:
|(OR): Union of conditions.&(AND): Intersection of conditions.~(NOT): Complement of a condition.
- Parentheses for grouping and precedence control.
Example Expression:
12@0 | 12@4 | 12@7 & ~3@0
12@0: Integers wheren % 12 == 0|: Logical OR (union).&: Logical AND (intersection).~: Logical NOT (complement).
Implementation Details
1. Lexer
The Lexer tokenizes the input string, handling:
- Integers (e.g.,
12,-3). - Operators (
@,|,&,~). - Parentheses (
(,)). - Whitespace (ignored).
2. Parser
The Parser constructs an AST from the token stream using recursive descent with operator precedence:
- Prefix parsing: Handles
~(NOT) and parentheses. - Infix parsing: Handles
|(OR) and&(AND) with precedence rules. - Optimizations: Simplifies double negations (e.g.,
~~3@1→3@1).
3. AST Evaluation
The AST nodes implement an Evaluate(n int) bool method:
SieveNode: Checks ifnsatisfies a modular conditionBinaryOpNode: Evaluates logical operations (|,&).NotNode: Negates the evaluation of its child node.
Example AST Evaluation:
func (bon BinaryOpNode) Evaluate(n int) bool {
switch bon.Op {
case TokenOr: return bon.Left.Evaluate(n) || bon.Right.Evaluate(n)
case TokenAnd: return bon.Left.Evaluate(n) && bon.Right.Evaluate(n)
default: return false
}
}
func (sn SieveNode) Evaluate(n int) bool {
return sn.Sieve.Check(n) // Checks n % Modulus == Residue
}
By integrating sieves with stochastic processes, kumo achieves controlled randomness, balancing structure and unpredictability in algorithmic composition.
MIDI Events
The midi package handles the core event generation logic and MIDI device communication.
ports.go: Detects and connects to MIDI devices.timing.go: Handles the logic of the timing generation for the MIDI events (note duration, silence duration, quantization).pitch.go: Handles the logic for the pitch generation of MIDI events.generator.go: Uses the timing and pitch generators for producing the actual MIDI events.streamer.go: Handles the emission of generated events directly on the selected MIDI port.writer.go: Writes the generated MIDI events to a file.
Generator
Orchestrates MIDI event generation using:
- Pitch generation (filtered by sieves).
- Timing control (durations/intervals).
- Velocity sampling (dynamics).
PitchGenerator
The PitchGenerator precomputes first all the valid MIDI pitches that pass sieve constraints (e.g., 12@0 | 12@4 | 12@7 → C-E-G).
Then samples a raw pitch value, clamps it to the MIDI range (0–127), and find the closest valid pitch.
TimingGenerator
Controls rhythm using stochastic distributions for durations/intervals. It simply samples from the configured distribution and returns the duration value for notes and intervals.
It comes in 2 modes:
- Free: Returns the raw sampled values.
- Quantized: Snaps the sampled values to musical grids (e.g., 16th notes).
Event Generation Workflow
- Initialize
Generatorwith config (BPM, sieves, distributions). - Loop until duration is reached:
- Sample pitch (sieve-filtered).
- Sample velocity (1–127).
- Sample duration/interval (quantized or free).
- Emit
NoteOn/NoteOffevents.
- Handle errors (e.g., too many consecutive pitch failures).
Example Event:
on := MIDIEvent{Time: t, Type: "NoteOn", Channel: 1, Note: 60, Velocity: 100}
off := MIDIEvent{Time: t + duration, Type: "NoteOff", Channel: 1, Note: 60, Velocity: 0}
Future Work and Technical Considerations
Several areas of this tool offer opportunities for extension:
-
Cross-Platform MIDI Support: While
gomidi’srtmididrv(which utilizes C++ RTmidi) is designed for cross-platform compatibility, including Windows, successful compilation on Windows has proven challenging due to specificgo buildandcgoconfigurations. Addressing these compilation issues or implementing alternative backends for macOS (CoreMIDI) and Windows (WinRT MIDI) would provide broader out-of-the-box compatibility. -
Polyphonic Generation: Extend the generation sampling and filtering to support the creation of polyphonic musical sequences.
-
Dynamic Sieves: Allow sieves filtering to evolve dynamically over time.
-
Performance: More complex sieve expressions or high-tempo operation may require further optimization, including profiling of the evaluator and event scheduler.
-
Sieve Language Extensions: Future versions could support additional operators or features such as variables.
-
Visualization: A user interface or dashboard could make system state and generative behavior more observable during real-time operation.
-
Integrations: Integration with DAW as VST could increase
kumouse cases and usability for real composition.