DOCS
How to write it
Declare ports, then connect them. That is the whole idea. Every diagram below was compiled at build time by the published package from the source beside it — none was redrawn.
01Install
For the command line, install the CLI.
pnpm add @love-rox/kumihimo-cli
kumihimo build studio.khm -o studio.svg # draw it
kumihimo check studio.khm # validate only
kumihimo build studio.khm --watch # redraw on save
kumihimo export studio.khm drawio # editable draw.io file
kumihimo export studio.khm cable --stdout # cable schedule as TSVIt installs as both kumihimo and khm. Warnings do not fail the build unless you pass --strict.
There is a VS Code extension for writing it. Diagnostics as you type, and the diagram beside the source. The verdicts are the ones under "Signals and compatibility" below — it is the same compiler, running in the editor.
code --install-extension love-rox.kumihimo-vscodeThe preview opens with ⌘K V (Ctrl+K V on Windows and Linux) or the button in the editor title bar. It follows the editor's colour theme, and switches between the diagram and the cable, equipment and adapter schedules. Completions come from the compiler's own lists, and both the extension's words and the compiler's follow your display language. More on [the VS Code section of the front page](/en#vscode).
02Your first diagram
Declare the equipment, then wire port to port. The extension is .khm.
device pc "Laptop" as computer { out HDMI : hdmi }
device mon "Monitor" as display { in HDMI : hdmi }
pc.HDMI -> mon.HDMI : hdmi 5m "V-01"In device <id> "<label>" as <kind>, the <id> is how connections refer to the device and is not drawn. The "<label>" is what appears on the diagram.
03Devices and ports
device <id> "<label>" as <kind> {
in <port spec> : <signal> # input
out <port spec> : <signal> # output
io <port spec> : <signal> # bidirectional (Dante, Ethernet …)
@model "HyperDeck Studio HD Mini"
}A port spec takes four forms, so a sixteen-channel desk is not sixteen lines.
| Form | Example | Expands to |
|---|---|---|
| Single | SDI | SDI |
| List | L, R | L, R |
| Numeric range | 1..4 | 1, 2, 3, 4 |
| Prefixed range | CH[1..16] | CH1 … CH16 |
device mixer "Yamaha DM3" as mixer {
in CH[1..16] : xlr
out L, R : xlr
io DANTE : dante
}
device amp "Amp" as amplifier { in IN_L, IN_R : trs }
mixer.(L, R) -> amp.(IN_L, IN_R) : trs 3m "A-10"Declaration order is preserved and drawn. IN 1 never ends up below IN 2. Position within a device is meaningful information, so nothing gets reordered.
Sixteen connectors drawn at one pitch read as sixteen of the same thing. Real equipment is not like that: four HDMI inputs, then four SDI, then a pair of analogue jacks, and the panel says so by leaving room between them. A gap line leaves that room above whatever is declared next.
device sw "ATEM Mini Extreme" as switcher {
in 1..4 : hdmi
gap
in 5..8 : sdi
gap 2
in AUDIO_L, AUDIO_R : trs
out PGM : sdi
gap
out STREAM : lan
}One gap is half a port pitch, so gap 2 is a whole one. Consecutive gaps add up. A declaration that expands into many ports gets the space once, before the first of them: gap above in CH[1..16] is one space before CH1, not sixteen down the strip.
This is presentation only. No port, connection or schedule changes — the same diagram with every gap removed describes the same system.
The kind picks the shape: camera switcher mixer recorder player display projector speaker microphone amplifier computer converter matrix patchbay router interface generic. It defaults to generic.
Any @-prefixed line is metadata. It is not drawn, but it is carried into the equipment schedule and the exports.
04Connections
<device>.<port> <arrow> <device>.<port> : <signal> <modifier>*| Arrow | Meaning |
|---|---|
-> | one way, in the direction signal flows |
<-> | two way (Dante, Ethernet, control …) |
-- | no direction (power …) |
Modifiers may appear in any order and any may be omitted.
| Syntax | Meaning | Example |
|---|---|---|
<length> | cable length | 10m 30cm 2.5m 3ft |
"<label>" | cable number or name | "V-01" |
via "<part>" | adapter or converting lead | via "HDMI-DVI cable" |
[k=v, …] | arbitrary attributes | [connector=BNC, color=blue] |
device mixer "Desk" as mixer { out L, R : xlr io DANTE : dante }
device amp "Amp" as amplifier { in IN_L, IN_R : trs in AC : ac }
device netsw "Switch" as router { io 1 : dante }
device pdu "Power" as generic { out 1 : ac }
mixer.(L, R) -> amp.(IN_L, IN_R) : trs 3m "A-10" [color=blue]
mixer.DANTE <-> netsw.1 : dante 10m "N-01"
pdu.1 -- amp.AC : ac 2mWhen both ends name the same number of ports, they pair up in order. mixer.(L, R) -> amp.(IN_L, IN_R) is the same as writing two lines.
[color=…] is the cable jacket, overriding the signal type default. It is not decoration: it is how a run gets identified on site — "the blue one into 1" — so the colour follows through into the cable schedule. Colour names work in English or Japanese, and hex such as #0af works too. Anything else becomes a diagnostic and never reaches the drawing.
Length is a bare token of a number and a unit. # is reserved for comments, so it never prefixes a length.
05Groups
A frame around a location, a rack or a subsystem.
diagram "House" { direction: LR }
group stage "Stage" {
device cam1 "FX3" as camera { out SDI : sdi }
device mic1 "SM58" as microphone { out OUT : xlr }
}
group rack "Rack" {
device sw "ATEM" as switcher { in 1..2 : sdi out PGM : sdi }
device mixer "DM3" as mixer { in CH[1..2] : xlr out L, R : xlr }
}
cam1.SDI -> sw.1 : sdi 30m "V-01"
mic1.OUT -> mixer.CH1 : xlr 20m "A-01"A group can hold another group. A venue holds a stage and a rack; the stage holds the cameras. Both levels are real to whoever walks the site, and only the innermost one names the place a box is actually standing in.
diagram "House" { direction: LR }
group venue "Hall 3" {
group stage "Stage" {
device cam "FX3" as camera { out SDI : sdi }
}
group rack "Rack" {
device sw "ATEM" as switcher { in 1 : sdi }
}
}
cam.SDI -> sw.1 : sdi 30m "V-01"A device belongs to the group it is written in, not to that group’s parent. The equipment list names the innermost one, because that is the shelf somebody walks to.
06Signals and compatibility
This is where it stops being a drawing tool. Every connection is judged on whether it can physically work.
The verdict is reached from what the two ports themselves declare. The : <signal> on a connection describes the cable, and only fills in for an end that declares nothing. Get that backwards and a type is compared against itself, so no mismatch can ever be found.
| Verdict | Meaning | Treated as |
|---|---|---|
ok | a normal connection | silent |
lossy | works, but something is given up or a part is needed | warning |
incompatible | will not work without active conversion | reported |
The faults worth catching are the ones where the plug seats, the drawing looks right, and no signal arrives. Both cables below merely share a connector.
device ext "HDBaseT receiver" as interface { out CAT : hdbaset }
device netsw "L2 switch" as router { in 1 : lan }
device cam "Camera" as camera { out SDI : sdi }
device fs "Frame synchroniser" as generic { in REF : genlock }
ext.CAT -> netsw.1 : hdbaset 20m "N-01"
cam.SDI -> fs.REF : sdi 5m "V-90"- warning
signal-mismatchext.CAT → netsw.1: HDBaseT uses Cat cable and RJ45 but is not Ethernet. It does not go into a switch - warning
signal-mismatchcam.SDI → fs.REF: They only share BNC. A reference input will not lock to video
Every verdict carries its reason, and the reason follows through into the cable schedule. The same trap exists for dmx↔xlr, rca↔spdif, adat↔spdif, composite↔component and wordclock↔sdi.
A site standard can be stated once, with its reason. The reason reaches the diagnostic and the schedule, so why a connection was allowed is never lost from the drawing.
compat aes -> xlr : ok "house standard: under 10m"
compat xlr -> rca : lossy "always through a DI"The diagnostics you will meet most. Severities are configurable.
| Code | Meaning | Default |
|---|---|---|
signal-mismatch | the two ends disagree about the signal | warning |
adapter-required | an adapter is needed but not declared | warning |
adapter-insufficient | via declared but no cable can bridge this | error |
direction-mismatch | output to output, or input to input | error |
port-overbooked | more than one source into one input | error |
implicit-device | referred to an undeclared device | warning |
unconnected-port | a declared port wired to nothing | off |
Nothing throws. Every stage collects diagnostics and returns a best-effort result, so a faulty diagram still renders — a picture of a flawed system is exactly what an author needs in order to see the flaw.
07The vocabulary
Every word below is read out of the published package by this page. It is not a list copied by hand, so it cannot offer a word the compiler would reject or omit one it accepts. The VS Code completions come from the same place.
Signal types — after the : on a port or a connection.
Video
componentComponentBNC / RCAcompositeCompositeBNC / RCAdpDisplayPortDisplayPort / DisplayPort MinidviDVIDVI-D / DVI-IfiberFiberLC / SC / OpticalCONhdbasetHDBaseTRJ45hdmiHDMIHDMI / HDMI Mini / HDMI MicrondiNDIRJ45sdiSDIBNC / DIN 1.0/2.3st2110ST 2110RJ45 / SFPvgaVGAD-sub 15wireless-videoWireless videono connectorAudio
adatADATTOSLINKaesAES/EBUXLR / BNCdanteDanteRJ45iemIEMno connectormadiMADIBNC / SCopticalOpticalTOSLINKrcaRCARCAspdifS/PDIFRCA / TOSLINKspeakonSpeakonNL4 / NL8trrsTRRS 1/4"TRRS 1/4"trrs35TRRS 3.5mmTRRS 3.5mmtrsTRS 1/4"TRS 1/4"trs35TRS 3.5mmTRS 3.5mmuhfWireless (UHF)no connectorxlrXLRXLR-M / XLR-F / Mini XLR-M / Mini XLR-F / Mini XLR-4M / Mini XLR-4FControl
dmxDMXXLR-5 / XLR-3gpioGPIOTerminal / D-subirIRno connectormidiMIDIDIN-5 / TRS 3.5mmrs232RS-232D-sub 9rs422RS-422D-sub 9rs485RS-485D-sub 9 / XLRwireless-dmxWireless DMXno connectorNetwork
bluetoothBluetoothno connectorlanLANRJ45usbUSBUSB-A / USB-B / USB-C / USB Micro-B / USB Mini-BwifiWi-Fino connectorPower
acACIEC C13 / IEC C14 / NEMAdcDCBarrel / XLR-4poePoERJ45usbpdUSB PDUSB-CSync
genlockGenlockBNCtimecodeTimecodeBNC / XLRwordclockWord ClockBNCGeneric
genericA type that is not here can be declared with signal. See "Signals and compatibility".
Device kinds — after device … as.
cameraswitchermixerrecorderplayerdisplayprojectorspeakermicrophoneamplifiercomputerconvertertransmitterreceivermatrixpatchbayrouterinterfacegenericJacket colours — inside [color=…]. English and Japanese spellings resolve to the same swatch, and a hex literal like #0af works too.
red赤blue青green緑yellow黄orange橙purple紫black黒white白graygrey灰brown茶pink桃Length units — written against the number, with no space: 30m, 6ft.
mmcmminftThemes — inside diagram { theme: … }.
lightdarkmonoblueprint08Adapters
Declares that a passive adapter or converting lead sits in the run.
device pc "PC" as computer { out HDMI : hdmi }
device mon "Monitor" as display { in DVI : dvi }
pc.HDMI -> mon.DVI : hdmi 2m "V-01" via "HDMI-DVI cable"via is not a way to silence a warning. It is a declaration that puts a part on the schedule. The link gets a conversion mark and the adapter appears as a line item.
For pairings a cable can genuinely bridge (HDMI↔DVI, DP→HDMI …), declaring via clears the diagnostic; leaving it out is still reported, with the required part named. For pairings no cable can bridge (SDI→HDMI …), via does not clear it. Those need a powered box, which belongs in the diagram as a device rather than as a property of a cable.
# Wrong: no cable turns SDI into HDMI
cam.SDI -> mon.HDMI : sdi via "SDI-HDMI converter"
# Right: the converter is a device
device conv "BMD Mini Converter SDI-HDMI" as converter {
in SDI : sdi
out HDMI : hdmi
}
cam.SDI -> conv.SDI : sdi
conv.HDMI -> mon.HDMI : hdmi09Wireless
A radio path is not a cable, so it carries a frequency or a channel where a cable carries a length. It is drawn dashed.
device mic "Wireless mic" as microphone { out RF : uhf }
device rx "Receiver" as interface {
in RF : uhf
out CH1 : xlr
}
device desk "Desk" as mixer { in CH1 : xlr }
mic.RF -> rx.RF : uhf [ch=38]
rx.CH1 -> desk.CH1 : xlr 3m "A-01"Wire a microphone straight into the desk with no receiver and you get told. The boundary between wireless and wired is judged too.
10A signal riding on something else
NDI is video, but what flows down the wire is Ethernet — or, over Wi-Fi, radio. The drawing is about the NDI; the physics belongs to the Ethernet or the Wi-Fi. over keeps the two apart.
device cam "Roving camera" as camera { out WIFI : ndi }
device ap "Access point" as router { io WIFI : ndi
out LAN : ndi }
device pc "Recording PC" as recorder { in LAN : ndi }
cam.WIFI -> ap.WIFI : ndi over wifi [ch=36]
ap.LAN -> pc.LAN : ndi over lan 20m "N-01"The carrier decides the physics. An over wifi hop has no connector, takes a channel, and lands on the wireless schedule. An over lan run is RJ45, takes a length and a number, and lands on the cable one. The name and the colour come from the payload.
Cable schedule
| No. | From | To | Signal | Length | Source end | Far end |
|---|---|---|---|---|---|---|
| N-01 | Access point | Recording PC | NDI | 20m | — | — |
Wireless schedule
| No. | From | To | Signal | Over | Channel |
|---|---|---|---|---|---|
| — | Roving camera | Access point | NDI | Wi-Fi | ch 36 |
Without over, a signal is its own carrier and everything behaves exactly as before.
11Which connector is on the box
Where a signal type offers a choice, a port can say which one it has. Gender is a property of the socket, not of the cable.
device dk "Desk" as mixer { out CH[1..2] : xlr [connector=XLR-M] }
device sp "SP" as speaker { in IN : xlr [connector=XLR-F] }
dk.CH1 -> sp.IN : xlr 10m "A-01"The cable ends follow. A plug mates with the opposite gender, so a male output takes a female cable end. Written once per socket, every cable reaching that socket agrees with it.
Cable schedule
| No. | From | To | Signal | Length | Source end | Far end |
|---|---|---|---|---|---|---|
| A-01 | Desk | SP | XLR | 10m | XLR-F | XLR-M |
xlr is the only builtin whose connector list is a mating pair. Everywhere else the list means "one of these" — usb is A or B or C — and there the cable end is the same name rather than an opposite.
A smaller shell is a connector, not a type. A camera has micro HDMI, a switcher has full size, and the lead between them converts nothing — so hdmi carries all three rather than splitting into three signals. Splitting them would report a mismatch on a connection that works.
device cam "SONY α7 IV" as camera { out HDMI : hdmi [connector="HDMI Micro"] }
device sw "ATEM Mini" as switcher { in HDMI1 : hdmi [connector=HDMI] }
cam.HDMI -> sw.HDMI1 : hdmi 3m "V-01"The reason to write it is the schedule. HDMI Micro → HDMI is a different lead from HDMI → HDMI, and it is one you either packed or did not. The same goes for DisplayPort Mini, USB Micro-B, DIN 1.0/2.3, MIDI on TRS 3.5mm, and mini XLR on a bodypack.
Cable schedule
| No. | From | To | Signal | Length | Source end | Far end |
|---|---|---|---|---|---|---|
| V-01 | SONY α7 IV | ATEM Mini | HDMI | 3m | HDMI Micro | HDMI |
A part that only changes the shell is an adapter. Then the cable goes back to being an ordinary HDMI-to-HDMI, and the adapter is counted once on the parts list — which is where a thing that can be lost belongs.
device cam "SONY α7 IV" as camera { out HDMI : hdmi [connector="HDMI Micro"] }
adapter mh "micro-HDMI 変換アダプタ" { in IN : hdmi [connector="HDMI Micro"] out OUT : hdmi [connector=HDMI] }
device sw "ATEM Mini" as switcher { in HDMI1 : hdmi [connector=HDMI] }
cam.HDMI -> mh.IN : hdmi
mh.OUT -> sw.HDMI1 : hdmi 3m "V-01"Cable schedule
| No. | From | To | Signal | Length | Source end | Far end |
|---|---|---|---|---|---|---|
| V-01 | micro-HDMI 変換アダプタ | ATEM Mini | HDMI | 3m | HDMI | HDMI |
Parts list
| Part | Qty | Between |
|---|---|---|
| micro-HDMI 変換アダプタ | 1 | SONY α7 IV / ATEM Mini |
A value with a space in it goes in quotes: [connector="HDMI Micro"]. Most shell names have one.
12Moulded leads, and a length still to come
A fan-out whose tails are all moulded is a part and a cable at once. It goes in the van, it gets a number, and the person loading reads the cable schedule. as cable puts it there instead of on the parts list.
device dk "Desk" as mixer { out MAIN : xlr }
adapter fan "XLR 4-way lead" as cable 5m "C-01" {
in IN : xlr
out A : xlr
out B : xlr
}
device sp1 "SP1" as speaker { in IN : xlr }
device sp2 "SP2" as speaker { in IN : xlr }
dk.MAIN -> fan.IN : xlr
fan.A -> sp1.IN : xlr
fan.B -> sp2.IN : xlrCable schedule
| No. | From | To | Signal | Length | Source end | Far end |
|---|---|---|---|---|---|---|
| C-01 | XLR 4-way lead | Desk / SP1 / SP2 | XLR | 5m | — | — |
Parts list
(empty)
One object, one row — not one per plug. It leaves the parts list, so it is not counted twice. as cable takes a length and a cable number, the same ones a run takes.
Where the length is not settled yet, write ?m. Leaving it off already worked, and the blank it produced meant "not measured" and "nobody thought about it" at once. On a list somebody packs a van from, only one of those is a job still to do.
device dk "Desk" as mixer { out L, R, SUB : xlr }
device sp1 "SP1" as speaker { in IN : xlr }
device sp2 "SP2" as speaker { in IN : xlr }
device sub "Sub" as speaker { in IN : xlr }
dk.L -> sp1.IN : xlr 15m "A-01" # 測った
dk.R -> sp2.IN : xlr ?m "A-02" # ケーブルは要る。長さはこれから
dk.SUB -> sub.IN : xlr "A-03" # 長さについて何も言っていないCable schedule
| No. | From | To | Signal | Length | Source end | Far end |
|---|---|---|---|---|---|---|
| A-01 | Desk | SP1 | XLR | 15m | — | — |
| A-02 | Desk | SP2 | XLR | ?m | — | — |
| A-03 | Desk | Sub | XLR | — | — | — |
The unit is still written, because which unit it will be measured in is not the open question. ?m, ?ft, and any other unit the language knows.
13Equipment libraries
A desk's sixteen channels do not get rewritten per drawing. Define it once with model, instantiate as many as you like with device … from.
model dm3 "Yamaha DM3" as mixer {
in CH[1..16] : xlr
out L, R : xlr
@vendor "Yamaha"
}
device foh from dm3
device mon from dm3 "Monitor desk"Definitions in another file are pulled in with use. If an imported file also held devices or connections they are ignored, with a warning: a library is where definitions live, not drawings.
use "lib/yamaha.khm"
device foh from dm3 "FOH desk"14Appearance
diagram "Studio A" {
direction: LR # LR (left to right, default) | TB (top to bottom)
theme: light # light (default) | dark | mono | blueprint
spacing: 60 # gap between nodes, px
}| Theme | For |
|---|---|
light | Default. Screen and colour print |
dark | Dark screens |
mono | Black and white print and photocopies |
blueprint | Blueprint colouring, as facility drawings use |
mono uses no colour at all. Signals are distinguished by line style instead, and a jacket colour given with [color=…] is ignored — pretending a colour survived a photocopy helps nobody.
diagram "For monochrome printing" { theme: mono }
device cam "Camera" as camera { out SDI : sdi }
device sw "Switcher" as switcher { in 1 : sdi out PGM : sdi }
device rec "Recorder" as recorder { in SDI : sdi }
cam.SDI -> sw.1 : sdi 30m "V-01" [color=blue]
sw.PGM -> rec.SDI : sdi 2m "V-10"A theme can also be passed with -t/--theme, but a diagram { theme: … } in the source wins: the drawing knows how it is meant to look, the caller only knows a default.
15Output and embedding
Besides SVG, it exports an editable draw.io file and TSV cable and equipment schedules.
Markdown takes a kumihimo code fence directly; it becomes SVG at build time.
```kumihimo
cam.SDI -> sw.1 : sdi 30m "V-01"
```
import rehypeKumihimo from '@love-rox/kumihimo-rehype';
unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeKumihimo, { theme: "dark", onDiagnostics: report })
.use(rehypeStringify);There are React, Vue and Astro components too. Compiling is asynchronous, so the previous diagram stays on screen while a new one is produced, and a slow earlier compile can never overwrite a newer one.
import { Kumihimo, useKumihimo } from '@love-rox/kumihimo-react';
<Kumihimo source={src} theme="dark" onDiagnostics={report} />;
const { svg, diagram, diagnostics, pending, error } = useKumihimo(src);Use onDiagnostics if you would rather not publish a nice-looking picture of faulty wiring.