[Astro] #142 Web Audio API で構築するブラウザ完結型レトロシンセサイザー & FXエンジン — SynthRackGenerator v1.0 の実装記録
はじめに
ブラウザ標準の Web Audio API のみを活用し、外部ライブラリを一切使用せず完結するレトロサウンドジェネレーター&エフェクトエンジン 「SynthRackGenerator v1.0」 を構築しました。
主オシレーターとFM変調用副オシレーターの合成、ADSR エンベロープ、Biquad Filter、Bitcrusher・Stereo Delay などの DSP エフェクト、さらに 8 ステップのアルペジエーターや Canvas2D ビジュアライザーを備えたオールインワン型のモジュラー・サウンドモジュールです。
最終バージョン v1.0 では、作成したパッチをブラウザ内に保存・再呼び出しできる LocalStorage スロット機能 と、データ共有用の JSON / URL Hash 機能、および OfflineAudioContext による 16-bit PCM WAV エクスポート を統合しました。
| モジュール No. | モジュール名 | 主要機能・処理内容 |
|---|---|---|
| 01 | OSCILLATORS & FM | Square / Saw / Sine / Triangle 波形合成, Detune, FM変調, ノイズジェネレータ |
| 02 | ENVELOPES | AMP ADSR コントロール, Pitch Sweep (スイープ時間・深度) |
| 03 | FILTER & LFO | BiquadFilter (Lowpass/Highpass/Bandpass), Cutoff, Resonance, Pitch/Filter LFO |
| 04 | DSP EFFECTS & MASTER | Bitcrusher (WaveShaperNode), Stereo Delay (Feedback), Master Gain |
| — | VISUALIZER & KEYBOARD | 60fps オシロスコープ ↔ スペクトラム切替描画, PCキーボード DAW 演奏 |
| 05 | SEQUENCER | 8ステップアルペジエーター, BPM 制御, 4種スケールクオンタイズ |
| EX | DATA & STORAGE | LocalStorage パッチ管理, JSON/URL Hash 共有, OfflineAudioContext WAV 生成 |
スクリーンショット
動画(GIF)
1. 音響パイプラインと Node アーキテクチャ
Web Audio API の AudioNode を直列・並列に接続し、レトロゲーム効果音からFMシンセ音色までを柔軟に合成できるパイプラインを設計しました。
ノードグラフ構造
[ Primary OSC ] ───┐
├─► [ Main Gain ] ──► [ Filter ] ──► [ Bitcrusher ] ──► [ Delay ] ──► [ Master Gain ] ──► [ Analyser ] ──► Destination
[ FM Modulator ] ──┘ (AMP Envelope) (LFO Mod) (WaveShaper) (Feedback)
(Frequency Mod)
[ Noise Buffer ] ────► [ Noise Gain ] ────┘
発音時は、パラメータ変更に合わせて新しい OscillatorNode や BufferSourceNode を動的に生成し、設定された ADSR パラメータに基づいて AudioParam.setValueAtTime および linearRampToValueAtTime で時間変化を付与します。
2. 音源合成 & DSP エフェクトの実装
ADSR エンベロープ と Pitch Sweep
エンベロープ制御は AudioParam のオートメーション機能を利用し、クリックノイズを防止しながら滑らかな変化を実現しています。
const now = audioCtx.currentTime;
const gainNode = audioCtx.createGain();
// ADSR パラメータの適用
const attack = params.envAttack;
const decay = params.envDecay;
const sustain = params.envSustain;
const release = params.envRelease;
gainNode.gain.setValueAtTime(0.0001, now);
gainNode.gain.linearRampToValueAtTime(1.0, now + attack);
gainNode.gain.linearRampToValueAtTime(sustain, now + attack + decay);
// ノートオフ時のリリース処理
gainNode.gain.setValueAtTime(sustain, now + attack + decay + duration);
gainNode.gain.linearRampToValueAtTime(0.0001, now + attack + decay + duration + release);
Bitcrusher (WaveShaperNode)
ビットクラッシャー効果は、入力波形の振幅を量子化(ステップ化)するカスタム転送関数を作成し、WaveShaperNode に適用することでシミュレートしています。
function createBitcrusherCurve(bitDepth) {
const samples = 4096;
const curve = new Float32Array(samples);
const step = Math.pow(2, bitDepth);
for (let i = 0; i < samples; i++) {
const x = (i / samples) * 2 - 1;
// 振幅レベルを指定ビット数に量子化
curve[i] = Math.round(x * step) / step;
}
return curve;
}
const waveShaper = audioCtx.createWaveShaper();
waveShaper.curve = createBitcrusherCurve(params.bitDepth); // 例: 2 ~ 16 bit
3. 8ステップ・アルペジエーター & スケールクオンタイズ
シーケンサーモジュールは、指定された BPM に基づいてタイマー駆動し、各ステップの音高をノート番号から周波数(Hz)に変換してトリガーします。
スケール変換テーブル
スケール選択に応じて、ノートナンバーのインデックスを特定の音程セットにクオンタイズ(補正)します。
const SCALES = {
chromatic: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
major: [0, 2, 4, 5, 7, 9, 11],
minor: [0, 2, 3, 5, 7, 8, 10],
pentatonic: [0, 2, 4, 7, 9]
};
function quantizeToScale(noteIndex, scaleName) {
const scale = SCALES[scaleName] || SCALES.chromatic;
const octave = Math.floor(noteIndex / scale.length);
const scaleNote = scale[noteIndex % scale.length];
return octave * 12 + scaleNote;
}
// ノート番号から周波数(Hz)への変換式
function noteToFreq(note) {
return 440 * Math.pow(2, (note - 69) / 12);
}
PCキーボードの [Z]〜[M](C4〜B4)および [Q]〜[U](C5〜B5)キーによるリアルタイム演奏も可能で、キー入力時に即座にノードを生成・発音します。
4. リアルタイム Canvas2D ビジュアル表示
AnalyserNode から取得した音響データを Canvas2D 上に 60fps で描画します。トグルボタンにより波形(時間軸)とスペクトラム(FFT周波数軸)を切り替えることが可能です。
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 1024;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
function drawVisualizer() {
requestAnimationFrame(drawVisualizer);
if (visualizerMode === 'WAVEFORM') {
analyser.getByteTimeDomainData(dataArray);
drawWaveform(dataArray);
} else {
analyser.getByteFrequencyData(dataArray);
drawSpectrum(dataArray);
}
}
オシロスコープモードでは、振幅(128を中心に上下する波形)を lineTo で繋ぎ、発光表現のためにシャドウ効果 (shadowBlur) を乗せたシアン色で描画しています。
5. LocalStorage プリセット管理 & WAV Offline Rendering
1. LocalStorage スロット管理
ユーザーが作成したカスタム音色パッチをブラウザ内部に名前付きでキー保存し、ワンタップで読み出し・削除ができます。
const STORAGE_KEY = 'synth_rack_user_slots';
// スロット保存処理
function saveUserSlot(patchName, currentParams) {
const slots = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
slots[patchName] = currentParams;
localStorage.setItem(STORAGE_KEY, JSON.stringify(slots));
refreshSlotUI();
}
// 読み込み処理
function loadUserSlot(patchName) {
const slots = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
if (slots[patchName]) {
applyParamsToUI(slots[patchName]);
triggerSound(); // 確認用試聴
}
}
2. OfflineAudioContext による WAV ファイル書き出し
再生用のリアルタイム AudioContext と同一のノード構成を OfflineAudioContext 上に再構築し、非リアルタイムで高速レンダリングします。生成された AudioBuffer を 16-bit PCM WAVフォーマットのバイナリヘッダにエンコードしてダウンロードリンクを生成します。
async function exportToWav(params) {
const sampleRate = 44100;
const duration = calculateTotalDuration(params);
const offlineCtx = new OfflineAudioContext(1, sampleRate * duration, sampleRate);
// オフラインコンテキスト上で音響パイプラインを構築してレンダリング
buildAudioPipeline(offlineCtx, params);
const renderedBuffer = await offlineCtx.startRendering();
// AudioBuffer から 16-bit PCM WAV Blob を作成
const wavBlob = audioBufferToWavBlob(renderedBuffer);
const url = URL.createObjectURL(wavBlob);
const a = document.createElement('a');
a.href = url;
a.download = `${params.patchName || 'patch'}.wav`;
a.click();
}
6. まとめ
- 全機能ブラウザ完結: 外部音声ライブラリ(Howler.js や Tone.js 等)を一切使わず、純粋な Web Audio API で本格的なシンセサイザーとエフェクトモジュールを構築。
- 軽量かつ柔軟なパッチ管理: LocalStorage によるローカル保存に加え、Base64 化した URL ハッシュ共有機能と JSON ファイルの入出力に対応。
- 即時 WAV 書き出し:
OfflineAudioContextによる非リアルタイム描画で、作成した音色をその場でサウンド素材(WAV)として保存可能。 - レトロ&グラフィカルなUI: Canvas2D による波形/FFTビジュアライザーと DAW ライクなキーボード入力により、視覚的・直感的なサウンドビルドを実現。
音作りの試行錯誤からエクスポートまでを軽量な Web UI だけで実現する SynthRackGenerator v1.0。レトロゲーム開発の SE 作成や 8-bit サウンドデザインのツールとして、今後のプロジェクトでも活用していきます。