Quantcast
Channel: ひろまさ – hiromasa.another :o)
Viewing all 96 articles
Browse latest View live

YM2612 + SN76489 メガドライブ音源を ESP32 で鳴らす (基板製作編)

$
0
0

すごいぞメガドライブミニ!(2回目)

というわけで「YM2612 + SN76489 メガドライブ音源を ESP32 で鳴らす (基板製作編)」です。この記事で使われているソースコードや結線などは github で公開しています。

https://github.com/h1romas4/esp32-genesis-player

esp32-genesis-player (work in progress)

ここまでブレッドボードでがんばってきましたが、回路もそろそろ良さそうかな…ということでプリント基板を注文してみました。

github のほうに結線は書いていましたが、オーディオブロックも入れて回路は次のようにしてみました。(素人がかいていますのでおかしな部分はご容赦ください…)

KiCAD にて作成です。この流れから基板を注文できるようガーバーデーターをつくりました。

いくつかミスっていたり課題がありますのでガーバーデーターは Rev.B の時にコミットしたいと思います。

注文したのは FusionPCB さんで送料込み 5枚 $12 くらいでした。自分の基板がこんなに簡単につくれるようになってよい時代です。 🙂

ゴールデンウィーク & 労働節にかぶってしまいましたのでちょっと遅れましたが、待つこと 2週間くらいで… キター。

早速部品をはんだ付けです。

どきどきしてマイコンにつなぐも鳴らなくてしょんぼり(ソフト側を間違ってました…)

てなわけで基板が無事動作をはじめましたので、予てからやってみたかった、古代祐三さんが公開されている MUCOM88 データーを再生してみました。

https://www.ancient.co.jp/~mucom88/

MUCOM88は、古代祐三が自ら開発した、NEC PC-8801mkⅡSR以降に搭載されたYM2203及びYM2608(サウンドボードⅡ)を対象とする楽曲制作用ツールです。

https://onitama.tv/mucom88/

2018年、OBSLive年末生放送内で発表された、MUCOM88 Windowsのページです。
このページで公開されているアーカイブ内に、MUCOM88 Windowsフルセット、及び古代祐三氏サンプル曲データが含まれています。


現在この基板用の制御プログラムは VGM データーを再生するものになっていますので、MUCOM データーを VGM データーに変換する mucomMD2vgm を使わせていただいております。

https://github.com/kuma4649/mucomMD2vgm

このツールは、ユーザーが作成したmucom形式のMMLファイルを元にVGMファイルを作成します。

自分の VGM パーサーでは PCM 再生系で未対応のコマンドがありましたので(hack ですが)いくつか処理を追加して github にコミットしています。

というわけで…

Original Music Data (BARE KNUCKLE 2 Go Straight)
Copyright : (C) Yuzo Koshiro
License : CC BY-NC-ND 4.0
Name : MUCOM88 Windows Sample Music Data
Version : Ver1.7a
https://www.ancient.co.jp/~mucom88/
https://onitama.tv/mucom88/

MUCOM VGM Convert
mucomMD2vgm
https://github.com/kuma4649/mucomMD2vgm

感動です…。いろいろ蘇ってきて泣きそうです…。

というわけで、基板の方ですが、マイコンからのノイズのってしまっていたり、痛恨のコネクターを誤ってハーフピッチにしてしまうミスがあったりしますので、もう少しいじりながら改良していきたいと思います。

この基板は SPI と I2C 通信ができれば発声させることができますので、PC からの制御でゲームはエミュレーション、楽曲は実チップなどなどもできそうですので、ゆっくりつくっていきたいです。 🙂

関連


Maixduino K210/RISC-V マイコンでメガドライブエミュレーターを動作させる

$
0
0

twitter を眺めていましたらスイッチサイエンスさんから、かねてより興味があった RISC-V SoC のマイコンボードが発売されていましたので買ってみました。 LCD 付きで 4000円くらいなり。 🙂

CPU クロック 400MHz(600MHz)・メモリー 6MB(8MB) とかなり高性能なマイコンですので、ESP32 では諦めていたメガドライブのエミュレーターを動作させることに hello world がてら挑戦し、まずは起動できました。やった〜。

まだ起動するだけのものですがソースコードを github にコミットしています。

https://github.com/h1romas4/maixduino-genplus

Genesis-Plus-GX をエミュレーターのコアとして使わせてもらい、YM2612/SN76489 もエミュレーションされていますので、I2S にサンプリングバッファを流せば音も発声すると思います。

ちょっと原因不明ですが、まだ特定の条件でプログラムがダウンしてしまうことがあるようです。要調査。 アライメント関係の不具合で修正することができました。

また ROM のサイズは 128K 〜 640K バイトのものであれば最初の malloc には成功して起動します。大きなものはスタックオーバーフローするかもです。1Mバイト(ソニック2 などの 8M ROM) 以上は残念ながらメモリー不足で初期の malloc で落ちてしまいます。

さて、この記事では Maixduino 及び K210/RISC-V の開発環境の構成と、製作中に気がついた事をメモがてらまとめてみます。

開発環境の構成

Maixduino は SoC として K210 を使った開発ボードです。 Maixduino が提供する開発環境及びライブラリーは Arduino コアとして提供されています。

インストールガイドとソースコード

PlatformIO で Sipeed MAIXDUINO の設定がありますので、おそらく導入はこれを使うのが一番簡単です。

Maixduino Arduino コアは、K210 SoC 提供元の Kendryte が提供する K210 SDK とツールチェインに依存しています。K210 の SDK は OS なしの standalone 版と FreeRTOS 版があります。 Arduino コアでは standalone 版を使っています。

Maixduino Arduino コアのドキュメントはありますが現在は書きかけっぽいです。ライブラリの example のソースコードを参照するのがよいと思います。

この記事のメガドライブのエミュレータープロジェクトでは、勉強がてら Maixduino の Arduino コアを使わずに直接 kendyte-standalone-sdk を使ってプログラミングしました。

SDK のサンプルが以下から参照できます。

また K210 プログラミングガイドやペリフェラルなどの資料は次からダウンロードできます。

環境の構築はツールチェイン類を PC 上に配置して、

SDK をダウンロードしてきて、src ディレクトリの下に自分のプロジェクトを作成し、cmake して make すればOKです。

詳しい手順が上記のサイトの Usage にあります。

メガドライブエミュレータープロジェクトでは、ビルドに使う SDK のバージョンを固定したかったため、SDK をおなかに抱える構成としています。ビルド手順なども以下に記載していますのでご参考まで。 🙂

製作中に気がついた点

箇条書きにて。

  • Maixduino は K210 SDK Demo で使われているボードとペリフェラルの構成が異なり、LCD と SD カードはそれぞれ SPI0 と SPI1 に割り当てられている。また LCD コントローラーも異なる。 Arduino コアを参考に移植しました
  • K210 は RISC-V * 2 と AI コアと呼ばれる 3コア構成。このうち AI コアを有効にすると 2MB SRAM が追加で使えるようになる。ここここを参考
  • static 領域をおく .bss 領域が大きくないため、エミュレーターの移植では大きな static を動的に malloc するように修正
  • それでもエミュコアがメモリー不足でしたので、使わない機能を disable できるようにコアのソースを修正してなんとか起動。
  • CPU のクロックは 400MHz が標準設定だが、600MHz まで上げることができる
  • いろいろやっているも先人の方がおられることに気がつく。 Quake や DOOM、MMD が動いてる!すごい!

ボード付属のカメラで画像認識なんかもできるようなので、引き続き遊んでみようと思います。 🙂

関連

WSL + Alacritty で Powerline を使う

$
0
0

Windows 10 の WSL (Windows Subsystem for Linux) は Linux 環境が Windows 上で簡単に使えるため、UNIX 系の OS と親和性の高い開発系を持つウェブなどのプログラミングに大変便利です。

自分はこの WSL 環境に高速ターミナルエミュレーターである Alacritty から接続して使っていますが、日本語や Powerline を使う上でいくつか必要な設定がありますのでここに記載しておきます。(なお、残念ながら Windows 版ではまだ日本語のインライン入力はできません)

Alacritty の設定

Alacritty の設定ファイルは以下の位置にあります。(なお、AppData は隠しフォルダーです)

C:\Users\[ユーザ名]\AppData\Roaming\alacritty\alacritty.yml

Alacritty 起動時に cmd.exe ではなく WSL を直接起動する設定:

shell:
  program: /Windows/System32/wsl.exe

Windows の新しい API(conPTY / Windows 10 October 2018 update 以降)を有効にして、Powerline や vim でカーソル位置がずれないようにする:

enable_experimental_conpty_backend: true

Powerline を使うため Ricty Diminished w/ Powerline patched フォントを設定する:

font:
  normal:
    family: Ricty Diminished Discord for Powerline

WSL の設定

WSL と WSL 2(現在 insider build)の Ubuntu 18.04 で有効です。

標準で ja ロケールが入っておらず文字化けするのでロケール追加:

$ sudo locale-gen ja_JP.UTF-8
$ sudo /usr/sbin/update-locale LANG=ja_JP.UTF-8
$ echo $LANG # 設定されていなければいったんターミナル再起動
ja_JP.UTF-8

ネットワークがプロキシ配下の場合は以下を実施:

# for curl
echo 'proxy = "http://example.com:8080"' > ~/.curlrc

# for wget
echo 'http_proxy=http://example.com:8080' >> ~/.wgetrc
echo 'https_proxy=http://example.com:8080' >> ~/.wgetrc

# for other (include nodejs/pip)
echo 'export HTTP_PROXY="http://example.com:8080"' >> ~/.bashrc
echo 'export HTTPS_PROXY_="${HTTP_PROXY}"'  >> ~/.bashrc

# for apt
sudo echo 'Acquire::http::proxy "http://example.com:8080";' >> /etc/apt/apt.conf
sudo echo 'Acquire::https::proxy "http://example.com:8080";' >> /etc/apt/apt.conf

# for jvm (optional)
# export JAVA_OPTS="-DproxyHost=example.com -DproxyPort=8080"

Powerline を導入(WSL 1 は速度が遅いので、素早く動作する Rust 製の powerline-rs を使っています):

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ sudo apt install pkg-config libssl-dev
$ cargo install powerline-rs

powerline-rs を .bashrc にてプロンプトに設定:

$ vi ~/.bashrc # 一番下に追加
prompt() {
    PS1="$(powerline-rs --shell bash $?)"
}
PROMPT_COMMAND=prompt

以上、あとは tmux などを入れてあげると便利なターミナル環境になると思います。

なお、powerline-rs は tmux のステータス行には対応していませんので、tmux も powerline 化した場合は pip3 から powerline-status も導入すると良さそうです。

WSL 1 では git リポジトリがあるディレクトリなどで powerline がちょっと遅くなりますが、powerline-rs ならほぼほぼ問題ない速度で動作するようです。WSL2 ではファイルシステムの速度が大幅に速くなっていますので、普通の Linux と遜色ない速度で動作しています。

本題とはあんまり関係ないですが、Poweline 設定導入後 VSCode から WSL に VSCode WSL Extention で接続すると次のようになります。 (VSCode のターミナルも powerline フォントを設定してあげます)

xterm.js すごい。

WebAssembly/Emscripten を使ってエミュレーターをブラウザで動かす

$
0
0

WebAssembly を使うとブラウザー上でいろいろな言語のエコシステムが使えて楽しいなと、最近 Rust/WebAssembly で遊んでいたのですが、ふと C もやってみようかなと hello world 的にメガドライブエミュレーターを動かすことに挑戦してみました。

実は Emscripten はかなり前に一度挑戦していたのですが、ブラウザーで動作させる際のビルド周りがなかなか大変で、あまり大きなものは動かすことができませんでした。

再挑戦ということで調査したところ、昨今はビルド周りも整備されていてなかなかいい感じに環境ができあがっているようです。

この記事のソースコードは github で公開しています。解説よりも、ソースを見ていただいたほうが早いかもしれません。 🙂

https://github.com/h1romas4/wasm-genplus

Genesis-Plus-GX WebAssembly porting (work in progress)

Emscriten + webpack ビルド

よい製作にはよいビルドということで、JavaScript 系は webpack を使ってビルドし Emscriten/wasm を読み込むようにしています。

当初は emcc-loader という webpack の extention を使って、webpack から emcc(Emascriten のコンパイラ)を呼び出す形にしていたのですが、現在メンテナンスされていないようで Emascriten 1.39.0 では emcc が出力する JavaScript のグルーコードがエラーとなりうまく wasm をロードすることができませんでした。

Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option 

どうやら emcc-loader が生成するコードが指定している環境変数指定が非推奨となったということのようです。emcc-loader の次の場所(ENVIRONMENT: 行)をコメントアウトすると動作させることができました。

/**
 * Builds a loader script.
 */
