Alacritty 高速ターミナルエミュレーター

Rust 方面で気になっていた高速ターミナルエミュレーターである Alacritty を Ubuntu 18.10 にて試してみました。

まだ alpha levelとのことですが、2日ほど使い続けてみましたが不具合もなく、むしろ速くてとても快適でしたので常用させていただいております。:)

https://github.com/jwilm/alacritty

Alacritty is the fastest terminal emulator in existence. Using the GPU forrendering enables optimizations that simply aren’t possible in other emulators.Alacritty currently supports macOS and Linux, and Windows.

サイトに導入手順がありますが、Ubuntu の場合はライブラリ依存関係と Rust のツールチェインを入れビルドをかけると .deb が作成されパッケージとしてインストール・することができます。

Rust ツールチェインの導入

https://rustup.rs/

curl https://sh.rustup.rs -sSf | sh

依存関係の導入

https://github.com/jwilm/alacritty/blob/master/INSTALL.md#debianubuntu

apt-get install cmake libfreetype6-dev libfontconfig1-dev xclip

Alacritty のビルドとインストール

https://github.com/jwilm/alacritty#debianubuntu

git clone https://github.com/jwilm/alacritty.git
cd alacritty
cargo install cargo-deb
cargo deb --install

起動すると設定ファイルが、$HOME/.config/alacritty/alacritty.yaml として作成されますので、画面サイズやフォントの設定をします。自分はプロンプトなどに Powerline を使っていますのでフォントの指定を Ricty Diminished w/ Powerline patched に変更しました。

font:
  # Normal (roman) font face
  normal:
    family: Ricty Diminished Discord for Powerline
    # The `style` can be specified to pick a specific face.
    # style: Regular

ということで起動してみると、、

フォントずれも発生せず日本語環境でもいい感じに動作しました!

IM による漢字入力についてはまだインライン入力ができないようですが、カーソル位置の適切な場所に吹き出し形式でインサートすることができますので、大量に日本語を入力しない限り違和感はないと感じました。

マウスエミュレーションも動作し、スクリーンショットのように tmux を動作させるとペインの移動やスクロールなどがマウスで行うことができます。

tmux(アプリケーション)にマウスを奪わせたくない場合は、一般的なターミナルエミュレーターのアサインと同様 Shift キーを押しながらマウス操作すればOKです。また OS に対するコピーアンドペーストは、Shift + Ctrl + c や v などが使えます。

検証もかねまして 2日ほどがちゃがちゃいじってみましたが、不具合を起こすこともなく快適に動作しています。すごい 🙂

(画面を覆い尽くす)Ctrl + Enter のフルスクリーンモードがあれば自分的には完璧だったのですが、issue も上がっていましたので少し経てば実装されるものと思います。

Alacritty は Windows 版も開発が進んでおり先日起動が成功したようです。

ためにし WSL(bash on Windows)で試したところ、マウスエミュレーションが動作しませんでしたが、かなり動き始めているようです。(残念ながら IM による日本語入力窓はへんな位置にポップアップしてしまいました)

Windows 上の Alacritty で WSL を起動する場合は $HOME\alacritty.yaml のシェルの指定を次のようにします。

shell:
  program: "C:\\Windows\\sysnative\\bash.exe"

Rust でつくられているということで高速で強固。自分は Rust の勉強中にてソースコードもかなり参考になりそうです。継続してウォッチしていきたいと思います。 🙂

Rust/WebAssembly でレトロシンセをエミュレートする

以前から WebAssembly を使ってレトロシンセ音源をエミュレートしてブラウザーで発声させてみたいと思っていたのですが、Rust が WebAssembly に直接コンパイルできるようになったのをきっかけに挑戦し、なんとか動かすことができました。

以下からデモを見ることができます。 🙂

Synth emurator by Rust/WebAssembly

Rust/WebAssembly を使いレトロシンセサイザーをウェブブラウザ上でエミュレートして楽曲を再生します。

WebAssembly 非対応の IE を除く、PC とモバイルのほとんどのブラウザーで動作すると思います。(なお、iOS 11 Safari と Android Chrome はサンプリングレートを無視してしまう処理があるようで高め・速めで再生されています。iOS 12 Safari では修正されたようです。)

ソースコードも github にコミットしました。

https://github.com/h1romas4/rust-synth-emulation

