Skip to content

WebAssembly ガイド ​

libsonare は WebAssembly にコンパイルでき、ブラウザで直接オーディオ解析ができます。重要な原則として、その API が扱うのは生の .mp3/.wav ファイルではなく、デコード済みのオーディオサンプル(数値が並んだモノラルの Float32Array)です。サンプルを得る方法は 2 通りで、Web Audio API や別の JavaScript デコーダで自分でファイルをデコードするか、エンコード済みバイト列を Audio.fromMemory* ヘルパーに渡してデコードを任せます。全体の流れは下の表のとおりです。

このページは、ブラウザアプリを作る人向けです。Python スクリプト、ターミナルのバッチ処理、ネイティブデスクトップツールを作る場合は、先に はじめに で別の実行環境を選んでください。

このページでは、セットアップ、単発呼び出しの API、マスタリングを扱います。Web Worker との連携、パフォーマンスチューニング、React の例は WASM の応用的な使い方 を、ライブのストリーミング解析、逆再構成、リアルタイムボイスチェンジャーは WASM のストリーミングとリアルタイム処理 を参照してください。

ブラウザでの考え方 ​

手順内容
1. ファイルを取得するfetch、<input type="file">、ドラッグ & ドロップ、その他のブラウザ入力を使う
2. 音声をデコードするAudio.fromMemory(...)、Audio.fromMemoryWithBrowserFallback(...)、AudioContext.decodeAudioData(...)、または独自のデコーダを使う
3. サンプルを選ぶ1 つのモノラルチャンネルを渡す、ステレオを自分でダウンミックスする、または対応するステレオ API を使う
4. libsonare を呼ぶサンプルと sampleRate を解析、編集、マスタリング、ミキシング API に渡す

初学者がつまずきやすい点は、MP3 の ArrayBuffer をそのまま解析関数へ渡してしまうことです。先にデコードしてください。ブラウザ版 libsonare が扱うのは、圧縮ファイルのバイト列ではなく PCM サンプルです。

セットアップで問題が起きたときは、まず次の 3 点を確認してください。

  • DSP 関数を呼ぶ前に await init() が完了している。
  • sampleRate には、デコードしたサンプルのレート(通常は audioBuffer.sampleRate)を渡している。
  • サンプル配列は PCM 音声(Float32Array)であり、エンコード済みファイルのバイト列ではない。
Float32Array・PCM・モノラル・ダウンミックスとは?
  • PCM サンプル は、圧縮されていない生の波形で、振幅値の長い列です。MP3/WAV のファイルは圧縮・梱包されたバイト列で、デコードすると PCM になります。
  • Float32Array は、そのサンプルを 32bit 浮動小数点(通常 −1〜1 の範囲)で 1 サンプル 1 要素として保持する、Web Audio API が使う JavaScript の typed array です。libsonare のブラウザ API はこれをそのまま受け取ります。
  • モノラル/ダウンミックス: モノラルは 1 チャンネルです。ステレオは左右の独立したチャンネルを持ち、ダウンミックスはそれらを 1 つにまとめます(通常は平均)。これでモノラル API に 1 チャンネルを渡せます。

このページで身につくこと ​

このページを読むと、次のことを判断・実装できるようになります。

  • WASM パッケージを正しくインストールし、初期化できる。
  • ブラウザ上のファイルを PCM へデコードし、正しいチャンネルとサンプルレートの組を libsonare に渡せる。
  • 1 回呼び出しの関数、Audio、StreamAnalyzer、StreamingMasteringChain、Mixer、RealtimeEngine を使い分けられる。
  • ブラウザアプリとして出す前に、バンドルサイズ、Worker、AudioWorklet のトレードオフを理解できる。

インストール ​

npm/yarn ​

bash
npm install @libraz/libsonare
bash
yarn add @libraz/libsonare
bash
pnpm add @libraz/libsonare

CDN ​

html
<script type="module">
  import { init, detectBpm } from 'https://unpkg.com/@libraz/libsonare';
</script>

基本的な使い方 ​

typescript
import { init, detectBpm, detectKey, analyze } from '@libraz/libsonare';

async function analyzeAudio() {
  // WASM モジュールを初期化
  await init();

  // AudioContext からオーディオデータを取得
  const audioCtx = new AudioContext();
  const response = await fetch('music.mp3');
  const arrayBuffer = await response.arrayBuffer();
  const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);

  // 1 つのモノラルチャンネルを取得。ステレオ両方を反映したい場合は明示的にダウンミックスします。
  const samples = audioBuffer.getChannelData(0);
  const sampleRate = audioBuffer.sampleRate;

  // BPM を検出
  const bpm = detectBpm(samples, sampleRate);
  console.log(`BPM: ${bpm}`);

  // キーを検出
  const key = detectKey(samples, sampleRate);
  console.log(`キー: ${key.name}`);

  // まとめて解析
  const result = analyze(samples, sampleRate);
  console.log(result);
}
CLI での同等操作

