家族でAlexaに音楽を流しながら、リビングの照明をクラブみたいに動かせたら楽しそうだと思いつきました。照明は7台、どれも自作Matterコントローラmatで動いています。問題は、Echoはスピーカーから出す音を外部に渡してくれないことです。音を取ってビートを検出する道は無いので、BPM(1分あたりの拍数)を人が決めて、そのテンポで拍を刻む「疑似同期」で十分それっぽく見えるはずだと判断しました。

家の中でどう動かすか

家族が触る入口はmandoだけです。拍を刻む仕組みも、新しいシェルスクリプトやcronを足すのではなく、mando自身に長く動き続けるループを持たせる方針にしました。mandoに外部コードを増やしたくなかったためです。

mandoにkind = "loop"という新しいデバイス種別を追加します。設定ファイルには、1拍ごとに実行するコマンドの配列(beat)と、小節の頭だけ先に実行するコマンドの配列(bar)を並べます。

[[device]]
name  = "club_living"
alias = "クラブ(リビング)"
kind  = "loop"
bpms  = [100, 120, 128]
max_minutes = 60
bar_beats = 4
beat = [
  ["mat", "group", "level", "--group", "living_lights", "--percent", "100"],
  ["mat", "group", "level", "--group", "living_lights", "--percent", "8", "--transition", "{fade}"],
]
bar = [
  ["mat", "group", "color", "--group", "living_lights", "--name", "magenta"],
  ["mat", "group", "color", "--group", "living_lights", "--name", "blue"],
  ["mat", "group", "color", "--group", "living_lights", "--name", "purple"],
  ["mat", "group", "color", "--group", "living_lights", "--name", "cyan"],
]
on_stop = [
  ["mat", "group", "color-temp", "--group", "living_lights", "--mireds", "370"],
  ["mat", "group", "level", "--group", "living_lights", "--percent", "100"],
]

mat group level / mat group colorは、7台を1つのグループとして一斉制御するMatterのgroupcastコマンドです。{fade}は拍の長さの80%を0.1秒単位に丸めた値に置き換わります。拍ごとに「100%に戻す→8%までフェード」を繰り返すと、照明がパルスします。小節が切り替わるたびにbarの色が順番に回るので、4色で一周する色替えになります。

実機7台で先に試した

設計より先に、2026年10月5日に実機で試しました。リビングの照明7台、120BPMで、方式ごとの違いを確かめています。

方式結果
groupcast(mat group color / level)を1拍2〜3発全拍送信でき、実行時間70〜300ms、スケジュールからのずれは最大5ms。7台ほぼ揃って変わる
unicastを7台に順番に送る全部3秒でタイムアウト。直後に1台だけ単発で送っても2.3秒かかった。不採用

groupcastなら1回の送信で7台に同時に届くのに対し、unicastは1台ごとに個別の応答を待つぶん遅くなります。拍のテンポに乗せるには速さが必須なので、ここでunicastは切り捨てました。

見た目も試しました。最初に明るさを100%で固定して全色を順番に回してみたところ、虹色に見えるだけでクラブらしさは出ませんでした。採用したのは、毎拍「100%→8%」のパルスと、4拍ごとの色替えを組み合わせる形です。色は暗めの寒色系(マゼンタ・ブルー・パープル・シアン)にしています。

選んだこと、捨てたこと

シェルスクリプトではなくmando本体でループを持つ。選択肢としては外部のcronやスクリプトに拍のタイミングを任せる形もありましたが、mando本体以外にコードを増やしたくないという方針があり、ループタスクはmandoのRust側に実装することにしました。

BPMの入力はタップで。毎回数値を打つのは面倒なので、ボタンをテンポに合わせて叩くと、直近8回までの間隔の中央値からBPMを算出する「タップ」ボタンをUIに用意します。叩き終えて1.5秒経つと、その場でそのBPMで再生が始まる設計です。

拍の遅れは積まない。各拍は開始時刻からの絶対スケジュールで発行し、処理が重くて次の拍に遅れた場合は、遅れた拍を飛ばして未来の拍に合わせ直します。遅れを引きずると、曲が終わる頃には拍がどんどんズレていくためです。

1拍の失敗は無視する。コマンドが1回失敗してもループは止めません。音楽の演出中に拍が1つ欠けるくらいは許容し、連続10拍失敗したときだけ警告を出す設計にしています。確認読みを挟まず常に「送ったら次」で進めるのは、ほかの機器につけている「状態を確認してから表示する」というmandoの原則とは違う部分です。演出用の機器なので、確実さよりテンポを優先しました。

次にやること

ここまでは設計と、実機での事前検証です。mando本体への実装はこれからで、まだリビングで鳴らしてはいません。設定ファイルへのデバイス追加とビルドを済ませれば、既存のmat groupコマンドをそのまま使えるので、配備自体は大きな作業にはならない見込みです。実際に動かしてみた結果は、また別の記事で答え合わせをします。