Zellij ターミナルワークスペースが持つ Wasm プラグインシステム

WebAssembly Advent Calendar 2024 の 10 日目の記事です。

この記事では、WebAssembly をリアルワールドで実用的に活用している Zellij について紹介したいと思います。

Zellij について

Zellij はいわゆる tmux や screen などのターミナルマルチプレクサの機能をもつ Rust でかかれた “terminal workspace” です。

https://zellij.dev

A terminal workspace with batteries included

ターミナルエミュレータ上で端末セッションの管理やタブ・ペインといった操作を加え、コマンドライン環境を強力にサポートしてくれます。

同様の機能を有する tmux は時間を使ってコンフィグレーションし使い方を身につける必要がありますが、Zellij は操作ガイドも常に表示することもでき、瞬間的に導入できるお勧めのターミナルマルチプレクサとなっています。

Zellij と WebAssembly

Zellij は一般的なターミナルマルチプレクサの機能に加え、WebAssembly によるプラグインシステムを持っており、任意の Wasm 対応のプログラミング言語で機能の拡張が可能です。具体的には、

  • ホットキーからペインを開き UI 含めた任意の処理を実行。
  • レイアウトに常に表示する情報ペインを実装。
  • パイプから入力された文字列処理して戻す。

などなどの処理を Wasm でかいたプログラムをもって実行できます。

プラグインは.wasm バイナリとして配布され、Zellij から実行時にダイナミックに読み込まれ内部の wasmtime ランタイムで実行されます。

このことから、amd64 や aarch64、riscv64(つまり各種 Linux や macOS)などのプラットフォームを気にせず、同一のプラグインバイナリを安全に動作させることができて非常に便利です。

プラグインの API は次のようになっています。

Rust の例:

use zellij_tile::prelude::*;

#[derive(Default)]
struct State {
}
register_plugin!(State);

impl ZellijPlugin for State {
    fn load(&mut self, configuration: BTreeMap<String, String>) {
        // ...snip...
    }

    fn update(&mut self, event: Event) -> bool {
        let mut should_render: bool = false;
        // ...snip...
        should_render
    }

    fn render(&mut self, rows: usize, cols: usize) {
    }
}

WebAssembly プラグインであるのに関わらず、引数のシグネチャが非常にリッチな形になっているのが確認できますが、これは Protocol Buffers の仲介をもって ABI の安定化がされています。

https://github.com/zellij-org/zellij/tree/main/zellij-utils/src/plugin_api

将来的には Component Model / WIT になるのかもしれませんが、現在用いることができるインターフェース手法のひとつとして個人的にとても参考になりました。

Zellij プラグインはお気に入りの言語でプラットフォームを気にせず思いついたアイディアをすぐ実装できて楽しいのではないかと思いますので、Wasm プレイとしても良ければ…!

https://github.com/zellij-org/awesome-zellij

A list of resources for Zellij workspace: plugins, tutorials and configuration settings.

というわけで、Wasm でお手軽に拡張できる Zellij の紹介でした。- 最後にいくつか自分がつくったプラグインを貼り付けて記事を終わりたいと思います。

zellij-datetime – WASI から現在時刻をシステムインターフェースし、タイムゾーンを選択して表示

zellij-imagebox – パイプから画像バイナリをもらって、いい感じにリサイズ・減色して sixel graphics でペイン上に出力

Ubuntu 22.04 LTS から Ubuntu 24.04 LTS にアップグレードメモ

Ubuntu 24.04 LTS リリースからしばらく経ちましたが、ようやくメインで使っている ThinkPad P14s (AMD) を Ubuntu 22.04 から 24.04 にアップグレードしました。導入メモ。

アップグレード:

sudo do-release-upgrade

30分ほどで特に問題なく終了。

uname -a
Linux thinkpad-p14s 6.8.0-48-generic #48-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 27 14:04:52 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

導入後の Ubuntu 24.04 LTS (Wayland セッション):