同じ 1 ファイル確認を sonare コマンドラインで行う場合です(ブラウザでは使えません)。

bash
sonare bpm music.mp3
sonare key music.mp3
sonare analyze music.mp3 --json

下のデモは、同じデータの流れを視覚化したものです。デコード済みサンプルを入力し、時間 × 周波数の表示を出力します。ブラウザページでこの種の結果を描画できれば、WASM パッケージの読み込み、初期化、デコード、サンプルレートの受け渡しがつながっていると確認できます。

STFT · SPECTRALIDLE
STFT — 時間と周波数を同時に見る

400 Hz から 1 kHz へ上昇するトーンに、4 つのクリックを重ねた信号。各列が1つの短時間スペクトルで、明るいほどエネルギーが大きい。nFft を切り替えると、256 ではクリックは細い線になり、トーンは幅約 170 Hz の帯になる。4096 ではトーンは幅約 20 Hz の線になり、クリックは約 90 ms にわたってにじむ(幅はいずれもピークから 6 dB 下がった位置で測った値)。

nFft(窓長)

ブラウザビルドには librosa 互換ヘルパーも含まれるため、既存の librosa のレシピをそのまま移植できます。用途別の関数一覧はヘルパーと変換に、librosa との対応関係は librosa 互換性にまとめています。

ブラウザ内ミキシング ​

WASM パッケージからミキシングエンジンも使えます。ステムを一括でレンダーするだけなら mixStereo(...)、バス、センド、インサートオートメーション、ゴニオメーター、ストリップメーターが必要ならシーン JSON から作る Mixer を使います。

typescript
import { init, Mixer, mixStereo, mixingScenePresetJson } from '@libraz/libsonare';

await init();

const rendered = mixStereo([vocalL, musicL], [vocalR, musicR], sampleRate, {
  faderDb: [-3, -12],
  pan: [0, -0.2],
  width: [1, 0.9],
});

const mixer = Mixer.fromSceneJson(mixingScenePresetJson('vocalReverbSend'), sampleRate, 512);
mixer.scheduleFaderAutomation(0, sampleRate * 4, -6, 's-curve');
const block = mixer.processStereo([vocalBlockL, musicBlockL], [vocalBlockR, musicBlockR]);
const meter = mixer.stripMeter(0, 'postFader');
mixer.delete();

詳しくは ミキシングエンジン を参照してください。

CLI での同等操作

組み込みミキサーシーンを sonare コマンドラインでレンダリングする場合です。

bash
sonare mix \
  --preset vocalReverbSend \
  --input vocal.wav \
  --input music.wav \
  -o mixed.wav

Audio クラス ​

スタンドアロン関数の代わりに、Audio クラスをオブジェクト指向的に使うこともできます。サンプルとサンプルレートを内部で保持するため、毎回の呼び出しで渡し直す必要がありません。

typescript
import { init, Audio } from '@libraz/libsonare';

await init();

const audioCtx = new AudioContext();
const response = await fetch('music.mp3');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);

// Audio インスタンスを作成
const audio = Audio.fromBuffer(
  audioBuffer.getChannelData(0),
  audioBuffer.sampleRate
);

// 解析
const bpm = audio.detectBpm();
const key = audio.detectKey();
const result = audio.analyze();

// エフェクト
const { harmonic, percussive } = audio.hpss();
const stretched = audio.timeStretch(1.5);
const shifted = audio.pitchShift(2);

// 特徴量
const mel = audio.melSpectrogram();
const mfcc = audio.mfcc();
const chroma = audio.chroma();
const pitch = audio.pitchPyin();

console.log(`BPM: ${bpm}, キー: ${key.name}`);
console.log(`中央値ピッチ: ${pitch.medianF0.toFixed(1)} Hz`);
CLI での同等操作

同じ呼び出しを Python CLI で行う場合です。

bash
sonare analyze music.mp3 --json
sonare hpss music.mp3 -o separated --json
sonare pitch-shift music.wav --semitones 2 -o shifted.wav
sonare pitch music.mp3 --algorithm pyin --json

インスタンスメソッドの一覧は Audio クラスのリファレンスを参照してください。

ブラウザ内マスタリング ​

