create-sound について
テキストプロンプトや提供された音声から TypeScript の SoundDefinition を作る、エージェント向けのサウンド設計スキルです。イベントに応じた生成経路を選び、ムードやレイヤーのルールを適用し、音声分析やプレビューの手順も扱います。
主な機能
- 01プロンプトと音声に応じた生成経路の選択
- 02イベントに基づくサウンドレイヤー選択
- 03ムードと調整ルールの適用
- 04ピッチ、エンベロープ、波形の分析
- 05任意の WAV レンダリングと検証
活用シーン
- 01イベントのプロンプトから UI サウンドを作成
- 02音声分析で得た定義をムードに合わせて調整
- 03サウンドスプライトを分析して音を再構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ raphaelsalaja/ audio --skill create-soundインストール後、Claude Code で/create-soundを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.jsonpackage.jsonREADME.mdrules/_sections.md_template.mdeffect-bandpass-noise-swoosh.mdeffect-bitcrush-lofi.mdeffect-fm-bell.mdeffect-lowpass-warmth.mdeffect-reverb-tail.mdevent-click.mdevent-complete.mdevent-error.mdevent-modal-close.mdevent-modal-open.mdevent-notification.mdevent-success.mdevent-swoosh.mdevent-tap.mdevent-tick.mdevent-toggle.mdevent-whoosh.mdinterpret-classify-waveform.mdinterpret-detect-effects.mdinterpret-detect-filter.mdinterpret-detect-lfo.mdinterpret-extract-envelope.mdinterpret-extract-fundamental.mdinterpret-extract-source.mdinterpret-multi-layer.md詳細
サウンドの作成
src/build.mjsによりrules/*.mdから生成されます。手動で編集しないでください。
pipeline-detect-input で生成パスを選択し、該当するセクションに沿って進めます。
1. 生成パイプライン
エージェントが最初から最後まで実行する手順です。サウンド作成のリクエストに対応するときは、ここから始めてください。
1.1 入力モードを検出してリクエストを振り分ける (CRITICAL)
ユーザーが提供した内容に基づいて、実行するパスを決定します。
| 入力 | パス |
|---|---|
| プロンプトのみ(音声の添付なし) | interpret-* をスキップします。pipeline-pick-base-layer に進みます。 |
| 音声ファイルのみ | すべての interpret-* ルールを実行します。event-* / mood-* はスキップします。 |
| プロンプトと音声の両方 | 先に interpret-* を実行し、測定した SoundDefinition に対する調整レイヤーとしてプロンプトを扱います。 |
音声の検出
*.wav、*.mp3、*.flac、*.ogg に一致する添付ファイル、またはユーザーが参照したパスのうち、音声ファイルを指すものを探します(スプライトと同じ場所にある JSON マニフェスト(*.json)も、音声パスの存在を示すシグナルです)。
調整の例(プロンプトと音声)
| プロンプトの修飾語 | 測定した定義への調整 |
|---|---|
| 「暖かみを加える」 | filter: { type: "lowpass", frequency: 2500 } を追加します |
| 「短く」/「歯切れよく」 | envelope.decay を <= 0.06 に制限します |
| 「明るく」 | ローパスのカットオフを削除するか、引き上げます |
| 「リバーブを加える」 | effects: [{ type: "reverb", decay: 0.5, mix: 0.15 }] を追加します |
| 「1オクターブ下げる」 | source.frequency(または start/end の両方)を半分にします |
このステップの出力
次のような内部メモを作成します。
Input: prompt + audio
Plan: run interpret-* on out/click.wav, then refine with mood-warm.
その後、次のパイプラインステップに進みます。
1.2 プロンプトのイベントクラスからベースレイヤーを選ぶ (CRITICAL)
プロンプトをトークン化し、最も強いイベントクラスのシグナルを見つけます。event-* ルールと照合します。
トークンマップ
| プロンプト内のトークン | イベントルール |
|---|---|
| click、tap、key、press、button | event-click / event-tap |
| tick、scroll、snap、focus | event-tick |
| success、complete、win、achievement、level-up、confetti | event-success / event-complete |
| error、fail、wrong、invalid、delete、destroy | event-error |
| modal、dialog、popup、drawer、sheet、sidebar、dropdown、menu | event-modal-open / event-modal-close |
| swoosh、slide、transition、page、tab | event-swoosh / event-whoosh |
| notification、alert、ding、bell、mention、badge | event-notification |
| toggle、switch、on、off | event-toggle |
方向を示すトークン(開く/閉じる)
- 「open」、「appear」、「in」、「show」、「expand」、「confirm」→ ピッチを上昇させます。
- 「close」、「dismiss」、「out」、「hide」、「collapse」、「cancel」→ ピッチを下降させます。
出力
選択したイベントルールの example からコピーした、開始用の SoundDefinition リテラルです。次のステップ(pipeline-apply-mood)でこれを変更します。
イベントクラスが明確に判定できない場合は、event-click をデフォルトとして、ムードを表す形容詞で調整します。
1.3 ベースレイヤーにムードを表す形容詞を適用する (HIGH)
pipeline-pick-base-layer が開始用の SoundDefinition を生成したら、プロンプトから形容詞のトークンを探し、各 mood-* ルールの変更を順番に適用します。
適用順序
- 音源の形状を表す形容詞(
warm、bright、glassy、metallic、lofi、retro、organic)—source.type、source.fmを変更するか、filterを追加します。 - エンベロープを表す形容詞(
punchy、airy)—envelope.attack/envelope.decayを変更します。 - エフェクトを表す形容詞(
reverby、delayed、crushed)—effectsに追加します。
競合の解決
warm+bright→ 後のトークンを優先します。lofi+glassy→ 両方を適用しますが、effectsの項目数は 2 個までにします。punchy+airy→ 互いに独立しています(エンベロープと音源)。両方を適用します。
既存の定義に対する調整(音声 + プロンプトのパス)
入力モードが prompt + audio の場合、各形容詞は最初から作成するのではなく、測定済みの定義を調整するものとして扱います。
| 形容詞 | 調整 |
|---|---|
| warmer | filter.frequency を追加するか、周波数を下げます(約 2500 Hz のローパス)。 |
| brighter | ローパスを削除するか、カットオフ周波数を 6 kHz より高くします。 |
| punchier | envelope.decay <= 0.06 に制限し、envelope.attack: 0 に設定します。 |
| longer | envelope.decay を延長し、存在しない場合は release を追加します。 |
| crisper | gain を少し上げ、fm: { ratio: 0.5, depth: 50 } を追加します。 |
出力
変更後の SoundDefinition。pipeline-decide-layering に引き継ぎます。
1.4 単層にするか複数層にするかを決定する (MEDIUM-HIGH)
| イベントの種類 | デフォルト |
|---|---|
| クリック、タップ、ティック、ホバー、フォーカス、スウッシュ | 1 層(Layer) |
| トグル、コピー、送信、同期 | 2 層(delay を使ったピッチの組み合わせ) |
| 成功、完了、レベルアップ、紙吹雪 | 3 層以上(delay を使った段階的なコード) |
| エラー、削除 | 2 層(sawtooth + square) |
具体的な形については、layer-single、layer-octave-pair、layer-ascending-chord、layer-click-plus-body を参照してください。
単一の Layer を MultiLayerSound に昇格する
プロンプトまたは調整で複数の層が必要な場合は、次のようにラップします。
{
layers: [<existing layer>, <new layer>],
// optional global effects, e.g. sidechain compressor, master EQ
}
各層の gain 値の合計は、約 0.6 以下にしてください(validate-gain-budget を参照)。
MultiLayerSound を単一の Layer に降格する
ムードの適用後に層が 1 つだけ残る場合は、1 要素の MultiLayerSound ではなく、内側の Layer を直接出力します。どちらも検証に通りますが、単一層の形式が標準的な簡潔な形式です。
1.5 出力し、必要に応じてレンダリングし、必要に応じて往復する (HIGH)
1. 出力
.web-kits/<patch>.ts ファイルにそのまま貼り付けられる TypeScript スニペットを必ず返します。
import type { SoundDefinition } from "@web-kits/audio";
export const myClick: SoundDefinition = {
source: { type: "sine", frequency: 1300, fm: { ratio: 0.5, depth: 60 } },
envelope: { decay: 0.012, release: 0.004 },
gain: 0.18,
};
さらに、適用したプロンプトのトークンを示す、根拠を説明する 1 行を添えます。
「click」→
event-clickをベースにしました。「warm」→ 1.3 kHz ではデフォルトの sine を維持し、追加のフィルターは不要と判断しました。
2. 任意のプレビューのレンダリング
ユーザーが WAV を求めた場合(または出力を自分で評価する場合)は、packages/audio/src/offline.ts を使用します。
import { renderToWav } from "@web-kits/audio";
import { writeFile } from "node:fs/promises";
const blob = await renderToWav(myClick, { duration: 0.3 });
await writeFile("preview.wav", Buffer.from(await blob.arrayBuffer()));
duration は attack + decay + release + 0.05(短い余韻)以上にします。リバーブがある場合は、さらに長くしてください。
3. オプションのラウンドトリップ検証
プロンプトから生成し、結果が意図に合っているか確認したい場合は、レンダリングした WAV に対して interpret-* ルールを実行し、測定値と意図した値の差分を確認します。
| フィールド | 許容差 |
|---|---|
| 基本周波数(Hz) | ±5% |
| アタック | ±2 ms |
| ディケイ | ±10% |
| スペクトル重心 | 選択した波形の想定値の±20% |
許容範囲を超える差がある場合は、定義を調整し(多くの場合、gain を上げる/下げる、envelope をよりタイトにする、または filter.frequency を調整します)、再度レンダリングしてください。
2. 音声の解釈
ユーザーが音声ファイルを共有したときに実行する FFT 分析のサブステップです。
2.1 ソース音声を取得して分割する (HIGH)
ユーザーが単一ファイル、またはスプライト(多数のサウンドを含む 1 つのファイル)を共有しました。FFT 処理を始める前に、サウンドごとに 1 つのモノラル WAV をディスクに用意します。
npm パッケージのスプライト
npm pack <package-name> --pack-destination /tmp
tar -xzf /tmp/<package-name>-*.tgz -C /tmp
MP3/WAV と、サウンド名を時間オフセットに対応付ける JSON マニフェストを探します。
マニフェストに基づく分割
ffmpeg -i sprite.mp3 \
-ss <start_seconds> -t <duration_seconds> \
-acodec pcm_s16le -ar 44100 \
output/<name>.wav
無音検出による分割(マニフェストなし)
ffmpeg -i sprite.mp3 -af silencedetect=noise=-40dB:d=0.05 -f null -
silence_start/silence_end の行を読み取り、無音区間の間を切り出します。
出力規則
サウンドごとの WAV は out/<name>.wav に保存します(モノラル、44.1 kHz、16-bit PCM)。後続の interpret ルールは src/analyze.py の analyze.load_mono(path) を呼び出します。
2.2 基本周波数とピッチスイープを抽出する (HIGH)
複数の時間スライスでスペクトルをサンプリングし、固定ピッチとスイープの両方を検出します。
from analyze import load_mono, analyze_slice
sample_rate, data = load_mono("out/click.wav")
slices = [0, 5, 10, 20, 50] # ms
freqs_over_time = [analyze_slice(data, sample_rate, t) for t in slices]
対応付け
| 観測結果 | 出力 |
|---|---|
| すべてのスライスが±5%以内 | source.frequency: <Hz>(固定) |
| スライス間で低下 | source.frequency: { start: <high>, end: <low> } |
| スライス間で上昇 | source.frequency: { start: <low>, end: <high> } |
ヒント
- アタック時にクリック過渡音がある場合は、最初の1~2 msをスキップします。FFTにノイズが混入します。
- 非常に短いサウンド(< 20 ms)では、スライス数を減らし、ウィンドウサイズを小さくします。
- スペクトル漏れを抑えるため、FFTの前にハニング窓を適用します(
analyze_sliceで適用済みです)。
2.3 振幅からADSRエンベロープを抽出する (HIGH)
時間領域の振幅を平滑化し、アタック/ピーク/サステイン/終了を検出して、各ADSR段階を導出します。
from analyze import load_mono, extract_envelope
sample_rate, data = load_mono("out/click.wav")
env = extract_envelope(data, sample_rate)
# -> { "attack": 0.0008, "decay": 0.012, "sustain": 0.0, "release": 0.005 }
出力形式
この辞書は Envelope 型に1対1で対応します。
envelope: {
attack: env.attack, // 0 if percussive
decay: env.decay,
sustain: env.sustain, // 0 for transient sounds, 0-1 for sustained
release: env.release,
}
ヒューリスティクス
sustain < 0.01-> フィールドを省略します。サウンドはパーカッシブです。attack < 0.001->attack: 0に設定します。release < 0.005-> 終端のクリックを防ぐため、0.005に切り上げます。
2.4 倍音からオシレーターの波形を分類する (HIGH)
最初の8つの倍音の振幅を基本波と比較します。
import numpy as np
from scipy.fft import rfft, rfftfreq
from analyze import classify_waveform
segment = data[:int(sample_rate * 0.02)].astype(float)
segment *= np.hanning(len(segment))
spectrum = np.abs(rfft(segment))
freqs = rfftfreq(len(segment), 1 / sample_rate)
waveform = classify_waveform(spectrum, freqs, fundamental_freq)
# -> "sine" | "triangle" | "square" | "sawtooth" | "wavetable"
対応付け
| パターン | source.type |
|---|---|
| 基本波のみで、倍音が -40 dB 未満 | sine |
| 奇数次倍音が1/nで減衰 | triangle |
| 奇数次倍音がおおむね同じ振幅 | square |
| すべての倍音が1/nで減衰 | sawtooth |
| カスタムの倍音プロファイル(上記のいずれでもない) | wavetable |
| 明確な倍音構造がなく、広帯域エネルギーがある | noise |
ウェーブテーブルに切り替える場合
倍音プロファイルが一般的なオシレーターと一致しない場合は、代わりに倍音列を抽出します。
from analyze import extract_harmonics
harmonics = extract_harmonics(spectrum, freqs, fundamental_freq, num_harmonics=16)
# -> { source: { type: "wavetable", harmonics, frequency: fundamental_freq } }
ノイズの色
基本波のない広帯域信号では、スペクトル傾斜によって分類します。
from analyze import classify_noise_color
color = classify_noise_color(spectrum, freqs) # "white" | "pink" | "brown"
# -> { source: { type: "noise", color } }
2.5 フィルターの種類、カットオフ、レゾナンスを検出する (MEDIUM-HIGH)
測定したスペクトルを、特定したオシレーターに対して想定されるスペクトルと比較します。
スペクトル重心によるカットオフ
from analyze import spectral_centroid
centroid = spectral_centroid(spectrum, freqs)
基本周波数が 440 Hz の場合に予想される重心は、サイン波が約 440、三角波が約 880、のこぎり波が約 2200、矩形波が約 1760 です。測定した重心が予想より大幅に低い場合は、lowpass がかかっているため、-3 dB の点からカットオフを推定します。
ロールオフによるフィルタータイプ
| 観測結果 | filter.type |
|---|---|
| 高周波のロールオフがソース波形から予想されるより急である | lowpass |
| 低周波のロールオフ | highpass |
| 狭い周波数帯域が通過する | bandpass |
| 狭いノッチが除去されている | notch |
| カットオフ付近に共振ピークがある | 高いresonance |
レゾナンス(Q)
from analyze import estimate_resonance
q = estimate_resonance(spectrum, freqs, cutoff_hz)
# Returns 0.1 - 20.0
フィルターエンベロープ
時間の経過とともに明るさが変化する(明るいアタックから徐々に鈍くなる)場合、フィルターエンベロープが存在します。
from analyze import detect_filter_envelope
env = detect_filter_envelope(data, sample_rate)
# -> { "peak": 4000, "resting": 800, "decay_ms": 50 } or None
次のように対応します。
filter: {
type: "lowpass",
frequency: env.resting,
envelope: { attack: 0, peak: env.peak, decay: env.decay_ms / 1000 },
}
2.6 ソース後のエフェクトを検出する (中程度)
各検出器が返すのは信頼度を伴うヒントであり、確実な結果ではありません。エフェクトはソースやエンベロープより抽出が難しいため、判別が曖昧な場合は信頼度を低く報告してください。
リバーブ
from analyze import detect_reverb
result = detect_reverb(data, sample_rate, envelope_end_ms=120)
# -> { "type": "reverb", "decay": 0.6 } or None
ディレイ(自己相関)
from analyze import detect_delay
result = detect_delay(data, sample_rate)
# -> { "type": "delay", "time": 0.25, "feedback": 0.3 } or None
FM 合成
基本周波数に対する整数比ではない周波数比のスペクトル側波帯は、FM を示します。
from analyze import detect_fm
fm = detect_fm(spectrum, freqs, fundamental_freq)
# -> { "fm": { "ratio": 0.5, "depth": 80 } } or None
これは source.fm: { ratio, depth } に対応します(独立したエフェクトではありません)。
トレモロとビブラート
1~20 Hz 帯域の周期的な振幅変調または周波数変調は、トレモロ/ビブラートを示唆します。振幅またはピッチを時間の経過に沿って追跡し、detect_lfo を呼び出してください(interpret-detect-lfo を参照)。
ビットクラッシャー/ディストーション
| 時間領域の特徴 | エフェクト |
|---|---|
| エイリアシングアーティファクトを伴う段階的/量子化された波形 | bitcrusher |
| 高調波が加わった平坦な頂部の波形 | distortion |
コーラス/フランジャー/フェイザー
時間とともに掃引するコムフィルターのパターンによって、スペクトルに移動するノッチが生じます。アルゴリズムでの判別は困難なため、人による確認が必要であることを示してください。
2.7 LFO 変調を検出する (低~中程度)
LFO は、パラメーターに周期的な変調を加える、音声帯域未満(0.1~20 Hz)の信号です。パラメーターを時間の経過に沿って追跡した後、detect_lfo を実行します。
from analyze import detect_lfo
# 1. Track amplitude (or pitch, or spectral centroid) at regular intervals
window_ms = 10
samples_per_window = int(sample_rate * window_ms / 1000)
amp_over_time = [
float(np.max(np.abs(data[i:i + samples_per_window])))
for i in range(0, len(data) - samples_per_window, samples_per_window)
]
# 2. Detect periodicity
lfo = detect_lfo(np.array(amp_over_time), 1000 / window_ms)
# -> { "frequency": 5.0, "depth": 0.12 } or None
追跡するパラメーターごとの対応
| 追跡するパラメーター | LFO の対象 |
|---|---|
| 振幅 | gain |
| ピッチ | frequency または detune |
| スペクトル重心 | filter.frequency |
| パン位置 | pan |
出力
lfo: { type: "sine", frequency: lfo.frequency, depth: lfo.depth, target: "gain" }
変調の形状に基づいて type を選択します。滑らかな正弦波は sine、急峻なランプは sawtooth、急激な切り替わりは square です。
2.8 多層サウンドとステレオ定位を検出する (中程度)
複数の基本周波数 → MultiLayerSound
スペクトルのピークを調べます。強いピークが 2 つ以上あり、共通する 1 つの基本周波数の整数倍でない場合、そのサウンドはレイヤー化されています。
from scipy.signal import find_peaks
peaks, props = find_peaks(spectrum, height=float(np.max(spectrum)) * 0.2)
peak_freqs = sorted(freqs[peaks])
# Check pairwise ratios. If no shared fundamental explains all peaks, treat as layered.
検出された各基本周波数について、パイプライン全体(周波数、エンベロープ、波形、フィルター、エフェクト)を実行し、基本周波数ごとに 1 つの Layer を出力します。
{
layers: [
{ source: { ... }, envelope: { ... }, gain: 0.2 },
{ source: { ... }, envelope: { ... }, gain: 0.15, delay: 0.04 },
]
}
通常、先行するレイヤーの delay は 0(省略)に設定します。後続のレイヤーでは、測定した開始タイミングの差に合わせて delay を設定します。
ステレオとパン
from analyze import analyze_stereo
stereo = analyze_stereo(data)
# -> { "pan": 0.3, "stereo_width": 0.7 }
pan の絶対値 | 出力 |
|---|---|
< 0.05 | 省略(pan: 0 がデフォルト) |
0.05 - 1 | pan: <value> |
stereo_width > 0.5 かつ |pan| < 0.05 の場合は、ステレオエフェクト(コーラス、デュアルレイヤー)がかかっている可能性があります。-0.5/+0.5 にパンを設定した 2 つのレイヤーに分割することを検討してください。
フォールバック
音を合成できない場合(複雑なトランジェント、録音素材、還元できない質感)は、次にフォールバックしてください。
{ source: { type: "sample", url: "..." } }
再合成するのではなく、元のオーディオファイルを直接使用する必要があることを記載してください。
3. UI イベントのレシピ
UI イベントの種類ごとの具体的な SoundDefinition テンプレートです。プロンプトの経路でベースレイヤーとして使用されます。
3.1 クリック - サイン波 + 弱い FM、非常に短いディケイ (HIGH)
軽い FM を加えた、短く上昇するサイン波スイープです。スイープがクリックに「キレ」を与え、FM が金属的にならない範囲で倍音の厚みを加えます。
誤り(ディケイが長すぎて、チャイムのように聞こえます):
{ source: { type: "sine", frequency: 1300 }, envelope: { decay: 0.5 }, gain: 0.18 }
正解:
{
source: { type: "sine", frequency: { start: 200, end: 700 }, fm: { ratio: 0.5, depth: 80 } },
envelope: { attack: 0, decay: 0.06, sustain: 0, release: 0.02 },
gain: 0.25,
}
参照:.web-kits/core.ts click。
3.2 完了 - 4 音の上昇アルペジオ (MEDIUM-HIGH)
success と同じ C メジャーの三和音ですが、上に C6 を加え、delay の間隔を 15 ms と短くしています。これにより、個別の音程として聞こえるのではなく、ひと続きの動作として聞こえます。
参照:.web-kits/core.ts complete。
3.3 エラー - のこぎり波 + 方形波のレイヤーと下降スイープ (HIGH)
オクターブ違いの 2 つの下降スイープを重ねています。ローパスフィルターにより、耳障りにならないようにしています。同じ形状を delete にも使えます(ディケイを少し長くします)。
誤り(フィルターがなく、ブザーのように聞こえます):
{ source: { type: "sawtooth", frequency: { start: 320, end: 140 } }, envelope: { decay: 0.25 }, gain: 0.22 }
参照:.web-kits/core.ts error、_delete。
3.4 モーダルを閉じる - 下降するサイン波スイープ (MEDIUM)
modalOpen の逆です。閉じる動作は表示する動作より控えめに感じられるよう、範囲を狭くしています。同じ理由で gain も少し低くします。
drawer-close には 800 -> 350 を使用します。dropdown-close には 900 -> 500 を使用します。
参照:.web-kits/core.ts modalClose、drawerClose、dropdownClose。
3.5 モーダルを開く - 上昇するサイン波スイープ (MEDIUM)
約 430 Hz から約 1400 Hz まで 80 ms かけてスイープする、単一のサイン波です。FM もフィルターも使用しません。クリーンな音で「出現」を表現します。
drawer-open には、開始周波数を少し低く(約 350 Hz)し、ゲインも低く(約 0.08)します。
dropdown-open には、範囲を狭く(500 -> 1200)し、ディケイを約 60 ms にします。
参照:.web-kits/core.ts modalOpen、drawerOpen、dropdownOpen。
3.6 通知 - FM を豊かに含むサイン波と軽いリバーブ (HIGH)
完全 5 度違いの FM ベルを 2 つ、100 ms の delay を挟んで鳴らします。fm.ratio: 1.5 が非調和なきらめきを生み、各レイヤーに同じリバーブをかけることで、音が一体になります。
ding には、単一レイヤー、fm.ratio: 3.5、リバーブの decay: 0.8 を使用します。
mention には、より低い基音(660 Hz)、fm.ratio: 2.5、少し長めのアタックを使用します。
参照:.web-kits/core.ts notification、ding、mention、badge。
3.7 成功 - 3 音の上昇サイン波コード (HIGH)
C5 / E5 / G5 のサイン波を 3 つ重ね、0.07 s の delay を順に加えます。最も高い音には短い上昇スイープ(G5 -> A5)を加え、コードがただ止まるのではなく「上向きに」解決するようにします。
各レイヤーのゲインの合計は 0.45 で、0.6 の上限を十分に下回っています。
参照:.web-kits/core.ts success。
3.8 スウッシュ - スイープするバンドパスを通したホワイトノイズ (MEDIUM)
中心周波数が 300 Hz から 4 kHz までスイープするバンドパスフィルターで、ホワイトノイズを成形します。スイープの方向が動作を表します。ピークが静止時の周波数より高ければ上向きのスウッシュ、低ければ(例:静止時 2500、ピーク 400)下向きのスウッシュです。
slide-up には、ピークを 3500 にした同様の形状を使用します。slide-down にはピンクノイズに切り替え、envelope: { decay: 0.12, peak: 500 } を使用します(フィルターエンベロープにアタックはありません)。
参照:.web-kits/core.ts swoosh、slide、slideUp、slideDown。
3.9 タップ - 固定された高音のサイン波 + FM、極短 (HIGH)
スイープなしの高音を 1 つ使い、強い FM を加えて、ディケイを 20 ms 未満にします。これが「キー押下」の基本形です。
誤り(周波数が低すぎて、ドスンという音に聞こえます):
{ source: { type: "sine", frequency: 200 }, envelope: { decay: 0.015 }, gain: 0.2 }
正解:
{
source: { type: "sine", frequency: 1300, fm: { ratio: 0.5, depth: 100 } },
envelope: { attack: 0, decay: 0.015, sustain: 0, release: 0.005 },
gain: 0.2,
}
参照:.web-kits/core.ts tap、keyPress。
3.10 Tick - 可能な限りかすかなサイン波 (MEDIUM)
タップ系で最も高い周波数です。減衰は 15 ms 未満です。Tick は頻繁に鳴り、支配的になってはいけないため、gain は約 0.15 に制限します。
スクロールスナップでは gain を 0.08 に、フォーカス/フォーカス解除では 0.04~0.06 に下げます。
参考:.web-kits/core.ts の tick、scrollSnap、focus、blur。
3.11 Toggle - 遅延を挟んだペアのサイン波(方向が重要) (MEDIUM)
短いサイン波を 2 つ使用します。C7(2093 Hz)と G7(3136 Hz)を 25 ms 間隔で鳴らします。
toggle-on:低い音を先に鳴らし、その後に高い音を鳴らします(上昇=有効化)。toggle-off:高い音を先に鳴らし、その後に低い音を鳴らします(下降=無効化)。
同じ構成は copy(1200 Hz の後に 1400 Hz、間隔は 40 ms)や sync(C5 の後に G5)にも使用できます。
参考:.web-kits/core.ts の toggleOn、toggleOff、copy、sync。
3.12 Whoosh - ページ全体の遷移向けの、より長くゆっくりしたスウッシュ (LOW-MEDIUM)
Swoosh と同じ構成ですが、すべてを引き伸ばします。ジェスチャーが穏やかに始まるよう、フィルターのアタックを 4 倍長くします(0.04 s と 0.01 s の比較)。時間幅が長いため、gain を少し高くします。
pageEnter はホワイトノイズと中心周波数 3000 のバンドパスを使用します。pageExit はピンクノイズを使用し、バンドパスのエンベロープを反転させます(減衰のみ、ピークは 400)。
参考:.web-kits/core.ts の whoosh、pageEnter、pageExit。
4. ムードの語彙
形容詞からノブへのマッピングを基本レシピに重ねます。
4.1 Airy - ノイズソース+ピーク周波数の高いバンドパス (LOW-MEDIUM)
変更内容:
sourceを{ type: "noise", color: "white" }に置き換えます。filterを、ピークが高い周波数(4~6 kHz)に達するバンドパスエンベロープに置き換えます。- 音が急に立ち上がるのではなく、フェードインするように、
envelope.attackを 0.02~0.04 s に延ばします。 gainを 0.08~0.12 に下げます。
基本レシピが音程のある音(サイン波、三角波など)の場合、このムードではソースを完全に置き換えます。これは構造上の変更です。
4.2 Bright - ローパスなし、必要に応じて FM のきらめき (MEDIUM)
変更内容:
lowpassタイプのfilterがあれば削除するか、カットオフを 6 kHz より高くします。- 基本レシピで
triangleを使用していた場合は、きらめきを加えるためにfm: { ratio: 2.5, depth: 50 }を設定したsineに変更します。 gainを少し上げても構いません(+0.02)が、予算内に収めてください。
4.3 Glassy - 高い FM 比率+リバーブ (MEDIUM)
変更内容:
source.type: "sine"。source.fm: { ratio: 3.5, depth: 200-300 }。effects: [{ type: "reverb", decay: 0.7, damping: 0.5, mix: 0.15 }]を追加します。- ベル音が鳴り響くように、
envelope.decayを少なくとも 0.3 s に延ばします。
参考:.web-kits/core.ts の ding、sparkle、star。
4.4 Lo-fi - ビットクラッシャー+ローパス (MEDIUM)
変更内容:
filter: { type: "lowpass", frequency: 1500 }を追加します。effects: [{ type: "bitcrusher", bits: 6-8, mix: 0.7-1 }]を追加します。- ビットクラッシングによって知覚される音量が上がるため、必要に応じて
gainを 0.02 下げます。
mood-retro とよく合います。
4.5 Metallic - 非調和 FM 比率 (MEDIUM)
変更内容:
source.type: "sine"(より刺激的な音にする場合はsquare)。source.fm: { ratio: 2.76, depth: 300-400 }。2.76 は.web-kits/core.tsのbadgeで使用されている非調和比率で、ベルの金属音のように聞こえます。- リリースを短くします。金属音を持続させてはいけません。
mood-warm との併用は避けてください。互いの効果が打ち消されます。
参考:.web-kits/core.ts の badge。
4.6 Organic - 三角波+わずかなデチューン+軽いリバーブ (LOW-MEDIUM)
変更内容:
source.type: "triangle"。- ごくわずかな音程の揺らぎを加えるため、
source.detune: 5-10を追加します。 - 立ち上がりが硬いクリック音にならないよう、
envelope.attackを 0 から 0.003~0.008 s に上げます。 - 小さなリバーブ(
mix: 0.05-0.1)を追加します。
mood-warm とよく合います。自然な質感を損なうため、mood-metallic や mood-lofi との併用は避けてください。
4.7 パンチのある音 — アタックなし、非常に短いディケイ (MEDIUM)
変更:
envelope.attack: 0。envelope.decay: <= 0.06。envelope.sustain: 0。envelope.release: <= 0.015。- エネルギーがより短い時間に集中するため、
gainを +0.05 上げても問題ありません。
音源の形状によるムードとは独立しているため、warm/bright/glassy/metallic の上に適用します。
4.8 レトロ — 矩形波またはノコギリ波+ローパス+ビットクラッシャー (MEDIUM)
変更:
source.type: "square"(または"sawtooth")。- エイリアシングを和らげるため、
filter: { type: "lowpass", frequency: 3000 }を追加します。 effects: [{ type: "bitcrusher", bits: 8, sampleRateReduction: 2-4, mix: 1 }]を末尾に追加します。
上昇または段階的なピッチスイープ(コイン、パワーアップ)と自然に組み合わせられます。
4.9 温かみのある音 — ローパス+軽いリバーブ (MEDIUM)
ベースレシピの上に適用する変更:
filter: { type: "lowpass", frequency: 2500 }(または 2〜3 kHz)を追加します。- 必要に応じて
effects: [{ type: "reverb", decay: 0.4, mix: 0.1 }]を追加します。 - ベースで
sawtoothまたはsquareを使用している場合は、音源自体をより丸みのある音にするため、triangleに変更します。
ベースにすでにローパスがある場合は、カットオフを約 30% 下げます。
5. レイヤリングパターン
1 レイヤー、2 レイヤー、またはコードスタックを使う場面。
5.1 上昇するコード — ディレイを段階的に設定した 3〜4 レイヤー (MEDIUM)
3〜4 個のサイン波レイヤーでメジャートライアド(C-E-G または C-E-G-C)を構成します。「音符らしく」感じさせるには delay を約 70 ms ずつ増やし、「ひとつの動作のように」感じさせるには約 15 ms ずつ増やします。
最上位のレイヤーには小さな上昇スイープを加え、コードが止まるのではなく解決するようにします。
レイヤー数は 4 以下にしてください。レイヤーのゲインの合計は <= 0.6 にします。あるレイヤーの sustain > 0 の場合は、余韻が段違いにならないよう、すべてのレイヤーで同程度のサステイン値を使用します。
5.2 クリック+本体 — 持続音にトランジェントレイヤーを重ねる (MEDIUM)
2 つのレイヤーを同時に鳴らします(delay なし)。
- サブ 10 ms のディケイを持つ高周波のトランジェント(3〜5 kHz)—「スティック」。
- より長いディケイを持つ低周波の本体(80〜300 Hz)—「ドラム」。
用途:送信ボタン、確定操作、ドラムのような UI フィードバックなど、体感上の重みが必要なあらゆる場面です。両方のレイヤーで同じ音源 type(通常は sine)を使い、ひとつのイベントとして聞こえるようにします。
ゲインはおおむね均等にし、トランジェントを本体より少し小さくします。
5.3 オクターブペア — 1 オクターブ離し、ディレイを付けた 2 レイヤー (MEDIUM)
完全五度またはオクターブ離れた 2 つのレイヤーを、20〜50 ms の delay で隔てます。低音を先にするか高音を先にするかで、「オン」と「オフ」、「開く」と「閉じる」などを表現できます。
レイヤーのゲインの合計は 0.5 未満にしてください。2 つ目の音が途切れて聞こえないよう、両方のエンベロープを一致させます。
2 レイヤーを超える構成が必要になった場合は、代わりに layer-ascending-chord を使います。
5.4 単一レイヤー — Layer を直接出力する (HIGH)
レシピで音源が 1 つだけ必要な場合は、Layer の形状を直接出力します({ layers: [...] } でラップしません)。エンジンはどちらの形式も受け付けますが、ラップしない Layer 形式が標準的な簡潔表現です。
const sound: SoundDefinition = {
source: { type: "sine", frequency: 1300 },
envelope: { decay: 0.012, release: 0.004 },
gain: 0.18,
};
クリック、タップ、ティック、ホバー、フォーカス、ぼかし、スクロールスナップ、単音の通知、シンプルなスウッシュに使用します。
6. エフェクトのレシピ
各エフェクトタイプを使う場面と方法。
6.1 バンドパスノイズのスウッシュ — フィルターエンベロープで動きを表現 (MEDIUM)
レシピはレイヤーの effects ではなく、filter に設定します。
filter: {
type: "bandpass",
frequency: <resting Hz>,
resonance: 1-3,
envelope: { attack: 0.01-0.04, peak: <target Hz>, decay: 0.08-0.2 },
}
- ピークが静止時の値より上なら上昇するスウッシュです。
- ピークが静止時の値より下なら下降するスウッシュです。
resonanceが高い(>2)と口笛のようになり、低い(<1.5)と幅広い音になります。
音源は noise(鋭い音にはホワイト、柔らかい音にはピンク)にします。音源の振幅エンベロープは、ノイズの鳴る範囲をゲートするだけです。
6.2 ビットクラッシャー — レトロ/ローファイな仕上げ (LOW-MEDIUM)
bits:4〜8。低いほどザラつきが増します。4 未満ではノイズになります。sampleRateReduction:1(オフ)〜8(強いエイリアシング)。bits: 8と組み合わせると、8 ビットゲーム機のような音になります。mix:通常は 1 です。ビットクラッシュした音にドライ信号を混ぜると、濁って聞こえます。
エイリアシングの角を和らげるため、square または sawtooth の音源とローパスを組み合わせるのが最適です。
effect-reverb-tail との重ねがけは避けてください。量子化ノイズが広がってしまいます。
6.3 FMベル - 高い比率、高い深度 (MEDIUM)
source.fm: { ratio, depth } はエフェクトノードではなく、構造的な要素です。ベル音を作るには、次のようにします。
ratio:調和的なベル音には2.5~3.5、「バッジ」のような非調和の金属音には2.76を指定します。depth:150~400を指定します。値が高いほど、音が鋭くなります。envelope.decay:ベルの響きを残すため、0.3秒以上にします。
明るい「ディン」という音には、ratio: 3.5、depth: 250を指定し、リバーブ(decay: 0.7, mix: 0.15)を加えます。
芯のある鈍い「ドン」という音には、ratio: 0.5、depth: 200を指定し、短いエンベロープを使います。
mood-glassyまたはmood-metallicと組み合わせます。
6.4 ローパスで温かみを加える - 最も安全に追加できるフィルター (MEDIUM)
filter: { type: "lowpass", frequency: 2500, resonance: 0.7 }
frequency:「温かみ」を出すには1500~3000 Hzを指定します。1000未満では音がこもり始めます。resonance:省略するか、0.7~1.5に設定します。2を超えると、カットオフ周波数自体が口笛のように鳴り始めます。
リバーブ、FM、ほとんどのムードと安全に組み合わせられます。どのような音源でも、耳障りな音を手早く取り除けます。
動的に温かみを加える場合(明るいアタックから温かみのあるサステインへ)は、フィルターエンベロープを追加します。
filter: {
type: "lowpass",
frequency: 2500,
envelope: { attack: 0, peak: 6000, decay: 0.08 },
}
6.5 リバーブの余韻 - 小さな空間、控えめなミックス (MEDIUM)
UI のデフォルトのリバーブ:
decay:0.3~0.6秒。damping:0.4~0.6(余韻の高周波を抑えます。これを設定しないと、リバーブが金属的に聞こえます)。mix:0.08~0.15。0.2を超えると、音楽制作向けのエフェクトのように感じられ始めます。
ベルのようなサウンド(通知、ディン)でレイヤーごとにリバーブをかけるには、各音が独立して響くよう、リバーブをレイヤーのeffects配列内に配置します。コードや遷移に共有のリバーブをかけるには、MultiLayerSoundの最上位のeffectsに配置します。
リバーブとディレイを重ねないでください。どちらか一方を選びます。
7. 出力の検証
出力するすべてのSoundDefinitionは、ユーザーに返す前にチェックに合格する必要があります。
7.1 長さの上限 - トランジェントは1秒、絶対最大3秒 (MEDIUM)
推定合計時間:
estimated = (envelope.attack ?? 0)
+ envelope.decay
+ (envelope.release ?? 0)
+ max(0, longestEffectTail) // reverb decay, delay time * 4
目標:
- クリック/タップ/チック/ホバー/フォーカス:<= 0.1秒。
- トグル/コピー/同期:<= 0.2秒。
- モーダル/ドロワー/ドロップダウンの開閉:<= 0.3秒。
- 成功/完了/通知:<= 0.8秒。
- シュッという音/ページ遷移:<= 0.5秒。
厳格な上限は3秒です。これより長い音はUIサウンドに適しません。
validateスクリプトは推定時間を計算し、3秒を超えるレイヤーにフラグを付けます。
7.2 エンベロープの妥当性 - ディケイを0にしない、リリースなしでサステインを無限にしない (HIGH)
必須:
envelope.decay > 0(常に)。最小値として0.005を設定します。envelope.sustain > 0の場合、envelope.releaseを指定し、> 0にする必要があります。
推奨:
envelope.attack:パーカッシブな音では0、持続音では0.003~0.05、アンビエントサウンドでは最大0.1。envelope.decay + envelope.release:UIサウンドでは2秒以下にします。それを超えると、インターフェイスのフィードバックではなく音楽になってしまいます。envelope.sustain:トランジェントでは0、「余韻が残る」音では0.03~0.15、ホールドするループでのみ0.3~0.7。
validateスクリプトは、decay <= 0、releaseなしでのsustain > 0、および3秒を超える合計時間にフラグを付けます。
7.3 周波数の範囲 - 20 Hz~20 kHz、両端の値にも意味を持たせる (HIGH)
厳格な範囲:
source.frequency(またはスイープのstart/endの両方):20 Hz <= f <= 20000 Hz。filter.frequency:20 Hz <= f <= 20000 Hz。filter.envelope.peak:filter.frequencyと同じ範囲。
推奨するUIの範囲:
- 音程のある音源:80 Hz <= f <= 8000 Hz。
- 高域のトランジェントレイヤー(クリック音、スティック音):最大5 kHz。
- 低域レイヤー(芯、ドラム):60~200 Hz。
8 kHzを超える音はスマートフォンのスピーカーでは聞こえないおそれがあり、60 Hzを下回る音はノートパソコンのスピーカーでは聞こえないおそれがあります。
validateスクリプトは、厳格な範囲外のすべての周波数にフラグを付けます。
7.4 ゲインの予算 — レイヤー全体のゲインを0.6未満に保つ (HIGH)
単一レイヤー:
- 一般的なUIイベントでは、
gainを0.04~0.3にします。 - バックグラウンドのティック/スクロールスナップ:0.04~0.10。
- 中程度の重要度(クリック、タップ、ホバー):0.12~0.20。
- 高い重要度(成功、通知):0.16~0.25。
複数レイヤー:
- すべての
layer.gainの合計は0.6以下でなければなりません。 - 上限を超えた場合は、どれか1つを下げるのではなく、すべてのレイヤーを比例してスケーリングします。
サウンドに強めのリバーブ(mix > 0.15)またはディストーションが含まれる場合は、ゲインの予算を20%下げます。
validateスクリプトは、個々のレイヤーが0.4を超えている場合と、合計が0.6を超えている場合の両方を警告します。
7.5 スキーマへの準拠 — patch.schema.jsonに対して検証する (CRITICAL)
出力されるすべてのSoundDefinitionは、packages/audio/schemas/patch.schema.json(#/$defs/SoundDefinition)に対して検証に合格する必要があります。
よくある誤り:
envelopeでdecayが欠落している(必須)。lfoでtargetが欠落している(必須)。panを[-1, 1]の範囲外に設定している。lowpass | highpass | bandpass | notch | allpass | peaking | lowshelf | highshelf | iirのいずれでもないfilter.typeを使用している。LayerまたはMultiLayerSoundにないトップレベルフィールド(例:name、description)を追加している。スキーマではadditionalProperties: falseに設定されています。MultiLayerSound.effects(ミックス済みバス上のチェーン)とLayer.effects(単一レイヤー上のチェーン)を混同している。
validateスクリプトは、各ルールのexampleフィールドに対してJSON Schemaバリデーターを実行します。違反が1つでもあると、ビルドは中断されます。