Good work! 😀

無効になったサードパーティリポジトリを再有効化:

以下に手順があります。

Firefox は標準の snap 版ではなく Mozilla 提供のリポジトリのがお勧めです。

GNOME Shell Extention 再有効化:

sudo apt install gnome-shell-extension-manager
  • Clipboard Indicator (クリップボードマネージャ)
  • Current Monitor Window/App Switcher (マルチモニタや仮想画面で Alt(Super) + Tab の出力をカレント画面のアプリだけに抑止)

ふと今気がついたのですが、クリップボードマネージャが画像にも対応していました。グッド。

オーディオ系:

Pipewire の設定で USB-DAC の 96KHz をデフォルトとかにする場合。

  • /usr/share/pipewire/pipewire.conf
  • /usr/share/pipewire/pipewire-pulse.conf
context.properties = {
    ## ..snip..
    default.clock.rate          = 96000
    default.clock.allowed-rates = [ 48000, 96000 ]
    ## ..snip..
}

接続確認。

pw-dump | jq '.[] | select(.type == "PipeWire:Interface:Node" ) | .info.props'

Audacious 出力サンプリングレート設定:

プラグイン – エフェクト – サンプリング周波数コンバータより。(96KHz とかにアップサンプリングする場合)

Wayland 系メモ:

Firefox のピクチャーインピクチャーがデフォルトで最前面表示にならない。右クリックして「最前面に維持する」でうまくいく。

  • OBS Studio の自画面キャプチャはまだうまくいかない。(ので使う場合は X.org セッションに切り替えて使用)
  • 画面キャプチャの Frameshot もキャプチャできずに停止してしまうような動作。いったん GNOME Shell 標準のものでがんばる。
  • Shotcut などアプリ起動のスプラッシュ画面(ウインドウ枠なし)が画面センターではなく、左上とかに表示されるようだ。

Command Line Interface:

fzf が 0.44.1 (debian) となり、罫線が右に伸び切らない表示になったため以下を設定して解消。

export RUNEWIDTH_EASTASIAN=0

GoのTUIで表示が崩れる場合

gnome-terminalを使用している場合は、設定の「曖昧幅の文字(W)」と環境変数RUNEWIDTH_EASTASIANを一致させよう。

OneDrive

Ubuntu 22.04 LTS では onedriver を使っていましたが、24.04 LTS から Ubuntu 標準のオンラインアカウント接続を使うように変更しました。

オンラインアカウント設定時に謎の Client ID などを聞かれますが、ブランクのままサインイン押下後に通常の Microsoft アカウントでログインすれば OK のようです。

関連

KORG NTS-1 digital kit mkII で Rust のカスタムオシレータ制作

KORG の組み立てシンセキットである NTS-1 digital kit mkII は、C/C++ の SDK が公開されており、自分でプログラミングしたオシレータやエフェクタを本体に転送して動作させられる、とても面白いシンセサイザーです。

NTS-1 digital kit mkII – PROGRAMMABLE SYNTHESIZER KIT | KORG (Japan)

作って、繋いで、触って、手のひらサイズの強力なシンセサイザーが帰ってきた !! 進化したカスタマイズにより解き放たれるパワー

かねてより、API が公開されていてカスタムソフトシンセが発音できるハードウェアが欲しいなと考えていましたので、購入して遊んでみました。

PC との接続

本機には USB-C ポートがあり、PC からは MIDI IN/OUT デバイスとして見えます。

ユーティリティとして提供されている KORG KONTROL でカスタムオシレータやエフェクタの転送が MIDI SysEx 経由で行えます。

なお、Windows 接続で他に多くの MIDI 機器が接続されている環境では、KORG MIDI ドライバーが上位の方にいないと、ファームウェアアップデートや KORG KONTROL から本機を認識できないと思われる事象がうちの環境ではありました。(いったん他の MIDI ポートドライバを削除して入れ替えるなどしていたら修正されました)