/ja/mastering デモは、このページで説明しているものと同じ WASM パッケージを使用しています。音源のデコードはブラウザで行い、マスタリング処理は Web Worker で実行し、レンダリング後の WAV と JSON レポートはローカルで生成されます。masterAudio、masteringChain、名前付きプロセッサはマスタリングのリファレンスで説明しています。

実装の詳細は マスタリング実装, ブラウザ内ローカル処理, マスタリングチェーン, ステレオ、リミッター、ラウドネスコントロール を参照してください。

マスタリング API には、JSON ベースのアシスタント出力、音源プロファイル、配信プラットフォーム別のプレビューを返す masteringAssistantSuggest(...)、masteringAudioProfile(...)、masteringStreamingPreview(...) も含まれます。

CLI での同等操作

シンプルなラウドネス正規化マスターを sonare コマンドラインで作る場合です。

bash
sonare mastering track.wav --target-lufs -14 --ceiling-db -1 -o master.wav

ファイル入力 ​

WASM の多くの API はデコード済み PCM サンプルを受け取ります。エンコード済みバイト列を使う場合、WAV/MP3 なら Audio.fromMemory(...) を使います。AAC、OGG、FLAC など同梱デコーダが読めない形式も扱いたい場合は、Audio.fromMemoryWithBrowserFallback(...) を使うと、必要に応じて AudioContext.decodeAudioData() によるブラウザ側デコードへ切り替わります。

typescript
async function analyzeFile(file: File) {
  await init();
  const audioCtx = new AudioContext();

  const arrayBuffer = await file.arrayBuffer();
  const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
  const samples = audioBuffer.getChannelData(0);

  return analyze(samples, audioBuffer.sampleRate);
}

// ファイル入力での使用
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const result = await analyzeFile(file);
  console.log(`BPM: ${result.bpm}`);
});

全チャンネルを保持する ​

Audio.fromMemory(...) はマルチチャンネルのファイルをデコードしながらモノラルに畳み込みます。チャンネルを保持するには decodeChannels でデコードします。

関数チャンネルの保持デコーダー戻り値
Audio.fromMemoryしない(モノラルに畳み込み)WASM 内の WAV/MP3Audio
Audio.fromMemoryWithBrowserFallbackしない(モノラルに畳み込み)WASM、次に decodeAudioDataPromise<Audio>
decodeChannelsするWASM 内の WAV/MP3{ sampleRate, channels }
decodeChannelsWithBrowserFallback(bytes, options?)するWASM、次に decodeAudioData(AAC、OGG、FLAC)Promise<{ sampleRate, channels }>
downmix(channels, targetLayout, options?)狭めるなしFloat32Array[]

channels は、元のチャンネル順(5.1 は L R C LFE Ls Rs)の Float32Array の配列です。downmix はこれを、targetLayout で指定したより狭いレイアウトへ折りたたみます。

targetLayout結果
0モノラル
1ステレオ
25.1
37.1

規則は ITU-R BS.775(センターとサラウンドを -3 dB、LFE は除外)で、アップミックスはできません。元のレイアウトはチャンネル数(1、2、6、8)から判断し、それ以外のチャンネル数はモノラルへの折りたたみ(単純平均)だけが可能です。折りたたみは正規化されないため、フルスケールに近いプレーンが複数あるソースはフルスケールを超えることがあります(1.0 の 6 プレーンはステレオ目標で 2.414 になります)。省略可能な第 3 引数 { normalize, includeLfe } で挙動を変えられます。normalize: true は最悪ケース(全入力がフルスケールで同相)でも 1 以下に収まるよう折りたたみをスケールします(ラウドネスを合わせるのではなくピークで割る方式です)。includeLfe: true は LFE プレーンを捨てずに -3 dB でフロントペアへ加えます。どちらも既定は false です。...WithBrowserFallback 系の関数はマルチチャンネルの AudioBuffer に同じ規則を適用するので、どちらのデコーダーが動いても同じサンプルになります。downmix は、32 ビット整数の範囲の整数でない targetLayout に RangeError を、数値でないものに TypeError を投げます。

typescript
import { decodeChannels, downmix, init } from '@libraz/libsonare';

await init();
const { sampleRate, channels } = decodeChannels(new Uint8Array(await file.arrayBuffer()));
const stereo = channels.length > 2 ? downmix(channels, 1) : channels;

クリップを OPFS に保存する ​

