最新のすべてのブラウザーでサポートされているWebAssembly(または「Wasm」)は、Webのユーザーエクスペリエンスを開発する方法を変革しています。これは単純なバイナリ実行可能形式であり、他のプログラミング言語で記述されたライブラリまたはプログラム全体をWebブラウザで実行できます。
開発者は、次のような生産性を高める方法を探すことがよくあります。
フロントエンド開発者向けに、WebAssemblyは3つすべてを提供し、ネイティブのモバイルまたはデスクトップエクスペリエンスに真に匹敵するWebアプリUIの検索に答えます。また、C ++やGo!などのJavaScript以外の言語で記述されたライブラリを使用することもできます。
このWasm / Rustチュートリアルでは、ギターチューナーのような単純なピッチ検出アプリを作成します。ブラウザに組み込まれているオーディオ機能を使用し、 モバイルデバイスでも、60フレーム/秒(FPS)で実行されます。 Web Audio APIを理解している必要はなく、精通している必要もありません。 さび このチュートリアルに従うこと。ただし、JavaScriptの快適さが期待されます。
注:残念ながら、この記事の執筆時点では、この記事で使用されている手法(Web Audio APIに固有)はFirefoxではまだ機能していません。したがって、当面は、FirefoxでのWasmおよびWeb Audio APIのサポートが優れているにもかかわらず、このチュートリアルではChrome、Chromium、またはEdgeをお勧めします。
AudioWorklet
を使用するブラウザでの高性能オーディオ処理のためのブラウザのAPI注:この記事の「理由」よりも「方法」に関心がある場合は、気軽に直接アクセスしてください。 チュートリアル 。
WebAssemblyを使用することが理にかなっている理由はいくつかあります。
WebAssemblyの人気は確実に高まり続けるでしょう。ただし、すべてのWeb開発に適しているわけではありません。
多くのプログラミング言語はWasmにコンパイルされますが、この例ではRustを選択しました。 Rustは2010年にMozillaによって作成され、人気が高まっています。さびは 頂点 StackOverflowの2020年の開発者調査で「最も愛されている言語」を表しています。しかし、WebAssemblyでRustを使用する理由は、単なる流行を超えています。
Rustの多くの利点には、学習曲線も急勾配であるため、適切なプログラミング言語の選択は、コードの開発と保守を行うチームの構成など、さまざまな要因によって異なります。
Rustを使用してWebAssemblyでプログラミングしているので、そもそもWasmにつながったパフォーマンス上の利点を得るためにRustをどのように使用できるでしょうか。急速に更新されるGUIを備えたアプリケーションがユーザーに「スムーズ」に感じられるようにするには、画面のハードウェアと同じくらい定期的に表示を更新できる必要があります。これは通常60FPSであるため、アプリケーションは約16.7ミリ秒(1,000ミリ秒/ 60 FPS)以内にユーザーインターフェイスを再描画できる必要があります。
このアプリケーションは、現在のピッチをリアルタイムで検出して表示します。つまり、検出の計算と描画を組み合わせて、フレームあたり16.7ミリ秒以内に収める必要があります。次のセクションでは、ブラウザのサポートを利用して、別のスレッドでオーディオを分析します。 一方 メインスレッドが機能します。計算と描画が行われるため、これはパフォーマンスにとって大きなメリットです。 各 16.7ミリ秒を自由に使用できます。
このアプリケーションでは、高性能のWebAssemblyオーディオモジュールを使用してピッチ検出を実行します。さらに、計算がメインスレッドで実行されないようにします。
物事をシンプルに保ち、メインスレッドでピッチ検出を実行できないのはなぜですか?
Webオーディオワークレットを使用すると、オーディオ処理がメインスレッドを維持できないため、アプリはスムーズな60FPSを達成し続けることができます。オーディオ処理が遅すぎて遅れると、オーディオの遅れなどの他の影響が発生します。ただし、UXはユーザーに応答し続けます。
アルゴリズムは、真または偽のコンピューティングアプリケーションに限定されています
このチュートリアルでは、Node.jsとnpx
がインストールされていることを前提としています。持っていない場合npx
すでに、npm
を使用できます(Node.jsに付属しています)インストールするには:
npm install -g npx
このWasm / Rustチュートリアルでは、Reactを使用します。
ターミナルで、次のコマンドを実行します。
npx create-react-app wasm-audio-app cd wasm-audio-app
これはnpx
を使用しますcreate-react-app
を実行しますコマンド(Facebookが管理する対応するパッケージに含まれています)を使用して、ディレクトリwasm-audio-app
に新しいReactアプリケーションを作成します。
create-react-app
Reactベースのシングルページアプリケーション(SPA)を生成するためのCLIです。 Reactで新しいプロジェクトを始めるのが信じられないほど簡単になります。ただし、出力プロジェクトには、置き換える必要のある定型コードが含まれています。
まず、開発全体を通してアプリケーションを単体テストすることを強くお勧めしますが、テストはこのチュートリアルの範囲を超えています。そこで、先に進んでsrc/App.test.js
を削除しますおよびsrc/setupTests.js
。
このアプリケーションには、5つの主要なJavaScriptコンポーネントがあります。
public/wasm-audio/wasm-audio.js
ピッチ検出アルゴリズムを提供するWasmモジュールへのJavaScriptバインディングが含まれています。public/PitchProcessor.js
オーディオ処理が行われる場所です。 Web Audioレンダリングスレッドで実行され、WasmAPIを使用します。src/PitchNode.js
Web Audioグラフに接続され、メインスレッドで実行されるWebAudioノードの実装が含まれています。src/setupAudio.js
WebブラウザAPIを使用して、利用可能なオーディオ録音デバイスにアクセスします。src/App.js
およびsrc/App.css
アプリケーションのユーザーインターフェイスを構成します。
アプリケーションの中心を直接掘り下げて、WasmモジュールのRustコードを定義しましょう。次に、Webオーディオ関連のJavaScriptのさまざまな部分をコーディングし、UIで終了します。
Rustコードは、オーディオサンプルの配列から音楽のピッチを計算します。
あなたはフォローすることができます これらの指示 開発用のRustチェーンを構築します。
wasm-pack
Rustで生成されたWebAssemblyコンポーネントを構築、テスト、公開できます。まだの場合は、 wasm-packをインストールします 。
cargo-generate
既存のGitリポジトリをテンプレートとして活用することで、新しいRustプロジェクトを稼働させるのに役立ちます。これを使用して、ブラウザーからWebAssemblyを使用してアクセスできるRustの単純なオーディオアナライザーをブートストラップします。
cargo
を使用するRustチェーンに付属のツールで、cargo-generate
をインストールできます。
cargo install cargo-generate
インストール(数分かかる場合があります)が完了すると、Rustプロジェクトを作成する準備が整います。
アプリのルートフォルダーから、プロジェクトテンプレートのクローンを作成します。
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
新しいプロジェクト名の入力を求められたら、wasm-audio
と入力します。
wasm-audio
でディレクトリには、Cargo.toml
があります。次の内容のファイル:
[package] name = 'wasm-audio' version = '0.1.0' authors = ['Your Name < [email protected] '] edition = '2018' [lib] crate-type = ['cdylib', 'rlib'] [features] default = ['console_error_panic_hook'] [dependencies] wasm-bindgen = '0.2.63' ...
Cargo.toml
Rustパッケージ(Rustは「クレート」と呼びます)を定義するために使用され、package.json
と同様の機能をRustアプリに提供します。 JavaScriptアプリケーションに対して行います。
[package]
セクションは、パッケージを公式に公開するときに使用されるメタデータを定義します パッケージレジストリ さびの。
[lib]
このセクションでは、Rustコンパイルプロセスからの出力形式について説明します。ここで、「cdylib」はRustに、別の言語(この場合はJavaScript)からロードできる「動的システムライブラリ」を生成するように指示し、「rlib」を含めると、生成されたライブラリに関するメタデータを含む静的ライブラリを追加するようにRustに指示します。この2番目の指定子は、私たちの目的には必要ありません-依存関係としてこのクレートを消費するさらなるRustモジュールの開発を支援します-しかし、そのままにしておくのは安全です。
[features]
では、Rustにオプション機能console_error_panic_hook
を含めるように依頼します。 Rustのunhandled-errorsメカニズム(panic
と呼ばれる)をデバッグ用の開発ツールに表示されるコンソールエラーに変換する機能を提供します。
最後に、[dependencies]
これが依存するすべてのクレートを一覧表示します。箱から出して提供される唯一の依存関係はwasm-bindgen
であり、これはWasmモジュールへのJavaScriptバインディングの自動生成を提供します。
このアプリの目的は、ミュージシャンの声や楽器のピッチをリアルタイムで検出できるようにすることです。これを可能な限り迅速に実行するために、WebAssemblyモジュールはピッチの計算を担当します。単一音声のピッチ検出には、既存のRustに実装されている「McLeod」ピッチ方式を使用します pitch-detection
図書館。
Node.jsパッケージマネージャー(npm)と同様に、RustにはCargoと呼ばれる独自のパッケージマネージャーが含まれています。これにより、Rustクレートレジストリに公開されているパッケージを簡単にインストールできます。
依存関係を追加するには、Cargo.toml
を編集し、pitch-detection
の行を追加します依存関係セクションへ:
[dependencies] wasm-bindgen = '0.2.63' pitch-detection = '0.1'
これにより、Cargoはpitch-detection
をダウンロードしてインストールするように指示されます。次のcargo build
中の依存関係または、WebAssemblyをターゲットにしているため、これは次のwasm-pack
で実行されます。
最初に、後で説明する目的の便利なユーティリティを定義するファイルを追加します。
作成wasm-audio/src/utils.rs
貼り付けます このファイルの内容 それに。
生成されたコードをwasm-audio/lib.rs
で置き換えます次のコードを使用して、高速フーリエ変換(FFT)アルゴリズムを介してピッチ検出を実行します。
use pitch_detection::{McLeodDetector, PitchDetector}; use wasm_bindgen::prelude::*; mod utils; #[wasm_bindgen] pub struct WasmPitchDetector { sample_rate: usize, fft_size: usize, detector: McLeodDetector, } #[wasm_bindgen] impl WasmPitchDetector { pub fn new(sample_rate: usize, fft_size: usize) -> WasmPitchDetector { utils::set_panic_hook(); let fft_pad = fft_size / 2; WasmPitchDetector { sample_rate, fft_size, detector: McLeodDetector::::new(fft_size, fft_pad), } } pub fn detect_pitch(&mut self, audio_samples: Vec) -> f32 { if audio_samples.len() pitch.frequency, None => 0.0, } } }
これをさらに詳しく調べてみましょう。
#[wasm_bindgen]
wasm_bindgen
JavaScriptとRust間のバインディングを実装するのに役立つRustマクロです。このマクロは、WebAssemblyにコンパイルされると、クラスへのJavaScriptバインディングを作成するようコンパイラーに指示します。上記のRustコードは、Wasmモジュールとの間の呼び出しの単なる薄いラッパーであるJavaScriptバインディングに変換されます。 JavaScript間の直接共有メモリと組み合わされた抽象化の軽い層は、Wasmが優れたパフォーマンスを提供するのに役立ちます。
#[wasm_bindgen] pub struct WasmPitchDetector { sample_rate: usize, fft_size: usize, detector: McLeodDetector, } #[wasm_bindgen] impl WasmPitchDetector { ... }
Rustにはクラスの概念がありません。むしろ、 オブジェクトのデータ struct
で記述されますそして その振る舞い impl
sまたはtrait
sを介して。
単純な関数ではなく、オブジェクトを介してピッチ検出機能を公開するのはなぜですか?そのため、内部のMcLeodDetectorで使用されるデータ構造のみを初期化します。 一度 、WasmPitchDetector
の作成中。これにより、detect_pitch
が維持されます動作中の高価なメモリ割り当てを回避することにより、高速に機能します。
pub fn new(sample_rate: usize, fft_size: usize) -> WasmPitchDetector { utils::set_panic_hook(); let fft_pad = fft_size / 2; WasmPitchDetector { sample_rate, fft_size, detector: McLeodDetector::::new(fft_size, fft_pad), } }
Rustアプリケーションで簡単に回復できないエラーが発生した場合、panic!
を呼び出すのが一般的です。大きい。これにより、Rustはエラーを報告し、アプリケーションをすぐに終了するように指示されます。パニックを利用することは、エラー処理戦略が実施される前の初期の開発に特に役立ちます。これにより、誤った仮定をすばやく見つけることができます。
呼び出しutils::set_panic_hook()
セットアップ中に1回実行すると、ブラウザ開発ツールにパニックメッセージが表示されます。
次に、各分析FFTに適用されるゼロパディングの量であるfft_pad
を定義します。パディングは、アルゴリズムで使用されるウィンドウ関数と組み合わせて、分析が入力されたサンプリングされたオーディオデータ間を移動するときに結果を「スムーズ」にするのに役立ちます。 FFTの半分の長さのパッドを使用すると、多くの機器でうまく機能します。
最後に、Rustは最後のステートメントの結果を自動的に返すため、WasmPitchDetector
structステートメントはnew()
の戻り値です。
残りのimpl WasmPitchDetector
Rustコードは、ピッチを検出するためのAPIを定義します。
pub fn detect_pitch(&mut self, audio_samples: Vec) -> f32 { ... }
これは、Rustでのメンバー関数定義の外観です。パブリックメンバーdetect_pitch
WasmPitchDetector
に追加されます。その最初の引数は、&mut
を含む同じタイプのインスタンス化されたオブジェクトへの可変参照(struct
)です。およびimpl
フィールド-ただし、以下に示すように、これは呼び出し時に自動的に渡されます。
さらに、メンバー関数は32ビット浮動小数点数の任意のサイズの配列を受け取り、単一の数を返します。ここでは、これらのサンプル全体で計算された結果のピッチ(Hz単位)になります。
if audio_samples.len() 上記のコードは、有効なピッチ分析を実行するのに十分なサンプルが関数に提供されたかどうかを検出します。そうでない場合、さびpanic!
マクロが呼び出され、Wasmがすぐに終了し、エラーメッセージがブラウザのdev-toolsコンソールに出力されます。
let optional_pitch = self.detector.get_pitch( &audio_samples, self.sample_rate, POWER_THRESHOLD, CLARITY_THRESHOLD, );
これにより、サードパーティのライブラリが呼び出され、最新のオーディオサンプルからピッチが計算されます。 POWER_THRESHOLD
およびCLARITY_THRESHOLD
アルゴリズムの感度を調整するために調整できます。
最後に、match
を介した浮動小数点値の暗黙の戻りがあります。キーワード。switch
と同様に機能します。他の言語でのステートメント。 Some()
およびNone
ヌルポインタ例外に遭遇することなく、ケースを適切に処理しましょう。
WebAssemblyアプリケーションの構築
Rustアプリケーションを開発する場合、通常のビルド手順はcargo build
を使用してビルドを呼び出すことです。ただし、Wasmモジュールを生成しているため、wasm-pack
を使用します。これにより、Wasmをターゲットにするときに構文が簡単になります。 (結果のJavaScriptバインディングをnpmレジストリに公開することもできますが、それはこのチュートリアルの範囲外です。)
wasm-pack
さまざまなビルドターゲットをサポートします。モジュールはWebAudioワークレットから直接使用するため、web
をターゲットにします。オプション。その他のターゲットには、webpackなどのバンドラー用またはNode.jsからの消費用のビルドが含まれます。これはwasm-audio/
から実行しますサブディレクトリ:
wasm-pack build --target web
成功すると、npmモジュールが./pkg
の下に作成されます。
これは、独自に自動生成されたpackage.json
を備えたJavaScriptモジュールです。これは、必要に応じてnpmレジストリに公開できます。今のところ簡単にするために、これをコピーして貼り付けるだけですpkg
私たちのフォルダの下public/wasm-audio
:
cp -R ./wasm-audio/pkg ./public/wasm-audio
これで、Webアプリ、より具体的にはPitchProcessor
で使用できるRustWasmモジュールを作成しました。
cfoは会社で何を意味します
2.私たちのPitchProcessor
クラス(ネイティブに基づくAudioWorkletProcessor
)
このアプリケーションでは、最近広くブラウザとの互換性が得られたオーディオ処理標準を使用します。具体的には、Web Audio APIを使用して、カスタムで高価な計算を実行します AudioWorkletProcessor
。その後、対応するカスタムを作成しますAudioWorkletNode
クラス(これをPitchNode
と呼びます)をメインスレッドへのブリッジとして使用します。
新しいファイルを作成するpublic/PitchProcessor.js
次のコードを貼り付けます。
import init, { WasmPitchDetector } from './wasm-audio/wasm_audio.js'; class PitchProcessor extends AudioWorkletProcessor { constructor() { super(); // Initialized to an array holding a buffer of samples for analysis later - // once we know how many samples need to be stored. Meanwhile, an empty // array is used, so that early calls to process() with empty channels // do not break initialization. this.samples = []; this.totalSamples = 0; // Listen to events from the PitchNode running on the main thread. this.port.onmessage = (event) => this.onmessage(event.data); this.detector = null; } onmessage(event) { if (event.type === 'send-wasm-module') { // PitchNode has sent us a message containing the Wasm library to load into // our context as well as information about the audio device used for // recording. init(WebAssembly.compile(event.wasmBytes)).then(() => { this.port.postMessage({ type: 'wasm-module-loaded' }); }); } else if (event.type === 'init-detector') { const { sampleRate, numAudioSamplesPerAnalysis } = event; // Store this because we use it later to detect when we have enough recorded // audio samples for our first analysis. this.numAudioSamplesPerAnalysis = numAudioSamplesPerAnalysis; this.detector = WasmPitchDetector.new(sampleRate, numAudioSamplesPerAnalysis); // Holds a buffer of audio sample values that we'll send to the Wasm module // for analysis at regular intervals. this.samples = new Array(numAudioSamplesPerAnalysis).fill(0); this.totalSamples = 0; } }; process(inputs, outputs) { // inputs contains incoming audio samples for further processing. outputs // contains the audio samples resulting from any processing performed by us. // Here, we are performing analysis only to detect pitches so do not modify // outputs. // inputs holds one or more 'channels' of samples. For example, a microphone // that records 'in stereo' would provide two channels. For this simple app, // we use assume either 'mono' input or the 'left' channel if microphone is // stereo. const inputChannels = inputs[0]; // inputSamples holds an array of new samples to process. const inputSamples = inputChannels[0]; // In the AudioWorklet spec, process() is called whenever exactly 128 new // audio samples have arrived. We simplify the logic for filling up the // buffer by making an assumption that the analysis size is 128 samples or // larger and is a power of 2. if (this.totalSamples PitchProcessor
PitchNode
のコンパニオンですただし、メインスレッドで実行された作業をブロックせずにオーディオ処理の計算を実行できるように、別のスレッドで実行されます。
主に、PitchProcessor
:
'send-wasm-module'
を処理しますPitchNode
から送信されたイベントWasmモジュールをコンパイルしてワークレットにロードします。完了すると、PitchNode
が可能になります'wasm-module-loaded'
を送信して知るイベント。 PitchNode
間のすべての通信のため、このコールバックアプローチが必要です。およびPitchProcessor
スレッドの境界を越え、同期的に実行できません。 'init-detector'
にも応答しますPitchNode
からのイベントWasmPitchDetector
を構成します。 - ブラウザのオーディオグラフから受信したオーディオサンプルを処理し、ピッチ検出の計算をWasmモジュールに委任してから、検出されたピッチを
PitchNode
に送り返します。 (ピッチをonPitchDetectedCallback
を介してReactレイヤーに送信します)。 - 自分自身を登録します 特定の一意の名前で。このようにして、ブラウザは、ネイティブの
PitchNode
であるAudioWorkletNode
の基本クラスを介して、PitchProcessor
をインスタンス化する方法を認識します。後でPitchNode
構築されます。 setupAudio.js
を参照してください。
次の図は、PitchNode
間のイベントの流れを視覚化したものです。およびPitchProcessor
:

ランタイムイベントメッセージ。
3.Webオーディオワークレットコードを追加します
PitchNode.js
カスタムピッチ検出オーディオ処理へのインターフェイスを提供します。 PitchNode
オブジェクトは、AudioWorklet
で動作するWebAssemblyモジュールを使用してピッチを検出するメカニズムです。スレッドはメインスレッドに移動し、レンダリングのためにReactを実行します。
src/PitchNode.js
では、組み込みをサブクラス化します AudioWorkletNode
Web Audio APIの:
export default class PitchNode extends AudioWorkletNode { /** * Initialize the Audio processor by sending the fetched WebAssembly module to * the processor worklet. * * @param {ArrayBuffer} wasmBytes Sequence of bytes representing the entire * WASM module that will handle pitch detection. * @param {number} numAudioSamplesPerAnalysis Number of audio samples used * for each analysis. Must be a power of 2. */ init(wasmBytes, onPitchDetectedCallback, numAudioSamplesPerAnalysis) { this.onPitchDetectedCallback = onPitchDetectedCallback; this.numAudioSamplesPerAnalysis = numAudioSamplesPerAnalysis; // Listen to messages sent from the audio processor. this.port.onmessage = (event) => this.onmessage(event.data); this.port.postMessage({ type: 'send-wasm-module', wasmBytes, }); } // Handle an uncaught exception thrown in the PitchProcessor. onprocessorerror(err) { console.log( `An error from AudioWorkletProcessor.process() occurred: ${err}` ); }; onmessage(event) { if (event.type === 'wasm-module-loaded') { // The Wasm module was successfully sent to the PitchProcessor running on the // AudioWorklet thread and compiled. This is our cue to configure the pitch // detector. this.port.postMessage({ type: 'init-detector', sampleRate: this.context.sampleRate, numAudioSamplesPerAnalysis: this.numAudioSamplesPerAnalysis }); } else if (event.type === 'pitch') { // A pitch was detected. Invoke our callback which will result in the UI updating. this.onPitchDetectedCallback(event.pitch); } } }
PitchNode
によって実行される主要なタスクは:
- WebAssemblyモジュールを生のバイトのシーケンス(
setupAudio.js
から渡されたバイト)として、PitchProcessor
で実行されるAudioWorklet
に送信します。糸。これがPitchProcessor
の方法ですピッチ検出Wasmモジュールをロードします。 PitchProcessor
によって送信されたイベントを処理しますWasmが正常にコンパイルされ、ピッチ検出構成情報を渡す別のイベントが送信されたとき。 PitchProcessor
から到着する検出されたピッチを処理しますそしてそれらをUI関数に転送しますsetLatestPitch()
onPitchDetectedCallback()
経由。
注:オブジェクトのこのコードはメインスレッドで実行されるため、これが高価でフレームレートの低下を引き起こす場合に備えて、検出されたピッチでそれ以上の処理を実行することは避けてください。
4.Webオーディオを設定するためのコードを追加します
Webアプリケーションがクライアントマシンのマイクからのライブ入力にアクセスして処理するには、次のことが必要です。
- 接続されているマイクにアクセスするためのブラウザのユーザー権限を取得します
- マイクの出力にオーディオストリームオブジェクトとしてアクセスします
- 着信オーディオストリームサンプルを処理し、検出されたピッチのシーケンスを生成するコードを添付します
src/setupAudio.js
では、これを実行し、Wasmモジュールを非同期でロードして、PitchNodeをアタッチする前にPitchNodeを初期化できるようにします。
import PitchNode from './PitchNode'; async function getWebAudioMediaStream() { if (!window.navigator.mediaDevices) { throw new Error( 'This browser does not support web audio or it is not enabled.' ); } try { const result = await window.navigator.mediaDevices.getUserMedia({ audio: true, video: false, }); return result; } catch (e) { switch (e.name) { case 'NotAllowedError': throw new Error( 'A recording device was found but has been disallowed for this application. Enable the device in the browser settings.' ); case 'NotFoundError': throw new Error( 'No recording device was found. Please attach a microphone and click Retry.' ); default: throw e; } } } export async function setupAudio(onPitchDetectedCallback) { // Get the browser audio. Awaits user 'allowing' it for the current tab. const mediaStream = await getWebAudioMediaStream(); const context = new window.AudioContext(); const audioSource = context.createMediaStreamSource(mediaStream); let node; try { // Fetch the WebAssembly module that performs pitch detection. const response = await window.fetch('wasm-audio/wasm_audio_bg.wasm'); const wasmBytes = await response.arrayBuffer(); // Add our audio processor worklet to the context. const processorUrl = 'PitchProcessor.js'; try { await context.audioWorklet.addModule(processorUrl); } catch (e) { throw new Error( `Failed to load audio analyzer worklet at url: ${processorUrl}. Further info: ${e.message}` ); } // Create the AudioWorkletNode which enables the main JavaScript thread to // communicate with the audio processor (which runs in a Worklet). node = new PitchNode(context, 'PitchProcessor'); // numAudioSamplesPerAnalysis specifies the number of consecutive audio samples that // the pitch detection algorithm calculates for each unit of work. Larger values tend // to produce slightly more accurate results but are more expensive to compute and // can lead to notes being missed in faster passages i.e. where the music note is // changing rapidly. 1024 is usually a good balance between efficiency and accuracy // for music analysis. const numAudioSamplesPerAnalysis = 1024; // Send the Wasm module to the audio node which in turn passes it to the // processor running in the Worklet thread. Also, pass any configuration // parameters for the Wasm detection algorithm. node.init(wasmBytes, onPitchDetectedCallback, numAudioSamplesPerAnalysis); // Connect the audio source (microphone output) to our analysis node. audioSource.connect(node); // Connect our analysis node to the output. Required even though we do not // output any audio. Allows further downstream audio processing or output to // occur. node.connect(context.destination); } catch (err) { throw new Error( `Failed to load audio analyzer WASM module. Further info: ${err.message}` ); } return { context, node }; }
これは、WebAssemblyモジュールがpublic/wasm-audio
でロードできることを前提としています。これは、前のRustセクションで実行しました。
5.アプリケーションUIを定義します
ピッチ検出器の基本的なユーザーインターフェイスを定義しましょう。 src/App.js
の内容を置き換えます次のコードで:
import React from 'react'; import './App.css'; import { setupAudio } from './setupAudio'; function PitchReadout({ running, latestPitch }) { return ( {latestPitch ? `Latest pitch: ${latestPitch.toFixed(1)} Hz` : running ? 'Listening...' : 'Paused'} ); } function AudioRecorderControl() { // Ensure the latest state of the audio module is reflected in the UI // by defining some variables (and a setter function for updating them) // that are managed by React, passing their initial values to useState. // 1. audio is the object returned from the initial audio setup that // will be used to start/stop the audio based on user input. While // this is initialized once in our simple application, it is good // practice to let React know about any state that _could_ change // again. const [audio, setAudio] = React.useState(undefined); // 2. running holds whether the application is currently recording and // processing audio and is used to provide button text (Start vs Stop). const [running, setRunning] = React.useState(false); // 3. latestPitch holds the latest detected pitch to be displayed in // the UI. const [latestPitch, setLatestPitch] = React.useState(undefined); // Initial state. Initialize the web audio once a user gesture on the page // has been registered. if (!audio) { return ( { setAudio(await setupAudio(setLatestPitch)); setRunning(true); }} > Start listening ); } // Audio already initialized. Suspend / resume based on its current state. const { context } = audio; return ( { if (running) { await context.suspend(); setRunning(context.state === 'running'); } else { await context.resume(); setRunning(context.state === 'running'); } }} disabled={context.state !== 'running' && context.state !== 'suspended'} > {running ? 'Pause' : 'Resume'} ); } function App() { return ( Wasm Audio Tutorial ); } export default App;
そして、App.css
を置き換えますいくつかの基本的なスタイルで:
.App { display: flex; flex-direction: column; align-items: center; text-align: center; background-color: #282c34; min-height: 100vh; color: white; justify-content: center; } .App-header { font-size: 1.5rem; margin: 10%; } .App-content { margin-top: 15vh; height: 85vh; } .Pitch-readout { margin-top: 5vh; font-size: 3rem; } button { background-color: rgb(26, 115, 232); border: none; outline: none; color: white; margin: 1em; padding: 10px 14px; border-radius: 4px; width: 190px; text-transform: capitalize; cursor: pointer; font-size: 1.5rem; } button:hover { background-color: rgb(45, 125, 252); }
これで、アプリを実行する準備が整いましたが、最初に対処する必要がある落とし穴があります。
WebAssembly / Rustチュートリアル:とても近い!
yarn
を実行するとおよびyarn start
、ブラウザに切り替えて、音声を録音しようとすると(ChromeまたはChromiumを使用し、開発者ツールを開いた状態で)、いくつかのエラーが発生します。

Wasm要件は幅広いサポートがありますが、Worklet仕様にはまだ含まれていません。
最初のエラーTextDecoder is not defined
は、ブラウザーがwasm_audio.js
のコンテンツを実行しようとしたときに発生します。これにより、Wasm JavaScriptラッパーのロードに失敗し、コンソールに2番目のエラーが表示されます。
この問題の根本的な原因は、RustのWasmパッケージジェネレーターによって生成されたモジュールがTextDecoder
を想定していることです。 (およびTextEncoder
)はブラウザによって提供されます。この仮定は、Wasmモジュールがメインスレッドまたはワーカースレッドから実行されている最新のブラウザーにも当てはまります。ただし、ワークレット(このチュートリアルで必要なAudioWorklet
コンテキストなど)の場合、TextDecoder
およびTextEncoder
まだ仕様の一部ではないため、利用できません。
TextDecoder
Rustのフラットでパックされた共有メモリ表現からJavaScriptが使用する文字列形式に変換するために、RustWasmコードジェネレーターが必要とします。言い換えると、Wasmコードジェネレーターによって生成された文字列を表示するために、 TextEncoder
およびTextDecoder
定義する必要があります 。
この問題は、WebAssemblyの比較的新しい症状です。ブラウザーのサポートが改善され、一般的なWebAssemblyパターンをすぐにサポートできるようになると、これらの問題は解消される可能性があります。
今のところ、TextDecoder
のポリフィルを定義することで回避できます。
新しいファイルを作成するpublic/TextEncoder.js
public/PitchProcessor.js
からインポートします:
import './TextEncoder.js';
これがimport
であることを確認してくださいステートメントはwasm_audio
の前にありますインポート。
期限切れのアカウントとは何ですか
最後に、貼り付けます この実装 にTextEncoder.js
(の礼儀 @Yaffle GitHubで)。
Firefoxの質問
前述のように、アプリでWasmとWeb Audioワークレットを組み合わせる方法は、Firefoxでは機能しません。上記のシムを使用しても、「リスニングを開始」ボタンをクリックすると、次のようになります。
Unhandled Rejection (Error): Failed to load audio analyzer WASM module. Further info: Failed to load audio analyzer worklet at url: PitchProcessor.js. Further info: The operation was aborted.
Firefoxが まだサポートしていません AudioWorklets
からモジュールをインポートする—私たちにとって、それはPitchProcessor.js
です。 AudioWorklet
で実行中糸。
完成したアプリケーション
完了したら、ページをリロードするだけです。アプリはエラーなしで読み込まれるはずです。 「リスニングを開始」をクリックして、ブラウザがマイクにアクセスできるようにします。 Wasmを使用してJavaScriptで記述された非常に基本的なピッチ検出器が表示されます。

リアルタイムのピッチ検出。
Rustを使用したWebAssemblyでのプログラミング:リアルタイムWebオーディオソリューション
このチュートリアルでは、WebAssemblyを使用して計算コストの高いオーディオ処理を実行するWebアプリケーションを最初から作成しました。 WebAssemblyを使用すると、Rustのネイティブに近いパフォーマンスを利用して、ピッチ検出を実行できます。さらに、この作業は別のスレッドで実行できるため、メインのJavaScriptスレッドはレンダリングに集中して、モバイルデバイスでも滑らかで滑らかなフレームレートをサポートできます。
Wasm / RustとWebオーディオのポイント
- 最新のブラウザーは、Webアプリ内でパフォーマンスの高いオーディオ(およびビデオ)のキャプチャと処理を提供します。
- さびは持っています Wasmのための素晴らしいツール 、WebAssemblyを組み込んだプロジェクトに最適な言語として推奨するのに役立ちます。
- 計算量の多い作業は、Wasmを使用してブラウザーで効率的に実行できます。
WebAssemblyには多くの利点がありますが、注意すべきWasmの落とし穴がいくつかあります。
- ワークレット内のWasmのツールはまだ進化しています。たとえば、JavaScriptとWasmの間で文字列を渡すために必要な独自のバージョンのTextEncoderおよびTextDecoder機能を実装する必要がありました。これは、
AudioWorklet
に文字列がないためです。環境。それと、Wasmサポート用のJavascriptバインディングをAudioWorklet
からインポートします。 Firefoxではまだ利用できません。 - 私たちが開発したアプリケーションは非常に単純でしたが、WebAssemblyモジュールを構築し、
AudioWorklet
からロードしました。重要なセットアップが必要でした。プロジェクトにWasmを導入すると、ツールの複雑さが増します。これは覚えておくことが重要です。
あなたの便宜のために、 このGitHubリポジトリ 最終的な完成したプロジェクトが含まれています。バックエンド開発も行う場合は、WebAssemblyを介してRustを使用することもできます。 Node.js内 。
ApeeScapeエンジニアリングブログの詳細:
基本を理解する
WebAssemblyは言語ですか?
WebAssemblyはプログラミング言語ですが、人間が直接作成することを目的とした言語ではありません。むしろ、他の高級言語からコンパクトなバイナリバイトコード形式にコンパイルされ、Webを介した効率的な転送と、今日のブラウザでの実行が可能になります。
WebAssemblyは何に適していますか?
WebAssemblyを使用すると、JavaScript以外の言語で記述されたソフトウェアをブラウザーでシームレスに実行できます。これにより、Web開発者は、特定の言語の独自の利点を活用したり、Webの利便性と遍在性によって既存のライブラリを再利用したりできます。
WebAssemblyを高速化する理由は何ですか?
WebAssemblyプログラムは、コンパクトなバイナリ表現を使用しているため、JavaScriptよりもブラウザへの転送が高速です。 Rustのような高性能言語も、一般的に高速実行のWasmバイトコードに変換されます。
WebAssemblyは何で書かれていますか?
WebAssemblyプログラムは、JavaScriptよりもWeb経由での転送を高速化するコンパクトなバイナリバイトコード表現を使用します。このバイトコードは、人間が直接記述することを意図したものではなく、C / C ++やRustなどの高級言語で記述されたコードをコンパイルするときに生成されます。
Rustプログラミング言語は何に使用されますか?
Rustは、堅牢なメモリモデル、優れた同時実行サポート、および小さなランタイムフットプリントを備えているため、オペレーティングシステム、デバイスドライバー、組み込みプログラムなどのシステムレベルのソフトウェアに最適です。また、グラフィックスやデータ処理の要件が厳しいWebアプリ向けの強力なWebAssemblyオプションでもあります。
なぜRustはとても速いのですか?
Rustプログラムは、コードが最適化されたマシンレベルの命令にコンパイルされるため高速であり、Rustはガベージコレクションを使用しないため、プログラマーはメモリの使用方法を完全に制御できます。これにより、一貫性のある予測可能なパフォーマンスが得られます。