空音開発Kuon R&D
初めての方は DSDファイル完全ガイド をご覧ください。本ページは技術深堀り向けです。

KUON DSD の仕組み

世界初のブラウザDSDプレーヤー。Rustで実装したWasm音声エンジン。

DSD とは

DSD(ダイレクト・ストリーム・デジタル)は、Super Audio CD(SACD)に採用された1ビット・超高速シグマデルタ変調フォーマットです。PCMのように離散的なサンプルを保存するのではなく、DSDは非常に高いサンプリング周波数での連続ビットストリームを保存します。

DSD サンプリング周波数:

  • DSD64 — 2.8224 MHz (CD周波数の約64倍)
  • DSD128 — 5.6448 MHz (CD周波数の約128倍)
  • DSD256 — 11.2896 MHz (CD周波数の約256倍)

チャレンジ: ブラウザでDSDを再生することが「不可能」な理由

ブラウザのオーディオAPIはPCM形式向けに設計されており、DSDビットストリーム向けではありません。ブラウザでDSDを再生する際には、いくつかの障壁が現れます:

1.

ネイティブDSDサポートなし — Web Audio APIはPCM(Float32Array)のみを受け入れる

2.

ファイルサイズ: DSDアルバムはギガバイト単位(DSD256以上はさらに大きい)

3.

JavaScriptはMHz周波数のDSP処理に対して、特殊な加速なしでは遅すぎる

4.

フィルター設計: デシメーションフィルターは70dB以上のエイリアシング成分を抑制する必要がある

DSF & DSDIFF: バイナリファイルフォーマット

DSDオーディオは2つのコンテナフォーマットで提供されます。私たちのWasmエンジンは両方をサポートする必要があります:

DSF(DSD Stream File)

シンプルで直線的なフォーマット: DSD Chunk → fmt Chunk → data Chunk. オフセットは固定。

DSDIFF(フィリップス "DSD Interchange File Format")

ネストされたチャンク構造: FRM8(IFF形式64ビットコンテナ) → DSD → PROP → CHNL → CMPR → DSDデータ。

fmt Chunkの重要なバイトオフセット:

  • +24: オーディオチャンネル数
  • +28: DSDサンプルレートコード
  • +32: サンプルあたりのビット数(DSDは常に8)

Rust WebAssemblyエンジン: 核となるイノベーション

DSDをブラウザで動作させるための秘訣は、コンパイルされたRustコードです。理由は以下の通りです:

ゼロコストな抽象化 — オーディオ処理中にガベージコレクションが一時停止しない

SIMD的な最適化 — コンパイラはバイトレベルの演算をベクトル化できる

FIR畳み込みでJavaScriptより10倍高速(ベンチマーク結果)

コンパイルパイプライン

wasm-pack build --target web

出力: .wasmバイナリ(~50KB)、JavaScriptグルーコード、TypeScript定義

Signal Flow Architecture

DSD Bitstream
Byte LUT
FIR Filter
Decimation
PCM Output

バイトレベル ルックアップテーブル(LUT): 超高速ビット抽出

DSDはサンプルあたり8ビットです。つまり、各バイトは8個のシグマデルタビットをエンコードします。単純なアプローチでは、すべてのバイトの8ビットすべてをループし、それらのフロート値を計算します。これは遅い。

ソリューション: 256エントリのテーブルを事前計算します。各インデックス(0–255)は直接8個のフロート値にマップされます:

LUT[0x55] = [+1.0, +1.0, -1.0, +1.0, +1.0, -1.0, +1.0, +1.0]  // バイナリ: 01010101

ブランチングなし、ビットシフトオーバーヘッドなし — ただ配列ルックアップ+メモリアクセス。

Blackman-Sinc FIRデシメーションフィルター: 数学的な中心

DSDをPCMに変換するには、高品質なローパスフィルターが必要です。Blackman窓付きsinc FIRフィルターを使用します:

Sinc関数(理想的なローパス)

sinc(x) = sin(πx) / (πx)

sinc関数は数学的に「完璧」です — その周波数応答は、ゼロのサイドローブを備えたブリックウォール型のローパスです。

Blackman窓(サイドローブ抑制)

w(n) = 0.42 − 0.5 cos(2πn/N) + 0.08 cos(4πn/N)

sincタップに適用すると、Blackman窓は滑らかにロールオフし、-74dBのサイドローブ抑制を実現します。これはエイリアシングアーティファクトを防ぎます。

畳み込み: デシメーション操作

y[n] = Σ h[k] × x[n − k]   (各出力サンプルに対して)

各出力PCMサンプルに対して、フィルター係数を周辺のDSDビットで乗算し、合計します。タップ数(係数)はデシメーション比に応じてスケーリングします。

DSD64→44.1kHzは~8192タップが必要です。DSD256→44.1kHzは~32,768タップが必要です。

ストリーミングチャンク処理: ギガバイトファイルの処理

高解像度DSD256アルバムは5~8GBになる場合があります。ファイル全体をメモリにロードすることはできません。代わりに、チャンク処理を使用します:

  1. ユーザーがDSDファイルを選択
  2. 8MBのチャンクがFileReader.readAsArrayBuffer(blob.slice(offset, offset + 8MB))経由で読み取られます
  3. 各チャンクはWasm(DSDパーサー→LUT→FIR→Float32出力)によって独立して解析されます
  4. 結果は成長するFloat32Arrayに累積されます(メモリはわずか~16MBを超えません)

WAVエクスポート: 24ビットPCMエンコーディング

デシメーション後、Float32サンプルは24ビット整数PCMに変換し、RIFF/WAVEコンテナでラップされます:

  1. Float32クランピング: max(−1.0, min(+1.0, sample))
  2. Int24に量子化: round(sample × 8388607) — 24ビット符号付き範囲
  3. サンプルあたり3バイトをパック(Blob.arrayBuffer用リトルエンディアン)
  4. RIFF/WAVEヘッダー(44バイト)に正しいチャンクサイズを付加します

パフォーマンス: リアルワールドベンチマーク

私たちの実装は巨大なDSDファイルで実戦テストされています:

  • DSD64アルバム(2.1GB)→192kHz WAV: ~45秒
  • Rust Wasm対JavaScript: FIR畳み込みで~10倍高速

Wasmが勝つ理由:

  • FIR処理中にガベージコレクション一時停止がない
  • 線形メモリレイアウト — キャッシュフレンドリーな配列アクセス
  • 緊いループでのコンパイラSIMD最適化

関連記事

KUON DSD を試すDDP の仕組み