PSG(SN76489) VGM player by Rust

さて、初めての Rust だったこともあり製作に結構時間がかかりましたので顛末でも…。

何はなくとも Rust 言語を覚えなければということで、ちょうどオライリーから「プログラミング言語 Rust」が発売されたので購入。

読み進めるも若干飛ばし気味に進む展開に、まだ早かった…と先に公式ドキュメント版プログラミング言語 Rust を読みました。

プログラミング言語Rust: 2nd Edition”の日本語版PDFを作成した

プログラミング言語Rust: 2nd Editionの日本語版PDFを公開しました!

550ページ以上の素晴らしい翻訳と組版で本当に感謝しかありません…。2週間ほどかかりましたが最後まで通して読むことができました(オライリーは少しできるようになってから読んだほうがいいかもしれません)

合わせて、海外のハッカーさんが Rust でライブコーディングしている youtube 動画を見ながらプログラムの組み立て方などを覚えています。こちらも非常に参考になりました。

そんなこんなで半月ほどかけて、C 言語でかかれた SN76498(PSG)エミュレーターを Rust に移植し、PCM サンプリングファイルを出力させることに成功。 🙂

このプログラムを元に WebAssembly 化していきました。

Rust 側での状態の保持

WebAssembly は JavaScript と WebAssembly 間で関数を公開し、互いに呼び出すことができますが、WebAssembly(Rust)側で状態を保持したいことがあります。

今回のプログラムの構成は JavaScript 側で AudioContext イベントを回し、発声バッファが必要になったタイミングで Rust 側で PSG をエミュレーションし 2048 サンプルごとに渡すようなロジックになっていますが、Rust 側では楽曲のどこまで再生したかなどなどを覚えている必要があります。

保持したいデーターをつめた Rust 側の構造体は次のようにしました。

struct VgmPlay {
    sn76489: SN76489,
    vgmpos: usize,
    remain_frame_size: usize,
    vgmend: bool,
    buffer: [f32; MAX_BUFFRE_SIZE],
    vgmdata: [u8; 65536]
}

C言語であれば static にしておけば OK ですが、Rust の static は実行前に大きさが決まっていないとコンパイルエラーとなるため、lazy_static! マクロを用いて Mutex 内にこの構造体を保持しています。

lazy_static! {
    static ref DATA: Mutex<VgmPlay> =
        Mutex::new(VgmPlay::from());
}

JavaScript に公開する関数では Mutex をロックした上で中の構造体を取得し、構造体に impl した関数を呼び出します。

#[no_mangle]
pub unsafe extern "C" fn play() -> f32 {
    let vgmplay = &mut DATA.lock().unwrap();
    vgmplay.play() as f32
}

ブラウザに実装された JavaScript はシングルスレッドであるため、関数に再入がかかったり同じ構造体を使う別な関数が呼び出されることはありませんが、Mutex につめておくと安心ですね。 このあたりは次の記事が大変参考になりました。

Rocket – A Rust game running on WASM
Technically, this isn’t necessary in the case of Javascript, since there will only be one thread. Still, the type system knows nothing about that… Hence the mutex.

感謝。

メモリーの共有

JavaScript/WebAssembly 間でメモリーのポインタを共有をすることができます。今回はサンプリングバッファを割当て、Rust 側で PSG をレンダリングしたメモリーをそのまま JavaScript からアクセスして AudioContext に書き込むことで発声させています。

Rust 側でメモリーの位置を返却。

#[no_mangle]
pub unsafe extern "C" fn get_audio_buffer() -> *const f32 {
    let vgmplay = &mut DATA.lock().unwrap();
    &amp;(vgmplay.buffer[0])
}

JavaScript 側で ArrayBuffer としてアクセス。

wasm_audio_buffer = new Float32Array(
    wasm_memory.buffer, wasm_exports.get_audio_buffer(), SAMPLE_LENGTH);
// ...
ev.outputBuffer.getChannelData(0).set(wasm_audio_buffer);

また、楽曲データーである .vgm ファイルを http して Rust のメモリーに格納することもしています。

Rust で上記のサンプリングバッファ同様 vgm_data のポインタを関数で公開した上で JavaScript 側から fetch した値を Uint8Array として set。