writeOpfsClip(path, channels) は、チャンネルごとの Float32Array を、ヘッダーなしのリトルエンディアン・インターリーブ float32 として Origin Private File System に書き込みます。OPFS のクリップページリーダーが読む形式と同じです。importOpfsClip(path, source) は ArrayBuffer、Blob、AudioBuffer を受け取り、全チャンネルを保ち、リサンプリングせずに元の sampleRate を返します。どちらも OPFS ワーカーで動き、開いているページプロバイダーが読んでいるパスへ書くと InvalidState を投げます。そのクリップをエンジンへページングする方法はリアルタイムとストリーミングを参照してください。

進捗レポート ​

typescript
import { init, analyzeWithProgress } from '@libraz/libsonare';

await init();

const result = analyzeWithProgress(samples, sampleRate, (progress, stage) => {
  const percent = Math.round(progress * 100);
  console.log(`${stage}: ${percent}%`);

  // UI を更新
  progressBar.style.width = `${percent}%`;
  statusText.textContent = stage;
});

キャンセル ​

時間のかかるオフラインの解析・マスタリング呼び出しは cancel コールバックを受け取ります。onProgress が進捗を報告するのと同じ境界でポーリングされるため、間違ったファイルを読み込んでしまったユーザーがレンダリングの完了を待つ必要はありません。true を返すと呼び出しは中断され、コード 8(Cancelled)の SonareError で例外になります。キャンセルされた呼び出しは出力を確保しません。途中結果を読むことはできないので、「マスターが途中まで出来た」ではなく「何も起きなかった」として扱ってください。完全な例は長い処理をキャンセルするを参照してください。

ステレオからモノラルへの変換 ​

typescript
async function getMonoSamples(audioBuffer: AudioBuffer): Promise<Float32Array> {
  if (audioBuffer.numberOfChannels === 1) {
    return audioBuffer.getChannelData(0);
  }

  // ステレオをモノラルにミックス。5.1 / 7.1 のソースには decodeChannels() と downmix() を使う。
  const left = audioBuffer.getChannelData(0);
  const right = audioBuffer.getChannelData(1);
  const mono = new Float32Array(left.length);

  for (let i = 0; i < left.length; i++) {
    mono[i] = (left[i] + right[i]) / 2;
  }

  return mono;
}

ブラウザ互換性 ​

同梱モジュールは WebAssembly SIMD でビルドされているため、最小バージョンは SIMD に対応したブラウザで決まります。

ブラウザ最小バージョン
Chrome91+
Edge91+
Firefox89+
Safari16.4+

要件: WebAssembly SIMD、BigInt、Web Audio API。JavaScript バンドルのターゲットは ECMAScript 2020 です。

パッケージの構成物 ​

公開パッケージには、連携するいくつかの構成物が含まれます。

  • メインモジュール: sonare.js と sonare.wasm。解析・マスタリング・ミキシング・編集の各 API を支える Emscripten ビルドです。
  • メイン API エントリ: パッケージの index(index.js / index.d.ts)は import ... from '@libraz/libsonare' を支える tsup バンドルです。解析・マスタリング・ミキシング・編集の全 API を公開します。
  • AudioWorklet エントリ: worklet.js / worklet.d.ts。独立した自己完結型の tsup バンドル(コード分割なし、AudioWorkletGlobalScope への移植を想定)で、SonareEngine API、worklet プロセッサクラスとその登録ヘルパー、SonareStreamAnalyzerNode(オーディオグラフの枝のライブ解析)、リングバッファプロトコル(engineMixerMasterTarget、ENGINE_MIXER_PARAM_FADER_DB、ENGINE_MIXER_PARAM_PAN、SONARE_METER_RING_PROTOCOL_VERSION を含む)を収録します。worklet レルムが独自の WASM インスタンスを初期化できるよう、メインエントリから init、isInitialized、EXPECTED_ABI_VERSION のみを再エクスポートします。再生用とミキサー用のプロセッサは、プロセッサオプションの wasmBinary でコンパイル済みモジュールを受け取り、worklet レルムにモジュールがなければ自分で初期化して、準備ができた時点で ready を送ります。ボイスチェンジャー、ミキサー、エンジン、再生のプロセッサは AudioWorkletGlobalScope のサンプルレートで動作します。異なる sampleRate オプションは RangeError で拒否され、worklet スコープの外ではこのオプションが必須です。共有メモリのリングはそれぞれ createSonare*RingBuffer ヘルパーで作り、worklet 側とメインスレッド側のコードは同じパッケージバージョンから読み込んでください(メーターリングはプロトコル 3、スペクトラム、スコープ、テレメトリのリングはプロトコル 1)。手でサイズを決めたバッファは、プロセッサの構築時に拒否されます。オーディオスレッドで起きた失敗は、クラスと SonareError のコードを保ったままメインスレッドに届きます。
  • 解析専用モジュール: @libraz/libsonare/analysis エントリの背後にある sonare-analysis.js / sonare-analysis.wasm(生のアセットは @libraz/libsonare/wasm/analysis)。マスタリング・ミキシング・リアルタイム・プロジェクトのバインディングを外してコンパイルしています。CI はサイズをレポートに記録しますが、増加だけでビルドを失敗させません。
  • オフライン Worker エントリ: @libraz/libsonare/worker の背後にある worker.js。OfflineWorkerClient の Worker 側です。
  • ボーカル編集 Worker エントリ: @libraz/libsonare/vocal-edit-worker の背後にある vocal_edit_worker.js。VocalEditWorkerClient の Worker 側です。
  • ボイスチェンジャーの JSON Schema: 2 つのプリセットスキーマが schemas/ 以下に同梱されるため、ホストは何も取得せずにプリセット文書を検証できます。

