DOCS

書き方

ポートを宣言して、つなぐ。それだけです。下の図はすべて、左のソースを公開済みパッケージでビルド時にコンパイルした結果で、描き直したものではありません。

01入れる

コマンドラインで使う場合は CLI を入れます。

shell
pnpm add @love-rox/kumihimo-cli

kumihimo build studio.khm -o studio.svg   # 描く
kumihimo check studio.khm                 # 検証だけ
kumihimo build studio.khm --watch         # 保存で描き直す
kumihimo export studio.khm drawio         # 編集できる draw.io ファイル
kumihimo export studio.khm cable --stdout # ケーブル表を TSV で

kumihimokhm の両方の名前で入ります。警告は既定ではビルドを失敗させません。失敗させたいときは --strict を付けます。

02最初の一枚

機材を宣言して、ポートどうしをつなぎます。拡張子は .khm です。

first.khmclean
device pc  "ノートPC"  as computer { out HDMI : hdmi }
device mon "モニター" as display  { in  HDMI : hdmi }

pc.HDMI -> mon.HDMI : hdmi 5m "V-01"
output
V-01 5mノートPCHDMIモニターHDMI

device <id> "<ラベル>" as <種別><id> は接続を書くときの名前で、図には出ません。図に出るのは "<ラベル>" の方です。

03機材とポート

構文
device <id> "<ラベル>" as <種別> {
  in   <ポート指定> : <信号>   # 入力
  out  <ポート指定> : <信号>   # 出力
  io   <ポート指定> : <信号>   # 双方向(Dante, Ethernet …)
  @model "HyperDeck Studio HD Mini"
}

ポート指定には4つの書き方があります。16ch の卓を16行書く必要はありません。

書き方展開
単体SDISDI
並べるL, RL, R
数の範囲1..41, 2, 3, 4
接頭辞つき範囲CH[1..16]CH1CH16
ports.khmclean
device mixer "Yamaha DM3" as mixer {
  in  CH[1..16] : xlr
  out L, R      : xlr
  io  DANTE     : dante
}
device amp "アンプ" as amplifier { in IN_L, IN_R : trs }

mixer.(L, R) -> amp.(IN_L, IN_R) : trs 3m "A-10"
output
A-10 3mA-10 3mYamaha DM3CH1CH2CH3CH4CH5CH6CH7CH8CH9CH10CH11CH12CH13CH14CH15CH16LRDANTEアンプIN_LIN_R

宣言した順序がそのまま描かれます。IN 1IN 2 の下に来ることはありません。機材の中での位置は意味を持つ情報なので、並べ替えません。

端子が等間隔で16個並ぶと、16個の同じものにしか見えません。実機はそうではなく、HDMI が4つ、SDI が4つ、アナログが2つ、と面板が余白で区切っています。gap の行を置くと、その次に宣言されるものの上に余白が入ります。

gap.khmclean
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
}
output
ATEM Mini Extreme12345678AUDIO_LAUDIO_RPGMSTREAM

gap ひとつがポート間隔の半分、gap 2 で1ポート分です。連続して書けば加算されます。複数のポートに展開される宣言でも、余白が入るのは最初の1つの前だけです。in CH[1..16] の上の gapCH1 の前の1箇所であって、16箇所ではありません。

これは見た目だけの指定です。ポートも結線も一覧表も変わりません。すべての gap を消した図は、同じ系統を表します。

種別は形を決めます。camera switcher mixer recorder player display projector speaker microphone amplifier computer converter matrix patchbay router interface generic から選びます。省略すると generic です。

@ で始まる行は任意のメタデータです。図には出ませんが、機材表と各種エクスポートに載ります。

04つなぐ

構文
<機材>.<ポート> <矢印> <機材>.<ポート> : <信号> <修飾子>*
矢印意味
->一方向。信号が流れる向き
<->双方向(Dante, Ethernet, 制御 …)
--向きなし(電源 …)

修飾子は順不同で、どれも省略できます。

書き方意味
<長さ>ケーブル長10m 30cm 2.5m 3ft
"<ラベル>"ケーブル番号や名前"V-01"
via "<部品>"変換ケーブルやアダプタvia "HDMI-DVI変換"
[k=v, …]任意の属性[connector=BNC, color=青]
connect.khmclean
device mixer "卓"       as mixer     { out L, R : xlr  io DANTE : dante }
device amp   "アンプ"   as amplifier { in  IN_L, IN_R : trs  in AC : ac }
device netsw "スイッチ" as router    { io  1 : dante }
device pdu   "電源"     as generic   { out 1 : ac }

