Skip to content

ミキシングエンジン

ミキシングは、別々の複数トラック(ボーカル、ベース、ドラム、音楽ベッドなど)の音量バランスを取り、1 つのステレオ出力へまとめる工程です。

libsonare には、マスタリングプロセッサと同じ C++ DSP コアの上に、リアルタイム処理を意識したミキシング/ルーティングエンジンが備わっています。DAW ホストを組み込まずに、自分のアプリの中に小さなミキサーを作れます。

ストリップバスセンドフェーダーパンオートメーション という言葉に馴染みがなければ、先に ミキシングの基礎 を読んでください。本ページはこれらの意味を理解している前提で、libsonare がそれをどう実装しているか何を呼べば目的を達成できるかに集中します。

最初に押さえる 3 語

ストリップは 1 トラック分の処理列、バスは複数トラックをまとめる行き先、センドは元の音を残したまま別のバスへ分岐する送り量です。この 3 つが分かると、フェーダー、パン、リバーブ送り、メーターの位置関係を追いやすくなります。

パイプライン内でのミキシングの位置

工程役割
解析トラックが「何か」を調べる
編集1 トラックのタイミングやピッチを直す
ミキシング複数トラックをステレオバスへまとめる
マスタリング仕上がったステレオミックスを配信向けに磨く

ミキシングは「ステムのフォルダ」を「1 曲」に変える工程です。通常はミックスしてから、その結果をマスタリングします。

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

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

  • 一括ステムレンダーには mixStereo、状態を持つルーティングには Mixer.fromSceneJson を選べる。
  • トリム、極性、ディレイ、EQ、インサート、フェーダー、パン、幅、センド、メーターを通る信号の順番を追える。
  • プリフェーダー/ポストフェーダーセンドの違いを理解し、典型的なルーティングミスを避けられる。
  • リアルタイム制約を壊さずにオートメーションをスケジュールし、メーターを読める。
  • どの時点でフィールド別リファレンスの ミキシングシーン JSON へ進むべきか判断できる。

正しい入口を選ぶ

エンジンは意図的に 2 段階に分かれています。まず上の行から検討し、追加の制御が本当に必要になったときだけ下の行へ進んでください。

あなたの状況使う API理由
少数のステレオステムを 1 ファイルにまとめたいmixStereo(...)1 回の呼び出し、配列だけ、後始末も不要
センド・バス・インサート・オートメーション・メーター・保存可能なプロジェクトが必要Mixer.fromSceneJson(...)ミキサー状態を保持し、シリアライズできる
ブラウザの AudioWorklet や音声コールバック内で使うWASM Mixer.createRealtimeBuffer()バッファを再利用し、ブロックごとに確保しない

1 つのエンジン、すべての実行環境

同じミキサーエンジンが WASM/JS、Node ネイティブ、Python、C ABI、CLI から使えます。名前は各言語の慣習に従います(mixStereomix_stereofromSceneJsonfrom_scene_json)が、ルーティンググラフ・シーン JSON・DSP は同一です。CLI は永続的な Mixer オブジェクトではなく、シーンのレンダー入口を提供します。実行環境ごとの一覧は バインディング対応表 を参照してください。

チャンネルストリップを信号順にたどる

チャンネルストリップは 1 本のトラックレーンです。ストリップが音声を処理する順序を理解することが、各コントロールの効果を予測する鍵になります。たとえば、なぜポストフェーダーセンドはフェーダーの後に続き、プリフェーダーセンドはそうならないのか、が分かります。

ENGINE · LANE MIXERIDLE
エンジンのレーンミキサー — 再生エンジン内のフェーダーとミュート

3 つの MIDI クリップがリアルタイムエンジンでループします。各トラックはレーンを 1 つ占有し、専用のチャンネルストリップを持ちます。フェーダーはストリップのセッターを、ミュートは setSoloMute を呼びます。下の各バンドはエンジンの実際のレーン別出力で、操作のたびに renderOffline で描き直されます。

