夢の跡
github.com
頑張ったけど無理でした。
やりたかったことは WebAssembly を使った音声操作です。
動機
Rust 実装の音声ライブラリについて調べてたところ、cpal に行き当たりました。
んで、何とそこには Emscripten という文字が。
お? ブラウザでも動くの?? となぜか WebAssembly で書いてみようと思ったのです。
制限時間は1日。
いや対応してないやん
それはそうなのですが、WebAssembly で動くと
- 必要な速度が出れば、ブラウザ上で音声規格の配布ができる
- 上記が達成できれば、標準化を狙った規格の試行錯誤ができる
- 標準化にこぎつけブラウザに組み込むとなった段階で、組み込める(かもしれない)コードが出来上がってる
こう書くと、風が吹けば桶屋が儲かる感がすさまじい。おかしいな、こんなはずでは…
それはともかく、速度を優先されてかつ私がすぐ試せるものとなると、大学時代にかじった音声処理でした。
ので、それでお試し。
やったこと
まずはネイティブ実装
#[no_mangle]
pub extern "C" fn hoge() -> f64{
let device = rodio::default_output_device().unwrap();
let sink = Sink::new(&device);
let source = rodio::source::SineWave::new(440).take_duration(Duration::from_secs(3));
sink.append(source);
sink.sleep_until_end();
1.0
}
サイン波を3秒再生する実装です。
使用したライブラリは rodio で、ドキュメントとかを見ながら実装しました。
ローカルでの WebAssembly の実行環境
nmi.jp
上記ページを参考させてもらいました。
<html lang="ja">
<head>
<meta charset="utf-8">
<script>
fetch('../target/wasm32-unknown-unknown/debug/wasm_sample.wasm')
.then(response => response.arrayBuffer())
.then(function(bytes) {
return WebAssembly.instantiate(bytes, {});
})
.then(results => {
console.log(results.instance.exports.fuga(2));
});
</script>
</head>
<body>
</body>
</html>
IntelliJ で HTML を書いて右上にマウスカーソルを動かすと、HTTP サーバーとして動いてくれます。
ので、今回はそれを使いました。お試しなので許して。
WebAssembly 呼び出せてるよなー、の確認で加算関数を作って呼び出し。
無事呼び出せたので次。
ImportObj の定義
いよいよ音声再生だーと呼び出してみたところ、即失敗。
WebAssembly.instantiate
にてエラーが発生していて、エラーメッセージは以下でした。
TypeError: import object field 'env' is not an Object
env とは???
で、エラーメッセージで調べてみると、以下を見つけました。
Rocket - A Rust game running on WASM
なるほどなー、足りない関数の対応表を渡してやる必要があるわけですな。
ということで、そのままパクる。
関数の呼び出しに成功しました。んが!
WebAssembly 環境の追加
動きませんでした。 そらそうだよね!
print デバッグをやって確認したところ、1行目でこけてる模様。
rodio と cpal をローカルにクローンして、書き換えながら確認しました。
#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd",
target_os = "macos", target_os = "ios", target_os = "emscripten")))]
use null as cpal_impl;
use std::error::Error;
use std::fmt;
use std::iter;
use std::ops::{Deref, DerefMut};
mod null;
mod samples_formats;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
#[path = "alsa/mod.rs"]
mod cpal_impl;
#[cfg(windows)]
#[path = "wasapi/mod.rs"]
mod cpal_impl;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[path = "coreaudio/mod.rs"]
mod cpal_impl;
#[cfg(target_os = "emscripten")]
#[path = "emscripten/mod.rs"]
mod cpal_impl;
引用元 cpal/lib.rs at 77ce5eba06447275ce6a094ac053e6775272ad8c · tomaka/cpal · GitHub
当たり前なのですが WASM の文字がなく、結果として未対応環境の null
に行きついてエラーになってました。そらそうだ。
同じブラウザで動かすし、内部で使われてる stdweb は WebAssembly 対応してるぽいから、emscripten
の記載部分に wasm 追加すればいいかなー、と安直に考えて追加しました。
中を見たところ WebAudio に対応してるかを判定してたので、WebAssembly から API 呼び出しできればいけるかなーってところでした。
追加方法の参考リンクは以下です。
https://stackoverflow.com/questions/48350087/how-do-i-conditionally-compile-for-webassembly-in-rust
https://github.com/koute/stdweb/blob/master/src/webcore/ffi/mod.rs
記載方法はこんな感じ。
#[cfg(any(target_os = "emscripten", all(target_arch = "wasm32", target_os = "unknown")))]
#[path = "emscripten/mod.rs"]
mod cpal_impl;
Cargo.toml も併せて修正。
[target.'cfg(any(target_os = "emscripten",all(target_arch = "wasm32", target_os = "unknown")))'.dependencies]
stdweb = { version = "0.1.3", default-features = false }
んでなんと通ってしまった!
そして次のエラー。
sinf/cosf の補填
突破だーと思ったところ、またもやエラーでした。
LinkError: import object field 'sinf' is not a Function
なるほどな? 関数が見つからん、といってるので、先ほどの関数の対応表に sinf
と cosf
を追加して、突破です。
で、次のエラー。
LinkError: "import object field 'emscripten_asm_const_int' is not a Function" にて挫折・終了
軽く調べて C の header ファイルに行きついた瞬間に、これはちょっとこれまでのやつと違う気がする、となってきました。
C から Javascript の関数を呼び出すための関数、らしいのですが、今そういったことをやってないんですよね。
関数の役割的に sinf
のように補填すればよい、ということでもなさそうです。
んで、stdweb の実装に確かに記載をみつけ、mod.rs
での切り替えや macro.rs
での呼び出しも見つけましたが、よくわからんぞーとなってきて心折れてきたところで夜でした。
難しい
そんな簡単にいくわけがない、とわかっていたものの、中途半端になってしまいました。
いつかリベンジ。いつか…