async buildLoaderScript(baseScriptContent : string, options : LoaderOption) {
    const config = {
        ENVIRONMENT: options.environment,
    };

動かせるようになったものの、emcc-loader はプログラムに修正がかかるとフルビルドになる動作となり、少し大きめのプロジェクトだと時間がかかるため、今回は emcc-loader は諦めて C の部分は通常の cmake / make でビルドするようにしています。

この場合で emcc で出力される wasm/JavaScript のグルーコードを webpack で読み込むときは、リンカーオプションを次のように指定しモジュール化してあげます。

add_compile_flags(LD
    "-s DEMANGLE_SUPPORT=1"
    "-s ALLOW_MEMORY_GROWTH=1"
    "-s MODULARIZE=1"
)

この上で自分の JavaScript から、emcc が出力したグルーコード JS を import して次のようにすると、 wasm が async でロードされ wasm モジュールが操作できるようになります。

import wasm from './genplus.js';

let gens;

wasm().then(function(module) {
    gens = module;
    gens._init();
    // ...
});

モジュールとなったグルーコードを wasm() などと受けて then 以下で取得します。

WebAssembly とインターフェースする C の関数は次のように定義し、JavaScript からはモジュールから _ 付の関数として呼び出しすることができます。

void EMSCRIPTEN_KEEPALIVE init(void)
{
    // ...
}

また、C側の make についてですが cmake、make ともに Emscripten のラッパーコマンドが準備されていて、これを経由して cmake / make することでコンパイルオプションなどを自動的に調整してくれるようです。

emcmake cmake ..
emmake make

github のほうにビルド手順を記載してあります。

メモリーの共有

WebAssembly 側で malloc したメモリーはモジュールの HEAP*.buffer ビュー経由で JavaScript から見ることができ、JS 側の TypedArray 経由でアクセスできます。

vram = new Uint8ClampedArray(gens.HEAPU8.buffer, gens._get_frame_buffer_ref(), CANVAS_WIDTH * CANVAS_HEIGHT * 4);
uint32_t *frame_buffer;

void EMSCRIPTEN_KEEPALIVE init(void)
{
    // ...
    frame_buffer = malloc(sizeof(uint32_t) * VIDEO_WIDTH * VIDEO_HEIGHT);
    // ...
}

uint32_t* EMSCRIPTEN_KEEPALIVE get_frame_buffer_ref(void) {
    return frame_buffer;
}

エミュレーターで作成した VRAM (iint32_t) を JS 側で取得して canvas にそのまま描画しています。

ちなみに、この例では C 側は uint32_t ですが、canvas は RGBA を Uint8ClampedArray ビューで扱うためエンディアンで逆になってしまい色がおかしくなりました。。ややはまり。

ソースコードデバッグ

C 側のソースですが、emcc がソースマップ出力に対応しているためブラウザ(Firefox で確認)でデバッグブレイクが可能です。ただし、変数の値などはみることはできないようです。

ソースマップを出力するためには emcc のコンパイルオプションで -g4 を指定し、–source-map-base オプションを指定します。

# source map option (but not working)
#    -g4
#    --source-map-base src/main/c

その上で、ソースコードがブラウザーから見えるように http の領域に配置します。

devServer: {
    inline: true,
    contentBase: [
        path.join(__dirname, '/docs'), // eslint-disable-line
        // for sourcemap - src/main/c
        path.join(__dirname, '/'), // eslint-disable-line
    ],

基本的にはこれで止まるのですが、残念ながら現在 –source-map-base オプションが wasm の場合はうまく効かず、出力がすべてフルパスになってしまうようです。(emcc-loader だといい感じに source-map がでていたので何か方法があるのかもしれません)

とりあえず、出力された source-map のフルパス部分を置換して http から見えるパスにしてあげれば動作します。

WebAssembly/Emascripten 上でプログラムを動作させると、malloc に対して境界値チェックが働くパターンがあるようです。実は移植当初 ROM のおしり 0x800000 から SRAM がマッピングされているのに気が付かず(ネイティブだと動いてしまっていた)、wasm がダウンしてしまっていたのですが、ソースコードマッピングすることで場所を特定することができました。

C 側の軽いデバッグの場合は、EM_ASM_ マクロでブラウザーのコンソールに文字列を出力できます。

#include <emscripten/emscripten.h>

uint8_t *rom_buffer;

void console_log() {
    EM_ASM_({
        console.log('genplus_buffer0: ' + $0.toString(16));
    }, buffer[0x100]);
}

Emscriten 移植のこつ

箇条書きにて。

  • C 側はネイティブでも動作を確認する環境をつくり、Emscripten でコンパイルすると切り分けが早いです。前述の境界値系のエラーが wasm ででた場合はソースをアタッチするといいと思います。
  • WebAssembly の場合、イベントや入出力系は全て JavaScript 側の役目になりますので、一貫して JS -> wasm の呼び出しパターンでプログラムを構成すると処理の粒度的に分かりやすくなりそうです。
  • C のプログラムを動かすというよりも、ビルドや JS とのインターフェース周りを調査するのに時間がかかりました。逆説的には、そこがクリアできれば大抵のものが動かせそうです。

というわけで、WebAssembly はいろいろできて楽しいですね。エミュレーターのほうですがまだコントローラーをつないでないので、Gamepad API で接続してみたいと思います。 🙂

ついに iOS でエミュレーターが動かせる…!

関連

今年 WebAssembly でつくった3つのアプリ

$
0
0

WebAssembly Advent Calendar 2019 の 11日目の記事です。🦀

WebAssembly の登場で C/C++/Rust など JavaScript 以外の言語のエコシステムをウェブブラウザーに持ち込むことができるようになり嬉しいな〜ということで、どのくらい動くのかという検証もかねて、3つほどアプリをつくって動作させてみました。

Emscripten 編

C言語でかかれたゲーム機メガドライブのエミュレーター Genesis-Plus-GX に WebAssembly 用のインターフェースを追加し、Emscripten でコンパイルして動作させてみました。

かなり重めのサウンドコアエミュレーションを有効にしてコンパイルしているのですが、iOS Safari を含め非常に高速に動作しています。(ベアナックル2が最後までプレイできました 🙂

主に Firefox と iOS Safari で確認しています。 Firefox は7段キーボード世代の ThinkPad(いつのだ…)で動作させていますが 60 fps 維持できています。

ソースコードを以下で公開しています。cmake/make と webpack でビルドできるようになっています。

Genesis-Plus-GX WebAssembly porting

https://github.com/h1romas4/wasm-genplus

ROM を吸い出す環境がない方は、homebrew の ROM が動作するかもしれません。.wasm はコンパイル済みのバイナリをコミットしていますので node だけあれば遊べると思います。

ゲームパッドのアサインは手持ちの XBOX ONE 用になっていますので適当に修正ください。。ちなみに、iOS 13 から PS4/XBOX ONE コントローラーサポートが入りましたが、Safari の GamePad API からも接続できました。

Emscripten 環境で少し詰まったのは次のポイントでした。

Emscripten を webpack からモジュールとして import する方法:

リンカオプションで MODULARIZE=1 を指定。

add_compile_flags(LD
    "-s MODULARIZE=1"
)

JavaScirpt で import して module を取得

import wasm from './genplus.js';

wasm().then(function(module) {
    gens = module;
});

Wasm 側で malloc したメモリーポインタの取得する方法:

モジュールの module.HEAPU8.buffer など HEAP* ビューで取得。

wasm().then(function(module) {
    gens = module;
    // memory allocate
    gens._init();
    // load rom
    fetch(ROM_PATH).then(response => response.arrayBuffer())
    .then(bytes => {
        // create buffer from wasm
        romdata = new Uint8Array(gens.HEAPU8.buffer, gens._get_rom_buffer_ref(bytes.byteLength), bytes.byteLength);
        romdata.set(new Uint8Array(bytes));
        message("TOUCH HERE!");
        initialized = true;
    });
});

大きめの static のアロケートに失敗した場合:

リンカオプションで初期メモリーサイズを指定。

add_compile_flags(LD
    "-s ALLOW_MEMORY_GROWTH=1"
    "-s TOTAL_MEMORY=32MB"
)

Rust / wasm-pack 編 (1)

Rust/wasm-pack で最初につくったアプリです。

Wasm 側でアロケートしたメモリーを仮想 VRAM として、Rust で何も考えずにむちゃ描きしたらどれくらいの速度になるだろうということで試したものになります。

デモサイトから実際に動作するところが見れます。

https://github.com/h1romas4/wasm-canvas-bitblt

sin/cos で画像回転させながらラスタースクロール的な動きをさせていますが、思うままにプログラムをかいているため RGBA の 4Byte 転送を全画素で何度も回していたりします。

ちょっと興味があったのが、速くなるかなと Rust の unsafe のブロック転送を使い、

unsafe {
    ptr::copy_nonoverlapping(
        [color.0, color.1, color.2, 0xff].as_ptr(),
        self.vram.as_mut_ptr().offset(pos),
        4,
    );
}

のようにしてみたのですが、これは Wasm 的には単純なループで展開されてコンパイルされていました。これは今後 Bulk memory operations が入りコンパイラが対応することで改善するかもしれません。

WebAssembly/bulk-memory-operations

Some people have mentioned that memcpy and memmove functions are hot when profiling some WebAssembly benchmarks.

なお、このプログラムは前述の古い ThinkPad T420s では 45fps そこそこでしたが、iPhone X では余裕で 60fps でていました。速い。。

Rust / wasm-pack 編 (2)

最初の Emscripten メガドライブエミュレーターから、ゲーム機の音源部分(FM音源・PSG)を取り出しプログラムを C言語から Rust に移植したものです。

エミュレーターから音源 LSI に発行するコマンドを横取りして保存した、ゲームミュージックを楽しむ VGM という形式のファイルがありますが、それを再生するプレイヤーになっています。

YM2612/SN76489 VGM player by Rust

こちらもデモサイトから動作を見ることができます。自分がつくったサンプル VGM をひとつ入れています。しょぼいですがクリックで鳴ります。本当はもっとすごい楽曲が再生できます。。

.vgm を準備できる方はドラッグアンドドロップしてみてください。(なお全て WebAssembly で処理してますので、サーバーにファイルアップロードはされません。安心してお試しください)

ソースは以下から参照できます。

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

デバッグ手法:

プロジェクトを pure Rust 部分と、Wasm 部分に分けて構成しています。現在 WebAssemby のデバッグ環境はまだ整っていませんので、複雑な処理はネイティブで実行できる環境で行うと良さそうです。

Wasm のデバッグ環境については Chrome が DRAWF に対応しつつあるとのことで(まだステップ実行のみ)、今後整っていくのではないかと思います。

Improved WebAssembly debugging in Chrome DevTools

As a first step, DevTools now supports native source mapping using this information, so you can start debugging Wasm modules produced by any of these compilers without resorting to the disassembled format or having to use any custom scripts.

ライブラリの活用:

本プレイヤーアプリですが、.vgz と呼ばれる .gz 圧縮された .vgm ファイルの再生にも対応させています。

WebAssembly/Rust は stdlib でコンパイルできますので、pure Rust の Gzip, and Zlib ライブラリーである flate2 を dependencies に追加してコンパイルして、ファイル展開させてみたところ問題なく動作しました。

[dependencies]
flate2 = "1.0"

この辺は各言語のエコシステムを活用できる Wasm の強みだなと感じます。

Runtime Error: Index out of bounds.:

移植中 Rust のオブジェクトを JavaScript から new した際に、Index out of bounds. が発生してオブジェクトがつくられない事象が発生しました。ぱっと原因が分からなかったため、ソースを削る方向で試していくと、[0; 50000] ほどの配列の初期化の部分で発生していました。

Make stack size configurable

Currently the stack-size for local variables of the generated wasm code is preconfigured to be 1048576 bytes. It is easy to reach this limit,

どうやら stack-size の初期値が小さいということで、.cargo/config に次の記述をして回避しています。

[target.wasm32-unknown-unknown]
rustflags = [
  "-C", "link-args=-z stack-size=32000000",
]

WebAssembly 登場にてウェブブラウザーで好きな言語で、好きなプログラムを動かせるようになって嬉しいです。

今後も継続してウォッチしていきたいと思います。

マイコン MCU で AssemblyScript + WebAssembly/Wasm3 を動かす

$
0
0

WebAssembly の登場でウェブブラウザー上で C/C++/Rust といった高速で動作する言語を使うことができるようになりましたが、一方でそのコンパクトな実装は、マイコンなどの小型のコンピューターでスクリプト言語を省メモリーで素早く実行する環境ももたらすことになりそうです。

この記事では WebAssembly のインタープリター実装のひとつである Wasm3 を活用して、ESP32 / K210 MPU で TypeScript のサブセットである AssemblyScript を動かす方法を解説しています。

ウェブブラウザーとマイコンで同じスクリプトが動作するのは感動的です。 🙂

https://raw.githubusercontent.com/h1romas4/maixduino-wasm3-testing/master/docs/images/maixduino-wasm3-02.jpg
Maixduino マイコンとウェブブラウザーで動作する同じ Conway’s Game of Life スクリプト

本稿は 2020年2月 の AssemblyScript 0.9.2、Wasm3 は 0.4.6 時点の情報です。API フリーズはしていませんので、今後のバージョンアップで変わる部分がある可能性があることだけご留意ください。

ソースコードやビルド方法は以下の github リポジトリーにコミットしてあります。Wasm3 と AssemblyScript や各マイコンの SDK のバージョンは git のタグなどで固定していますので、どの時期でもビルドし動作させられると思います。

M5Stack (ESP32) 版:

https://github.com/h1romas4/m5stack-wasm3-testing
WebAssembly interpreter Wasm3 on M5Stack (work in progress)

Maixduino (K210) 版:

https://github.com/h1romas4/maixduino-wasm3-testing
WebAssembly interpreter Wasm3 on Maixduino (work in progress)

Wasm3

Wasm3 は C言語でかかれた WebAssembly のインタープリター実装です。非常にコンパクトで高速に動作し、ESP32 や K210、ARM などの MCU(マイコン)を含む、さまざまな実行環境で動作を検証しながらつくられています。

https://github.com/wasm3/wasm3
The fastest WebAssembly interpreter

マイコンの C/C++ ツールチェイン/SDK により Wasm3 をビルドし、(例えば)main.c の中から Wasm3 の実行環境コールし、事前に AssemblScript などでかかれたプログラムをビルドして出力しておいた .wasm バイナリーを読ませることで WebAssembly を実行することが可能です。

Wasm3 は小さなメモリーで動くことも特徴となっており、公式ドキュメントで

Minimum useful system requirements: ~64Kb for code and ~10Kb RAM

となっています。 Limited support ですが最少で flash 128KB、RAM 16KB の AVR マイコンでも動作するようです。

なお、この記事で紹介している M5Stack と Maixduino のスペックは次のようになっています。

NameMCUClockFlashRAM
M5Stack BasicESP32240MHz4MB520KB
MaixduinoK210400MHz(600MHz)16MB6MB(8MB)

M5Stack は RAM がいくつかのエリアに分かれていて大きなメモリーの malloc に少々コツがいりますので、M5Stack Basic よりも 追加で PSRAM が 4MB ついている M5Stack Fire のほうが試しやすいかもしれません。

Maixduino については AI コア使用可否とクロック設定により括弧内の性能で動作させています。

AssemblyScript

AssemblyScript は WebAssembly 向けのコンパイラー言語です。TypeScript のサブセットとしてつくられており、WebAssembly アセンブラ命令へのバインディングと、それを活用してつくられた JavaScript の標準関数によく似た Standard library を持ちます。 Map や Array といった関数をスクリプトで利用可能です。

一部 Math や Date 関数、ウェブブラウザーでよく使われる console.log() 関数などを使う場合は、ホスト環境上に定義された関数に依存があり、マイコンで動作させる場合はそれらを C言語の関数として準備してあげます。実行に必要なバインディングは std/bindings にて export declare function 定義されています。

assemblyscript/std/assembly/bindings/

一点不明だったのが、env.abort() 関数で、Standard library を使おうとすると export されるようです。ドキュメントに記載がありました!

assemblyscript/std/assembly/builtins.ts

// @ts-ignore: decorator
@external("env", "abort")
declare function abort(
  message?: string | null,
  fileName?: string | null,
  lineNumber?: u32,
  columnNumber?: u32
): void;

ちょっとあれこれやってみたのですが、うまく C言語の関数にバインドできなかったので AssemblyScript の –use オプションでブランクを設定し export しないようにしています。

asc assembly/index.ts -b build/app.wasm -t build/app.wat --runtime full --use abort=

さて、同じ原理で、AssemblyScript のユーザー関数も export declare function としてホスト環境上の関数にリンクすることができますので、マイコンのハードウェア操作を行う関数を準備しておけば、AssemblyScript 内からマイコンの機能を呼び出すことができます。

Wasm3 で AssemblyScript から Arduino の digitalWrite 関数を呼び出す例:

AssemblyScript – arduino.ts

@external("arduino", "digitalWrite")
export declare function digitalWrite(pin: u32, value: u32): void;

// C側の関数呼び出し
arduino.digitalWrite(2, 1);

Arduino ホスト – main.cpp (m3 が Wasm3 です)

#include <m3_api_defs.h>
#include <m3_env.h>
#include <Arduino.h>

m3ApiRawFunction(m3_arduino_digitalWrite)
{
  // 引数取得
  m3ApiGetArg(uint32_t, pin)
  m3ApiGetArg(uint32_t, value)
  // Arduino 関数呼び出し
  digitalWrite(pin, value);
  m3ApiSuccess();
}

M3Result m3_LinkArduino(IM3Runtime runtime)
{
  IM3Module module = runtime->modules;
  const char *arduino = "arduino";
  // arduino.digitalWrite 関数を m3_arduino_digitalWrite にリンク
  m3_LinkRawFunction(module, arduino, "digitalWrite", "v(ii)", &amp;m3_arduino_digitalWrite);
}

また、WebAssembly からホストするマシンのファイルシステムやネットワークにアクセスする WASI API への対応も進められているようです。WASI はまだ策定段階ですが、これらの API も将来マイコンで使えるようになるかもしれません。(まだ関数名が違う部分もありそうですが Wasm3 でも一部対応しています

AssemblyScript とホスト間のインターフェースは関数呼び出し以外にもメモリーを共有する方法が準備されており、AssemblyScript のコンパイルオプションの -memoryBase が Wasm3 との組み合わせで便利でした。

Static memory

Memory starts with static data, like strings and arrays (of constant values) the compiler encountered while translating the program. Unlike in other languages, there is no concept of a stack in AssemblyScript and it instead relies on WebAssembly’s execution stack exclusively.

A custom region of memory can be reserved using the --memoryBase option. For example, if one needs an image buffer of exactly N bytes, instead of allocating it one could reserve that space, telling the compiler to place its own static data afterwards, partitioning memory in this order:

これは WebAssembly でアロケートするメモリーの 0番地から指定した任意のバイト数をリザーブするオプションで、AssemblyScript の load / store 命令によりアクセスすることができます。

package.json

asc assembly/index.ts -b build/app.wasm -t build/app.wat --memoryBase 57600 --runtime none --validate --sourceMap --optimize

後述のサンプルではこの機能を使い、アロケートしたメモリーを仮想 VRAM として見立て、AssemblyScript からメモリー書き込み後、マイコン側で LCD に転送することで画面描画を行っています。

index.ts

@inline
function pget(x: u32, y: u32): u8 {
    return load<u8>(y * width + x);
}

@inline
function pset(x: u32, y: u32, v: u8): void {
    store<u8>(y * width + x, v);
}

main.cpp – Wasm3 の m3_GetMemory 関数で memoryBase のポインターを取得して LCD に転送する例:

// bitblt
uint8_t* vram = (uint8_t*)(m3_GetMemory(runtime, 0, 0));
M5.Lcd.pushImage(40, 0, 240, 240, vram, true);

WebAssembly interpreter Wasm3 on M5Stack 編

ESP32/M5Stack で、AssemblyScript/Wasm3 にてフィボナッチ数列の計算と仮想 VRAM の転送による LCD 描画のサンプルを作成してみました。プログラムやビルドの方法などは以下のリンクを参照ください。

https://github.com/h1romas4/m5stack-wasm3-testing

ESP32 特有な部分としては、Wasm3 の関数が高速に動作する IRAM 上に配置されるように Wasm3 のコンパイルオプションを構成しています。

component.mk

CFLAGS += -DESP32
# CFLAGS += -DM3_IN_IRAM
CFLAGS += -Dd_m3LogOutput=true
CFLAGS += -Dd_m3VerboseLogs=true
CFLAGS += -O3
CFLAGS += -freorder-blocks
# CFLAGS += -Dd_m3FixedHeap=96000
# CFLAGS += -Dd_m3MaxFunctionStackHeight=128
# CFLAGS += -Dd_m3CodePageAlignSize=1024
# CFLAGS += -Dd_m3EnableOptimizations=1

# COMPONENT_ADD_LDFRAGMENTS += linker.lf

本来 linker.lf の指定で IRAM 上に載るはずなのですが、指定の仕方が悪いのかうまく効かなかったため M3_IN_IRAM を無効にして関数に IRAM_ATTR を付けています。

なお、フィボナッチ数列のサンプルについては、fib(19) くらいまでいくとおそらく再起が深くなりすぎメモリーが足りなくなります。@wasm3_engine さんより ESP32 の動作は今後さらに改善されるというコメントをいただいています。このような深い再起のない通常のプログラムであれば問題なく動作します。

VRAM 転送で円を描画しているサンプルは、240x240x8bit の領域を前述の memoryBase コンパイルオプションを使って AssemblyScript で確保しています。

M5Stack は本来 320×240 解像度ですが残念ながらそのサイズを指定すると malloc に失敗してしまいました。おそらく memoryBase ではなくて C側で malloc してポインターを受け渡せばいける気がしますが、方法について調査中です。

なお、描画速度ですが、このサンプルは LCD SPI に対して VRAM を最適化なしに M5.Lcd.pushImage 関数で単純に送信しているため速くありません。DMA などを活用すれば改善しそうです。

WebAssembly interpreter Wasm3 on Maixduino 編

Maixduino(K210) 上で M5Stack と同じ VRAM テストと、AssemblyScript の公式サンプルにありました Conway’s Game of Life を移植してみました。プログラムやビルドの方法などは以下のリンクを参照ください。

https://github.com/h1romas4/maixduino-wasm3-testing

Conway’s Game of Life デモは、AssemblyScript の Math.random() 関数を使っており、前述の通り Standard library の Math を使うためには binding を実装しなくてはなりませんが、使っているのが random 関数だけでしたので、ちょっとずるをしてその部分だけ実装して使うようにしています。

Math.ts

export declare function random(): f32;

index.ts

import * as Math from "./Math";

  for (let y = 0; y < h; ++y) {
    for (let x = 0; x < w; ++x) {
      set(x, y, Math.random() > 0.1 ? BGR_DEAD &amp; 0x00ffffff : BGR_ALIVE | 0xff000000);
    }
  }

main.c

m3ApiRawFunction(math_randome) {
    m3ApiReturnType (float_t)
    m3ApiReturn     ((float_t)rand() / RAND_MAX);
}

M3Result LinkFunction(IM3Runtime runtime) {
    IM3Module module = runtime->modules;
    const char* math = "Math";
    m3_LinkRawFunction(module, math, "random", "i()",  &amp;math_randome);
    return m3Err_none;
}

なお M5Stack と同様に、マイコンからの VRAM の LCD 転送については最適化しておらず、Conway’s Game of Life に関してはウェブブラウザーの canvas 形式である 32bit ARGB 形式を 16bit RGB に単純にループで変換していますので、かなり速度改善の余地があると思います。


Wasm3 公式サイトの Wasm3 vs other languages によりますと、Wasm3 の実行速度はマイコンでよく用いられる Micropython よりも 20倍以上高速な結果がでています。

                                             fib(40)
-----------------------------------------------------------------------------------------
LuaJIT             jit                         1.15s
Node v10.15        jit                         2.97s ▲ faster
Wasm3              interp                      3.83s
Lua 5.1            interp                     16.65s ▼ slower
Python 2.7         interp                     34.08s
Python 3.4         interp                     35.67s
Micropython v1.11  interp                     85,00s
Espruino 2v04      interp                       >20m

自分の所感でも非常にコンパクトで速いことが確認でき、マイコン上のユーザーインターフェース構築やルール定義など、柔軟性がありかつ安全でなければいけない領域で大いに活用できるのはないかと感じました。

引き続きチャレンジしたいと思います!

関連

Rust 製スタティックサイトジェネレーター Zola をつかう

$
0
0

はじめに

Rust 製のスタティックサイトジェネレーターの Zola を使ってウェブサイトをひとつつくってみました。本記事はその時に書いた Zola の使い方メモになります。

スタティックサイトジェネレーターを使ったウェブサイト製作に至った経緯ですが、

  • ジオシティーズが 2020/3末に完全にサービス終了する。
  • 伴って、自分が持っていたジオシティーズのサイトのファイルが消えてしまうので救出。
  • せっかくだから内容はそのままに、昨今のモバイルや SNS 対応をしてウェブに再登場させておこう。

といったところです。 😀

今回復活させようとしている旧式サイトは、同じフォーマットの雑多なメモがたくさんおいてあるタイプで、このような形式のウェブサイトをつくる(修正する)場合は、テンプレートととなる .html をひとつ作成し、記事を流し込む方式を取りたくなります。

そこで Zola “スタティックサイトジェネレーター” を用いて、記事となる部分を旧式サイトから Markdown として抽出作成し、最後にビルドをかけ .html をたくさん生成する方式をとることにしました。

WordPress などの CMS を用いなかったのは、そもそも古のコンテンツのため手離れをよくしたかったというのが理由です。関連して、このサイトはドメインもジオシティーズに習い、自分のものを使わず無料のホスティングサービス(ここでは Netlify を利用)に依存したものを使うことにしました。

この記事の内容で Zola でリニューアルしたウェブサイトは次から見ることができます。

適当メモ – maple4estry –

適当に作ったり書いたりしたものを、もしかしたら誰かの役に立つかもと、 記載してみているサイトです。

あ、あと関係ないですが、もちろんリンクフリーです。 こんなところでよければ好きなページにはってください。

ご覧の通り、ページテンプレートはモバイル対応や SNS OGP 対応など最新化してみたものの、コンテンツは15年以上前の古い芸風ですゆえ、内容は気にしないでください。。。

Zola とは

Zola はスタティックサイトジェネレーターと呼ばれるソフトウェアのひとつです。

Zola

Your one-stop static site engine
Forget dependencies. Everything you need in one binary.

Rust でつくられていて(自分が少し Rust をやっていることもあり)勉強も兼ねて選択してみました。同様のソフトでは Go 製の Hugo が有名でしょうか。

なお、現時点 Zola はまだバージョン 0.10.0 で API フリーズしていませんので、この記事の内容は将来変わる可能性があります。ご了承ください。

自分が気がついた Zola の特徴を少しあげておきます。

  • Rust でつくられており高速性を重視。
    • ちなみに、今回のような 100 記事未満の小さなサイトであれば測る時間もないくらいで処理が終了しました。
  • html テンプレートエンジンに、同じく Rust でできた Tera を用いている。
    • “Used to Jinja2, Django templates, Liquid or Twig? You will feel right at home.”とのことで構文は Jinja2 ベースのようです。
    • 神社と寺? 🙂
    • Zola と Tera はプロジェクトリードが同じ方です。
  • Markdown のパーサーを、これもまた Rust でできた CommonMark 互換の pulldown-cmarkを使っている。
    • ソースコードのシンタックスハイライトも標準で対応。
    • ### などに日本語を書いた場合も id を英語スラッグに変換する rust-unidecode が有効になっている。

Zola の導入

macOS、Linux、Windows 版の各導入方法がドキュメントにあります。

Installation

Zola provides pre-built binaries for MacOS, Linux and Windows on the GitHub release page.

自分は Ubuntu に snap のパッケージがありましたので、こちらで導入しています。

snap install --edge zola

初期プロジェクトの作成

Zola を入れると zola コマンドが使えるようになり、新規プロジェクトの作成やファイル監視からの自動ビルドの機能が使えるようになります。

まずは任意のフォルダーで次のコマンドで、ひとつのウェブサイトに対応する初期プロジェクトを作成します。ここでは仮に example.comexample の という名称にしました。

いくつか質問がありますので適宜答えていきます。ここでは次のように設定しています。

  • CSS プリプロセッサーである Sass の利用を有効化。
  • Markdown 中のソースコードブロックを色付けする syntax highlighting を有効化。
  • サイト検索用の JSON インデックスの生成を無効化。
$ zola init example
Welcome to Zola!
Please answer a few questions to get started quickly.
Any choices made can be changed by modifying the `config.toml` file later.
> What is the URL of your site? (https://example.com): https://example.com
> Do you want to enable Sass compilation? [Y/n]: Y
> Do you want to enable syntax highlighting? [y/N]: y
> Do you want to build a search index of the content? [y/N]: N

Done! Your site was created in "/home/hiromasa/web/example"

Get started by moving into the directory and using the built-in server: `zola serve`
Visit https://www.getzola.org for the full documentation.

処理が終わると次のような構造でディレクトリとファイルが作成されます。

  • content/
    • 記事になる Markdown (.md)ファイルや記事の画像リソースなどを格納します。フォルダーも作成可能で、この配下の構造とサイトの URL の構造が一致します。
  • templates/
    • 記事を挿入する Tera 記述を使った .html テンプレートファイルを格納します。
  • sass/
    • CSS をつくつための .scss を格納します。Zola には Sass コンパイラが含まれているためここにつくった .scss は自動でビルドがかかり .css になります。
  • static/
    • サイトの .html で使うヘッダー画像や .js などを格納します。この配下のファイルは。サイトの URL の / 直下に配置されます。
  • themes/
    • Zola サイトなどで提供されているテーマを格納します。既存のテーマを活用することにより templates/ テンプレートを自らつくることなくcontent/ 内の記事を流し込み、手軽にサイトをつくることができます。
  • public/
    • Zola でビルドした結果でできるたくさんの .html が格納されるデフォルトフォルダです。この中身をインターネットに公開します。
  • config.toml
    • サイト名や URL などのサイトの設定を記述します。zola init 時の設定が保存されています。
$ cd exmaple
$ ls -laF
合計 32
drwxr-xr-x 7 hiromasa hiromasa 4096  3月 14 17:12 ./
drwxr-xr-x 5 hiromasa hiromasa 4096  3月 14 17:12 ../
drwxr-xr-x 2 hiromasa hiromasa 4096  3月 14 17:12 content/
drwxr-xr-x 2 hiromasa hiromasa 4096  3月 14 17:12 sass/
drwxr-xr-x 2 hiromasa hiromasa 4096  3月 14 17:12 static/
drwxr-xr-x 2 hiromasa hiromasa 4096  3月 14 17:12 templates/
drwxr-xr-x 2 hiromasa hiromasa 4096  3月 14 17:12 themes/
-rw-r--r-- 1 hiromasa hiromasa  471  3月 14 17:12 config.toml

この記事では Zola を知る上で最初の取り掛かりになるであろう、templates/ テンプレートファイルと content/ のコンテンツの作成方法について解説します。

Tera テンプレートエンジン

Zola に内蔵された Tera テンプレートエンジンは(Zola 専用というわけではなく)汎用的なものです。資料確認の順番的にはまずは Tera テンプレート仕様ドキュメントからみると理解しやすいと思います。

Tera の基本的な構文:

  • {{ 変数名 }} − 変数の内容を html に出力
  • {% 命令 %}ifforblock などの命令
  • {# コメント #} − コメント

典型的な Tera テンプレートを含んだ Zola の .html は次のようになると思います。

  • {{ config.title }} のようにして変数の内容を出力。config.title などの変数は Zola によって設定される。後述。
  • {{ get_url(path="book.css") | safe }} のようにして関数を呼び出し値を出力。get_url 関数は Zola 提供。safe は Tera 提供の値エスケープフィルター処理。
  • {% block 名称 %} で名称ブロックを生成。後述。

次のファイルをつくって templates/ に格納します。

base.html(任意の名前) − サイトの基本的な構造をつくる(空の{% block 名称 %} ブロックを事前にあちこち定義しているのがポイントです)

{# このファイルは base.html #}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width">
    <title>
        {% block title %}{% endblock title %}
    </title>
    {% block js %}
    {% endblock js %}
    {% block css %}
    <link rel="stylesheet" href="{{ get_url(path="style.css") | safe }}">
    {% endblock css %}
    {% block extra_head %}
    {% endblock extra_head %}
</head>

<body>
    <div class="page">
        <div class="page__content">
            <div class="book-content">
                {% block content %}
                {% endblock content %}
                <footer><a href="{{ config.base_url }}">{{ config.title }}</a></footer>
            </div>
        </div>
    </div>
    {% block js_body %}
    {% endblock js_body %}
</body>
</html>

index.htmlbase.html を Tera の extends 機能で継承指定し、親となった base.html 内で設定した block に出力したいコンテンツを合成する。

{# このファイルは index.html #}
{% extends "base.html" %}

{% block title %}
    {{ config.title }}
{% endblock title %}

{% block content %}
<header>最終更新日 <time datetime="{{ section.extra.date }}">{{ section.extra.date }}</time></header>
<h1>{{ section.title }}</h1>
{{ section.content | safe }}
{% endblock content %}

config.titlesection.titlesection.content などは Zola から設定される変数で、具体的に言えば content/ に配置した .md で設定した内容が挿入されます。

この例のおおざっぱな Zola の処理の流れとしては、.md 内のコンテンツが変数として index.html に挿入され、それが base.html に合成され最終的な .html が生成されるということになります。

Zola コンテンツとテンプレートファイル

templates/ に格納する Tera テンプレートは、Zola のファイル命名規約によりウェブサイト上の次の位置にコンテンツを出力します。

  • index.html − ホームページ(いわゆるサイトのトップ・フロントページ)
  • section.html − 各フォルダ階層のセクション(主に目次・アーカイブ)
  • page.html − ページ

前項「Tera テンプレートエンジン」でつくった index.html は、サイトのホームページ(フロントページ)で出力されるテンプレートファイルということになります。

index.html が継承で使用しているテンプレートファイル base.html は、Zola の命名規約から”外れていれば”なんでもかまいません。Tera テンプレートの機能でモジュール分割などを行う場合もこれらをファイル名を除けば、自由にファイルをつくることができます。

次に、テンプレートファイルとコンテンツとなる .md の対応の基本は次のようになっています。

  • index.htmlcontent/_index.md
  • section.htmlcontent/**/_index.md(フォルダの中の _index.md)
  • page.htmlcontent/ 配下の任意名の .md ファイル

そしてサイトの URL と content/ のフォルダーツリーが対応します。

Zola を使ったウェブサイトの構成の基本はこれらの動きを利用し、

  • content/ 配下内の .md ファイルとフォルダー配置でサイトのツリーをつくる。
  • content/ 直下及びその配下のフォルダーに配置した _index.md でそれらの目次(アーカイブ)をつくる。
  • それぞれのコンテンツは index.htmlsection.htmlpage.html で出力する。(content/ 内にフォルダーがない場合は section.html は不要)

という思想になっています。

index.htmlsection.html テンプレートはセクション系の仲間です。index.html のほうがホームページに配置される特殊な section.html と考えたほうがいいかもです。本で言えば目次とセクションの関係ですね。

さて、テンプレートファイルで使える変数についてですが、上記のセクション系のテンプレートでは sectionpage.html では page を前置した名称でアクセスできます。

index.html 抜粋 − 変数に section.titlesection.content が使われている。

{# このファイルは index.html #}
{% block content %}
<header>最終更新日 <time datetime="{{ section.extra.date }}">{{ section.extra.date }}</time></header>
<h1>{{ section.title }}</h1>
{{ section.content | safe }}
{% endblock content %}

そして、これらの変数は各 .md 内の上部 +++ ブロック「フロントマター」で設定できます。

content/_index.mdindex.html に対応)

+++
title = "適当メモ - maple4estry -"
extra.date = 2020-03-14
+++

適当に作ったり書いたりしたものを、もしかしたら誰かの役に立つかもと、
記載してみているサイトです。

また、フロントマター外の通常の Markdown コンテンツについては content という変数に取得することができます。

フロントマターで設定することができる変数及び、テンプレートから読み出せる変数は次のドキュメントに記載があります。

なお、{{ __tera_context }} とテンプレートに書くとその場で使える変数一覧が見えます。ビルドで変数がないというエラーになった場合は確認してみましょう。

Overview

If you are not sure what variables are available in a template, you can place {{ __tera_context }} in the template to print the whole context.

注意点として、各テンプレートでフロントマターの変数を取得する場合はそれぞれ sectionpage を前置するのを忘れないようにします。またコンフィグ値は config を前置します。

またフロントマター内で extra. を変数に前置するとユーザー定義変数となり、テンプレートで読み出すことが出来ます。

これらを利用したページのテンプレートとフロントマターを示します。

content/test.md

+++
title = "へごへごメモ"
date = 2020-03-13
extra.image = "./hegohego.png"
+++

## はじめに

や゛め゛て゛く゛た゛さ゛い゛よ゛~

page.html

{# このファイルは page.html #}
{% extends "base.html" %}

{% block title %}
    {{ page.title }}
{% endblock title %}

{% block content %}
  <header>最終更新日 <time datetime="{{ page.date }}">{{ page.date }}</time></header>
  <h1>{{ page.title }}</h1>
  {{ page.content | safe }}
{% endblock content %}

{% block extra_head %}
  <link rel="canonical" href="{{ current_url | safe }}" />
  {% if page.extra.image is defined %}
    <meta name="twitter:text:title" content="{{ page.title }}" />
    <meta name="twitter:image" content="{{ resize_image(path=page.extra.image, width=640, height=360, op='fit_width') | safe }}" />
    <meta name="twitter:card" content="summary_large_image" />
  {% endif %}
{% endblock extra_head %}

このテンプレートには次のような要素が入っています。

  • {% block 名称 %} により base.html 内に配置された titlecontentextra_head の3ブロックの内容を定義している。
  • page.html のページテンプレートなので変数の前に page. を前置して取得している。
  • .md のフロントマターで extra.image ユーザー変数を定義し、テンプレート内で page.extra.image で取得し、Tara テンプレートの条件分岐機能で、変数が存在すれば twitter カードの出力をしている。この際にアイキャッチ画像を resize_image 関数を使い任意の大きさで生成している。

その他の機能

以上の仕組みが分かると残りはドキュメントを読めば知恵と勇気でなんとかなると思います。

主なものにリンクをしておきます。

  • 特定の .md ファイルで特例的に別なテンプレートファイルを指定したい。
  • _index.md でアーカイブを出力したい。
    • section.pages 変数に配下のページの配列が入っているのでループして出力。
  • いろいろなサイト内の URL を取得したい。使える関数が知りたい。
  • 画像リサイズしたい
  • sitemap.xml を出力したい。
  • よく分からなくなってしまった。
    • 配布されているテーマのソースコードが大変参考になります。
    • Zola のテーマファイルは、themes 配下とプロジェクトルート配下の構造を同じに持ち、プロジェクトルートのファイルがあれば優先されるタイプの実装になっています。(親で上書きできる WordPress の子テーマと同じ感じです)

ビルド

コンテンツの .mdtemplates/ が準備できたらビルドしてみます。以下のコマンドで Zola 内蔵の http サーバーが起動され確認もすることができます。

$ zola serve
Building site...
-> Creating 57 pages (0 orphan), 0 sections, and processing 0 images
Done in 237ms.

Listening for changes in /home/hiromasa/web/maple4estry/{content, config.toml, static, templates, themes, sass}
Press Ctrl+C to stop

Web server is available at http://127.0.0.1:1111

zola serve で起動後はファイルの修正にウォッチがかかり、テンプレートやコンテンツまた Sass の修正による自動ビルドがかかるようになっています。

なお、サンプルのソースで行っているような resize_image などの画像リサイズ処理はウォッチ対象ではなさそうです。その場合は build コマンドで別途ビルドしてあげると出力されます。

$ zola build

ウェブサイトの公開

うまくできたら public 配下に生成されたファイルをウェブサーバーに載せるだけです。

ホスティングサービス Netlify 使う場合は Zola のビルドに対応していますので、以下のドキュメントどおりプロジェクトルートディレクトリに netlify.toml を配置して、public を除外して、git リポジトリーにコミットしてあげれば自動でデプロイしてくれます。便利ですね。

Netlify | Zola

Netlify provides best practices like SSL, CDN distribution, caching and continuous deployment with no effort. This site is hosted by Netlify and automatically deployed on commits.

Zola はここであげた以外にも、Markdown 内で利用可能なショートコードやフィルターなど便利な機能がまだまだあるようです。

導入も簡単で高速にビルドでき、特に大きなドキュメント系のウェブサイトで威力を発揮しそうです。個人的には AsciiDoc にも対応してくれると嬉しいですが(まだ Rust でのパーサーはなさそうですが)、、引き続き使っていきたいと思います。

Netlify Functions で検索エンジン API をつくる

$
0
0

はじめに

ビシバシチャンプ…。

ふと思いつきまして Netlify で使える Lambda なサービス、Netlify Functions を用いて参照系検索 API を作成してみました。無料枠での挑戦です。

この作業中にいくつか Netlify の知見がたまりましたので、ここに掲載したいと思います。全ソースコードへのリンクを文末につけています。何かの参考になればと思います。

Netlify Functions

Netlify Functions は静的ファイルホスティングサービス Netlify が提供する、くだけて言えば Amazon AWS Lambda に対するプログラムの自動デプロイの仕組みで、Netlify のアカウントにて(クレジットカード登録が必要な AWS アカウントなしに)Lambda を利用することができます。

無料枠でも比較的制限が少なく、現在のスペックは次のようになります。

  • us-east-1 AWS Lambda region
  • 1024MB of memory
  • 10 second execution limit

自分が注目したのは 1024MB(1GB) ものメモリーがアサインされる点で、これを参照データーベースとして使えないかと思い立ったのが事の始まりでした。

Netlify からは DynamoDB などのデーターストアは提供されていませんので、JavaScript のオブジェクト(JSON) を key-value ストアにして全てメモリーで処理させる作戦です。

いったん JSON がメモリーにロードさえできてしまえば連想配列の検索処理時間はないに等しいですので、鍵は Lambda 初期起動が実行制限となっている 10 second execution limit(10秒)以内に巨大な辞書を読み終えられるかどうかということになります。

今回実装した検索 API について

530個、総容量約 8MB に及ぶ日本語テキストファイルを形態素解析し全文検索する API です。

対象のテキストファイルは専門用語がたくさん書かれたメタ情報のない自由形式となっており、改行の位置などを含めて機械にはコンピューターには処理しにくい形式です。具体的にはあるフリーソフトウェアの 20年分のリリースノート日本語訳を対象としています。

検索リクエストの都度、Lambda でテキストファイルの改行処理や形態素解析をしながら検索結果を返却するのは処理が厳しいため、このあたりの部分は Github Action で上で動作するバッチにしています。

参考までに手順としては、

  1. 全テキストファイルをパースし必要な部分では改行を次の行に接続するなどの正規化後、
  2. これもバッチ中に生成した専門用語を辞書登録した MeCab で形態素解析
  3. 名詞やサ変動詞などを抽出し「検索ワード」(12000語程度)としストップワードなどを除外(10000語程度)
  4. 「検索ワード」でテキストファイルを再検索 grep や awk や Python を使い「検索結果」として出現した前後の行を加え { "検索ワード" : [検索結果], } な巨大 JSON を生成

この手順で生成されたキーバリューの JSON は 100MB以上、.bz2 圧縮で 10MB という大きさになります。 また、リリースノートテキストファイルが追加される都度、Github Action の push フックからスクリプトが動作し辞書の再生成が行われます。

スクリプトの一部

######################################################################
## 検索ワード辞書作成
######################################################################

# mecab で英単語・記号を除外する名詞(一般、固有、サ変接続)を抽出する
SEARCH_DIC_TMP_WORDS=$(mktemp)
find ${WHATSNEW_DIR} -maxdepth 1 -name ${WHATSNEW_NAME} | while read path
do
    # テキストファイルで、日本語改行後の行頭にスペースがある場合は
    # 連続する文章として連結する。英単語の場合は連結しない。
    SEARCH_DIC_TMP_REGEX=$(mktemp)
    sed -z -r 's/([亜-熙ぁ-んァ-ヶー])\n[ | ]*([亜-熙ぁ-んァ-ヶー]+)/\1\2/g' ${path} > ${SEARCH_DIC_TMP_REGEX}
    # MeCab による形態素解析
    mecab ${SEARCH_DIC_TMP_REGEX} \
        -u ${MECAB_DIC} \
        | egrep -v '^[!-~]+.+\*$' \
        | egrep '^.+[[:space:]]名詞,(一般|固有|サ変接続)' \
        | awk '{ print $1 }' \
        | sort \
        | uniq \
        >> ${SEARCH_DIC_TMP_WORDS}
    rm ${SEARCH_DIC_TMP_REGEX}
done

次のリンクから実際に API が動いている様子をみることができます。画面は API のテスト用に Vue(Vuetify) で暫定的につくったもので Github Pages においていますが、裏側では Netlify Functions が呼ばれています。

https://h1romas4.github.io/e2j-api-web/

継続でアクセスがない場合は Lambda が寝てしまいますので 1度目の検索に数秒かかりますが、2回目以降の検索は高速に動くハズです。(なんといっても Lambda 上の検索処理としては key-value 引き return dictionary[query] しているだけですので…)

「あ」とか「い」とか適当に日本語を入れていただけるとオートコンプリートが走りますので、この API が検索する内容がよく分からなくても動きは分かると思います。オートコンプリート部分が "検索ワード" で結果のリストが [検索結果], となっているイメージです。

Lambda 上で動作するソースコードイメージ

import * as common from "../common";
// (でかい)辞書 JSON import
import whatsnewj from "../json/whatsnewj.json";

exports.handler = async (event) => {
  let answer = [];
  let query = event.queryStringParameters.q;
  if(whatsnewj[query]) {
    answer = whatsnewj[query]; // 検索(これだけ)
  }
  return {
    statusCode: 200,
    headers: common.jsonHeader,
    body: JSON.stringify(answer)
  }
};

Lambda のインスタンス

Netlify Functions から起動する Lambda のインスタンスは一定の時間起動し続け、(無料枠では?)ひとつのインスタンスに固定されていそう。ファイルシステムとして /tmp を使うことも可能。もちろんアルゴリズムとしてはステートの性質をあてにしてはならない。

インスタンス数については、開発中にプログラムを変更するも新旧のプログラムが交互に値を返してくるような挙動を何度か見せて最初デバッグで混乱しました。

CDN のキャッシュなのかインスタンスが 2つ起動したのか判断がしにくかったのですが、http header の age の値が 1〜2 (秒) となっていて、またURL に影響のないユニークな引数をつけても同様の結果でしたので、古いインスタンスが残ったように見えました。何度かデプロイをしていると消えるような気がします。


本検索 API は、いまや世界中のありとあやゆる機械をエミュレートするに至った「MAME」という歴史の長いエミュレーターソフトウェアのリリースノートテキストファイル(.txt)日本語版を形態素解析し全文検索します。コードネームは mametan と名付けました。

テキストデータの元となっているのは、20年に渡り MAME 文書の日本語翻訳しておられます、MAME E2J さんの翻訳テキストファイルで大変貴重な情報です。

Netlify Functions のデプロイの仕組み

Netlify はプロジェクトのルートに配置された、netlify.toml を認識してサイトのビルドを行います。 以下がこのプロジェクトで使った設定の一部です。

[build]
publish = "generator/public"
functions = "dist/api"
command = "npm run build"

publish が通常のウェブサイトのコンテントルート(今回の API では未使用)で、functions が Lambda に配置する JavaScript を配置するディレクトリです。 1 JavaScript が 1エンドポイントになります。

今回の dist/api の配下は次のようになっていて、それぞれのファイル名が /.netlify/functions/ファイル名 という形式でエンドポイント URL となります。

mametan.js // 検索ワード返却 API
whatsnewj.js // 検索結果返却 API
release.js // リリース日返却 API

というわけで、Netlify Functions ビルド的には netlify.toem に指定した functions キーのディレクトリに AWS Lambda 形式の JavaScript を配置すれば良いだけということになります。

netlify.toemcommand キーは Netlify サイトデプロイ時に Netlify 上で自動的に実行されるコマンドを書くことができます。

典型的には AWS Lambda をかくためには、npm パッケージや webpack のバンドルを活用することになると思いますので、commandnpm run build などのビルドスクリプトを指定することになると思います。


Netlify 上のビルドで使えるコマンド

command や npm の script キー内で UNIX のコマンドを使うことができる。 cptar などが利用可能であるため比較的自由にビルドを組める。

パッケージの追加はできないと思われる(未確認)が、スタティックサイトジェネレートで使われる zolarubynode などなどが事前に導入済みで netlify.tomlバージョン指定が可能。Netlify 管理画面の Deploy log から見ると分かりやすい。

要はなんらかの形でファイルが publishfunctions に入ればそれぞれにデプロイされる。

Functions の URL マッピング

標準のままであると URL のエンドポイントが /.netlify/functions/ファイル名 と長くなってしまうので netlify.toml/v1/ファイル名/ 形式に rewrite しています。

redirectsstatus = 200設定すると rewrite になるようです

[[redirects]]
    from = "/v1/*"
    to = "/.netlify/functions/:splat"
    status = 200
    force = true

[[redirects]]
    from = "/v1/whatsnewj/ja/*"
    to = "/.netlify/functions/whatsnewj?q=:splat"
    status = 200
    force = true

ただ、/v1/*/v1/whatsnewj/ja/* のような URL を重ねるような設定はうまく効かないみたいです。何か間違ってるかな。。要調査。

netlify-lambda

というわけで .js をビルドして functions に指定したディレクトリに入れれば Lambda が動き出すわけですが、そもそもビルドをつくるのが大変だったり、ローカルでも動かしたりしたいというわけで、Netlify さんが netlify-lambda という便利な npm パッケージを準備してくれています。

というわけで、プロジェクトの package.json に記載する内容は次のような形になります。

{
  "name": "e2j-api",
  "version": "0.1.0",
  "scripts": {
    "devel": "netlify-lambda --timeout 20 --config ./webpack.functions.js serve script/api/endpoint",
    "build": "netlify-lambda --config ./webpack.functions.js build script/api/endpoint"
  },
  "dependencies": {
    "axios": "^0.19.2",
    "decompress": "^4.2.0",
    "decompress-tarbz2": "^4.1.1"
  },
  "devDependencies": {
    "netlify-lambda": "^1.6.3"
  }
}

devDependencies として netlify-lambda を入れることにより netlify-lambda コマンドを準備し、script のキーのところでコマンドを呼び出しビルドしています。dependencies では自分のプログラムで利用するパッケージが指定できます。


netlify-lambda の webpack 設定

netlify-lambda コマンドでは --config ./webpack.functions.js オプションでビルドで使われている webpack.config.js への追加設定が可能(なお netlify-lambda コマンドのソースからビルドで動作する webpack config が参照できる)

このプロジェクトでは、Lambda プログラムに対してミニマイズしても意味がないという理由で optimization: { minimize: false } を追加指定。

// webpack.functions.js
module.exports = {
  optimization: { minimize: false },
};

しかも、今回の場合巨大な 100M 超え .json を次のように webpack で import しているため、、

/**
 * e2j whatsnewj API
 */
import * as common from "../common";
// (でかい 100MB)辞書 JSON import
import whatsnewj from "../json/whatsnewj.json";

ミニマイズ時に JS のパーサーがダウンしてしまうという凄惨な事故が起きたため必須設定となっております。。

netlify-lambda の webpack や babel のバージョン

この辺から使われる webpack や babel などのバージョンを知ることができます。ビルドを修正したい場合は、このリポジトリをフォークして変更。自分の package.json にてそのフォークしたリポジトリーを使うようにすると簡単かも知れません。

{
  // ....
  "devDependencies": {
    // "netlify-lambda": "^1.6.3"
    "netlify-lambda": "git+https://github.com/h1romas4/netlify-lambda"
  }
}

Netlify Functions の性能リミット

というわけで、当初 120M を超える .json を抱えた .js を Netlify Functions に載せ恐る恐る動かした所… 10.01sec timeout という悲しいメッセージが…

  • でっかい .json を webpack で固めたものは Lambda 上でもそもそもパースに時間がかかる。これは webpack v4.35.3 でかなり高速化されているようです。ちなみに netlify-lambda のデフォルト webpack は現在 package-lock.json なしの ^4.17.1 指定。
  • AWS Lambda のデフォルト nodejs は 10 系。netlify.tomlAWS_LAMBDA_JS_RUNTIME でより高速になった 12系に変更可能。
  • あと辞書を 100MB ほどに縮小

いろいろ試した見たところ無事メモリー 522MB の Init Duration 6.9 秒で初期起動成功。

8:20:30 PM: 2020-03-27T11:20:30.079Z	5b35fc60-b9f0-4a43-a642-38a6764950a7	INFO	q=test
8:20:30 PM: Duration: 3.22 ms	Memory Usage: 522 MB	Init Duration: 6934.64 ms	

初回以降は、

8:22:08 PM: 2020-03-27T11:22:08.133Z 0b548212-c6b6-4621-b0ec-75747a9dff1a INFO q=麻雀
8:22:08 PM: Duration: 331.08 ms Memory Usage: 522 MB 8:22:53 PM: 2020-03-27T11:22:53.071Z ee639944-7168-4c74-8240-3630fba6da89 INFO q=麻雀
8:22:53 PM: Duration: 2.83 ms Memory Usage: 522 MB

一度目の検索ワード「麻雀」でのアクセスが 331ms で、同じ検索ワードだと 2.83ms ということで CDN? キャッシュ? が効いたのかな…?という感じの思ったとおりの動きになってくれました。

おそらく .json のサイズ感としてはこれくらいが限界の印象です。今回はこれで 20年分のデータとなっていますので、あと 10 年位動くかな。。よぼよぼ。。


Netlify のネットワーク帯域

実は当初、辞書のデーターはこのような .json を import する無理な方法は採らず、JavaScritp の配置したキャッシュ変数が空なら、 http リクエストで自身のコンテントルートに配置した .json.bz2 ファイルをネットワークから取得してキャッシュ変数にロードしようと考えていました。

/**
 * response cache on AWS Lambda
 */
let jsonCache = {};

/**
 * async http request
 *
 * @param url
 * @return JSON
 */
export async function getJsonHttp(url) {
  if(url in jsonCache) return jsonCache[url];
  try {
    if(url.match(/^.+\.tar\.bz2$/)) {
      // for .tar.bz2 arcive
      const res = await axios.get(url, { responseType: "arraybuffer" });
      const filename = crypto.createHash('sha1').update(url).digest('hex');
      const tar = "/tmp/" + filename + ".tar.bz2";
      await fsp.writeFile(tar, res.data);
      let ret = await decompress(tar, null, { plugins: [ decompressTarBz2() ] });
      let whatsnewjson = JSON.parse(ret[0].data.toString('utf-8'));
      await fsp.unlink(tar);
      jsonCache[url] = whatsnewjson;
    } else {
      // for plane json
      const res = await axios.get(url);
      jsonCache[url] = res.data;
    }
    return jsonCache[url];
  } catch (error) {
    console.log(error);
    throw Error(error);
  }
}

.bz2 圧縮であればファイルサイズは 10MB 程度になっていまいたので、同じ AWS 内の通信なので間に合うかなという皮算用でしたが残念ながら間に合わずあえなく断念。

1MB 未満の小さな .bz2 ファイルや .json であれば時間内に動作しロードすることができました。あまり関係ないですが decompress-tarbz2 は WebAssembly で動作しているようでプラットフォームを選ばず高速に動作することを確認。 decompress-tarxz はネイティブを呼んでる模様。

Netlify 外の仕組みになってしまいますが、自前の S3 などにファイルを配置して aws-sdk で取り出すと巨大なデーターのロードもすんなりいくのかもしれません(未確認)

リクエスト回数・時間制限

Netlify Functions 無料枠には API 12,500 回リクエスト、処理時間 100時間の制限があります。

二晩くらい API たたいてテストしていた気がしますが、大人気 API にでもならない限り制限にかかることはなさそうです。 😀

Counts every time a function was invoked in the current billing period.
832/125,000
124,168 requests left

Run time Combined run time of all function requests in the current billing period.
15 minutes/100 hours
100 hours left

終わりに

というわけで、API としては無茶方式のサンプルになってしまいましたが、内容中に何か役立つ知見があったら幸いです。

この作業のきっかけとなりました、20年に渡り MAME 文書の翻訳をされている MAME E2J さんに感謝です。ちなみに先日サイトを WordPress でリビルドされておりアーカイブが大変見やすくなっていますので、お好きな方はぜひ。

MAME E2J
a bridge across cultures

最後にソースファイルへのリンクを貼っておきます!

Github Actions による形態素解析 & Netlify Lambda プロジェクト

https://github.com/h1romas4/e2j-api

Vue の API テスト用画面

https://github.com/h1romas4/e2j-api-web


M5Stack Core2 SDK でメガドライブエミュレーターをビルドする

$
0
0

はじめに

しばらく品切れが続いていました M5Stack Core2 を買うことができました。嬉しいです。 😀

Core2 には PSRAM が 8M ついているということで、そのあたりを確認しつつ、ハローワールドがてらメガドライブエミュレーター(Genesis Plus GX)を移植してブートさせてみました。

ソースコードを github で公開しています。

Genesis-Plus-GX M5Stack Core2 porting (no optimize and super slow)

https://github.com/h1romas4/m5stack-genplus

まだビルドしてエミュレーターの起動を確認したところまでのソース断面となっていますので、最適化はまったくしておらずとっても遅いです。

後述しますが、単純に不足したメモリーを SRAM から拡張のクロックが 80MHz PSRAM にメモリーを逃したのと、LCD への仮想 VRAM 転送で何も工夫をしていないためです。

(速くする方法を思いついたらちょこちょこいじるかもしれません。また、いい方法があったらぜひお教えください!)

この記事では M5Stack Core2 上でこういった少し大きめのソースをビルドするノウハウと、PSRAM の使い方を紹介してみたいと思います。

esp-idf のビルドシステムを使った M5Stack Core2 アプリのビルド

M5Stack Core2 の標準の C/C++ 開発キットは Arduino IDE となっていますが、移植元となっている Genesis-Plus-GX のソースツリーが比較的大きいのと、各コンパイルオプションなども細かく修正しながら作業したかったので、esp-idf ビルドシステムを使って M5Stack Core2 向けのバイナリーの作成を行っています。

esp-idf が提供する Makefile を用いるとプロジェクトの構成を次のようにすることができます。なお、esp-idf の 4系のバージョンでは cmake を使うように変わっていますが、ここでは esp-idf 3.3 系を使うため Makefile 方式としています。

+ components
    + arduino (https://github.com/espressif/arduino-esp32)
    + m5stack
        + M5Core2 (https://github.com/m5stack/M5Core2)
        component.mk
    + genplus 
        [ソース]
        component.mk
+ esp-idf (https://github.com/espressif/esp-idf)
+ main
    main.cpp
    component.mk
sdkconfig
Makefile

maincomponents ディレクトリの構造と各コンポーネントしたに配置された components.mk とルートの sdkconfigMakefile が esp-idf のビルドシステムが認識する要素です。

そのまま使いたい時は、m5stack-genplusgit clone --recursive していらないファイル消すのが簡単かもです。:D

プロジェクトテンプレートリポジトリーと How to create のドキュメントをつくりました。また、github actions で自動ビルドもかけれるようにしています。使う場合は github にログインして以下から Use This Template ボタンを押して、自身のリポジトリーを作成してプログラミングを開始すると便利です。

https://github.com/h1romas4/m5stack-core2-template

esp-idf build system template for M5Stack Core2.

さて、M5Core2 ライブラリーが依存するプロジェクトは esp-idf と arduino-esp2 ですが、現在 Arduino IDE で提供されている一式がどの断面を使っているのかが分からなかったので、ビルドが通るものを試行錯誤して、以下のバージョンでソースツリーに git submodule で固定して導入しています。

現在リリース版の arduino-esp32 は v1.0.4 の esp-idf 3.2 依存ですが、これではビルドが通らず未リリースの arduino-esp32 v1.0.5 相当で、esp-idf は 3.3 の最終コミットを使っています。 (どうも esp-idf v3.2 系及び v3.3.5 タグだと必要なファイルがなかったり、コンパイルエラーになるなどビルドが通りませんでした)

nameversionhash
esp-idflatest-3.3b64b375
arduino-esp32latest3da04ce
M5Core2latestcc551d4

git submodule で導入しておくと、バージョンが切り替わったときの上げ下げやヒストリーの確認も容易かと思います。(このようにしておくとプロジェクトごとに依存のバージョンを固定できます)

ビルドは、各 OS に対応した xtensa-esp32 のツールチェイン(gcc)を導入後、make するだけで M5Stack Core2 向けのバイナリーが作成できます。 m5stack-genplus の github をビルドする手順は次のようになります。 (Windows では MSYS2 の窓より…)

# submodule があるので --recursive 指定 
git clone --recursive https://github.com/h1romas4/m5stack-genplus.git
cd m5stack-genplus
# This repository includes eps-idf
export IDF_PATH=$(pwd)/esp-idf
# CPU 4コア CPU の場合 -j4 とコアすると速い
make -j4
# 書き込み & 実行
make flash monitor

sdkconfig ファイル

sdkconfig ファイルは esp-idf や esp-idf に対応した各コンポーネントが使う define の定義です。これは make menuconfig コマンドでメニューから生成することができます。

m5stack-genplus リポジトリーには sdkconfig がコミットされていますので各種設定済みで、変えるのは転送先のシリアルポートの設定くらいですが、esp-idf や arduino-esp32 のどの機能を使うかなどなどの設定ができます。

保存すると sdkconfig に値が書き込まれますが、基本的に make や C/C++ 中の define 定義集のようなものなので、キー名でソースコードを grep すると何が有効になったのかが分かります。

component.mk ファイル

各 component.mk の中にはそのコンポーネントで使われるソースファイルやインクルードファイルの位置、CFLAGS などのコンパイルオプションが定義できます。

Genesis Plus GX モジュールでは次のように定義しています。

COMPONENT_SRCDIRS := core core/z80 core/m68k core/input_hw core/sound core/cart_hw core/cart_hw/svp core/ntsc m5stack
COMPONENT_ADD_INCLUDEDIRS := core m5stack core/cart_hw core/cart_hw/svp core/cd_hw core/debug core/input_hw core/m68k core/ntsc core/sound core/themor core/z80

CFLAGS := \
    -DLSB_FIRST \
    -DUSE_16BPP_RENDERING \
    -DMAXROMSIZE=131072 \
    -DHAVE_ALLOCA_H \
    -DALT_RENDERER \
    -DALIGN_LONG \
    -DM5STACK \
    -fomit-frame-pointer \
    -Wno-strict-aliasing \
    -mlongcalls

利用できるオプション値は次のドキュメントから参照できます。

https://docs.espressif.com/projects/esp-idf/en/v3.3.3/api-guides/build-system.html#optional-project-variables

Optional Project Variables

ライブラリーなどを M5Stack に移植して動作させる時は、CFLAGSCPPFLAGS などが容易に設定できますので便利に使えるのではないかと思います。components ディレクトリに任意の名前でディレクトリを作成してソースをがばっと入れ、component.mk を書いてあげればビルド対象になります。

またライブラリーにユーザー設定がある場合は Kconfig.projbuild をつくって配置することで、make menuconfig(sdkconfig) 用のメニュー定義も入れることができるようです。

PSRAM の使い方 (.bss セグメントを逃がす)

esp-idf で大きめのプログラムをリンクすると、

region `dram0_0_seg' overflowed by 1995968 bytes

このようなエラーメッセージでリンクできない場合があります。これは static の領域を確保する .bss セグメントが足りない場合に出力されます。Genesis Plus GX のビルドでは当初、上記のように 2MB 近く足りませんでした。

この .bss セグメントを PSRAM にもっていくオプションがあり、次の menuconfig の設定から有効にすることができます。

https://docs.espressif.com/projects/esp-idf/en/v3.3.4/api-guides/external-ram.html

→ Component config → ESP32-specific → SPI RAM config 
→ Allow .bss segment placed in external memory

define 値的には CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY となりますので esp-idf を grep してみると分かりやすいです。このオプションを有効にした後、ソースファイル上の static 変数に EXT_RAM_ATTR をつけると、PSRAM に領域を持っていくことができます。

#ifdef M5STACK
#include "esp_attr.h"
#endif

#ifdef M5STACK
EXT_RAM_ATTR static uint32 bp_lut[0x10000];
#else
static uint32 bp_lut[0x10000];
#endif

イメージ的にはこんな感じになります。 EXT_RAM_ATTR は未定義はブランクなので、これでも大丈夫です。

#ifdef M5STACK
EXT_RAM_ATTR
#endif
static uint32 bp_lut[0x10000];

なお、PSRAM は CPU から最大 80MHz の SPI で接続されており、通常の SRAM より速度が遅く、転送レートは最大で SRAM 960MB/sec に対して 40MB/sec ほどのようです。このため、主要なロジックで使われるメモリーを載せると処理速度が低下してしまうはずです。

How slow is PSRAM vs SRAM (anyone have quantitative info?)

Internal SRAM is 32bit @ 240MHz max, so 960MByte/second. PSRAM is 4-bit @ 80MHz, so 40MByte/second.

PSRAM の使い方 (malloc)

PSRAM がコンフィグレーションで有効になっていると、通常の malloc 関数で PSRAM も使ってくれます。

https://docs.espressif.com/projects/esp-idf/en/v3.3.4/api-guides/external-ram.html

Support for external RAM

PSRAM ではなく速い SRAM 側に確定で確保したいなど、明示的にどちらから取得するかを決めたい場合は、次の設定ができます。

→ Component config → ESP32-specific → SPI RAM config

また、M5Stack Core2 には 8MB の PSRAM が搭載されていますが、使えるのは 4MB までのようで、それより上の 4MB を使う場合は himem API で別途取得するようです。(今回は未検証)

https://docs.espressif.com/projects/esp-idf/en/v3.3.4/api-reference/system/himem.html

The himem allocation API

VS Code の設定(おまけ)

あんまり関係ありませんが、開発は VS Code + C/C++ Extention で行いました。インテリセンスの効きもよく大変良いです。

VS Code ではプロジェクトに .vscode/c_cpp_properties.json を配置して次のように設定すると便利です。

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/build/include",
                "${workspaceFolder}/components/arduino/cores/**",
                "${workspaceFolder}/components/arduino/libraries/**",
                "${workspaceFolder}/components/arduino/variants/esp32",
                "${workspaceFolder}/components/m5stack/M5Core2/**",
                "${workspaceFolder}/esp-idf/components/**"
            ],
            "defines": [
                "ESP32=1",
                "ARDUINO_ARCH_ESP32=1",
                "BOARD_HAS_PSRAM",
                "ARDUINO=10800",
                "M5STACK",
                "LSB_FIRST",
                "USE_16BPP_RENDERING",
                "MAXROMSIZE=131072",
                "HAVE_ALLOCA_H",
                "ALT_RENDERER",
                "ALIGN_LONG"
            ],
            "intelliSenseMode": "gcc-x64",
            "compilerPath": "~/devel/toolchain/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc",
            "cStandard": "c11",
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

defines にはコンパイルオプションで指定した define を定義しておくといい感じにパーサーが感知して色分けしてくれます。sdkconfig の define 値については、自動的に生成された build/include/sdkconfig.h を読むことで自動的に反映するようになっています。

例えばですが、上記のようにコンパイルオプションで定義された M5STACK 値の ifdef が有効になって else がグレーアウトしますので分かりやすいです。

おわりに

というわけで、M5Stack Core2 ハローハッピーワールドでした。

他にもいくつかプログラムを動かしてみましたが、タッチセンサーもよく動いて M5Stack Core2 楽しいです。ビルドも固まりましたので、引き続き何かつくってみたいです!

WebAssembly をウェブブラウザーで活用する(1)

$
0
0

この記事は ゆるWeb勉強会@札幌 Advent Calendar 2020 の 12日目です 😀

今年 2020年、WebAssembly は W3C 勧告に到達し、モダンウェブブラウザーで安心して WebAssembly を活用できるようになった年となりました。

また、ウェブブラウザー外で WebAssembly を動作させ、さまざまな環境で動作するユニバーサルバイナリーとして、コンテナー技術やマイコンなどで動作させる動きも広がった年でもありました。

この記事では、まずはウェブブラウザー視点から、WebAssembly が現在どのように活用され初めているのかを紹介してみたいと思います。

WebAssembly でできること、できないこと

WebAssembly の実態は低レベルマシンコードで、ウェブブラウザーなどに実装される WebAssembly のランタイムがこれを読み込み、プログラムとして実行する環境です。

現在、さまざまなコンピューター言語が WebAssembly に対応しており、それらの言語でかかれたプログラムを WebAssembly にコンパイルし、ウェブブラウザーで実行することができます。

ウェブブラウザーではこれまで、プログラム言語としては JavaScript しか動作しませんでしたが、WebAssembly の登場により、JavaScript 以外の言語のプログラムも実行することができるようになりました。

WebAssembly に「ネイティブ」対応している言語は多くありますが、自分が把握している言語は次のとおりです。

  • C/C++
  • Rust
  • AssemblyScript
  • Go
  • Swift

「ネイティブ」と書いたのには訳があり、WebAssembly 上で動作する言語はその他にも多数存在し、たとえば Python なども動作しますが、スクリプト言語のインタープリタや JIT は、元をただせば C/C++ でつくられており、WebAssembly が C/C++ に対応していることに立脚すると、それらのインタープリタ自体を WebAssembly で動作させてしまえば、ウェブブラウザーで Python が動作する、、そういう仕組みになっています。(C#/WebAssembly などはこの方式です)

さて、このように WebAssembly では多数の言語が動作しますが、その言語でつくられたソフトウェアはそのままでは動作するというわけではありません。ウェブブラウザーで動作する WebAssembly は現在のところ、DOM などのウェブブラウザーの機能(Web IDL)にアクセスすることができないからです。

つまりユーザーへの入出力部分(画面を描くであるとか、クリックを受け付けるとか)は、従来の JavaScript で作成し、処理の部分のみを WebAssembly に投げるようにプログラムを構成します。これが現在のウェブブラウザー上の WebAssembly でできない部分です。

ちなみに、今年 W3C 勧告となった WebAssembly の仕様は MVP(最初)の仕様群となっています。

ウェブブラウザーを含む各 WebAssembly ランタイムは次の仕様となる Proposals を絶賛先行実装中で、これらの実装が進むと WebAssembly から直接ウェブブラウザーの機能(Web IDL) へのアクセスも可能になります。(そのようになるように作業が進められています)

実際に使えるようになるのは、来年か再来年かになるかと思いますが、、楽しみです!

WebAssembly 活用例

WebAssembly のできることできないことが分かったところで、WebAssembly の活用例をみながら、どうやって実装しているのかを紹介していきたいと思います。

Ruffle A Flash Player emulator written in Rust

まずは、ニュースなどでもでていましたので知っている方もいらっしゃるかもですが、Adobe Flash を WebAssembly で実装した Ruffle。

https://github.com/ruffle-rs/ruffle

A Flash Player emulator written in Rust

ウェブブラウザーのプラグインで Flash サポート終了するなら、WebAssembly でつくってしまえば良いのでは?という発想よりつくられた、そのまま .swf ファイルが実行できるプログラムで Rust 製です。

デモが次のサイトから見ることができます。お手持ちの .swf があれば動かしてみると面白いかもしれません。

https://ruffle.rs/demo/

描画は canvas を、音声は WebAudio を JavaScript でインターフェースし、.swf の解析や実行を Rust 側で行っています。

同様に Microsoft Silverlight も WebAssembly に移植した実装が存在します。

https://opensilver.net/

ffmpeg.wasm

ウェブブラウザーが、どのような画像形式や動画再生をサポートするのかでやきもきする時代は WebAssembly の登場により終焉を迎えました。なぜならば、それらのデコーダーは C/C++ でかかれているからです。つまりそのプログラムをそのまま WebAssembly にしてしまえば、どのような画像でも動画でも再生できてしまいます。

というわけで、ffmpeg は様々な動画コーデックをもつ有名な C/C++ でかかれたオープンソースですが、これを WebAssembly コンパイルにしてインターフェースしたのが ffmpeg.wasm になります。動画再生だけはなく生成やエンコードも可能です。

https://github.com/ffmpegwasm/ffmpeg.wasm

FFmpeg for browser and node, powered by WebAssembly

ffmpeg.wasm は npm パッケージ化されていますので、使いたいなぁと思ったら、package.json の依存に加えるだけで使うことができます。

このような感じで WebAssembly できていると知らずに node のパッケージを使っていることも増えているのではないかと思います。(自分が確認したものでは .gz を展開するライブラリーが .wasm を使っているパターンがありました。

poton Rust/WebAssembly image processing library

動画に続いて画像処理系のライブラリーです。

これまでも画像に対してフィルターをかけたいなどといった場合は、CSS でネイティブに備わる機能が使えましたが、自分の思ったとおりの処理をしたい場合は、JavaScript で行う必要がありました。

もちろん JavaScript でもプログラミング可能ですが、画像処理に関してはプログラムの書きやすさを考慮すると他の言語を使ったほうが有利です。また WebAssembly にすることで高速化がかなり期待できます。

poton は Rust で実装された画像処理ライブラリーで、減色やリサイズ、フィルター、回転などなどの処理を WebAssemby で行うことができます。

デモが次のサイトから確認できます。

https://silvia-odwyer.github.io/photon/demo.html

こちらのライブラリーも npm 化されていますので、手軽に自分のプログラムから利用可能です。


ウェブブラウザーで動作する WebAssembly は、高速性もさることながら、さまざまな言語のエコシステムをそのまま活用できるのが大きな魅力の一つです。

WebAssembly 登場以前は、行いたいと思った処理を JavaScript で書き直す必要がありましたが、今後はその必要がなくなり、特に C/C++/Rust でかかれたすぐれたライブラリーをそのまま使うことができます。

次の回では、C/C++ もしくは Rust でかかれたプログラムを WebAssembly に移植して動作させるデモをやってみたいと思います!(続く

関連

WebAssembly をウェブブラウザーで活用する(2)

$
0
0

この記事はゆるWeb勉強会@札幌 Advent Calendar 2021の 3日目の記事です。

去年のアドベントカレンダーより…

自分は、去年のゆるWebアドベントカレンダーで書きました…

WebAssembly をウェブブラウザーで活用する(1)

次の回では、C/C++ もしくは Rust でかかれたプログラムを WebAssembly に移植して動作させるデモをやってみたいと思います!(続く

次の回になるまで 1年かかりました…(すいませんw

というわけで、今年の記事はここ数ヶ月取り組んでいました C/C++/Rust のプログラムを WebAssembly で動作させた顛末を例に、Wasm(WebAssembly の略です) の周辺技術事情やキーワードを書いていきたいと思います。

WebAssembly とは

WebAssembly はバイナリーの実行コード形式です。C/C++/Rust/AssemblyScript をはじめとするさまざまな言語を .wasm 拡張子のバイトコードにコンパイルすることができ、それらの言語でかかれたプログラムを、ウェブブラウザーや WebAssembly ランタイム上で動作させることができます。

画像

特にウェブブラウザーは、これまでコンピュータ言語として JavaScript のみをネイティブでサポートしていましたが、WebAssembly の仕組みを使うことで、別の言語でかかれたソフトウェアやライブラリーを活用することができるようになりました。

中でも C/C++/Rust はシステムやミドルウェアよりのライブラリーの実装が多くありますので、画像・動画処理、文字列パーサー、仮想マシン、3D 処理、ファイル圧縮展開、そして今回取り上げる音声処理など従来 JavaScript で再実装が必要になっていた部分を、WebAssembly にビルドするという手順だけで手軽にウェブブラウザーに持ち込めます。

まずは、今年2021年に気になった WebAssembly の実装をいくつか紹介します。

⏩ Adobe Photoshop の WebAssembly による実装:

おそらく画像処理部分の C/C++ コードをクライアント版と共有していると思います。

Adobe PhotoshopにWebブラウザ版が登場。何ができる?

配信中では宇宙飛行士のヘルメットガラスの反射を修正ツールで取り除き、また色味を調整するなどの内容が披露。操作に伴うレスポンス遅延等もみられない、スムーズな編集を実現していることが示された。

ちなみに、Chromium 系のブラウザーの開発者ツールに、直接 C/C++ ソースのデバッグができる仕組み(と拡張)が追加されたのは、Web 版 Photoshop をつくっていたからという話がありました。

https://twitter.com/h1romas4/status/1348242121631830017

⏩ WebAssembly で動作可能な Rust でかかれた形態素解析 Goya:

日本語形態素解析がウェブブラウザー上で動作します。形態素解析系はビルドが大変だったりしますので、.wasm 版があるのは大変ありがたいです。

WebAssemblyの形態素解析器GoyaをRustで作った

Goyaという形態素解析器を Rust で作りました。本記事は利用者目線で Goya の紹介をします。技術的な詳細については別途記事を書きます。

⏩ Python の WebAssembly 版の Pyodide:

CPython が C/C++ で実装されていることを利用して、インタープリタ・ランタイムごと Wasm にビルドし、ウェブブラウザー上で Python を実行し、導入の操作なしに Python を使うことができます。

Try Pyodide (no installation needed)

Try Pyodide in a REPL directly in your browser. For further information, see the documentation.

⏩ PHP の WebAssembly 版:

Pyodide と同様に PHP のインタープリタも C/C++ ですので Wasm にビルドしてしまえば、あろうことか PHP がウェブブラウザーで動作します。 😀

PIB: PHP in Browser (and Node.js) aka php-wasm

https://seanmorris.github.io/php-wasm/

WebAssembly で実装されたシンセサイザー

今回の Wasm 実装例とするプログラムは WebAssembly にビルドされたシンセサイザーです。

次のリンクから動作を確認できます。(後述しますがマルチスレッド(SharedArrayBuffer) を使っている関係で iOS/Safari では現在動作しません)

https://chipstream.netlify.app/

画面をクリックするとサンプルの音楽が再生されたと思いますが、この音声波形生成(シンセサイズ)を C++/Rust でかかれた WebAssembly で行っています。

シンセサイザーとは “音” を電子機器を使って生成するハードウェア・ソフトウェアの総称です。

音を電子機器でつくる方式はいろいろあり、現在は PCM と呼ばれる録音した波形をコンピュータで変化させる方式が主流で、イメージとしてはピアノのラの音(440Hz)をマイクで収録し、それをコンピュータの演算で 880Hz にして高いラにして発音させるようなことをしています。(実際にはもっと複雑です)

ここで作成したシンセサイザーは FM 音源と呼ばれるシンセサイザーの方式で、サイン波(口で発声するのなら、”ポー”みたいな電子音)をサイン波で変調することで倍音を加え、デモのような電子音のような、そうでないような美しい不思議な音を発音させることができます。

画像

1980年代に非常に多く使われたシンセサイザーの方式で、日本で一番なじみがあるのが山手線のホームで流れる発車音のエレピの音だと思います。また 1990年台のゲーム基板や、2000年台初頭のガラケーにも搭載されていましたので、ゲームセンターや着メロで聞き覚えがある方もいらっしゃるかもしれません。

WebAssembly でできること

よくWebAssembly は計算しかできないと言われますが、まずはその通りで今回のシンセサイザーの例でも波形の計算を C++/Rust で行っています。

音声波形計算の単純な例をあげると、1秒を 44100(サンプリング周波数)で割った配列をつくり、16bit(量子化ビット数)の -32768 〜 32767 の値で 440Hz のサイン波をつくれば”ポー(ラー)”波形の計算となります。このシンセサイザーに、ラを0.5秒鳴らせといった演奏データ(シーケンス)を渡すことで音楽となる手はずです。

WebAssembly が計算しかできないと言われるゆえんは、WebAssembly からホストとなる(この場合はウェブブラウザー)の機能には直接アクセスできないことで(ユーザと対話できない)、今回の場合も計算された波形はブラウザーの JavaScript を経由して WebAudio API に渡して発音してもらうようなプログラムとなっています。

ソースコードを以下に公開していますので興味がある方は覗いてみてください。

実際には ymfm という C++ でかかれた YAMAHA FM 音源チップのエミュレーションライブラリーを、演奏データの制御やサウンドチップのサンプリングレートのコンバートを行う Rust のプログラムとリンクする形で動作しています。

https://github.com/h1romas4/libymfm.wasm

This repository is an experimental WebAssembly build of the [ymfm](https://github.com/aaronsgiles/ymfm) Yamaha FM sound cores library.

使わせてもらっている ymfm ライブラリーのソースコードは一切触れることなく、そのまま WebAssembly にビルドしています。Rust と C++ をリンクして動作する Wasm の例としても見ることができるかもしれません。

その他、リアルタイム音声処理に関しては処理の遅れが音切れという形で現実世界に悪影響を及ぼしますので、マルチスレッド(WebWorker)を使っていたり、同期するために SharedArrayBuffer を使っていたり、従来から使われている ScriptAudioProcesser ではなく AudioWorklet を使っているなど、 WebAssembly との組み合わせもあまり見ないサンプルになっていますので、JavaScript 的にも、もしかすると参考になるかもしれません。

なお、スレッド同期に使っている SharedArrayBuffer の iOS/Safari での実装が 2021年 12月現在テクニカルプレビュー中です。次のリリースあたりで使えるようになると思いますが、現在は Safari で動作しません。そのうちに動作し始めると思います。

ウェブブラウザーを超えて

さて、JavaScript がウェブブラウザーから飛び出し Node.js や Deno といった形で取り出されたように、WebAssembly にも同様なランタイムがあります。

いろいろな実装があり群雄割拠している状態ですが、自分が使っているものをいくつか紹介します。

wasmtime

Standalone JIT-style runtime for WebAssembly, using Cranelift

Wasmer

Run any code on any client. With WebAssembly and Wasmer.

Wasm3

The fastest WebAssembly interpreter, and the most universal runtime

wasmtime がリファレンス実装的な印象で、Wasmer は高速で各言語バインディングが充実、Wasm3 は JIT を使わないインタープリタ型で JIT 禁止の iOS 環境でも動作可能、また最小構成で Arduino などのマイコンでも動作といった特徴があります。

WebAssembly ランタイムはもちろん単体で .wasm を動作させることができますが、さまざまな言語をホストとして .wasm を実行をする仕組みが備わっています。

“wasmer-python” を利用して、この記事でウェブブラウザーで呼び出したシンセサイザーの .wasm ファイルとまったく同じものを Python から呼び出してサウンドプログラミングした動作例が次の動画となります。(ソースコード: https://github.com/h1romas4/libymfm.wasm/tree/main/examples/python

.wasm は OS を選びませんので、ランタイムと組み合わせることでウェブブラウザー(JavaScript)に限らずさまざまな言語から呼び出して活用することが可能です。

Wasmer の言語対応:

特に C/C++ でかかれたライブラリーのスクリプト言語からの呼び出しは、使われる範囲的にインターネット経由の入力が伴うことが多く、セキュリティーの面で気になる部分がでてきますが、Wasm をかぶせることでサンドボックスとなり安全性が高まり、また OS の依存もなくなりますので扱いが簡単です。 .wasm がひとつあればどこでも動作します。

PHP で画像処理したいと思ったら wasmer-php で Wasm ビルドした C/C++ ライブラリーを安全に使う…なんていうのもアイディアかもしれません。

最近ではソフトウェアのプラグインシステムとしても WebAssembly を活用し、そのソフトウェアがつくられた言語以外でプラグインをかけるようにしているしている例もみられるようになりました。

zellij

Zellij includes a layout system, and a plugin system allowing one to create plugins in any language that compiles to WebAssembly.

まとめ

  • WebAssembly はいろいろな言語でつくられたソフトウェア・ライブラリーを、ウェブブラウザーやランタイム上で OS を選ぶことなく動作させることができる。
  • WebAssembly のランタイムと各言語のバインディングを使うことで、言語を他の言語で拡張して使うこともできる。

WebAssembly の今後は「計算しかできない」を解決していく Interface Type や WASI といった仕様の策定・実装、GC やマルチスレッドの対応の強化が進み、活用範囲や対応言語も増え、目にする機会も増えるかなと思っています。クラウドの Lambda 的な部分での採用も増えていきそうです。

面白いことがあればぜひ教えてください。てなわけで、また来年(!?)…!

ツイッターやってます:

ゆるWeb勉強会

ゆるWeb勉強会とは札幌で開催している @tacck さん主催のWeb系勉強会で、現在はオンライン配信も行っていてどなたでも参加できます。いろんな方の発表を聴き、またわいわい質問もすることができますので、興味がある方は覗いてみてください!

ゆるWeb勉強会@札幌

主に「Web」というキーワードに関する、ゆるい勉強会です。 プログラムを始めたばかりの人も、仕事でバリバリやっている人も、どなたでも歓迎です!

「勉強し初めて、次に何をやればいいんだろう?」 「普段使っていない言語やフレームワーク、知っている人に導入部分を聞いてみたい。」 「いつもはバックエンドだけど、たまに触るフロントエンドの話も知りたい。」 「実際、みんな現場ではどうやって開発してるの?」

仕事・趣味を問わず、普段の仕事の内容も問わず、「Web系」に関することを、ゆるっとみんなで話してみたいです。

M5Stamp C3 用の開発向けボードを製作

$
0
0

M5Stack から発売されている ESP32-C3(RISC-V) が搭載されているマイコン、M5Stamp C3 用の開発向け基板を製作してみました!

Main Board

基板のガーバーデータや部品表、サンプルのプログラムを次の GitHub リポジトリーにコミットしてあります。

https://github.com/h1romas4/m5stamp-c3dev

This is a development board for the M5Stamp C3 (RISC-V/FreeRTOS).

ソースコード、基板ガーバーデータ含め MIT License に設定しています。

ガーバーデータを元にご自身で PCB メーカーに製造発注可能です。なお、リポジトリに含まれている .zip は Fusion PCB 向けの設定になっています。(おそらく他でも大丈夫と思いますが、KiCad 6 形式のファイルも含めていますので、設定を変えることもできます)

また、BOOTH で完成品の基板(部品なし)の頒布も行っています。 2枚セット 130円で、匿名配送可能な BOOTH あんしんパック送料込みで 500 円になるように設定しています。(個人で製造発注しているため在庫がない場合が多々あります。入荷お知らせメールを設定していただければと思います)

https://h1romas4.booth.pm/items/3688492

FG06-C3DEV REV.B (2枚) – M5Stamp C3 向けの開発用基板 – 部品なし基板のみ

基板の特徴としては

  • JTAG デバッグのための外部 USB Type-C ポートの接続。
  • よく見かける KMR-1.8 とマークされた安価な LCD/SD の SPI 接続。
  • 電源入力の選択。M5Stamp 側か USB Type-C 側かの切り替えジャンパーピン。
  • ローダーモードに入れるための GPIO9 に接続されたタクトスイッチ(通常のスイッチとしても利用可能です)。
  • 利用可能な GPIO を外部に公開するピンヘッダーの接続。
  • 使われていない GPIO は GPIO 0(SPI CS を想定)と JTAG とは競合する GPIO18, 19(I2C 接続を想定)
  • 名刺サイズ(90x55mm)の基板です。

となっていて、手軽に RISC-V やマイコンプログラミングを楽しみやすいものを目指しました。

GitHub にコミットしてあるサンプルプログラムには以下の実装が入っています。

  • 日本語 TrueType フォントの液晶への出力
  • SD カード内の PNG 画像の液晶への出力
  • WiFi 接続による NTP 同期
  • GPIO 18, 19 への I2C センサー接続テスト(UNIT ENV III の温度湿度気圧センサー)
  • Wasm3 による WebAssembly の実行(AssemblyScript を使ったアナログ時計のサンプル)
AssemblyScript and Wasm3

またその他の実装として、

  • TrueType フォントと .wasm バイナリーを格納している SPIFFS の使い方(parttool.pyspiffsgen.py ツール)
  • WiFi パスワードを格納している NVS(暗号化可能なキーバリューストア)の利用(nvs_partition_gen.py ツール)
  • esp-idf ビルドシステムを使ったライブラリーのビルド、依存関係の管理
  • Visual Studio Code の C/C++ Extention の設定と openocd と連携した JTAG デバッグ設定
  • AssembyScript をウェブブラウザーとマイコンで共有する手法

などなどがありますので、この基板に限らず ESP32-C3 や M5Stamp C3 を使う場合の参考になるかもしれません。 GitHub を眺めていただけたらと思います。

ビルドは Windows/Linux/macOS のそれぞれで正常に終了することを確認しています。

M5Stamp C3/ESP32-C3 は、メモリーの malloc もしやすく使いやすいマイコンだと感じました。 Wasm3 の末尾呼び出し最適化なども esp-idf の RISC-V コンパイラツールチェインでうまく動作するようです。

Wasm3/AssemblyScript については、M5Stack Core 2 版も作成しています。

https://github.com/h1romas4/m5stack-core2-wasm3-as

M5Stack Core2 With Wasm3/AssemblyScript Demo

M5Stamp C3 版と同じ .wasm を動作させています。ウェブブラウザーからも動作を見ることができます。

https://h1romas4.github.io/m5stamp-c3dev/asclock/

AssemblyScript Analog Clock Sensor values are dummy.

良ければ遊んでみて下さい 😀

Main Board

関連

Ubuntu 22.04 LTS アップグレードと Focusrite Scalett Solo オーディオインターフェース

$
0
0

Ubuntu 22.04 LTS がリリースされましたので、Raspberry Pi 400 と WSL2、そしてメインで使っている ThinkPad P14s に導入してみました。

Raspberry Pi 400:

導入済みの Ubuntu 20.04 LTS からのアップグレード。SSD 起動にしていますが、1時間ほどかかったでしょうか。無事 22.04 LTS となり、SDL の画面描画やオーディオ再生などうまく動作しているようです。

WSL2:

こちらは、Microsoft ストアより Ubuntu 22.04 LTS を選択して新規導入。ESP32/M5Stack のSDKである esp-idf に含まれる (riscv32-) gcc などのバイナリーパッケージが正常に動作することを確認。

ThinkPad P14s:

懸念していた esp-idf のバイナリーパッケージが WSL2 で正常動作しましたので、メインの ThinkPad P14s にも導入。 Ubuntu 20.04 LTS から Ubuntu 22.04LTS にアップグレードインストールしています。 30分ほど。

Wayland セッションを使うと、現在 Indicator Stickynotes(付箋)でウインドウ位置や Dock アイコンの制御、Alacritty のウインドウ描画が違和感のある感じになりましたので、いったん X.org セッションで動作させています。

ハードウェア的には 2点問題がでています。

Focusrite Scalett Solo オーディオインターフェース:

ThinkPad には USB オーディオインターフェースとして、Focusrite 社の Scalett Solo を接続していますが、ALSA からの認識で対応するサンプリングレートが低く(44100, 48000のみ)申告されてしまうという問題がでました。

$ uname -a
Linux thinkpad-p14s 5.15.0-27-generic #28-Ubuntu SMP Thu Apr 14 04:55:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

$ cat /proc/asound/USB/stream0
Focusrite Scarlett Solo USB at usb-0000:07:00.4-2, high speed : USB Audio

Playback:
  Status: Stop
  Interface 1
    Altset 1
    Format: S32_LE
    Channels: 2
    Endpoint: 0x01 (1 OUT) (SYNC)
    Rates: 44100, 48000
    Data packet interval: 125 us
    Bits: 24
    Channel map: FL FR

Ubuntu 20.04 LTS では次のように 44100, 48000, 88200, 96000, 176400, 192000 となります。

$ uname -a
Linux hiromasa-t420s 5.13.0-40-generic #45~20.04.1-Ubuntu SMP Mon Apr 4 09:38:31 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

$ cat /proc/asound/USB/stream0
Focusrite Scarlett Solo USB at usb-0000:00:1d.0-1.2, high speed : USB Audio

Playback:
  Status: Stop
  Interface 1
    Altset 1
    Format: S32_LE
    Channels: 2
    Endpoint: 0x01 (1 OUT) (SYNC)
    Rates: 44100, 48000, 88200, 96000, 176400, 192000
    Data packet interval: 125 us
    Bits: 24
    Channel map: FL FR

96KHz などが設定できないと、ハイレゾ系の音源や高サンプリングレートのソフトシンセの開発に支障がでるためやや悩み。

カーネルのソースを眺めていたところ、次のようなソースが追加されていることを発見。

/*
 * Many Focusrite devices supports a limited set of sampling rates per
 * altsetting. Maximum rate is exposed in the last 4 bytes of Format Type
 * descriptor which has a non-standard bLength = 10.
 */
static bool focusrite_valid_sample_rate(struct snd_usb_audio *chip,
					struct audioformat *fp,
					unsigned int rate)

Focusrite ではあるものの Solo が入っていないため、動作が不正しているのではないかと当たりをつけてさらにソースを追っていたところ、Skip reading sampe rate for devices というワークアラウンドフラグ(quirk_flags)がありましたので、試しに設定してみたところ、正しく(とは言わないかもですが…) 高いサンプリングレートも申告されるようになりました。(ちなみに device_setup=1 の指定で今回カーネルに追加された Focusrite のミキサードライバーが有効になるようです)

$ sudo vi /etc/modprobe.d/scarlett.conf
options snd_usb_audio vid=0x1235 pid=0x8211 device_setup=1 quirk_flags=0x1
$ cat /proc/asound/USB/stream0
Focusrite Scarlett Solo USB at usb-0000:07:00.4-2, high speed : USB Audio

Playback:
  Status: Running
    Interface = 1
    Altset = 1
    Packet Size = 144
    Momentary freq = 96000 Hz (0xc.0000)
  Interface 1
    Altset 1
    Format: S32_LE
    Channels: 2
    Endpoint: 0x01 (1 OUT) (SYNC)
    Rates: 44100, 48000, 88200, 96000, 176400, 192000
    Data packet interval: 125 us
    Bits: 24
    Channel map: FL FR

このことにより Jack も高いサンプリングレートで起動できるようになりました。(解決)

$ cat .jackdrc
/usr/bin/jackd -dalsa -dhw:USB -r96000 -p1024 -n2

ワークアラウンドなので ALSA に報告して修正してもらうのが正しいでしょうか。要調査。同件ですが Ubuntu 日本語フォーラムで質問させていただいておりました。

Apple AV アダプタ:

ThinkPad には外部の HDMI モニターを接続するために、USB-C Apple AV アダプタを転用して使っていましたが、接続したまま PC の電源を入れると画面が真っ黒になり Ubuntu 22.04 LTS がブートできない様子でした。5.15.0-27-generic にて確認。

対応する元気が出なかったので、いったん AV アダプタを使わず、本体の HDMI に直接モニターを接続するようにしたら good work となりました。(継続検証)

外部PPA と Snap アプリケーション:

登録している PPA は次のとおりです。

http://jp.archive.ubuntu.com/ubuntu jammy InRelease
http://jp.archive.ubuntu.com/ubuntu jammy-updates InRelease [109 kB]
http://jp.archive.ubuntu.com/ubuntu jammy-backports InRelease
http://packages.microsoft.com/repos/edge stable InRelease
http://jp.archive.ubuntu.com/ubuntu jammy-updates/main amd64 DEP-11 Metadata [6,632 B]
http://jp.archive.ubuntu.com/ubuntu jammy-updates/universe amd64 DEP-11 Metadata [19.2 kB]
https://packages.microsoft.com/repos/code stable InRelease [10.4 kB]
https://packages.microsoft.com/repos/code stable/main arm64 Packages [82.7 kB]
http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB]
https://packages.microsoft.com/repos/code stable/main armhf Packages [82.6 kB]
http://archive.ubuntu.com/ubuntu jammy InRelease
http://ppa.launchpad.net/mmk2410/intellij-idea/ubuntu jammy InRelease
https://deb.nodesource.com/node_16.x focal InRelease
http://security.ubuntu.com/ubuntu jammy-security/main amd64 DEP-11 Metadata [6,632 B]
https://ppa.launchpadcontent.net/kicad/kicad-6.0-releases/ubuntu jammy InRelease

VSCode や Microsoft Edge、KiCAD、Intellij IDEA、Node.js 16 などうまく動作しています。

Lovery Composer Snap Linux 版も良い感じです…! Linux でもレトロぴこぴこシンセの打ち込みを楽しめます。:D

デスクトップアクセサリーの MaCoPiX もソースコードからビルドして起動できました。

$ sudo apt install libgtk-3-dev
$ git clone https://github.com/chimari/MaCoPiX.git
$ cd MaCoPiX
$ ./configure
$ make -j8
$ src/macopix

Ubuntu 22.04 LTS は、Wayland セッションが前述の理由でまだ使えないのが寂しいですが、GNOME 系のアニメーションもスムーズになって良さそうな雰囲気です。Snap 版 Firefox も今の所、げ!というところはなさそうです…(今後あるかもですがw

LTS 版にて5年間(+5年延長)、引き続き Ubuntu にお世話になります。ちなみに、メインで使う OS を Ubuntu (8.04から) にしてから 14年くらい経つようです。ひー。

MSX ゲーム開発 2022年

$
0
0

1980年代から90年にかけて家庭に広く普及した MSX パソコン(当時はマイコンと言ってましたね!)にて、ゲームをつくる活動を 2022年に復活してみました…!

当時の開発は MSX-BASIC もしくは Z80 アセンブラをつかったものでしたが、今回は さまざまな 8bit Z80 系のレトロコンピュータに対応する Z88DK ツールチェインによる C 言語を使っています。

Home: z88dk

z88dk is the only C and assembler development kit that comes ready out-of-the-box to create programs for over 100 z80-family machines.

Z88DK のセットアップやサンプルコードについては、以下の文書にまとめています。C 言語ですが、BASIC より簡単かも、、ですので良ければ遊んでみてください。この記事で紹介しているゲームのソースコードへのリンクもつけています。

Z88DK を使って MSX のゲームをつくるための環境構築メモ

この文書は、Z80 を CPU に持つコンピュータ向けの C コンパイラ・アセンブラツールチェーンである Z88DK を使って MSX のゲームをつくるための環境構築メモです。

てなわけで、この Z88DK を使いまして、新作ゲーム(?) をふたつつくってみましたので紹介したいと思います。ゲームはウェブブラウザーで動作する MSX エミュレータから楽しめますので合わせてリンクをしています。

PONPON for MSX

80年代のコンピュータ誌に投稿された PONPON という名前の投稿プログラムの MSX クローンです。自分はプログラムポシェット誌でみて打ち込んで楽しんだ覚えがあります。

自動的に上下に移動する主人公「◯氏」を赤ブロックに激突しないように左右に操作して $ を取得して点数を競うゲームです。

次のリンクからウェブブラウザで遊べます。

WebMSX で PONPON を遊んでみる…!

記憶だけを頼りにつくっていますが、この MSX 版はプログラムポシェット掲載 PC88 PONPON の “改造版” の移植です。オリジナルは 40 * 25 桁(WIDTH 40,25) でもっと綺麗に壁や赤ブロックが並んでいたと思います。

当時小学生だった自分はごちゃごちゃ改造していて、確かこのような感じになった気がします、、懐かしいです。。

NOBORUNOCA for MSX

MSX の VDP はいわゆるスクロール機能を持たず、スクロールをしたい場合は通常 VRAM ブロック転送による PSG(8ドット) 単位スクロールとなりますが、これを PCG キャラクターをドットずらしで用意することでスムーズスクロールを実装する技がありました。

当時の自分はスムーズスクロールしてみたくても、プログラミング技術もあまりなく実装を諦めており、これまで数十年間心の何処かにひっかかっていた課題のひとつだったのですが、2022年になってようやく実装することができました。

というわけで、構想数十年、製作 5日の NOBORUNOCA です。

強制縦スクロールのワンキーためジャンプアクションゲームです。使うのは SPACE キーのみ…!のぼるのか…のぼらないのか…

次のリンクからウェブブラウザで遊べます。

WebMSX で NOBORUNOCA を遊んでみる…!

操作はシンプル、割と奥が深いを目指してつくってみました。ゲーム特有の落ち着けばなんとかなる…!がうまくつくれたと思いますので、良ければ遊んでみてください…!(ツイッター #NOBORUNOCA タグでみなさまのハイスコアを拝見しております… 😀

裏技的な攻略情報がいくつかあるので書いておきます。

  • 足場生成に関わるゲームの乱数シードはタイトル画面のパワーゲージ値によります。
  • レベルエクステンド時に、必ず穴がない休憩足場が生成されます。
  • ジャンプ上昇中、ジャンプ落下、歩き落下中もパワーゲージがチャージできます。
  • 落下中から、着地前直前十数フレームで、その場再ジャンプが可能な猶予フレームがあります。これは足場がない最下段でも適用可能なので、目押しスペース離しで復活できることがあります。また、成立するとボーナス点が入ってます。
  • ジャンプ落下中は足場判定が横に広がっています。このため左右の縦壁でも再ジャンプ可能なパターンがあります。

いろいろ実装していましたら、当時の BASIC ゲームも面白くなるようにいろいろ調整して楽しんでいたことを思い出してノスタルジー。

ゲームミュージック

マイコンゲームに音楽をつけるのも当時の課題のひとつで、サウンドドライバー問題とシーケンサーどうする問題がありましたが、今回 @aburi6800 さん の MSX Z80 サウンドドライバーと、いちまるまるゲームズさんの、Lovely Composer & あぶり6800さんコンバータにより、見事、課題解決することができました…!

ありがとうございました…!

Lovely Composer (ラブリーコンポーザ)

家庭用ゲーム機の作曲ソフトの方向性を受け継いだ、かわいい作曲ツール!
レトロゲームのようなピコピコサウンドの音楽や効果音を、楽しく手軽に作れます。

https://github.com/aburi6800/msx-PSGSoundDriver

MSX用のPSGサウンドドライバです。
z88dkのz80asmでコンパイルできる形にしています。

楽曲は YAMAHA MODX シンセで曲のスケッチをかいて、Lovely Composer に打ち込む形で楽曲をつくり、lc2asm コンバータでゲームに取り込む手法でつくっています。

今回は MSX 向けで矩形波 2/3ch だけの打ち込みとしていますが、Lovely Composer は矩形波以外も波形メモリ音源的な音などなどピコピコサウンドを手軽なプリセットと操作系で使え楽しいです。:D

MSX ゲーム取り込み前には、YM2149 での鳴り具合を確認するために、自分が以前からつくっていました WebAssembly の ymfm エミュレータを Python からコールして、サウンドドライバー互換で発音させるスクリプトも利用しています。

https://github.com/h1romas4/noborunoca/tree/main/tools/lcconv

楽曲のほうですが、自分は音楽の方が楽器は持っているもののほとんど素人ですが、、レトロっぽいコミカルさが出るように、少ない小節数にすると心に決め、メロディーのリズムに気をつけてかきました。

密かにちょっと気に入っていますので良ければ聴いてみてください…!

最後に

残念ながら手元に MSX 実機がないためまだ実機動作を自分で見れていないのですが、Simple ROM Cartridge を使わせていただいて、いつか動作させたい…!夢の ROM 版ゲーム…

MSX用カートリッジ64K Simple ROM Cartridge

ちなみに PONPON は 16KB で NOBORUNCA はほんの少しだけはみ出て 32KB ROM になっています。

書き込み準備ヨシ…!(ちなみに写っている PSP 版は fMSX エミュレータによる動作です)

ゲームのソースコード

両ゲームとも GitHub Actions で ROM のビルドができるように仕込んでいます。また、リリースページに .rom ファイルを置いています。

PONPON

https://github.com/h1romas4/z88dk-msx-template

NOBORUNOCA

https://github.com/h1romas4/noborunoca


関連

WebAssembly で動作する FM 音源ライブラリー libymfm.wasm

$
0
0

去年くらいからつくりはじめていた、libymfm.wasm ですが、GitHub のリポジトリーにコミットするだけで、ブログにあまりあれこれ書いていませんでした…!(ので書いてみます)

libymfm.wasm は WebAssembly 上で動作する(主に) FM 音源シンセサイザーをエミュレートして PCM を生成するライブラリーです。ゲームなどのプログラムへの組み込みを考えて作成されました。

https://github.com/h1romas4/libymfm.wasm

This repository is an experimental WebAssembly build of the [ymfm](https://github.com/aaronsgiles/ymfm) Yamaha FM sound cores library.

FM 音源エミュレータコアとしては、多目的エミュレーションフレームワーク MAME の新 YAMAHA FM 音源エミュレーションコアを由来とする C++ でかかれた ymfm を使わせて頂いています。

ymfm の作者は MAME の中の人のアーロンさんで、元々 MAME 内での実装だった新 YAMAHA FM 音源コアを 3rdparty ライブラリー化したのが ymfm となります。

従来の MAME の FM 音源エミュレーションコアは YM2151 や YM2203、YM2612 などなど別チップは別エミュレーションの実装となっていましたが、機能差分以外は同じ回路が使われているのではないかという仮説から、 decap 解析などとの比較を経て完成したライブラリーです。ということで、ymfm は多くの YM 系のチップをそのひとつでサポートしています。

さて、表題の libymfm.wasm ですが、ymfm を使いながら、Rust でかかれた次のような実装を加えています。

VGM/XGM 形式のシーケンサーを搭載

VGM/XGM 形式の演奏形式をサポートしています。ファイルを渡すだけで発音可能です。

VGM はサポートしているサウンドチップのみ、またデーターブロックの圧縮が未サポートなどフル実装ではありません。XGM は PCM に不具合ありで修正予定です。

サンプリングレートコンバート

各サウンドチップが出力するネイティブサンプリングレートはまちまちなので(YM2151 が 3.58MHz 動作で 55.9kHz 等々特殊です)、扱いやすいように指定したサンプリングレートにアップ、ダウンサンプリングで統一して PCM 出力します。

クロックの制御

ライブラリーに、出力サンプリングレートに対して何サンプル分の PCM が欲しいのか(時間をどれくらい進めるのか)を指定できます。これはゲーム組み込み用などで、1フレーム分のサンプルが欲しいケースや、バッファリング再生したい時に便利です。

WebAssembly 向けのインターフェース関数

ハイレベルインターフェースとして vgmplay xgmplay 関数、ローレベルインターフェースとして各サウンドチップに直接レジスターライトして、結果を任意のサンプリングレートとフレーム数で PCM 取得できます。

追加のサウンドチップ

ymfm サポート以外のサウンドチップも MAME からの移植でいくつか実装。主にメガドライブ、セガアーケード、X68K 構成を想定したチョイスです。

ライセンス

ymfm や追加音源、libymfm.wasm 全て BSD ライセンスになっています。


WebAssembly ライブラリー形式となっていますので、同一 .wasm シングルバイナリー(3MB 程度です)で Wasmer などの各言語向け WebAssembly バインディングを使うことで、ほとんどの OS、コンピュータ言語からコールして容易に使うことができます。 .wasm ファイルひとつで全環境動くので扱いやすいです。

リポジトリには Python からコールする例を入れています。リンク先の手順ですぐ発音すると思いますので、良ければ遊んでみてください…!

VGM/XGM 再生

関数に VGM/XGM ファイルを与えると指定したチャンクサイズで PCM を取得できます。

sample_vgmplay.py 抜粋

# Output sampling rate settings
SAMPLING_RATE = 44100
SAMPLING_CHUNK_SIZE = 4096

# ...snip...

# Create Wasm instance
chip_stream = ChipStream()

# Setup VGM
header, gd3 = chip_stream.create_vgm_instance(VGM_INDEX, "./vgm/ym2612.vgm", SAMPLING_RATE, SAMPLING_CHUNK_SIZE)
# Print VGM meta
print(header)
print(gd3)

# Play
while chip_stream.vgm_play(VGM_INDEX) == 0:
    # Get sampling referance
    s16le = chip_stream.vgm_get_sampling_ref(VGM_INDEX)
    # Sounds
    sample = pygame.mixer.Sound(buffer=s16le)
    pygame.mixer.Sound.play(sample)
    # Wait pygame mixer
    while pygame.mixer.get_busy() == True:
        pass

サウンドチップダイレクトコール

以下の例は あぶり6800 さんの Z80 MSX サウンドドライバー(1/60 tick) の Python によるシミュレートで、YM2149 の SSG を発音させています。

ソースコードでは YM2149 ひとつを扱っていますが、サウンドスロットにぽこぽこ複数の音源を追加してレジスターライトして PCM を取得できるイメージです。

sample_direct.py 抜粋

# Create Wasm instance
chip_stream = ChipStream()

# Setup sound slot
chip_stream.sound_slot_create(SOUND_SLOT_INDEX, SOUND_DRIVER_TICK_RATE, SAMPLING_RATE, SAMPLING_CHUNK_SIZE)

# Add "one" YM2149 sound chip in sound slot
chip_stream.sound_slot_add_sound_device(SOUND_SLOT_INDEX, SoundChipType.YM2149, 1, YM2149_CLOCK)

# YM2149 initialize (write reg: 0x7, data: 0b10111000)
mixing = 0b10111000
chip_stream.sound_slot_write(SOUND_SLOT_INDEX, SoundChipType.YM2149, 0, 0x7, mixing)

# ...snip...

# Write YM2149
chip_stream.sound_slot_write(SOUND_SLOT_INDEX, SoundChipType.YM2149, 0, track + 0x8, volume)

さて、libymfm.wasm のプログラム部分についてですが、Rust でかいており、C/C++ の ymfm を wasi-sdk でビルドした上で、Rust の wasm32-wasi とリンクする形でビルドしています。

このことから、.wasm は WASI の形になっています。(とはいえ、現在のところは WASI の機能はほとんど使っていません。一部、YM2608 の ROM ファイル読み込みで機能しています)

ウェブブラウザーインターフェースでは、WASI をブラウザー上で動作させるため、wasmer-js を使い、WASI バインディング関数をシミュレートして動作させています。

ウェブブラウザーインターフェースは次のリンクから試すことができ、演奏ファイルのドラッグアンドドロップ(複数可能)で楽曲が再生されるはずです。

https://chipstream.netlify.app

libymfm.wasm とは直接関係ありませんが、ウェブブラウザーの JavaScript の実装的には AudioWorklet と Woker と SharedArrayBuffer を使った音声出力の実装になっています。

iOS/macOS の Safari は現時点で SharedArrayBuffer に対応しているものの、どうも SharedArrayBuffer の notify がうまく飛ばないようで発音しません。(継続調査)

Windows/macOS/Linux の各ブラウザーで音切れしないように確認しながらつくっていますが、まだだめな環境があるかもです。。結構苦戦しました。。ちなみに手元の環境では、Linux の Firefox が一番素直に動作するようです。

てなわけで、みなさまのアプリに組み込みが容易になっている libymfm.wasm の紹介でした。新作ゲームに FM 音源ミュージックのリアルタイム再生など、いかがでしょうか…!

JavaScript、Python のサンプル実装含めてソースコードは、GitHub にコミットしてありますので、良ければ見てみてください。

https://github.com/h1romas4/libymfm.wasm

This repository is an experimental WebAssembly build of the [ymfm](https://github.com/aaronsgiles/ymfm) Yamaha FM sound cores library.

時折あるアップデート通知は https://twitter.com/h1romas4 のツイッターにて〜 😀

関連


Xbox Series S|X ヘッドセットの音量が小さい時 & Microsoft Edge の画面が小さい時

$
0
0

Xbox Series S|X 良いです…!贔屓目なしに Xbox 史上最高傑作のマシンではないかと思います。(XBOX を初代から買い続けたユーザより)

てなわけで 2022/10 現在のTIPS 的な設定を書きいておきます。(将来のバージョンでは改善されるかもしれません。また翻訳も変わるかもです)

Xbox Series S|X ヘッドセットの音量が小さい場合

Xbox コントローラー下部には有線のヘッドフォン・ヘッドセットが接続できます。

もしも、ヘッドセットが十分なボリュームで鳴らない場合は、ダッシュボードの次の部分から音量を上げてみてください。(自分は標準設定では予想の半分くらいしか鳴りませんでした)

しいたけボタンを押して、左サイドメニューの右下のスピーカーマーク。

「ヘッドセット音量」を上げる。

純正のワイヤードヘッドセットではこちらを最大にして、ヘッドセット側の右についてる大きなボリュームで調整すると良さそうです。お好みで。

ちなみに、設定のアクセサリーのハンバーガーメニューから「接続デバイス」からも遷移できますが、上の方法が簡単で早いかと思います。

Microsoft Edge の画面が小さい場合

Xbox Series S|X には Chromium ベースの Microsoft Edge が入っていますが、なぜか初期状態であるとフルサイズ・フルスクリーンで起動しません。

周りに謎の黒枠があってもったいない図。狭い。

修正後: 黒枠がなくなり広くなった図。

これで違和感なくウェブ版の Office 使えますね。

ちなみにこの Xbox 版 Microsoft Edge は PC 版の Microsoft Edge のバージョンと同期していて(ソースツリーを共有していると思います)、WebAssembly なども動作する本格的なウェブブラウザーです。

以下の設定でフル画面にすることができます。

設定 – 「テレビとディスプレイのオプション」

「ビデオ再現度とオーバースキャン」 (!?)

「アプリに境界線を追加」のチェックを外す。(分かるか〜w

別の影響がでるアプリ(UWP) があるかもしれませんので不具合あったら戻しましょう。

以上、細かすぎて分からない Xbox Series S|X の TIPS でした。

最後に、Xbox Series S|X の下位互換機能で遊べるおすすめゲームを紹介して終わろうと思います。

パンツァードラグーンオルタ

初代 XBOX より「パンツァードラグーンオルタ」(通常時 1080円)

パンツァードラグーン オルタ

SEGA • アクション & アドベンチャー • クラシック • レーシング & フライト • シューティング

セガの名作。Xbox Series S|X 下位互換機能によりアップスケールされ、初代 XBOX 版が 16:9 に対応していることもあり、現在でも全く違和感のないキレイなグラフィックが堪能できます。

NVIDIA!という感じの絵をだすパンツァードラグーンオルタ:(初代 XBOX

ゲームも大変よくできていて今やっても相当面白いです。お安いので未プレイの方はぜひ。

DEAD OR ALIVE シリーズ

格闘ゲーム DEAD OR ALIVE シリーズ。XBOX といえばこのゲームという方も多いかも。なんと Xbox では下位互換で DOA ナンバリングシリーズが全部遊べるようになっています。タスカルラスカル。ありがとー!

xbox.com 本家のソフトウェア検索がいけてなくてなかなか出てこないので、リンク集を作成しておきます。

初代 XBOX 系の DOA は下位互換機能で非常にキレイにアップスケールされています。綺麗すぎでは。

ちなみに発売日的な時系列では 3 より 2U(2 のリメイク) のほうが後で、遊べる要素が多い(コスが多いともいう)ので現在のお勧めは DOA2U でしょうか…!

DEAD OR ALIVE 2 Ultimate (初代 XBOX):

XBOX 360 からは DEAD OR ALIVE 4 が参戦。こちらは残念ながらアップスケールはされず画素は 720p のままのようですが、ポリゴン数増加や物理演算などの進化が見られます。

DEAD OR ALIVE 4 (XBOX 360):


… Xbox Series S|X 良い。(2回目)

Xbox Series S|X の使い方と小技

$
0
0

Xbox Series S|X 良いです…!(3回目)

2022/10 現在 Xbox Series S が在庫もあり購入しやすくなっています。もしかすると初 Xbox という方もいらっしゃるかもしれません。ようこそ Jump In…!

というわけで、Xbox Series S|X の使い方と小技などを書いてみたいと思います。

以下、現時点の情報でアップデートで構成が変わったり翻訳が変更になるかもしれません。

ホーム画面

Xbox を起動すると最初にでる画面です。デフォルトでたくさんサブメニュー(コントローラの下を押すと切り替わる)があるので圧倒されるかもしれません。

画面一番上にはよく使うもののショートカットが自動に配置されます。

その下からがサブメニューになりますが、いったん使うものだけに絞ると分かりやすくなります。画面右下「カスタマイズ」に割り当てられているコントローラのボタンを押します。

Game Pass と Microsoft Store がゲームの導入で活躍しますので、ここではこれだけ残してほかは表示しないようにしておきます。(慣れてきたらいろいろ追加すると良いでしょう)

Xbox Game Pass

Xbox Game Pass な月額サブスクリプションサービスのメニューです。

初回月 100円なのでかなりお得です。「全てのゲームを見る」から現在導入可能なゲームが一覧できます。加入している人は要チェック…!

Microsoft Store

ゲームが買えるストアです。Xbox Series S の場合は光学メディアスロットがありませんので、Microsoft Store からゲームをダウンロード購入します。通年的に何かしらの「セール」をしていますので、覗いてみましょう。

検索文字列の入力がコントローラで大変という場合は、何かしらの USB キーボードを接続すると良いと思います。普通に動作します。

ゲームの購入は Xbox 本体だけでなく、ウェブ上の Microsoft Store (Xbox と同じ Microsoft アカウントでログインのこと)からも可能です。ただ検索機能がいまいちなので、、目的が見つからない場合は Xbox 本体から検索してみるといいかもです。。

https://www.xbox.com/ja-JP/games/all-games

なお、Xbox 本体で対応していないと思われる(例えば) PayPal 決済がウェブ上からだと通るといった謎の仕様も現在あるようです。(そのうち修正されるかも…)

ウェブ上で買ったゲームは Xbox 本体の Microsoft Store でみると「インストール可能」になりますので、ぽちっとします。

ぼくのゲームはどこ?

所有しているゲームを一覧したい場合は、コントローラの Xbox ボタン(以下しいたけボタン)を押して「マイコレクション」「全て表示」を押します。

本体に導入済みのゲームが一覧されます。

また、本体から削除しているけれど所有権を持っているゲームは「フルライブラリ」「所有しているゲーム」から見ることができます。

所有しているゲームはこの画面からいつでも本体に導入可能です。

本体設定

本体設定は、しいたけボタンを押してでてくるサイドメニューの「プロフィールと設定」の設定から遷移するのが簡単です。

電源関連

起動した Xbox のシャットダウンを行うには、しいたけボタンを長押しします。

電源オフ時の挙動は設定「全般」「電源オプション」から指定できます。

電源オフ中にゲームのダウンロードを走らせたい場合は、「Xbox がオフの時、データ保存機能をオフにする」のチェックを外します。

なお、電源をオフにする方法で「シャットダウン」「スリープ」ともにダウンロードを走らせることができるとのこと。(自分はスリープにしているためシャットダウン時の動きは未検証)

ゲーム中にしいたけボタン

ゲーム中にしいたけボタンを押すと、いつでもサイドメニューが登場します。

別のゲームを起動するもよし、Microsoft Edge で攻略を検索するもよし。

また動画キャプチャーもここからできます。(スクショはコントローラのアップロードマークみたいな中央ボタンでいつでもできます)

別媒体への画像アップロードなどは「すべての共有オプションを表示」から。

ゲームを終わりたい

特に操作はありません。セーブされたことを確認したら、気持ち、しいたけボタンでホームに戻って電源をオフにすればいいかもです。またクイックレジューム対応のゲームは、再び起動すると元のシーンに戻ります。(完全に戻らないこともあるので、セーブは大切に…)

コントローラの電池の蓋が固くて全く開かない

個体差もあるかもですが、電池の蓋が固くて全く開かない場合がありますw Xbox One のはすぐ開くのですが。。

Xbox ワイヤレス コントローラーで乾電池を使用する

コントローラーに電池を取り付けるには、片手でコントローラーを上下逆さまに持ちます。 反対の親指で、矢印記号のバッテリー ドアを押し、矢印が指す方向にドアを動かします。 ドアを取り外したら、電池をコンパートメント内に示されている方向に取り外すか挿入します。

見た目通りの開き方をしますが、中央にフックがありますので、「中央(矢印記号のバッテリードア)を “思いっきり” 内側に押し込みながら」「上にスライド」、、

上(スライド)方向に力を加える前に、いったん内側の電池方向にぐいぐい押し込んでフックを外してから上へスライドするのがポイントす。

ゴム手袋装着などで力を集めるのも良いかも?。。グッドラック。

不具合かな?

Xbox ステータスサイトを見ると吉。

https://support.xbox.com/ja-JP/xbox-live-status

ゲームも含めてコンポーネントごとの詳細な不具合情報が見れます。

個別の不具合の場合は、以下からチャット or 電話サポートを受けられます。ちなみに、オンライン決済などの部分で何度かお世話になっていますが、非常に丁寧に対応していただきました。

https://support.xbox.com/ja-JP/contact-us

故障かな?

Microsoft アカウントにデバイスとして Xbox がひも付きます。xbox.com などでサインインして自分のアカウントが見れます。

「デバイスを修理に出す」というのがありますのでここから修理のオーダーができそうです。(未経験)

ゲームを買ってポイントをもらう

Xbox でお使いの Microsoft アカウントで Microsoft Rewards にサインアップしておくと、Microsoft Store でのゲーム購入でポイントが付きます。

https://www.microsoft.com/ja-jp/rewards

ポイントを獲得できる手段が豊富 オンラインで検索、ショッピング、ゲームをするだけでポイントが貯まります

特に Xbox Live Gold = Xbox Game Pass に加入していると LEVEL 2+ ステージ(最大)扱いでゲーム購入時のポイントが大幅にあがりますのでお得かもです。溜まったポイントは Microsoft Store や Amazon のギフトカードに交換できます。

てなわけで Xbox を引き続き楽しんでいきたい…!


関連記事

Xbox Series S|X コントローラのファームウェアをアップデートする

$
0
0

Xbox Series S|X のリリースノートで「Xbox コントローラーのファームウェア アップデート」とある場合に、手動でコントローラのファームウェアをアップデートする方法がぱっと分からなかったのでメモしておきます。

Xbox コントローラーのファームウェア アップデート

コントローラーを最新のソフトウェアにアップデートしておくと、コントローラーの機能と互換性を最大限に活かすことができます。10 月のアップデートでは、Xbox アダプティブ コントローラーに接続する USB フライト スティックの修正と、以下の Xbox コントローラーの修正を含むファームウェアのアップデートを行っています。

Xbox Series S|X の USB にコントローラを有線接続して次の操作をします。(未確認ですがもしかすると無線でも可能かもしれません)

しいたけボタンから設定画面へ。「デバイスと接続」から「アクセサリ」。

コントローラを選んで「…」ボタン。(ここがむずいw

ファームウェアバージョンを確認しつつ「今すぐ更新」(アップデートがある場合にこのボタンになります)

注意事項をしっかり読んで「続ける」

じっとがまんの子(2分くらい)

おめでとうございます。

「アップデートはありません」おうけいぼす。

以上です。PC 使わずにできて タスカルラスカル。

関連記事

AssemblyScript で WebAssembly を知る

$
0
0

毎年恒例 ゆるWeb勉強会@札幌 Advent Calendar 2022 への投稿です…!

札幌で開催しているWeb系勉強会、「ゆるWeb勉強会@札幌」の2022年アドベントカレンダーです。

過去発表内容のまとめ、発表する機会の無かったネタ、勉強会に参加したいけどできなかったので、などなど、Webに関するキーワードを中心としつつ自由に書いていってください!

毎年 WebAssembly 関連を投稿させていただいておりますが、今年は WebAssembly が理解の手助けとなるような、入門記事を書いてみたいと思います。

AssemblyScript 言語

最初に WebAssembly のおおよそですが、 プログラムソースコードをビルドすることにより Wasm バイナリー(.wasm)を出力し、このファイルを WebAssembly ランタイムに読ませることでプログラムを動作させる技術です。

この記事で取り上げる AssemblyScript 言語は、TypeScript Like なソースコードを Wasm バイナリーにビルドできるコンパイラーです。npm で簡単にツールチェインが導入でき、また WebAssembly 専用の言語となっているため、端的に理解しやすく、入門にも最適だと感じます。

The AssemblyScript Book

AssemblyScript compiles a variant of TypeScript (opens new window) (a typed superset of JavaScript) to WebAssembly (opens new window) using Binaryen (opens new window), looking like:

WebAssembly ランタイムを持つ主要ユーザのひとつはウェブブラウザーで、モバイルを含む主要 4 ウェブブラウザーで .wasm を実行可能です。また、ウェブブラウザーから JavaScript ランタイムが node.js として取り出されたように、ウェブブラウザー外で .wasm を動かす動きも近年活発です。

なお、.wasm のビルドに対応しているプログラム言語は、大きく 2種類のパターンがあり、直接 .wasm にビルドするものと、言語のインタープリタ(ランタイム)を .wasm にして、ソースコード(もしくは中間コード)をそのまま与えて動作するものに分けられます。

前者の代表は、Rust、C/C++、TinyGo などなど、そして AssemblyScript もこの仲間です。

後者は、Python(Pyodide)、Ruby、.NET/C# などが該当します。つまり、これらのインタープリタやランタイムが C/C++ であるということに立脚すると、前者の方式で Wasm ビルドできればスクリプトファイルが実行できる、というイメージです。

というわけで、本記事では WebAssembly 自身の理解の手助けとなるよう、直接プログラムが .wasm となり、ランタイムとのインターフェースもシンプルで分かりやすい AssemblyScript を使って解説を進めていきます。

サンプルプログラム

AssemblyScript/WebAssembly を使って時計を描画するプログラムをつくってみました。

次のような要素が含まれています。

  • WebAssembly から直接画面描画をすることができないので、WebAssembly ランタイムから import した、指定した位置に「点」を描画する関数を呼び出し時計を描いている。
  • 上記のような特性を逆に利用すると、ランタイム環境に依存する処理が差し替えられるため、同一 WebAssembly プログラムを、ウェブブラウザーとマイコン(M5Stack) で動作させるマルチプラットフォームの例ともなっています。

ソースコード:

https://github.com/h1romas4/m5stack-core2-wasm3-as

M5Stack Core2 With Wasm3/AssemblyScript Demo

WebAssembly プログラムとランタイムのインターフェース

WebAssembly の上のプログラムはよく「計算しかできない」と言われます。これは Wasm がサンドボックスで動作し、その外側のリソースにアクセスできないためです。

外界との接続との唯一のインターフェースは、それぞれの関数の export/import と、メモリーのポインターです。

JavaScript からみた、.wasm は引数の型が不自由な関数が追加されているように見えます。

:ランタイムに関数を公開する AssemblyScript のソースコード:

– 引数の型が u32 という見慣れない型になっている。

export function clock(x: u32, y: u32, r: u32): void {
    analogClock = new AnalogClock(x, y, r);
}

export function tick(): void {
    if(analogClock != null) {
        analogClock.tick();
    }
}

:export function が入った AssemblyScript を .wasm にビルドしたアセンブリーが出力(抜粋):

– 引数が i32 という WebAssembly の型になっている。

  (export "clock" (func 16))
  (export "tick" (func 28))

  (func (;16;) (type 1) (param i32 i32 i32)
  (func (;28;) (type 2)

そしてこの関数は JavaScript から次のように呼び出すことができます。

.wasm をロードする JavaScript のソースコード:

instance.exports.wasm に定義された関数を取得できる。

async function loadWasm() {
    const response = await fetch(new URL('../dist/app.wasm', import.meta.url));
    const responseArrayBuffer = new Uint8Array(await response.arrayBuffer());
    const wasm_bytes = new Uint8Array(responseArrayBuffer).buffer;
    let module = await WebAssembly.compile(wasm_bytes);
    const instance = await WebAssembly.instantiate(module, {
        ...createImports()
    });
    wasmExports = instance.exports;
};

.wasm でエクスポートされた関数を JavaScript からコールするソースコード:

instance.exports にエクスポートされた関数の引数に number 型を渡している。

(async function() {
    await loadWasm();
    wasmExports.clock(160, 120, 120);
    setInterval(() => {
        wasmExports.tick();
        wasmExports.__collect() // clean up all garbage
    }, 500);
})();

ポイントは .wasm 関数の引数には i32, i64, f32, f64 といった WebAssembly で使える(数値)型しかとれないことです。 JavaScript のオブジェクトのようなものや、文字列、もちろん DOM などの WebIDL の型も(現在のところ)そのまま渡して処理はできません。

AssemblyScript inherits WebAssembly’s more specific integer, floating point and reference types:

逆もしかりで、.wasm 側から JavaScript の関数を呼び出す際もこれらの引数型しかとることができません。

:JavaScript の関数を import する AssemblyScript のコード:

export declare function draw_pixel(x: i32, y: i32, color: i32): void;

export declare function をビルドした .wasm のアセンブリー(抜粋):

  (type (;1;) (func (param i32 i32 i32)))
  (import "c3dev" "draw_pixel" (func (;0;) (type 1)))

:AssemblyScript に関数を公開する JavaScript のコード:

async function loadWasm() {
    // ..snip...
    const instance = await WebAssembly.instantiate(module, {
        ...createImports()
    });
    // ..snip...
};

function createImports() {
    let imports = [];
    // ..snip...
    imports['c3dev'] = {
        'draw_pixel': (x, y, color) => {
            canvasContext.fillStyle = convertRGB565toStyle(color);
            canvasContext.fillRect(x, y, 1, 1);
        },
    // ..snip...
}

.wasm 側で import した i32 という数値型 を持つ draw_pixel 関数を呼び出し、最終的に ウェブブラウザーの Canvas に指定座標にひとつ点を描いています。マイコン動作ではこの部分が LCD に点をひとつ描く C の関数が呼び出すようにしています。

(ちなみに、点が描ければ円も線も計算で描けるということで、サンプルプログラムの時計は AssemblyScript の演算処理により全て点だけで描画を行っています)

インターフェースをつくる

以上のように、WebAssembly でプログラムをかく場合は、WebAssembly ランタイムをホストしている環境との連携インターフェースの設計が肝で、特性を考慮して、どのような処理単位にすると効率的に処理できるのかを考えていきます。

また、今回の例では用いていませんが、メモリーのポインター値を共有することで、VRAM や音声波形など大きな演算結果(配列)をインターフェースすることもでき、Wasm 内の演算結果を外部に持ち出すことができます。

このようなホストへのインターフェースの標準化の実装のひとつとして WASI があり、システム時間や乱数シード、ファイルシステムやネットワークへのアクセスが規定されています。(ただしウェブブラウザーには WASI API は実装されていません)

WASI も WebAssembly 型の関数が沢山あるだけですので、覗いてみると理解しやすいかと思います。

また、Rust の wasm-pack(wasm-bindgen) や Emscripten(C/C++) など他の WebAssembly ツールチェインは、Wasm 型との引数合わせやラップ、JavaScript の呼び出しをソースコードジェネレートなどを、自動でやってくれるものとして捉えると考えやすいです。

WebAssembly のインターフェースを知ることで周辺技術が何をしているのかが見えてきそうだな、ということで本記事は書かれました。

処理を速くしたい部分や、JavaScript 外で便利なライブラリー、、圧縮展開、画像動画音声処理、言語解析器などなどを見つけたら、「WebAssembly(型) なインターフェース」を追加して呼び出す。まずは WebAssembly の実用的なユースケースのひとつと思いますので、ぜひお試しください。

関連

Cubase で複数の YAMAHA Steinberg USB ASIO 機器がある場合にひとつしか表示されない

$
0
0

新規の Windows 11 に Cubase を導入していて分からなくなったので備忘録にて。

Windows に ASIO 機器を複数台接続した場合に、Cubase 上に選択したい機器のポートが表示されませんでした。

  • YAMAHA MODX7
  • YAHAMA AG06

いずれも YAMAHA Steinberg USB Driver にて管理。

Cubase のスタジオ設定より USB ASIO 項目からコントロールパネルを開き Device で使いたい機器を選択して OK ボタンを押下すると、その機器のポートがスタジオ設定に現れます。(ここが分からなかった)

接続機器を変更すると、オーディオコネクションが外れて発音しなくなるかもなので、Cubase -> スタジオ -> オーディオコネクションから再割当て。

ASIO コントロールパネルの Device 選択設定に Cubase が連動しているのに気がつくのに時間がかかりました。タブが ASIO として独立していることに納得。

以上です。(短い)

ついでにメモ。 opsix Native 単体起動時の ASIO と MIDI IN の設定は、右上ハンバーガメニューより「Sound Settings」から次の部分。

YAMAHA Steinberg USB ASIO は選択時にエラーになる場合があるけれど、がちゃがちゃしていると機器を見るけるようだメモ。

Viewing all 96 articles
Browse latest View live