リードのフェーダー
0 dB
ベースのフェーダー
0 dB
ドラムのフェーダー
0 dB
リードをミュート
ベースをミュート
ドラムをミュート

libsonare は各ブロックを次の順序で処理します。

上から順に読むと次のとおりです。

  1. 入力トリム — 何よりも先の純粋なゲイン段で、作業しやすいレベルを整えるために使います(ゲインステージング参照)。フェーダーとは別物で、トリムは「入口でレベルを整える」、フェーダーは「他トラックとのバランスを取る」ためのものです。
  2. 極性反転 — 左/右チャンネルの符号を反転します。別トラックと位相が逆に録れたトラックの補正に使います。
  3. チャンネルディレイ — ストリップごとのサンプル遅延です。トラックの時間整合に使うと同時に、エンジンのディレイ補償にも寄与します。
  4. EQ — 内蔵のパラメトリック EQ。既定ではプリフェーダー (フェーダーは EQ 後の信号を扱う)ですが、ポストフェーダーへ移動できます。
  5. プリフェーダーインサート — フェーダーよりで動く、あなたが指定したプロセッサ(コンプレッサー、ディエッサーなど)。
  6. プリフェーダータッププリフェーダーセンドとプリフェーダーメーターが読み取る地点です。フェーダーより前なので、フェーダーを動かしてもプリフェーダーセンドのレベルは変わりません
  7. フェーダー(+ VCA オフセット) — メインのレベル制御。VCA グループ(複数ストリップを一括で調整する 1 本のフェーダー)のオフセットがここで加算されるため、1 つのグループフェーダーで多数のストリップを、音声を再ルーティングせずに一括調整できます。
  8. パン — ストリップのパンモードとパンローに従ってステレオフィールド内に配置します。
  9. ポストフェーダーインサート (EQ をポストフェーダーへ移した場合は EQ も)— ポストフェーダーのレベルに反応すべきプロセッサ。
  10. ステレオ幅 — サイド信号を狭めたり広げたりします(モノラル互換性参照)。
  11. ポストフェーダータップ + ゴニオメーターポストフェーダーセンド、出力メーター、ゴニオメーター履歴バッファへ供給します。

プリフェーダーとポストフェーダーは見た目だけの違いではない

ポストフェーダーセンドはフェーダーに追従します。フェーダーを下げると、そのトラックから送ったリバーブも一緒に下がり、ドライ信号との比率が保たれます。プリフェーダーセンドはフェーダーから独立しており、ヘッドホン/キューミックスや完全ウェットなエフェクトリターンに向きます。ここを誤るのが最も多いルーティングミスです。

MIXER · SENDSIDLE
プリフェーダー送りとポストフェーダー送り — フェーダーを動かす

1 つのチャンネルが 2 つの送りとメイン出力に分かれます。チャンネルフェーダーを下げると、ポストフェーダー送りとメイン出力はそれに従います。フェーダーの後ろで分岐しているからです。プリフェーダー送りはフェーダーの前で分岐しているので、そのまま残ります。再生すると、ドライ音(ポストフェーダー)は消えていくのに、プリフェーダー送り(リバーブ/アックスのリターンの代役)はフェーダーを下げきっても鳴り続けます。プリフェーダーに送ったボーカルのリバーブが、ボーカルを下げても消えないのはこのためです。よくある送りの落とし穴です。

チャンネルフェーダー
0 dB
コードとの対応

上記の順序は ChannelStrip::process_segment そのものです。

text
input_trim -> 極性 -> alignment_delay -> eq(pre) -> プリインサート
  -> [pre タップ] -> fader(+VCA) -> panner -> eq(post)
  -> ポストインサート -> width -> [ゴニオメーター + post タップ]

pre/post タップは事前確保したスクラッチバッファなので、センドが音声スレッドで確保することはありません。スケジュールされた各オートメーションイベント(フェーダー、パン、幅、センド、インサート)はこの同じセグメントループ内のサンプル位置で適用されます。これがオートメーションをサンプル精度にしている仕組みです。

一括ミックス: mixStereo