mixer.(L, R) -> amp.(IN_L, IN_R) : trs   3m  "A-10" [color=青]
mixer.DANTE <-> netsw.1          : dante 10m "N-01"
pdu.1        -- amp.AC           : ac    2m
output
A-10 3mA-10 3mN-01 10m2mLRDANTEアンプIN_LIN_RACスイッチ1電源1

両端が同じ数のポートを並べると、順に対応します。mixer.(L, R) -> amp.(IN_L, IN_R) は2行書くのと同じです。

[color=…] はケーブルの被覆の色です。信号種別の既定色を上書きします。これは装飾ではなく、現場で線を特定する手段(「青いのを1番へ」)なので、色はケーブル表にも持ち越されます。日本語名(赤・青・緑・黄・橙・桃・紫・黒・白・灰・茶)と英語名、#0af のような16進が使えます。解釈できない値は診断になり、図には届きません。

長さは数と単位だけの裸のトークンです。# は行コメント専用なので、長さの前に付くことはありません。

05まとめる

場所・ラック・サブシステムを枠で囲みます。入れ子は v0.1 では1段までです。

group.khmclean
diagram "会場" { direction: LR }

group stage "ステージ" {
  device cam1 "FX3"  as camera     { out SDI : sdi }
  device mic1 "SM58" as microphone { out OUT : xlr }
}

group 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"
output
会場ステージラックV-01 30mA-01 20mFX3SDISM58OUTATEM12PGMDM3CH1CH2LR

06信号と互換判定

これが図を描くだけの道具との違うところです。すべての接続が「物理的に成立するか」を判定されます。

判定は両端のポートが宣言した型で行います。接続行の : <信号> が表すのはケーブルで、型を宣言していない端の穴埋めにしか使われません。ここを取り違えると、型を自分自身と比べることになり、どんな不一致も検出できなくなります。

判定意味扱い
ok普通の接続黙る
lossy通るが何かを失う、または部品が要る警告
incompatible能動変換なしには成立しない報告

検出する価値があるのは、プラグがぴたりと嵌まり、見た目に何もおかしくなく、そして何も出ない組み合わせです。下は2本ともコネクタを共有しているだけの配線です。

faults.khm2 diagnostic
device ext   "HDBaseT受信器"       as interface { out CAT : hdbaset }
device netsw "L2スイッチ"           as router    { in  1   : lan }
device cam   "カメラ"               as camera    { out SDI : sdi }
device fs    "フレームシンクロナイザ" as generic   { in  REF : genlock }

ext.CAT -> netsw.1 : hdbaset 20m "N-01"
cam.SDI -> fs.REF  : sdi     5m  "V-90"
output
N-01 20mV-90 5mHDBaseT受信器CATL2スイッチ1カメラSDIフレームシンクロナイザREF

判定には必ず理由が付き、その理由はケーブル表にも残ります。同種の組み合わせは他にも dmxxlrrcaspdifadatspdifcompositecomponentwordclocksdi があります。

現場の取り決めは compat で一度だけ、理由と一緒に書けます。理由は診断とケーブル表に流れるので、なぜ許したのかが図から失われません。

compat
compat aes -> xlr : ok    "社内基準:10m 未満なら可"
compat xlr -> rca : lossy "必ず DI を通す"

主な診断コードです。既定の重さは設定で変えられます。

コード意味既定
signal-mismatch両端が信号について食い違っている警告
adapter-requiredアダプタが要るのに宣言がない警告
adapter-insufficientvia はあるがケーブルでは橋渡しできないエラー
direction-mismatch出力どうし、入力どうしエラー
port-overbooked1つの入力に複数の送り出しエラー
implicit-device宣言していない機材を参照した警告
unconnected-port宣言したのに何もつながっていない既定オフ

例外は投げません。どの段階も診断を集めて最善の結果を返すので、誤りのある図もちゃんと描かれます。欠陥を見つけるために必要なのは、まさにその欠陥が写った絵だからです。

07変換をはさむ

受動的なアダプタや変換ケーブルが途中に入ることを宣言します。

via.khmclean
device pc  "PC"       as computer { out HDMI : hdmi }
device mon "モニター" as display  { in  DVI  : dvi }

pc.HDMI -> mon.DVI : hdmi 2m "V-01" via "HDMI-DVI変換ケーブル"
output
V-01 2m ⇄PCHDMIモニターDVI

via は警告を黙らせる道具ではありません。部品を資材表に載せる宣言です。線には変換の印が付き、アダプタが明細に出ます。

