This repository is an experimental WebAssembly build of the [ymfm](https://github.com/aaronsgiles/ymfm) Yamaha FM sound cores library.
This repository is an experimental WebAssembly build of the ymfm Yamaha FM sound cores library.
BSD-licensed Yamaha FM sound cores (OPM, OPN, OPL, and others)
libymfm.wasm
provide high-level and low-level WebAssembly interfaces to ymfm's sound chips and additional sound chips.
The high-level interface provides the vgm/xgm sequencer, while the low-level interface provides direct access to the sound chip. Both can retrieve PCM binary at a given sample rate and number of frames.
The WebAssembly interface can be called from many computer languages by using Wasmer.
chip | from | note |
---|---|---|
YM2149 | ymfm | |
YM2151 | ymfm | |
YM2203 | ymfm | |
YM2413 | ymfm | |
YM2608 | ymfm | |
YM2610/YM2610B | ymfm | |
YM2612 | ymfm | |
YM3526 | ymfm | |
Y8950 | ymfm | |
YM3812 | ymfm | |
YMF262 | ymfm | |
YMF278B | ymfm | |
SN76489 | MAME | Rust ports |
SEGAPCM | MAME | Rust ports |
PWM | MAME | Rust ports |
OKIM6258 | MAME | Rust ports |
C140/C219 | MAME | Rust ports |
OKIM6295 | MAME | Rust ports |
BSD 3-Clause License
Firefox or Chromium or Safari 16
or higher is recommended.
Source code:
https://github.com/h1romas4/libymfm.wasm/tree/main/examples/web
Options
$ wasmer run libymfm-cli.wasm -- -h
libymfm-cli 0.18.0
Hiromasa Tanaka <[email protected]>
libymfm CLI
USAGE:
libymfm-cli.wasm [OPTIONS] <filename>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--loop <loop> Loop count
-o, --output <output filepath> Output file path
-r, --rate <rate> Output sampling rate
ARGS:
<filename> Play .vgm/.vzg/.xgm/.xgz file path
Example 1 - Specify output file name
$ wasmer run libymfm-cli.wasm --mapdir /:./docs/vgm -- /ym2612.vgm -o ym2612.pcm
$ ffplay -f f32le -ar 44100 -ac 2 ./docs/vgm/ym2612.pcm
Example 2 - Direct play
$ wasmer run libymfm-cli.wasm --mapdir /:./docs/vgm -- /ym2612.vgm | ffplay -f f32le -ar 44100 -ac 2 -i -
Example 3 - Specify samplig rate
$ wasmer run libymfm-cli.wasm --mapdir /:./docs/vgm -- /ym2612.vgm -r 96000 | ffplay -f f32le -ar 96000 -ac 2 -i -
Source code:
https://github.com/h1romas4/libymfm.wasm/tree/main/examples/libymfm-cli
Install dependencies
cd examples/python
pip install -r requirements.txt
Run examples
# Simple VGM Player
python src/sample_vgmplay.py
# Simple XGM Player
python src/sample_xgmplay.py
# Sound chip direct access example
python src/sample_direct.py
# Pyxel impliments example
python src/sample_pyxel.py
VGM Play Example: sample_vgmplay.py
#
# VGM Play Example
#
import pygame
from wasm.chipstream import ChipStream
# VGM instance index
VGM_INDEX = 0
# Output sampling rate settings
SAMPLING_RATE = 44100
SAMPLING_CHUNK_SIZE = 4096
# Sound device init (signed 16bit)
pygame.mixer.pre_init(frequency=SAMPLING_RATE, size=-16, channels=2, buffer=SAMPLING_CHUNK_SIZE)
pygame.init()
# 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
# PyGame quit
pygame.quit()
# Drop instance
chip_stream.drop_vgm_instance(VGM_INDEX)
libymfm.wasm has a super basic extern c
Wasm interface.
#[no_mangle]
pub extern "C" fn vgm_create(
vgm_index_id: u32,
output_sampling_rate: u32,
output_sample_chunk_size: u32,
memory_index_id: u32,
) -> bool {
let vgmplay = VgmPlay::new(
SoundSlot::new(
driver::VGM_TICK_RATE,
output_sampling_rate,
output_sample_chunk_size as usize,
),
get_memory_bank()
.borrow_mut()
.get(memory_index_id as usize)
.unwrap(),
);
if vgmplay.is_err() {
return false;
}
get_vgm_bank()
.borrow_mut()
.insert(vgm_index_id as usize, vgmplay.unwrap());
true
}
As with the Python binding example, you could easily create an interface. It would also be possible to create a more type-structured interface.
Build require Rust 2021 edition and +nightly.
rustup install nightly
rustup target add wasm32-wasi
Setup wasi-sdk-20 - wasi-sdk-20.0-linux.tar.gz
requires Ubuntu 22.04.
Setup enviroment values:
.bashrc
export WASI_SDK_PATH=/home/hiromasa/devel/toolchain/wasi-sdk-20.0
export CARGO_TARGET_WASM32_WASI_LINKER=${WASI_SDK_PATH}/bin/lld
export CARGO_TARGET_WASM32_WASI_RUSTFLAGS="-L ${WASI_SDK_PATH}/share/wasi-sysroot/lib/wasm32-wasi"
Verify:
$ echo ${WASI_SDK_PATH}
/home/hiromasa/devel/toolchain/wasi-sdk-20.0
$ ls -alF ${WASI_SDK_PATH}
drwxr-xr-x 2 hiromasa hiromasa 4096 12月 3 2020 bin/
drwxr-xr-x 3 hiromasa hiromasa 4096 12月 3 2020 lib/
drwxr-xr-x 6 hiromasa hiromasa 4096 12月 3 2020 share/
$ ${WASI_SDK_PATH}/bin/clang -v
clang version 16.0.0
Target: wasm32-unknown-wasi
Thread model: posix
InstalledDir: /home/hiromasa/devel/toolchain/wasi-sdk-20.0/bin
Require --recursive
git clone --recursive https://github.com/h1romas4/libymfm.wasm
cd libymfm.wasm
cmake -DCMAKE_TOOLCHAIN_FILE=./cmake/wasi.cmake -S . -B build
cmake --build build --parallel $(nproc)
ls -laF dist/ | grep libymfm
-rw-rw-r-- 1 hiromasa hiromasa 480942 5月 25 13:21 libymfm.a
examples/web
)Install wasm-bindgen require (--version 0.2.78
)
cargo install wasm-bindgen-cli --version 0.2.78
Rust build and wasm-bindgen
Always add the +nightly flag. (-Z wasi-exec-model=reactor
flag is used, so nightly must be specified)
cargo +nightly build --release --target wasm32-wasi --features bindgen
wasm-bindgen target/wasm32-wasi/release/libymfm.wasm --out-dir ./examples/web/src/wasm/
npm
cd examples/web
npm install
npm run start
examples/python
)Rust build and copy .wasm to Python project
Always add the +nightly flag. (-Z wasi-exec-model=reactor
flag is used, so nightly must be specified)
cargo +nightly build --release --target wasm32-wasi
cp -p target/wasm32-wasi/release/libymfm.wasm ./examples/python/src/wasm/
examples/libymfm-cli
)Building the WASI command-line interface requires disabling WASI reactor
mode of the library, so the build requires a patch to the source code.
Pacth Cargo.toml
[lib]
# https://github.com/rust-lang/rust/pull/79997
# https://github.com/bazelbuild/rules_rust/issues/771
# crate-type = ["bin"] # disable this line
crate-type = ["cdylib", "rlib"] # enable this line
path = "src/rust/lib.rs"
Pacth src/rust/lib.rs
// #![no_main] // disable this line
Pacth .cargo/config
[target.wasm32-wasi]
rustflags = [
"-Ctarget-feature=+bulk-memory",
# "-Z", "wasi-exec-model=reactor", # disable this line
Build
cd examples/libymfm-cli
cargo +nightly build --target=wasm32-wasi --release
Verify:
ls -laF target/wasm32-wasi/release/*.wasm
-rwxrwxr-x 2 hiromasa hiromasa 2924223 5月 21 14:56 target/wasm32-wasi/release/libymfm-cli.wasm*
Since Rust currently does not allow create-type switching, the following modification to the source code is required for native debugging.
It is also required if you want to use this library as a simple native library.
Pacth Cargo.toml
[lib]
# https://github.com/rust-lang/rust/pull/79997
# https://github.com/bazelbuild/rules_rust/issues/771
# crate-type = ["bin"] # disable this line
crate-type = ["cdylib", "rlib"] # enable this line
path = "src/rust/lib.rs"
Pacth src/rust/lib.rs
// #![no_main] // disable this line
Buile or test on native
cmake -DCMAKE_TOOLCHAIN_FILE=./cmake/x86-64.cmake -S . -B build
cmake --build build --parallel $(nproc)
ls -laF dist/ | grep libymfm
-rw-rw-r-- 1 hiromasa hiromasa 680784 5月 25 13:23 libymfm.a
Native debugging can now be performed.
cargo build --release
cargo test ym2612_1 -- --nocapture
WASI Library
Essentially, wasm-bindgen is incompatible with wasm32-wasi.
improve panic message when compiling to wasi #2554
panicked at 'unknown instruction LocalTee