「ステムが数本あるので 1 ステレオファイルにしたい」なら、mixStereo だけで完結します。トラックごとの左/右チャンネルと、設定値の並列配列を受け取り、レンダリング済みマスターとトラックごとのメーターを返します。

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

await init();

const mix = mixStereo([vocalL, musicL], [vocalR, musicR], sampleRate, {
  inputTrimDb: [3, 0],     // 小さいボーカルをフェーダー前に持ち上げる
  faderDb: [-3, -12],      // バランス: ボーカルを前に、音楽を後ろに
  pan: [0, -0.2],          // 音楽をやや左へ
  width: [1, 0.9],         // モノラル安全のため音楽を少し狭める
  // panMode / muted は任意。これらもトラックごとの配列を受け付ける
});

// mix.left, mix.right  -> Float32Array マスター
// mix.meters[i]         -> トラックごとの MixMeterSnapshot(「メーター」参照)
python
import libsonare as sonare

mix = sonare.mix_stereo(
    [(vocal_l, vocal_r), (music_l, music_r)],
    sample_rate=48000,
    input_trim_db=[3, 0],   # 小さいボーカルをフェーダー前に持ち上げる
    fader_db=[-3, -12],     # バランス: ボーカルを前に、音楽を後ろに
    pan=[0, -0.2],          # 音楽をやや左へ
    width=[1, 0.9],         # モノラル安全のため音楽を少し狭める
)

# mix.left, mix.right -> マスターサンプル
# mix.meters[i]        -> トラックごとの MixMeterSnapshot(「メーター」参照)
bash
sonare mix \
  --preset vocalReverbSend \
  --input vocal.wav \
  --input music.wav \
  --sample-rate 48000 \
  -o master.wav

スカラーかトラックごとか

JavaScript の mixStereo オプションは、単一値(全トラックに適用)か配列(トラックごと)のどちらも受け付けます。faderDb: -3 は全体を 3 dB 下げ、faderDb: [-3, -12] は各トラックを個別に設定します。

Python の mix_stereo(...) では、fader_dbpanwidthmutedinput_trim_db はトラックごとの sequence を渡します。pan_mode は全トラック共通の 1 値またはトラックごとの sequence のどちらも使えます。

mixStereo はオフラインのユーティリティ、バッチスクリプト、ファイル出力までの最短経路に使います。リバーブセンド・サブグループ・オートメーション・保存可能なプロジェクトが必要になった時点で、シーンベースの Mixer へ移行してください。

シーンベースのミックス: Mixer

シーンは、ミキサー全体(ストリップ、そのインサートとセンド、バス、VCA グループ、それらの接続)を記述したプレーンな JSON です。Mixer.fromSceneJson(...) はその記述をルーティンググラフへコンパイルし、ブロック単位で処理できるようにします。

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

await init();

mixingScenePresetNames();                                  // ['vocalReverbSend', 'drumBusSubgroup', 'commentaryDucking']
const sceneJson = mixingScenePresetJson('vocalReverbSend'); // 編集可能な出発点

const mixer = Mixer.fromSceneJson(sceneJson, sampleRate, /* blockSize */ 512);
try {
  // ストリップごとのステレオ音声を 1 ブロック渡し、ルーティング済みステレオマスターを得る
  const out = mixer.processStereo(
    [vocalBlockL, returnBlockL],
    [vocalBlockR, returnBlockR],
  );

  const vocalMeter = mixer.stripMeter(0, 'postFader');     // 「メーター」参照
} finally {
  mixer.delete();   // WASM ハンドルは GC されない — 必ず解放する
}
python
import libsonare as sonare

sonare.mixing_scene_preset_names()                          # ['vocalReverbSend', 'drumBusSubgroup', 'commentaryDucking']
scene_json = sonare.mixing_scene_preset_json("vocalReverbSend")

