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 .mid file.

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 Distribution interface with a Sample() and String() methods.

  • The implementations of the above interface, for some of the most common probability distributions:

    • UniformDist
    • GaussianDist
    • PoissonDist

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@0 for n % 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 where n % 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@13@1).
3. AST Evaluation

The AST nodes implement an Evaluate(n int) bool method:

  • SieveNode: Checks if n satisfies a modular condition
  • BinaryOpNode: 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

  1. Initialize Generator with config (BPM, sieves, distributions).
  2. Loop until duration is reached:
    • Sample pitch (sieve-filtered).
    • Sample velocity (1–127).
    • Sample duration/interval (quantized or free).
    • Emit NoteOn/NoteOff events.
  3. 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’s rtmididrv (which utilizes C++ RTmidi) is designed for cross-platform compatibility, including Windows, successful compilation on Windows has proven challenging due to specific go build and cgo configurations. 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 kumo use cases and usability for real composition.