モジュールを自分でインスタンス化する場合 ​

sonare.js / sonare.wasm は、パッケージがラップしている Emscripten モジュールそのものです。通常のアプリケーションがこれに直接触れる必要はありません。@libraz/libsonare から import すればモジュールは初期化され、レルムごとに 1 インスタンスが保たれます。こちらがサポート対象の入口です。独自ローダー、非標準のバンドル先、ヒープを自前で管理するホストなど、モジュールを自分でインスタンス化する場合にかぎり、知っておくべき入力形式がひとつあります。

マスタリングチェーンのバインディングはフラット化済みの形式だけを受け取ります

モジュールのマスタリングチェーンのエントリが受け付ける設定形式はひとつだけです。コア自身のパラメータパーサが読む、フラット化されたパラメータ列です。代わりにネストした設定オブジェクトを渡すと、呼び出しは名指しで拒否されます。認識できた部分だけを適用して残りを落とすのではなく、読めない設定を明示したエラーが返ります。

npm パッケージの利用には影響しません。 masteringChain と masterAudio は、ネストした MasteringChainConfig を(リペア段や denoise の設定も含めて)モジュールへ渡す前にフラット化します。したがって @libraz/libsonare から import しているコードは、JavaScript API の記述どおりネストした設定を渡し続けられます。該当するのは、モジュールを直接インスタンス化してネストしたオブジェクトをバインディングにそのまま渡す呼び出し側だけで、その場合も得られるのは、一部だけ適用されたチェーンではなく明確なエラーです。

バンドルサイズ ​

このサイズ表が対象とするのはメインモジュールとメイン API エントリです。解析専用モジュール、realtime ランタイム、worklet バンドルは別の構成物で、ここには記載していません。解析専用モジュールは、マスタリング・ミキシング・リアルタイム・プロジェクトの各機能を含まない分、sonare.wasm よりかなり小さくなります。

ファイルサイズGzip
sonare.js~96 KB~17 KB
index.js~595 KB~118 KB
sonare.wasm~7,808 KB~2,656 KB
合計~8,500 KB~2,792 KB

トラブルシューティング ​

AudioContext が許可されない ​

モダンブラウザは AudioContext を作成する前にユーザー操作が必要:

typescript
document.addEventListener('click', async () => {
  const audioCtx = new AudioContext();
  await audioCtx.resume();
});

クロスオリジンの問題 ​

他のドメインからオーディオを読み込む場合:

typescript
const response = await fetch(url, {
  mode: 'cors',
  credentials: 'omit'
});

メモリの問題 ​

非常に長いオーディオファイルの場合、チャンクで解析を検討:

typescript
const CHUNK_DURATION = 60; // 秒

for (let start = 0; start < totalDuration; start += CHUNK_DURATION) {
  const chunk = samples.slice(
    start * sampleRate,
    (start + CHUNK_DURATION) * sampleRate
  );
  // チャンクを解析
}

どのエラーが投げられるか ​

ライブラリが起こす失敗は、数値の code と codeName を持つ構造化された SonareError です。拒否された引数(範囲外の値、未知の ID や名前、型の誤り)は素の RangeError か TypeError になるので、SonareError のハンドラーでは捕捉できません。先行する設定や解放済みのオブジェクトが原因の失敗は、コード付きの InvalidState です。クラスの表、失敗ごとの対応表、3 つのクラスをすべて扱う例はエラーハンドリングにあります。ほかのバインディングでの同じ失敗はエラー契約にまとめています。JavaScript とは別のツリーからビルドされた .wasm は、init() が AbiMismatch(コード 10)を投げます。よくある原因はインストールを参照してください。