mixer = sonare.Mixer.from_scene_json(scene_json, sample_rate=48000, block_size=512)
block = mixer.process_stereo(
    [vocal_block_l, return_block_l],
    [vocal_block_r, return_block_r],
)
vocal_meter = mixer.strip_meter(0, tap="postFader")         # 「メーター」参照
mixer.close()                                               # ネイティブハンドルを解放
bash
# ストリップごとの WAV 入力を組み込みシーンプリセットでレンダリング(ストリップごとに --input を 1 つ)
sonare mix --preset vocalReverbSend \
  --input vocal.wav --input reverb-return.wav \
  --sample-rate 48000 -o master.wav

# toSceneJson()/to_scene_json() で保存したシーン JSON ファイルを読み込むこともできます
sonare mix --scene my-scene.json --input vocal.wav --input reverb-return.wav -o master.wav

ミキサーは必ず解放する

Mixer はすべての embind オブジェクトと同様、JavaScript の GC では回収できない WASM ヒープハンドルを保持します。finally ブロックで mixer.delete()(エイリアスの destroy() も可。どちらの名前も WASM / Node 両バインディングで使えます)を呼んでください。ハンドルをリークすると、長時間のセッションで WASM メモリが徐々に枯渇します。

シーンスキーマの全体、各フィールド、注釈つきのプリセット JSON は ミキシングシーン JSON にあります。3 つの組み込みプリセット(vocalReverbSenddrumBusSubgroupcommentaryDucking)はブラックボックスではありません。読み込んで編集し、toSceneJson()(Python では to_scene_json())で再シリアライズすれば、実例で形式を学べます。

シーン読み込み後は sceneWarnings() を確認する

シーンを読み込むと、各インサートの params が監査されます。どのプロセッサも読まないキー(たいていはタイプミスか、別のプロセッサ向けのキー)は非致命的な警告として収集され、mixer.sceneWarnings()(Python は mixer.scene_warnings())で読み出せます。シーン自体は読み込まれ、該当キーは単に効果を持ちません。fromSceneJson(...) の直後に警告を読むのが、黙って「何もしないつまみ」を見つける最も安上がりな方法です。インサートが受け付けるキーは masteringInsertParamNames(name) で列挙できます。

インサートとセンド

これらはストリップがプロセッサを使う 2 つの方法で、それぞれ別の問いに答えます。

インサートセンド
信号経路直列 — 信号が通過する並列 — コピーをバスへ送る
典型用途トラック自身へのコンプ・EQ・ディエッサー複数トラックが共有するリバーブ/ディレイ
ウェット/ドライプロセッサ自身の mix で制御ドライはストリップに残り、送ったコピーだけ処理される
タップ位置該当フェーダー段の後プリ/ポストフェーダーを選べる

1 本のボーカルだけにかけたいリバーブはインサートで構いません。ボーカル・スネア・ギターで共有したいリバーブは、AUX バスへのセンドとし、そのリターンにリバーブを 1 つ置きます — リバーブは 1 インスタンス、ソースは複数、空間は一貫します。

バス、ロール、ルーティンググラフ

バスは共有の行き先です。ストリップはバスへ、バスは別のバスへ接続し、1 つのバスが master になります。各バスは role を持ちます。

ロール意味
master最終ステレオ出力。すべての信号が最終的にここへ届く。
auxセンドの並列行き先。通常はエフェクトリターン(リバーブ、ディレイ)。
submixマスターの前にまとめて処理するストリップ群(「ドラムバス」)。

特別なトークンは masteraux だけで、それ以外のロール文字列(submixsubgroupgroup など)はマスター以外の一般的なバスとして扱われます。組み込みの drumBusSubgroup プリセットはドラムバスに subgroup というラベルを付けているため、シーンを出力すると "role": "submix" ではなく "role": "subgroup" が表示されることがあります。

接続はグラフを形成します。Mixer.fromSceneJson はミキサー構築時にそのグラフをビルドしてコンパイルするため、返されたミキサーはすぐに処理できます。構築後は mixer.addBus(id, role?)role の既定は 'aux')と mixer.removeBus(id) でバスを追加・削除でき、mixer.busCount() で現在の本数を取得できます。トポロジーを変更するとグラフはダーティになり、次の processStereo 呼び出しで遅延再コンパイルされます(compile() を呼べば即時に再コンパイルできます)。