wasm_vgm_data = new Uint8Array(
    wasm_memory.buffer, wasm_exports.get_vgm_data(), MAX_VGM_DATA);
// load vgm data
fetch('./vgm/vgmsample.vgm')
    .then(response => response.arrayBuffer())
    .then(bytes => wasm_vgm_data.set(new Uint8Array(bytes)))
    .then(results => {
        // ...
    });

JavaScript/WebAssembly でメモリーが共有できるため、余分なコピーが発生せず高速に処理することができました。今回は実装していませんが、Rust 側でサンプリングをフーリエ変換してフレームバッファメモリーにビジュアライズして書き込み、canvas に転送なんてこともできると思います。(Web Audio API にも FFT がありますが :D)

Web Audio API

WebAssembly だけの話ってわけでもないのですが、ブラウザの Web Audio API の使い方にちょっと困りました。

Rust 側としてはサンプリングを全て再生したタイミングで次のサンプリングを送り込みたいのですがそれを Rust 側で知るすべがなかったため、JavaScript 側の AudioContext の onaudioprocess イベントを使い(バッファを吐ききると発動する) Rust 側からサンプリングを渡す方式としています。

残念ながら現在 onaudioprocess イベントは JavaScript のメインスレッドを使いきる可能性があるということで非推奨となっており、Audio Workers を使えということのようです。

Audio Workers

Audio workers を利用すると web worker のコンテキストで音声処理をおこなえます。Audio Workers は比較的新しいいくつかのインタフェース (2014 年 8 月 29 日に定義)によって定義されているため、これを実装したブラウザはまだありません。実装が完了すると、 ScriptProcessorNode, と JavaScript による音声処理 で述べた機能を置き換えることとなります。

が、まだ実装が進んでいないようです…。 とりあえず今のところ WebAssembly では、JavaScript のタイマーやイベントを契機に処理するという流れが良いかと感じました。

(追記:Audio Worker は仕様から消えて AudioWorklet になったようです)

というわけで、なんとなく WebAssembly でのプログラムの形がつかめてきました。 Rust も少しだけ分かってきましたのでまた何かつくってみようと思います。

WebAssembly はブラウザベンダー4社でつくってるだけあり、互換性もよく(今回 WebAssembly 的には何のトラブルもなく全てのブラウザで動作しました)、次のステップでは GC やスレッドも入ってくるということなので楽しみな技術です。

ついにブラウザーで好きなプログラムを動作させることができるようになって嬉しいす。 😀

M5Stack/ESP32 でメガドライブ音源をエミュレート

勉強会で見せていただいて気になっていた M5Stack を買ってみました。  🙂

内蔵スピーカーもついていますので hello world がてら、 PSG 音源をエミュレーションしてみようと始めたところ、なかなか鳴ってくれなくて苦労しました…

「エンディングI/SORCERIAN/Copyright© Nihon Falcom Corporation」

追記。FM音源 + YM2612 の DAC もエミュレートできて無事メガドライブ(GENESIS/MEGADRIVE)音源になりました 😀

勢いがあるうちに忘れないようメモしておきます。

ソースは github で公開しています。(力尽きたので不出来、手抜きの部分は大目に見てください)

https://github.com/h1romas4/m5stack-synth-emulation

GENESIS/MEGADRIVE(YM2612+SN76496) VGM player on ESP32/M5Stack

開発環境

PSG エミュレーターを移植するのに gcc ツールチェインを使いたかったので開発環境は(Arduino ではなく) ESP-IDF を使いました。 リンク先の通りに SDK ファイルを展開して、 $IDF_PATH を設定してあげれば OK です。

今回は Linux を使ってやりましたが、Windows 10 の場合は WSL を使うと楽かもしれません。(未確認)

M5Stack ESP-IDF 用の雛形が github に提供されていますので、手順の通り git clone して make menuconfig してシリアルを設定して、make flash monitor すれば hello world できると思います。

https://github.com/m5stack/M5Stack-IDF
To use as a M5Stack component of ESP-IDF

