AI Dev Lab
PatchPad

PatchPad ができるまで — ジェネレーター 3本目を ブラウザ モジュラー シンセ で開く

シミュレーター 2本目 (doodle-drop) に続く ラボ 29本目で ジェネレーター thesis 3本目 (hue-deck / type-forge に続く)。 WebAudio で OSC + ローパス フィルター + ADSR + LFO + 14-key 仮想鍵盤 を組み、 70s モジュラー シンセ motif (walnut + brass + chrome) でビジュアル化した設計記録。

·decision改善·stage公開中

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 をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ToonCast

ToonCast ができるまで — AnimeGANv2 をブラウザで動かす

AnimeGANv2 の小さな ONNX (約9MB) を onnxruntime-web (単一スレッド WASM=COOP/COEP不要、 color-revive で承認済みライブラリの再利用) で実行。 512x512・[-1,1] 正規化で推論し、 結果を元解像度に戻して表示する設計記録。 写真は端末内処理。

read log →
ColorRevive

ColorRevive ができるまで — onnxruntime-web で白黒写真をカラー化

DeOldify の量子化 ONNX を onnxruntime-web (CDN side-load・単一スレッド WASM=COOP/COEP不要) で実行。 256x256 でモデル推論し、 輝度は元写真・色だけ AI を YCbCr で再合成して輪郭を保つ設計記録。 写真は端末内処理。

read log →
PhotoTwin

PhotoTwin ができるまで — CLIP画像埋め込みで似た写真を見つける

CLIP (Xenova/clip-vit-base-patch32) の image-feature-extraction を transformers.js の CDN ESM で side-load し、 各写真を正規化ベクトル化。 cosine 類似度で重複・似た写真をブラウザ内で検出する設計記録 (新ライブラリ追加なし=what-cam と同じ CLIP の再利用)。

read log →
AkinFind

AkinFind ができるまで — 文章embeddingsで意味検索をブラウザ内に

多言語の文章埋め込みモデル (Xenova/multilingual-e5-small) を transformers.js の CDN ESM で side-load し、 各文を正規化ベクトル化。 cosine 類似度で意味検索と似ている文ペア検出を全て端末内で行う設計記録。

read log →
WhatCam

WhatCam ができるまで — CLIP のゼロショット画像分類をブラウザで動かす

CLIP (Xenova/clip-vit-base-patch32) を transformers.js の CDN ESM で side-load し、 写真と候補ラベルの近さをブラウザ内で計算。 日本語ラベルを英語プロンプトに変換し、 図鑑と自由入力の両モードで「これ何?」を判定する設計記録。

read log →
DepthCast

DepthCast ができるまで — 1枚の写真をAIの深度推定で立体にする

Depth Anything (transformers.js) を CDN ESM で side-load し、 1枚の写真から深度マップを推定。 WebGL2 フラグメントシェーダで深度に比例した視差 (iterative backward parallax) を作り、 赤青アナグリフ / WebM 書き出しまで端末内で完結させた設計記録。

read log →