トポロジー変更をした後は、次のタイミングが重要なブロックの前に compile() を呼んでください。トポロジー変更とは、たとえば次のような変更です。

  • バスの追加/削除
  • センドの追加/削除

パラメータ変更、オートメーション変更、および VCA グループの変更(追加・削除・ゲイン調整)には再コンパイルは不要です。VCA グループはメンバーストリップへのライブなゲインオフセット調整のみで、ルーティンググラフの一部ではありません。

strip の指定方法はランタイムで異なります。

ランタイムstrip の指定方法
WASMmixer 制御メソッドは数値のストリップインデックスを使います。シーン ID から指定したい場合は、先に stripById(id) でインデックスを取得します。
Node ネイティブ / Python多くの制御メソッドで、数値インデックスとストリップ ID 文字列の両方を受け取ります。

センドとバスの削除

ストリップのセンドは追加順のインデックスで指定します。removeSend(strip, sendIndex) で 1 つ削除すると、それより後ろのセンドはすべてインデックスが 1 つ前へ詰まります。削除した位置より大きいインデックスをキャッシュしていた場合は 1 つずれるので、削除後はインデックスを取得し直してください(ルーティングされた結果を読む前に再コンパイルまたは処理を行い、グラフを再構築させます)。

バスを削除すると、そのバスを指していたルーティングも整理されます。宛先がそのバスだったセンドは、送り先が存在しなくなるため破棄されます。フィードしていたバスを削除した後は、そのストリップのセンドを確認してください。

どこにも届かないバス

シーンをコンパイル(または処理)すると、明示的な submix または aux バスから master への経路がない場合に致命的でない警告が出ます。グラフ自体は動きますが、master へ届かないバスは出力に何も寄与しません。多くは意図的な行き止まりではなく、接続の付け忘れです。この警告は、追加した各バスが実際に master まで配線されているかを確認する合図と捉えてください。

VCA グループ

VCA グループは、複数ストリップの音声を再ルーティングせずにレベルだけをまとめて調整する 1 本のフェーダーです。VCA は voltage-controlled amplifier(電圧制御アンプ)の略で、歴史的には 1 つの制御電圧で複数のコンソールチャンネルを一括して増減していました。ここではそれをソフトウェアで再現したもので、音声を再ルーティングせずに 1 本のフェーダーでグループをオフセットします。「drums」VCA を 2 dB 下げると、キック・スネア・オーバーヘッドがそれぞれのバスへ流れたまま 3 つとも 2 dB 下がります。

グループの gainDb は、各メンバーがすでに持つフェーダー値に対する差分として、各メンバーのフェーダー段(上記ステップ 7)に加算されます。差分だけが適用されるため、グループ内でストリップごとに設定したフェーダーの微調整は失われません。グループフェーダーは個別のバランスを上書きせずに、セット全体を一括で上下できます。

ストリップのグループとグループゲインは addVcaGroup(id, gainDb, members) で追加し、setVcaGroupGainDb(...) で調整し、removeVcaGroup(id) で削除します。このグループ定義(ゲインとメンバー構成)はシーン JSON を往復します。setVcaOffsetDb(...) による調整は、セッション中に変更できるストリップごとのライブなオフセットです。これはストリップの vcaOffsetDb として保存され、シーン JSON を往復します。

ソロとミュートのロジック

  • setMuted(strip, true) はストリップを無音にします。
  • setSoloed(strip, true) は他のすべてのストリップを暗黙ミュートします — ただし setSoloSafe(...)ソロセーフに指定したものは除きます。エフェクトリターンのストリップをソロセーフにしておくと、ボーカルをソロにしてもそのリバーブリターンが聞こえます。

ソロ・ミュート・ソロセーフは、グラフ再コンパイルなしで次のブロックから有効になります。

パンモードとパンロー