また、公式の SDK (louge-sdk) で提供されているコマンドライン版の louge-cli は本機には未対応のようです。もしサウンドユニットの開発でコマンドライン転送が使いたい場合は、Python によるオープンソース実装の py-logue-tools を使わせていただくと便利です。

https://github.com/TarkanAl-Kazily/py-logue-tools

Implements a basic python-based MIDI librarian for the Korg NTS-1 Mk2 and similar family of Korg logue-sdk devices. This project is not affiliated with Korg.

サウンドユニットの開発

前述の louge-sdk の README 通りに環境設定し、API に従い C/C++ プログラミングして、できたサウンドユニットを USB-C 経由で転送して試す、というワークフローでうまくいくと思います。

ファームウェアの Ver1.2 未満であると API にいくつか不具合がありますのでアップデートしてからの開発がお勧めです。

本機のサウンドユニットの特徴としては、バイナリに ELF 形式を採用していて USB-C 転送後にダイナミックにユニットをロードしていることです。また、おそらく API のシステムコール先は Arm7 の短縮命令(Thumb)が用いられています。

このことから、普通に SDK でプログラミングしている分には遭遇しないかとは思いますが、いくつかのトラブルシューティングがありますので、ここに列挙しておきます。

音源 Import 時に KORG KONTROL が「ELF が間違っています」エラーでサウンドユニットを受け付けてくれない:

  • ELF バイナリから動的リンクをするダイナミックリンクテーブル rel.ptl エントリに、NTS-1 以外のシステムコールが入っている。
  • SDK の sysroot に入っていない関数がリンクされるとこの状態になります。Linux などに含まれる readelf -a コマンドで中を確認して呼び出しをなんとか解決してあげます。
  • また、ELF ヘッダーの先頭のマジックフラグも KORG KONTROL がバリデートしているようです。

音源 Import から転送までうまくいくもののサウンドユニットが認識されない:

  • 正常に初期化できないユニットは無効にされるようです。KORG KONTROL の状態と不一致して、その後の処理もおかしくなるようなので、いったん本機も KORG CONTROL もリセットしましょう。
  • なお本機のリセットは DELAY ボタンを押しながら電源投入です。(その後の操作はマニュアル参照のこと)
  • header.c で定義する unit_header 値がおかしい。例えば設定パラメータ数 num_params: 2, 項目を 2未満にすると無効になるようです。
  • 転送直後のサウンドウニットの初期化がうまくいかないことがある?
    いったん別ユニットを選択して、自分のユニットに再度設定すると初期化コードが走ったような経験があります。(気のせいかもです)

Rust によるサウンドユニットの制作

C/C++ でかけばいいのですが、完全に趣味、勉強もかねて Rust でつくってみました。リンカー部分でいろいろはまって時間がかかりましたが、以下のリポジトリにビルド手順やソースコードとサウンドユニットのバイナリを配置しています。

https://github.com/h1romas4/nts1mkii-rust-wave

This repository is a Rust ported build of the WAVES(osc) synthesizer for the NTS-1 digital kit mkII included in logue-sdk. If you are interested in creating an NTS-1 Digital Kit mkII sound source in Rust, perhaps this repository may be of some use to you.

README には新しいユニットの追加方法や、はまった部分をメモしてありますので、気になる方はご覧ください。

SDK のサンプルで入っている Waves オシレータをフィルター部分を除いて移植したものと、試しにつくってみた 32byte * 32 音色の波形メモリーオシレータを入れています。

プログラムは C/C++ SDK が提供する関数を bindgen でバインドする方式をとって動作しています。

SDK がインライン展開指定の関数が多いため、Rust 版は速度的、バイナリサイズ的にも若干不利になっており、また no_std 環境で unsafe コード多数で、Rust である意味あるかと言われると現状そうでなさそうな気もしますがw、いったん動くことの勇気として。