ケーブルで本当に橋渡しできる組み合わせ(HDMI↔DVI、DP→HDMI など)なら via で診断が消えます。宣言しなければ報告され、必要な部品の名前が示されます。一方、どんなケーブルでも橋渡しできない組み合わせ(SDI→HDMI など)では via を書いても診断は消えません。それは電源の要る箱なので、ケーブルの属性ではなく機材として図に置くべきものです。

converter.khm
# 誤り:SDI を HDMI に変えるケーブルは存在しない
cam.SDI -> mon.HDMI : sdi via "SDI-HDMI変換"

# 正しい:変換器は機材
device conv "BMD Mini Converter SDI-HDMI" as converter {
  in  SDI  : sdi
  out HDMI : hdmi
}
cam.SDI   -> conv.SDI : sdi
conv.HDMI -> mon.HDMI : hdmi

08無線

無線区間はケーブルではないので、長さの代わりに周波数やチャンネルを持ちます。線は破線で描かれます。

wireless.khmclean
device mic "ワイヤレスマイク" as microphone { out RF : uhf }
device rx  "受信機"           as interface  {
  in  RF  : uhf
  out CH1 : xlr
}
device desk "卓" as mixer { in CH1 : xlr }

mic.RF -> rx.RF     : uhf [ch=38]
rx.CH1 -> desk.CH1  : xlr 3m "A-01"
output
ch 38A-01 3mワイヤレスマイクRF受信機RFCH1CH1

受信機を通さずマイクを卓へ直結すれば、そこで指摘が出ます。無線と有線の境界も判定の対象です。

09機材ライブラリ

卓の16chを図ごとに書き直す必要はありません。model で一度定義し、device … from で何台でも実体化します。

library.khmclean
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 "モニター卓"
output
Yamaha DM3CH1CH2CH3CH4CH5CH6CH7CH8CH9CH10CH11CH12CH13CH14CH15CH16LRモニター卓CH1CH2CH3CH4CH5CH6CH7CH8CH9CH10CH11CH12CH13CH14CH15CH16LR

別ファイルに置いたものは use で読み込みます。読み込んだファイルに機材や接続が書かれていた場合は無視され、警告になります。ライブラリは定義を置く場所であって、図を置く場所ではないためです。

studio.khm
use "lib/yamaha.khm"

device foh from dm3 "FOH卓"

10見た目

diagram ブロック
diagram "Studio A" {
  direction: LR      # LR(左→右、既定)| TB(上→下)
  theme: light       # light(既定)| dark | mono | blueprint
  spacing: 60        # 機材どうしの間隔(px)
}
テーマ用途
light既定。画面とカラー印刷
dark暗い画面
mono白黒印刷とコピー
blueprint設備図面の青焼き調

mono は色をいっさい使いません。信号は線種で区別され、[color=…] で指定した被覆色は無視されます。コピーを通った色が残っているふりをしても誰の役にも立たないからです。

mono.khmclean
diagram "白黒印刷用" { theme: mono }

device cam "カメラ"       as camera   { out SDI : sdi }
device sw  "スイッチャー" as switcher { in 1 : sdi  out PGM : sdi }
device rec "レコーダー"   as recorder { in SDI : sdi }

cam.SDI -> sw.1    : sdi 30m "V-01" [color=青]
sw.PGM  -> rec.SDI : sdi 2m  "V-10"
output
白黒印刷用V-01 30mV-10 2mカメラSDIスイッチャー1PGMレコーダーSDI

テーマは -t/--theme でも渡せますが、ソース中の diagram { theme: … } が勝ちます。図は自分がどう見えるべきかを知っていて、呼び出し側は既定値しか知らないからです。

11出力と埋め込み

SVG のほか、編集できる draw.io ファイル、ケーブル表・機材表の TSV が出せます。

Markdown には kumihimo のコードフェンスをそのまま埋め込めます。ビルド時に SVG になります。

markdown
```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);

React・Vue・Astro のコンポーネントもあります。コンパイルは非同期で、新しい図ができるまで前の図が画面に残り、遅れて終わった古いコンパイルが新しい結果を上書きすることはありません。

React
import { Kumihimo, useKumihimo } from '@love-rox/kumihimo-react';

<Kumihimo source={src} theme="dark" onDiagnostics={report} />;

const { svg, diagram, diagnostics, pending, error } = useKumihimo(src);

onDiagnostics を使えば、誤配線のきれいな絵をそのまま公開してしまうことを避けられます。