パンモードは位置を左右へ どうマッピングするかパンローは中央をサイドに対してどれだけの音量にするかです。

  • balance — すでにステレオの素材向け。像を動かすのではなく片側を下げます。
  • stereoPan — ほぼモノラルのソースをフィールド内で動かす本来のパン。
  • dualPan — 左右独立の位置(setDualPan(...) で設定)。広いステレオトラックを内側へ畳むなど。

ストリップは setChannelDelaySamples(...) で自身の信号をサンプル単位で遅延させることもできます。パンに触れずに、近接マイクとルームマイクの時間合わせや、パラレル経路の補正を行うためのストリップ単位のアライメント調整です。

永続的な Mixer は、ストリップ内の他の段もライブで操作できます。いずれもグラフの再コンパイル不要のパラメータ変更で、次のブロックから有効になります。

  • setInputTrimDb(strip, db) — 信号フローの入力トリム段(mixStereoinputTrimDb オプションと同じゲイン)。フェーダーとは別物です。
  • setWidth(strip, width)ステレオ幅段(mixStereowidth オプション)。0 = モノラル、1 = 原音、1 より大きい値で広げます。
  • setPolarityInvert(strip, invertLeft, invertRight)極性反転段。ストリップの左/右チャンネルの符号を反転します。

JavaScript API のパンローは const3dBconst4.5dBconst6dBlinear0dB です。Python では同じ値を enum/int、または const-3dblinear-0db のような正規化文字列で指定します。

各パンローは中央を一定量だけ下げます。これにより、中央へパンした音が左右いっぱいへパンした音より大きくならないようにします。

パンロー中央の減衰用途
const3dB−3 dB多くの素材(既定)
const4.5dB−4.5 dBコンソールでよく使われる中間的な選択
const6dB−6 dBすでにモノラルで、持ち上げず中央に定位させたいソース
linear0dB0 dB知覚音量ではなく合算(モノラル)レベルを一定に保つ

定パワー則(3 / 4.5 / 6 dB)はパンしても知覚音量を一定に保ち、linear0dB は代わりに合算レベルを一定に保ちます。

サラウンドとマルチチャンネル

ステレオより広いバス向けに、ストリップは SurroundPan 位置(setSurroundPan(strip, { azimuth, divergence, lfe })、Python は set_surround_pan(strip, azimuth=..., divergence=..., lfe=...))を保持します。フェーズ 1 では azimuth(−180…180°、0 = 正面中央)・divergence(0 = 点音源、1 = 前方へ広がる)・lfe(0…1 の LFE プレーンへの送り)が有効で、elevationdistance は予約です。位置はシーンに保存され JSON で往復しますが、オフラインの Mixer は依然ステレオでレンダリングします。これらの値を消費するサラウンドパンナーはリアルタイムエンジンのサラウンドグループバスで動くため、ここで位置を設定し、サラウンドミックスはエンジンでレンダリングしてください。

オートメーション

時間変化するコントロールはすべて、最初の processStereo 呼び出しからの絶対サンプル位置でスケジュールします(再コンパイルでクロックは 0 に戻ります)。利用できるレーンは次のとおりです。

typescript
mixer.scheduleFaderAutomation(stripIndex, sampleRate * 8,  -6, 's-curve');   // 8 秒でボーカルを下げる
mixer.schedulePanAutomation(stripIndex, sampleRate * 12,  0.3, 'linear');
mixer.scheduleWidthAutomation(stripIndex, sampleRate * 12, 1.2, 'linear');
mixer.scheduleSendAutomation(stripIndex, sendIndex, sampleRate * 16, -12, 'hold');
mixer.scheduleInsertAutomation(stripIndex, insertIndex, paramId, sampleRate * 4, value, 'exponential');

補間カーブはイベント間の動き方を決めます。

カーブ用途
linear直線ランプ一般的なレベル/パンの移動
exponential速→遅自然に聞こえるフェード
s-curve入りと出の両方をなめらかにクリックのない滑らかな遷移
holdランプなしで跳ぶ段階的/即時の変化

インサートパラメータのオートメーション