ちなみに monitor の抜け方は ctrl + [ です。

自分がやったときは Makefile にバグがありいきなり、make flash できないという不具合に遭遇しています。

Detected overlap at address: 0xe000 for file:

これは Makefile にパッチする次のワークアラウンドで解消できました。(m5stack リポジトリは既に修正済みだと思います)

(esp-idf) make flash fails due to the overlapping of offset addresses #1724

https://github.com/espressif/arduino-esp32/issues/1724

static heap の上限

static heap をたくさん使おうとすると

region `dram0_0_seg' overflowed by 67900 bytes

とリンクエラーになります。 がびーん。

どうやら static heap にサイズ上限が決められているようなので、static で取得するのをやめて動的に heap_caps_malloc() して回避しました。

build/ 下に app-template.map というファイルができていて、static heap のサイズが分かります。(この場合 DECAY_TO_ATTACK 変数に 0x4000 取られています)

.bss._ZL15DECAY_TO_ATTACK
0x000000003ffbd1cc     0x4000 /home/..../libsynth.a(ym2612.o)

また、make size すると次のように現在のメモリーサイズが表示できます。

Total sizes:
 DRAM .data size:    6488 bytes
 DRAM .bss  size:  118632 bytes
Used static DRAM:  125120 bytes (  55616 available, 69.2% used)
Used static IRAM:   36140 bytes (  94932 available, 27.6% used)
      Flash code:  181978 bytes
    Flash rodata:   64104 bytes
Total image size:~ 288710 bytes (.bin may be padded larger)

static heap を回避しても malloc で失敗することもありますので、あちこち移動させてうまいことメモリーに載せていきます。 なお、heap_caps_get_free_size() で動作中の残りメモリーを知ることができるようです。

内蔵 DAC の鳴らし方

なかなか鳴らなくて PSG エミュレーションが悪いのか DAC の操作が悪いのかで苦労しました…。

I2S 経由の DAC は次のようなイメージで鳴ると思います。

static const i2s_port_t i2s_num = I2S_NUM_0; // i2s port number

i2s_config_t i2s_config = {
    .mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
    .sample_rate = SAMPLING_RATE, // #define SAMPLING_RATE 44100
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = static_cast<i2s_comm_format_t>(I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = 0,
    .dma_buf_count = 16,
    .dma_buf_len = 512,
    .use_apll = false,
    .fixed_mclk = 0
};

i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
i2s_set_pin(i2s_num, NULL);

size_t bytes_written = 0;
uint16_t buf[2];
buf[0] = 0x0000; // right sample
buf[1] = 0x0000; // left  sample
i2s_write((i2s_port_t)i2s_num, buf, sizeof(uint16_t) * 2, &bytes_written, portMAX_DELAY);

buf には符号付き符号なしリトルエンディアンのステレオサンプリングを渡します。(内蔵 DAC の場合は符号なしでした… どうにも音が割れると思ったら…)上のサンプルソースは 1フレームですが、この config で 4096 フレーム * 2(ステレオ)が一気に渡せました。

flash への任意データーの書き込み

partitions.csv ファイルをつくり make menuconfig (sdkconfig) で指定してあげることにより、内蔵の flash メモリーの任意の位置に esptool.py でバイナリを書き込みプログラムから read できます。

esptool.py --chip esp32 --port "/dev/ttyUSB0" --baud 115200 write_flash -fs 4MB 0x211000 "バイナリファイル"

プログラムから読み込むときは、

esp_partition_find_first() して esp_partition_mmap() でポインターがもらえます。便利。 🙂

その他

  • 普通に printf すると make flash monitor するだけで実行してコンソール出力されるので嬉しい。
  • プログラムが大きくなったせいか途中から “Make error of dangerous relocation” と怒られてリンクできなくなりました。 CFLAGS に -mlongcalls をつけることで解消しました。
  • M5STACK の内蔵スピーカーはそこそこ音が割れます。 PSG くらいがちょうど良さそうです。
  • 実はがんばって FM音源も移植したのですが、いい音で鳴りませんでした… さすが ESP32 だけあって処理速度は十分です。 ソースに残骸がありますので、気になる方は鳴らしてみてください…)
  • FM音源(YM2612)も鳴らすことができました!
  • YM2612 + SN76489 + YM2612 の内蔵 PCM をエミュレートしたら CPU の 1コアを使い切っているようです。

以前から充電できるポケステがあったらいいのになぁと思っていたのですが、ちょうどでてきてくれた M5Stack さん。 久しぶりに夢中になってしまいました。

今回の hello world で一通りの困りそうなことは踏んだ気がしますので、引き続き何か作っていきたいと思います。 🙂