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

去年くらいからつくりはじめていた、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 を取得できるイメージです。

前述の vgmplay(xgmplay) で使っているインターフェースは全て Wasm 側にも公開しているので、PCM ROM がある音源の操作も含め、原理的には自前で vgmplay をかけるスペックになっています。

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 がうまく飛ばないようで発音しません。どうやら AudioWorklet に渡された際に共有ではなくコピーになるようです。Safari 16 で修正されそうです(SharedArrayBuffer posted to AudioWorkletProcessor is not actually shared with the main thread

(2022/8 追記。Safari Technology Preview 149 で修正されているのを確認しました)

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 のツイッターにて〜 😀

関連

MSX ゲーム開発 2022年

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 をウェブブラウザーで活用する(1)

この記事は ゆる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 に移植して動作させるデモをやってみたいと思います!(続く

関連