关于 tonejs
tonejs 是一项指导使用 Tone.js 和 Web Audio API 构建浏览器交互式音乐应用的技能,面向音频应用开发者。内容涵盖合成、采样、节奏调度、效果处理及音画同步。
主要功能
- 01创建合成器与多音乐器
- 02播放音频文件与采样
- 03按音乐时间调度事件
- 04串接效果并自动化参数
- 05同步音频与视觉更新
适用场景
- 01构建步进音序器或鼓机
- 02为游戏添加音乐和音效
- 03制作实时音频可视化
- 04实现精确定时的音乐播放
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ plyght/ tonejs-skill --skill tonejs安装后在 Claude Code 中输入/tonejs确认已加载。
附带文件README.md
详细介绍
Tone.js 技能
通过 Tone.js 的高级抽象,使用 Web Audio API 在浏览器中构建交互式音乐应用。
何时使用此技能
在以下场景中使用 Tone.js:
- 创建合成器、采样器或乐器
- 构建步进音序器、鼓机或 DAW
- 为游戏添加音效或音乐
- 实现与声音同步的音频可视化
- 使用效果器实时处理音频
- 以精准时序安排音乐事件
- 处理音乐概念(音符、速度、小节)
核心概念
1. 上下文与初始化
AudioContext 必须由用户交互触发启动(浏览器要求):
import * as Tone from "tone";
// ALWAYS call Tone.start() from user interaction
document.querySelector("button").addEventListener("click", async () => {
await Tone.start();
console.log("Audio context ready");
// Now safe to play audio
});
2. 音频图与路由
所有音频节点都连接到通向 Tone.Destination(扬声器)的图中:
// Basic connection
const synth = new Tone.Synth().toDestination();
// Chain through effects
const synth = new Tone.Synth();
const filter = new Tone.Filter(400, "lowpass");
const delay = new Tone.FeedbackDelay(0.125, 0.5);
synth.chain(filter, delay, Tone.Destination);
// Parallel routing (split signal)
const reverb = new Tone.Reverb().toDestination();
const delay = new Tone.Delay(0.2).toDestination();
synth.connect(reverb);
synth.connect(delay);
3. 时间与调度
Tone.js 使用音乐记谱法抽象时间:
"4n"= 四分音符"8n"= 八分音符"2m"= 两个小节"8t"= 八分音符三连音- 数字 = 秒
重要:始终使用传递给回调的 time 参数:
// CORRECT - sample-accurate timing
const loop = new Tone.Loop((time) => {
synth.triggerAttackRelease("C4", "8n", time);
}, "4n");
// WRONG - JavaScript timing is imprecise
const loop = new Tone.Loop(() => {
synth.triggerAttackRelease("C4", "8n"); // Will drift
}, "4n");
4. Transport 系统
用于同步事件的全局计时器:
// Schedule events on the Transport
const loop = new Tone.Loop((time) => {
synth.triggerAttackRelease("C4", "8n", time);
}, "4n").start(0);
// Control the Transport
Tone.Transport.start();
Tone.Transport.stop();
Tone.Transport.pause();
Tone.Transport.bpm.value = 120; // Set tempo
分步说明
任务 1:创建基础合成器
- 导入 Tone.js
- 创建合成器并连接到输出
- 等待用户交互以启动音频
- 使用
triggerAttackRelease播放音符
import * as Tone from "tone";
const synth = new Tone.Synth().toDestination();
button.addEventListener("click", async () => {
await Tone.start();
// Play C4 for an eighth note
synth.triggerAttackRelease("C4", "8n");
});
任务 2:创建复音乐器
- 使用
PolySynth封装单音合成器 - 传入多个音符来演奏和弦
- 按需释放指定音符
const polySynth = new Tone.PolySynth(Tone.Synth).toDestination();
// Play a chord
polySynth.triggerAttack(["C4", "E4", "G4"]);
// Release specific notes
polySynth.triggerRelease(["E4"], "+1");
任务 3:加载并播放音频文件
- 创建
Player或Sampler - 等待
Tone.loaded()Promise 完成 - 开始播放
const player = new Tone.Player("https://example.com/audio.mp3").toDestination();
await Tone.loaded();
player.start();
// For multi-sample instruments
const sampler = new Tone.Sampler({
urls: {
C4: "C4.mp3",
"D#4": "Ds4.mp3",
"F#4": "Fs4.mp3",
},
baseUrl: "https://example.com/samples/",
}).toDestination();
await Tone.loaded();
sampler.triggerAttackRelease(["C4", "E4"], 1);
任务 4:创建循环模式
- 使用
Tone.Loop或Tone.Sequence创建模式 - 将 time 参数传递给乐器触发函数
- 启动循环和 Transport
const synth = new Tone.Synth().toDestination();
const loop = new Tone.Loop((time) => {
synth.triggerAttackRelease("C4", "8n", time);
}, "4n").start(0);
await Tone.start();
Tone.Transport.start();
任务 5:添加效果处理
- 创建效果器实例
- 按所需顺序连接(串联或并联)
- 按需调整湿/干混合比例
const synth = new Tone.Synth();
const distortion = new Tone.Distortion(0.4);
const reverb = new Tone.Reverb({
decay: 2.5,
wet: 0.5, // 50% effect, 50% dry
});
synth.chain(distortion, reverb, Tone.Destination);
任务 6:自动化参数
- 通过属性访问参数(例如
frequency、volume) - 使用
rampTo、linearRampTo、exponentialRampTo等方法 - 使用 time 参数安排更改
const osc = new Tone.Oscillator(440, "sine").toDestination();
osc.start();
// Ramp frequency to 880 Hz over 2 seconds
osc.frequency.rampTo(880, 2);
// Set value at specific time
osc.frequency.setValueAtTime(440, "+4");
// Exponential ramp (better for frequency)
osc.frequency.exponentialRampTo(220, 1, "+4");
任务 7:将视觉效果与音频同步
- 使用
Tone.Draw.schedule()更新视觉效果 - 在与音频事件相同的回调中安排更新
- 视觉效果会在音频播放前立即更新
const loop = new Tone.Loop((time) => {
synth.triggerAttackRelease("C4", "8n", time);
// Schedule visual update
Tone.Draw.schedule(() => {
element.classList.add("active");
}, time);
}, "4n");
常见模式
模式:步进音序器
const synth = new Tone.Synth().toDestination();
const notes = ["C4", "D4", "E4", "G4"];
const seq = new Tone.Sequence(
(time, note) => {
synth.triggerAttackRelease(note, "8n", time);
},
notes,
"8n"
).start(0);
Tone.Transport.start();
模式:概率播放
const loop = new Tone.Loop((time) => {
if (Math.random() > 0.5) {
synth.triggerAttackRelease("C4", "8n", time);
}
}, "8n");
模式:动态效果器参数
const filter = new Tone.Filter(1000, "lowpass").toDestination();
const lfo = new Tone.LFO(4, 200, 2000); // 4Hz, 200-2000Hz range
lfo.connect(filter.frequency);
lfo.start();
音效设计原则
核心见解
听觉处理速度比视觉快 10 倍(约 25 ms 对比约 250 ms)。声音能提供即时反馈,让交互感觉更灵敏。即使视觉反馈完全相同,有点击声的按钮也会让人感觉比没有点击声的按钮响应更快。
声音能瞬间传达情绪。 单个音调传达成功、错误或紧张感,比视觉编排更有效。音频和视觉共同讲述同一个故事时,体验会比单独使用任一形式更出色。
少即是多。 大多数交互应保持静音。将声音留给重要时刻:重要操作的确认、不可忽视的错误、状态转换和通知。为确保无障碍体验,声音始终应与视觉反馈配合——声音用于增强效果,而非取代视觉反馈。参考游戏的设计——它们在信息丰富、富有情感且不打扰人的音频反馈方面已经做得非常成熟。
设计理念
优秀的声音设计能够提升各种平台上的用户体验,包括 Web 应用、移动应用、桌面应用和游戏。这些原则普遍适用,无论是设计通知声音、UI 反馈还是音乐交互。
声音是一种人人都能理解的通用语言。设计音频时:
先思考基本问题:
- 这个应用/功能的本质是什么?
- 你想唤起什么情绪?
- 它如何与应用的视觉美学相匹配?
- 用户不看屏幕时,如何理解这次交互?
考虑使用情境:
- 用户会在哪里听到声音?(口袋里、办公桌旁、繁忙的街道上、安静的房间里)
- 用户当时在做什么?(工作、通勤、玩游戏)
- 用户会多久听到一次?(每天一次还是数百次)
通知声音设计
有效的通知声音具有以下特点:
1. 易于区分
- 创造独特的声音标识,让人能识别出应用
- 使用具有特色的音色或旋律模式
- 将简单元素叠加,增强辨识度
- 不要模仿系统默认声音或其他常见声音
2. 传达含义
- 声音应与消息相关联(不必直白,但要有所暗示)
- 水声或天气相关内容可用流动感音色,提醒可用金属音色,成功提示可用温暖音色
- 用音色和包络来暗示内容
- 采用抽象表现,而非音效
3. 友好且恰当
- 让紧迫程度与消息的重要性相匹配
- 柔和的声音:起音缓和(50ms 以上),音色平滑(正弦波、三角波)
- 紧急的声音:起音快速(<5ms),音色明亮(方波、FM 合成)
- 音量和明亮度可体现优先级
4. 简单清晰
- 避免复杂的叠加或密集的谐波内容
- 一到两个音符的模式比旋律更合适
- 反复聆听时,悦耳的音色更容易让人接受
- 清晰胜过巧思
5. 不打扰且适合重复播放
- 时长:通知声音最长为 0.3-0.8 秒
- 高频播放的声音应采用较柔和的音色(正弦波、三角波)
- 避免刺耳、复杂或过于明亮的音色
- 每天听到 50 次以上时仍应悦耳
6. 能穿透噪声,但不刺耳
- 中频(300-3000Hz)最有效
- 避免极高频(>8kHz)和极低频(<80Hz)
- 针对嘈杂环境进行设计,同时避免声音刺耳
- 三角波和正弦波比方波更柔和
UI 声音设计
对于按钮、交互和转场:
1. 少量使用
- 仅在重要的状态变化或确认操作时添加声音
- 静音往往是最佳选择
- 不要为每次悬停、鼠标经过或细微交互都添加声音
- 仅在有意义的时刻提供音频反馈
2. 音量应符合用途
- UI 声音:比通知声音低得多(-20 至 -30dB)
- 用户手持设备,细微的声音更合适
- 通知:音量应更高,以穿透环境噪声(-10 至 -15dB)
3. 同步至关重要
- 音频和视觉更新必须同时发生(容差 < 10ms)
- 不同步会让人感觉迟钝、不专业
- 使用精确调度,而非大致计时
4. 与交互特征相匹配
- 快速点击:时长短(<50ms),频率较高
- 重按:时长较长(100-200ms),频率较低
- 拖动操作:提供连续反馈或保持静音
- 操作成功:音高上升或音色明亮
- 操作失败:音高下降或音色暗沉
5. 传达深度和移动感
- 向前移动:音高随时间上升
- 向后移动:音高随时间下降
- 打开:包络扩展(淡入)
- 关闭:包络收缩(淡出)
- 包络形状可暗示物理动作
创意流程
1. 从音色素材开始
- 探索不同的合成类型(正弦波、三角波、方波、FM、AM、噪声)
- 录制或合成与概念相关的声音
- 实物、日常声音和传统乐器都适用
- 构建包含 5-10 种候选声音的音效库
2. 让声音匹配用途
- 每个 UI 元素都应有一种符合其特定用途的声音
- 按钮点击:快速且灵敏(高音、快速衰减)
- 切换开关:开和关状态使用两种不同的声音
- 通知:更长、更有辨识度
- 无需提供多个选项,选出最合适的即可
3. 使用任意声音来源
- 传统乐器:卡林巴琴、铃铛、钢琴、马林巴琴
- 实物材料:金属、玻璃、木材、塑料
- 合成声音:纯音、FM 合成、滤波噪声
- 非常规来源:烤面包机、门、机械声
- 声音来源不如最终音色特征重要
4. 通过分层丰富音色
- 组合 2-3 个简单元素,而不是使用一种复杂声音
- 基础音调 + 泛音层 + 质感
- 每一层都各有用途(饱满度、明亮度、特征)
- 即使加入分层,也要保持总时长简短
技术注意事项
1. 音频清晰
- 淡入/淡出以避免咔嗒声(10-50ms)
- 移除直流偏移
- 统一标准化音量
- 避免截断或突然结束
2. 频率滤波
- 使用 80Hz 高通滤波器(去除超低音)
- 小型扬声器无法重现低频
- 必要时使用 8kHz 低通滤波器(去除刺耳的高频)
- 将能量集中在 300-3000Hz,以达到最佳效果
3. 跨平台设计
- 以最差的扬声器系统为设计基准(手机、笔记本电脑)
- 耳机能呈现更多细节,但应针对扬声器进行优化
- 完全避免超低音(<80Hz)
- 中频适用于各种设备
4. 时长指南
- 通知:0.3-0.8 秒
- 按钮点击:0.05-0.1 秒
- 切换开关:0.05-0.15 秒
- 过渡:0.1-0.3 秒
- 所有 UI 声音都应简短,以尊重用户的注意力
5. 用户控制
- 始终提供关闭声音的设置
- 音量控制应独立于系统音量
- 按声音类型分别设置开关(通知与 UI 反馈)
- 遵循系统范围的静音开关设置
6. 精确同步
- 将音频安排在与视觉内容完全相同的时刻播放
- 10-20ms 的延迟可以被察觉,而且会让人感觉不对
- 使用精确的定时 API,而非 setTimeout/setInterval
- 音画同步对感知响应速度至关重要
设计检查清单
设计声音时的实现注意事项:
- 易于区分:使用独特的波形/包络,避免模仿系统默认声音
- 适合重复播放:使用简单、悦耳的音色(正弦波、三角波),而非复杂或刺耳的音色
- 跨平台:频率介于 300-3000Hz,并使用 80Hz 高通滤波
- 清晰可闻但不刺耳:使用中频,避免极高频(>8kHz)和极低频(<80Hz)
- 时长简短:通知为 0.3-0.8 秒,UI 反馈短于 0.1 秒
- 同步:音频触发应安排在视觉更新的同一时刻(容差 < 10ms)
- 用户控制:提供关闭声音和调节音量的设置选项
- 特征恰当:包络应与交互相匹配(快速轻触 = 短衰减,重按 = 较长衰减)
- 音频清晰:通过淡入/淡出避免咔嗒声,并滤除不需要的频率
- 表达含义:音色/音高应暗示信息内容(雨声对应液体感,提示音对应金属感,成功对应温暖感)
重要边缘情况和注意事项
1. 浏览器自动播放策略
必须在用户交互时调用 Tone.start()。否则不会播放任何音频。
// WRONG - will fail silently
Tone.Transport.start();
// CORRECT
button.addEventListener("click", async () => {
await Tone.start();
Tone.Transport.start();
});
2. 内存管理
完成后始终释放节点:
const synth = new Tone.Synth().toDestination();
// When finished
synth.dispose();
// For arrays of instruments
players.forEach((player) => player.dispose());
3. 定时精度
JavaScript 回调并不精确。始终使用时间参数:
// WRONG - will drift out of sync
setInterval(() => {
synth.triggerAttackRelease("C4", "8n");
}, 250);
// CORRECT - sample-accurate
new Tone.Loop((time) => {
synth.triggerAttackRelease("C4", "8n", time);
}, "4n").start(0);
4. 加载采样
播放前等待采样加载完成:
const sampler = new Tone.Sampler({
urls: { C4: "piano.mp3" },
baseUrl: "/audio/",
}).toDestination();
// WRONG - may not be loaded yet
sampler.triggerAttack("C4");
// CORRECT
await Tone.loaded();
sampler.triggerAttack("C4");
5. 单音与复音
基础合成器是单音的(一次播放一个音符):
// Only plays one note
const mono = new Tone.Synth().toDestination();
mono.triggerAttack(["C4", "E4", "G4"]); // Only C4 plays
// Plays all notes
const poly = new Tone.PolySynth(Tone.Synth).toDestination();
poly.triggerAttack(["C4", "E4", "G4"]); // All play
6. 音符格式
音符可以通过多种方式指定:
synth.triggerAttackRelease("C4", "8n"); // Pitch-octave notation
synth.triggerAttackRelease(440, "8n"); // Frequency in Hz
synth.triggerAttackRelease("A4", "8n"); // A4 = 440Hz
7. 传输时间与音频上下文时间
存在两种计时系统:
Tone.now(); // AudioContext time (always running)
Tone.Transport.seconds; // Transport time (starts at 0)
// Schedule on AudioContext
synth.triggerAttackRelease("C4", "8n", Tone.now() + 1);
// Schedule on Transport
Tone.Transport.schedule((time) => {
synth.triggerAttackRelease("C4", "8n", time);
}, "1m");
架构概览
ToneAudioNode (base class)
├── Source (audio generators)
│ ├── Oscillator, Player, Noise
│ └── Instrument
│ ├── Synth, FMSynth, AMSynth
│ ├── Sampler
│ └── PolySynth
├── Effect (audio processors)
│ ├── Filter, Delay, Reverb
│ ├── Distortion, Chorus, Phaser
│ └── PitchShift, FrequencyShifter
├── Component (building blocks)
│ ├── Envelope, Filter, LFO
│ └── Channel, Volume, Panner
└── Signal (parameter automation)
├── Signal, Add, Multiply
└── Scale, WaveShaper
快速参考
乐器类型
Tone.Synth- 基础单振荡器合成器Tone.FMSynth- 频率调制合成Tone.AMSynth- 振幅调制合成Tone.MonoSynth- 带滤波器和包络的单声部合成器Tone.DuoSynth- 双声部合成器Tone.MembraneSynth- 打击乐合成器Tone.MetalSynth- 金属音色Tone.NoiseSynth- 基于噪声的合成Tone.PluckSynth- 拨弦模型Tone.PolySynth- 多声部封装器Tone.Sampler- 多采样乐器
常见效果器
Tone.Filter- 低通、高通、带通等滤波器Tone.Reverb- 卷积混响Tone.Delay/Tone.FeedbackDelay- 回声效果Tone.Distortion- 波形塑形失真Tone.Chorus- 合唱效果Tone.Phaser- 移相效果Tone.PitchShift- 实时移调Tone.Compressor- 动态范围压缩Tone.Limiter- 砖墙限制器
时间记法
"4n"- 四分音符"8n"- 八分音符"16n"- 十六分音符"2m"- 两小节"8t"- 八分音符三连音"1:0:0"- 小节:拍:十六分音符0.5- 秒(数字)