scheduleInsertAutomation はインサートを結合シーケンス [プリインサート…, ポストインサート…] 内のインデックスで指定し、paramId はプロセッサ固有です。声の下でコンプのスレッショルドを動かしてダッキングするのが定番ですが、サイドチェインダッキングは commentaryDucking プリセットが配線済みです。

メーター

各ストリップ(およびマスター)は豊富な MixMeterSnapshot を公開します。mixStereometers[] からレンダー後に、または mixer.meterTap(strip, 'preFader' | 'postFader') でライブに読み取れます。stripMeter(...) は WASM/Python では便利な別名、Node ネイティブではポストフェーダーの簡易入口です。

フィールド意味
peakDbL / peakDbRチャンネルごとのサンプルピーク
truePeakDbL / truePeakDbR / maxTruePeakDbサンプル間 トゥルーピーク — DAC が実際に再構成する値
rmsDbL / rmsDbR短時間の平均レベル
momentaryLufs / shortTermLufs / integratedLufs400 ms / 3 s / 全体の ラウドネス
correlation−1…+1 の位相相関。+1 付近はモノラル安全、負は打ち消しの警告
monoCompatWidth / monoCompatPeak / monoCompatSideRms / likelyMonoCompatibleモノラル互換性の要約
gainReductionDbストリップ上のダイナミクスがどれだけ働いているか
seq変化検出用の単調増加スナップショットカウンター
METERS · LOUDNESSIDLE
ラウドネス計測 — LUFS・トゥルーピーク・レンジ

バーは再生中の瞬時ラウドネスを追い、パネルは時間ごとのラウドネスです。インテグレーテッド LUFS は全体を表す一つの数値、トゥルーピークはサンプル間も含む本当の上限、LRA はラウドネスの動く幅を表します。ウィンドウを切り替えると、速い瞬時メーターと滑らかな短時間メーターを比べられます。

ウィンドウ

ゴニオメーターとは

ゴニオメーター(ベクトルスコープとも呼びます)は、左信号と右信号を点群として描く表示です。ステレオ像を一目で示します。ほぼ垂直な線はモノラル、横に広がった塊は広いステレオ展開、横方向ににじむ表示はモノラルで打ち消し合いかねない位相の問題を警告します。correlation メーターを絵にしたものです。

ゴニオメーターは別の時間領域ビューです。readGoniometerLatest(strip, maxPoints) が直近の左/右サンプル対(古い→新しい)を返し、ステレオベクトルスコープを描画できます。latest read 経路は割り当てなしなので、UI からのポーリングで音声スレッドに余計な負荷を増やしません。

リアルタイムと AudioWorklet ブリッジ

ミキサーコアは予測可能な音声コールバックのために作られています。デノーマル対策、ロックフリーなパラメータ変更、事前確保された状態、グラフ単位のプラグインディレイ補償を備えます。

WASM ラッパーの processStereo は、呼び出すたびに結果用の配列を新たに確保します。オフラインでは問題ありませんが、リアルタイムの音声コールバック内では禁じられます。そうしたタイトなループでは、代わりに次のいずれかの確保なし経路を使ってください。

  • processStereoInto(inL, inR, outL, outR) — 呼び出し側が所有する配列へ書き込みます。
  • createRealtimeBuffer() — 再利用可能な WASM ヒープ上の入出力ビューを返します。入力を埋め、process() を呼び、outLeft/outRight を読む、を繰り返します。ビューはミキサーが所有し、delete() 後は無効になります。

ホストがストリップ入力の供給を止めても、先読み・リバーブ・ディレイの各プロセッサにはまだ音声が残っています。mixer.tailSamples() でコンパイル済みグラフ内の最大プロセッサテイル長(サンプル数)を読み取り、mixer.drainTailStereo(numSamples) でゼロ入力ブロックをレンダリングして、そのテイルをマスターへ吐き出させます。drainTailStereo は 1 ブロックをレンダリングするだけでループはしません。numSamples はミキサー構築時のブロックサイズが上限なので、tailSamples() 分のサンプルを取り切るまでブロックサイズ単位で繰り返してください。

