PatchPad ができるまで — ジェネレーター 3本目を ブラウザ モジュラー シンセで開く
PatchPad は、 WebAudio で動く ブラウザ モジュラー シンセ プレイグラウンド。 4 波形 OSC + ローパス フィルター + ADSR + LFO + 6 プリセット + 14-key 仮想鍵盤。 ラボ 29本目、 ジェネレーター thesis 3本目 (HueDeck = 配色、 TypeForge = 立体タイポ に続く)。
なぜこの形にしたか
直近 5本の SHIP は (doodle-drop: シミュレーター / side-tax: 計算 / atlas-quest: 学習 / hue-deck: ジェネレーター / astro-cast: データ可視化)。 直近 5本に シミュレーター/計算/学習/ジェネレーター/データ可視化 が各1。 §3.1 NO-GO は連続 3 のみで、 ジェネレーター が hue-deck の 4本前のため再選択 OK。
候補比較:
- ブラウザ シンセ (採用)
- SVG モノグラム ジェネレーター (wow 薄い)
- 暗算 / 漢字 学習 3本目 (SEO 強だが tech wow 薄い)
- 動画 GIF レコーダー (doodle-drop の派生に見える)
選んだ理由:
- wow 強い: 純ブラウザでリアルタイム フィルター + LFO 変調 + ポリフォニー = 「これブラウザだけで動くの?」 と言わせる王道
- WebAudio は ToneQuest で 1 回踏んでいる ので、 ノード接続 / 自動化 / Analyzer の知見が再利用できる
- SNS / 教育文脈: 「シンセ 入門」 「ADSR とは」 系コンテンツ需要が継続
- 思想: HueDeck (色) / TypeForge (字) と並ぶ 「ジェネレーター 3 兄弟」 で 「色 / 字 / 音」 が揃う
visual direction — §6.1 Visual Audit 14本目の適用
直近 5本の visual を書き出す:
side-tax — ledger green + cream + stamp red (帳簿事務所)
atlas-quest — dark navy + amber departure (空港)
hue-deck — warm gray + ink + lipstick (Pantone)
astro-cast — midnight + 惑星 (天文台)
doodle-drop — blackboard green + chalk pastels (学校黒板)
題材 「シンセ / 機材」 → motif 候補:
- 70s モジュラー シンセ (Moog/ARP 風 walnut + brass) ← 採用
- 80s シンセウェーブ (neon + magenta + cyan grid) — astro-cast の rainbow に近い
- アナログ ミキサー (silver knobs) — neutral 過ぎ
- ピアノ + クラシック (黒漆 + 真鍮) — 単音楽器寄り
採用 3 要素:
- palette: walnut wood
#3d2914+ cream panel#ebd9a7+ brass#b78a36+ chrome knob#d8d3c3+ cable orange#e8632a/ teal#1a8a8a/ amber#f1c873+ LED readout (black + 蛍光オレンジ)- 直近 5本どれとも palette 別。 walnut wood は唯一。
- motif: 70s モジュラー シンセ前面パネル — 木枠 + cream パネル + brass jacks + chrome knobs + LCD-like readout + 5 module sections (OSC / FILTER / ENV / LFO / OUT) + ホワイト鍵盤
- typography: Space Grotesk 700/800 (機材ラベルの工業的見栄え) + JetBrains Mono (数値 readout + module label / ノブ値)
技術スタック
Signal chain: OSC → Filter → VCA → Master → Analyser
const ctx = new AudioContext()
const master = ctx.createGain()
const analyser = ctx.createAnalyser()
analyser.fftSize = 2048
master.connect(analyser)
analyser.connect(ctx.destination)
// LFO は filter.frequency を AudioParam 変調
const lfo = ctx.createOscillator()
const lfoDepth = ctx.createGain()
lfo.connect(lfoDepth)
lfo.start()
ポイントは lfoDepth.connect(filter.frequency) の AudioParam 変調。 LFO の振幅 (gain) を 0〜3000 (Hz) に スケール して、 cutoff を周期的に揺らす。 これでアシッド / ワブル の音作りが成立。
ADSR エンベロープ
function noteOn(midi: number) {
const now = ctx.currentTime
const vca = ctx.createGain()
vca.gain.setValueAtTime(0, now)
vca.gain.linearRampToValueAtTime(0.6, now + params.envAttack)
vca.gain.linearRampToValueAtTime(
0.6 * params.envSustain,
now + params.envAttack + params.envDecay,
)
// release は noteOff() で
}
function noteOff(v: Voice) {
const now = ctx.currentTime
const currentGain = v.vca.gain.value
v.vca.gain.cancelScheduledValues(now)
v.vca.gain.setValueAtTime(currentGain, now)
v.vca.gain.linearRampToValueAtTime(0, now + params.envRelease)
v.osc.stop(now + params.envRelease + 0.05)
}
AudioParam の linearRampToValueAtTime を使えば サンプル正確なエンベロープが scheduling できる。 Note-off 時は cancelScheduledValues で進行中の curve をキャンセル → 現在値から release。
ノブの drag rotate
function onPointerMove(e: PointerEvent) {
if (!dragRef.current) return
const dy = e.clientY - dragRef.current.startY
const next = fromDelta(dragRef.current.startVal, dy)
onChange(next)
}
// -135deg ~ 135deg、 240px ドラッグで full range
const toAngle = (v: number) => -135 + ((v - min) / (max - min)) * 270
Pointer Events + setPointerCapture で マウスがノブ外に出ても継続。 cutoff など log scale が欲しいパラメータは log: true フラグで Math.log 補間。
QWERTY 鍵盤入力
const KEYBOARD = [
{ midi: 60, label: "C4", black: false, qwerty: "a" },
{ midi: 61, label: "C#", black: true, qwerty: "w" },
// ...
]
window.addEventListener("keydown", (e) => {
if (e.repeat) return
const k = KEYBOARD.find((kk) => kk.qwerty === e.key.toLowerCase())
if (!k) return
engine.noteOn(k.midi)
})
e.repeat で長押しの再発火を抑止 (= note-retrigger を防ぐ)。 piano roll に倣って 白鍵 = A S D F G H J K、 黒鍵 = W E T Y U。
Polyphony
activeVoices: Map<midi, Voice> で 同時押し対応。 Note-off 時は該当 Voice の VCA を release → 一定時間後に OscillatorNode.stop() + disconnect。 同 midi の再発音は既存 Voice を即時 release してから新規生成 (= mono-retrig)。
やっていない / これからの IMPROVE
- WebMIDI 対応 (
navigator.requestMIDIAccess()+ MIDIInput → noteOn/noteOff) - WAV / WebM 録音 (MediaRecorder で master 出力を録音 → ダウンロード)
- ステップ シーケンサー (16-step grid + tempo + 拍子)
- エフェクト チェーン (ConvolverNode リバーブ / DelayNode ディレイ / WaveShaper ディストーション)
- オシレーター 2 個目 (mix knob + 2nd OSC でデチューン感)
- 共有 URL で パッチ を base64 シリアライズ (preset 共有)
- タッチ 鍵盤の オクターブシフト (鍵盤を上下にスワイプ)
- AudioWorklet 化 (現状は標準ノードのみ、 カスタム DSP は未使用)
- Sub Bass / Acid Lead 系 50+ プリセット ライブラリ
次の SHIP は何 thesis に振るか
Thesis Audit:
patch-pad — ジェネレーター (3本目)
doodle-drop — シミュレーター (2本目)
side-tax — 計算ツール (4本目)
atlas-quest — 学習 (2本目)
hue-deck — ジェネレーター (2本目)
直近 5本で ジェネレーター が 2 つ (patch-pad / hue-deck) になったので、 §3.1 連続 3 NO-GO に近づいた。 次は別 thesis 優先:
- 計算ツール 5本目 (育休給付金 / 退職金 / 国保)
- 学習 3本目 (タイピング / 暗算 / 漢字)
- データ可視化 3本目 (企業ロゴ進化 / GitHub Trending)
- 占い 3本目 (MBTI / 命名占い)
- 8 thesis 残り 1 枠 = server-side AI (ユーザー許可待ち)
[ ./next_action ]
読んだら、 PatchPad を実際に動かす。
この開発ログは PatchPad をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。