Config files
A miditool config is one KDL file. The smallest useful one is a single line:
shuffle-lock seed=42Everything else, input and output selection, tempo, the remote, scenes, is optional and has a sensible default.
Where miditool looks
Section titled “Where miditool looks”miditool run resolves its config in order, first hit wins:
- A path on the command line:
miditool run examples/scrambled.kdl - The
MIDITOOL_CONFIGenvironment variable ./miditool.kdlin the working directory~/.miditool/config.kdl, the miditool home (setMIDITOOL_HOMEto move it)
When nothing is found, the first run creates ~/.miditool/config.kdl for you: a commented starter that passes MIDI through untouched until you enable something in it. The startup line names the config that won, and miditool doctor reports the same resolution.
A working-directory miditool.kdl beats the home config on purpose: keep a folder per piece or per experiment, and the home config remains your everyday setup. Script paths always resolve relative to whichever config file is running, so scripts next to the home config travel with it.
A KDL primer
Section titled “A KDL primer”KDL is a document language built from nodes. A node is a name followed by values on the same line: bare arguments (transpose 12, only-channels 1 2) and named properties (shuffle-lock seed=42 mode="free"). Strings are quoted, numbers are bare, and true/false are the booleans. A node can carry a block of child nodes in braces, which is how chain, fork, and scene hold their contents.
Comments run from // to the end of the line, and /- in front of a node comments out the whole node, children included, which is handy for muting one effect while experimenting. That is all the KDL you need for miditool.
The file shape
Section titled “The file shape”input "Roland" hide=true // optional: which keyboard, and whether to hide itoutput virtual="miditool Out" // optional: where the DAW listenstempo 96 // optional: bpm for beats= times, default 120remote port=8320 // optional: the web remote, off by defaultcontrol { next-scene key=108; } // optional: keyboard keys as gestures
shuffle-lock seed=42 // the effects: an implicit top-level chainvelocity-curve gamma=0.8The control block reserves keys on the keyboard itself as performance gestures and can hand scene switching to a clock; see Performing.
A substring of the input port’s name; miditool ports shows the candidates. Omit it and miditool picks a default. hide=true hides the raw source from every other app while miditool runs, which is the GarageBand fix; it is macOS only and ignored elsewhere.
output
Section titled “output”Exactly one of two properties:
output virtual="miditool Out"creates a virtual port with that exact name. This is the default when the node is omitted.output device="IAC"connects to an existing port whose name contains the substring, which is also how Windows works, via loopMIDI.
Giving both, or neither, is an error:
output virtual="miditool Out" device="IAC"Beats per minute, 20..=400, default 120. It only matters for effects that use beats= times: see Time and tempo.
remote
Section titled “remote”remote port=8320 serves the web remote on that port, 1..=65535. No remote node, no server. By default it binds 127.0.0.1 and answers this machine only; add bind="0.0.0.0" to open it to your local network for a phone or tablet:
remote port=8320 bind="0.0.0.0"bind= takes any IP address of the machine; anything that does not parse as one is rejected.
Scenes
Section titled “Scenes”Instead of bare effects, a config can hold named scene blocks, each a chain of its own. The remote switches between them mid-performance:
input "Roland"remote port=8320 bind="0.0.0.0"
scene "scrambled" { shuffle-lock seed=42}scene "echo storm" switch="kill" { echo repeats=6 time="300ms" decay=0.8}switch= says what happens to sounding notes when you leave the scene: "let-ring" (the default) lets them ring out; "kill" cuts them. Scene names must be unique and non-empty, compared exactly as written, and every scene needs at least one effect.
Anywhere an effect can go, script "wedge.lua" runs your own Luau code on every event, with the path resolved against the config file’s directory; see Scripting.
Bare effects are shorthand for a single scene named main. The two styles do not mix; this is an error:
scene "scrambled" { shuffle-lock seed=42}velocity-curve gamma=0.8 // loose effect outside any sceneErrors
Section titled “Errors”Bad values are rejected with the node name and the constraint, like tempo: beats per minute must be within 20..=400, got 500. Every range is listed on the page for its node or effect. A running miditool keeps its current graph when a config edit fails, so errors cost nothing but the time to read them.