typescript
let remaining = mixer.tailSamples();
while (remaining > 0) {
  const n = Math.min(remaining, blockSize);
  const tail = mixer.drainTailStereo(n);   // ゼロ入力ブロックを 1 つ
  exportBlock(tail.left, tail.right);
  remaining -= n;
}
デノーマル対策とは?

デノーマル(非正規化数)は、ゼロに極めて近い非常に小さな浮動小数点数で、多くの CPU では通常の値よりはるかに遅く処理されます。

音声コールバックでは、リバーブやディレイのテイルが減衰してサンプルがゼロに近づくときにこの範囲へ入り込み、処理時間が急に跳ね上がってドロップアウトを招くことがあります。デノーマル対策はこうした極小値をゼロに切り捨て、各ブロックの処理時間を予測可能に保ちます。

トポロジー変更はリアルタイム安全ではない

バス・センドの追加はグラフを dirty にし、次の processStereo で再コンパイル(確保が起こりうる)します。構造変更はセットアップ中か重要でないブロックで行い、compile() を呼んでから、タイトなレンダーループに入ってください。フェーダー/パン/センド/インサートの操作、オートメーション、および VCA グループの変更はループ内で問題ありません。

レイテンシとプラグインディレイ補償 (PDC)

先読み処理(たとえばドラムバス上のリミッター)はレイテンシを足します。グラフのある経路だけ遅延し別経路が遅延しないと、両者はマスターでずれて到達します。

エンジンは各経路のレイテンシを測定し、短い経路を長い経路に合わせて遅延させることで、マスターですべてを揃えます。これがプラグインディレイ補償です。ストリップごとの channelDelaySamples も同じ計算に入るため、変更後は再コンパイルして PDC を再実行してください。

レシピ

数本のステムをバランスしてバウンス

最速のミックス。バスもオートメーションもなし — トリム、バランス、レンダリングだけ。

typescript
const mix = mixStereo(lefts, rights, sampleRate, {
  inputTrimDb: trims,   // 各ステムを妥当な作業レベルへ
  faderDb: balances,    // 互いのバランスを取る
  pan: positions,
});
exportWav(mix.left, mix.right, mix.sampleRate);
AUX センドで共有するボーカルリバーブ

リバーブ 1 つを、ポストフェーダーセンドで送り、専用ストリップで返す — vocalReverbSend プリセットの縮小版です。

typescript
const mixer = Mixer.fromSceneJson(mixingScenePresetJson('vocalReverbSend'), sampleRate, 512);
// strip 0 = "vocal"(EQ + コンプのインサート、"vocal-verb" AUX へのポストフェーダーセンド)
// strip 1 = "vocal-verb-return"(プレートリバーブのインサート、マスターへ返す)
mixer.setSendDb(0, 0, -10);          // リバーブを増やす
mixer.compile();                      // トポロジー編集後のみ必要

センドはポストフェーダーなので、ボーカルフェーダーを下げるオートメーションはリバーブも一緒に下げます。

パラレルコンプつきドラムサブグループ

キック/スネア/オーバーヘッドを 1 つの submix バスへ送り、パラレルコンプとテープでまとめ、VCA で一括調整する — drumBusSubgroup プリセットです。

typescript
const mixer = Mixer.fromSceneJson(mixingScenePresetJson('drumBusSubgroup'), sampleRate, 512);
mixer.setVcaOffsetDb(/* ドラムメンバーのストリップ */ 0, -1.5);  // キット全体をライブで調整
ポッドキャスト/実況のダッキング

サイドチェインで音楽ベッドを話声の下に自動で下げます。commentaryDucking プリセットは host ストリップをキーにした dynamics.sidechainRouter をベッドに配置します。

typescript
const mixer = Mixer.fromSceneJson(mixingScenePresetJson('commentaryDucking'), sampleRate, 512);
// host + guest は "voices" として VCA グループ化。music-bed ストリップは host が話すたびにダッキングする

関連