☰
Tone.js 浏览器交互音乐开发指南:从安装、合成器到 Transport 调度与效果器路由的完整实践
2026/10/3 1:52:18 网站建设 项目流程
  • 音频处理
  • 前端

【免费下载链接】Tone.js

A Web Audio framework for making interactive music in the browser.

项目地址:https://gitcode.com/gh_mirrors/to/Tone.js
点击查看免费下载

Tone.js 是一个构建在 Web Audio API 之上的浏览器音频框架,专为"在浏览器中创作交互式音乐"而设计,其架构同时面向音乐人与音频程序员。本文以仓库根目录的 README.md 为骨架,结合 Tone/ 目录下的核心源码与 examples/ 目录中的可运行示例,系统讲解从安装引入、单音合成器控制、时间系统、音频启动约束,到 Transport 全局调度、复音合成器、采样回放、效果器路由、信号自动化与 AudioContext 管理的完整实战路径。读完本文,你将能够独立搭建一个可交互的浏览器音乐应用,并理解每个 API 背后的源码级原理。

一、Tone.js 是什么:面向音乐人与程序员的 Web Audio 框架

Tone.js 的核心定位在 README.md 的开篇描述中非常明确:它是一个用于在浏览器中创建交互式音乐的 Web Audio 框架。其架构设计有两个目标读者:

  • 音乐人:开箱即用地获得 DAW(数字音频工作站)级别的能力,如全局 Transport(用于同步与调度事件的"时间轴")、预置合成器(synths)与效果器(effects);
  • 音频程序员:获得高性能的底层构建块,用于自行搭建合成器、效果器与复杂的控制信号(control signals)。

从源码结构看,这一"高低兼顾"的设计在仓库目录中体现得非常清晰(见 Tone/ 目录树):

  • Tone/source/:音频源,包括振荡器族(Oscillator、OmniOscillator、FatOscillator、FMOscillator等)与采样回放(Player、Players、Sampler等);
  • Tone/instrument/:乐器层,包括Synth、MonoSynth、FMSynth、AMSynth、PolySynth等;
  • Tone/effect/:效果器层,包括失真、滤波、延迟、混响等;
  • Tone/component/:可组合的组件(包络、滤波器、通道处理等);
  • Tone/signal/:信号层,这是整个框架"一切皆信号"能力的根基;
  • Tone/core/:核心设施,包括 Context、Transport、时间类型系统、工具类。

以Tone.Synth为例,其实现(见 Tone/instrument/Synth.ts)只做了两件事:一个OmniOscillator路由经过一个AmplitudeEnvelope,这正是"预置乐器 = 底层构建块的组合"这一哲学的直接体现。

二、安装与引入:npm 与 CDN 两种方式

根据 README.md 的 Installation 一节与 package.json 中的发布配置,Tone.js 支持两种引入方式。

方式一:npm 本地安装

npm install tone // 安装最新稳定版 npm install tone@next // 或者使用 "next" 预发布版本

说明:tone@next对应仓库 dev 分支的持续构建产物(详见下文"测试与版本"一节)。当前仓库 package.json 中name为tone,type为module,main指向build/esm/index.js,说明安装后可通过 ES Module 方式引入。

安装完成后,使用 JavaScript 的import语法引入:

import * as Tone from "tone";

方式二:unpkg CDN 直接挂载

Tone.js 同时托管在 unpkg.com,可在 HTML 文档中直接以<script>标签引入,只要保证它出现在任何项目脚本之前:

<script src="http://unpkg.com/tone"></script>

这一用法的完整可运行示例见仓库中的 examples/simpleHtml.html,它展示了最简单的组合:页面一个按钮,点击时创建Tone.Synth并播放一个音符:

<script src="https://unpkg.com/tone"></script> <script> function playNote() { // create a synth const synth = new Tone.Synth().toDestination(); // play a note from that synth synth.triggerAttackRelease("C4", "8n"); } </script> ... <button onclick="playNote()">Click me to play note!</button>

注意这个示例将音符触发放在按钮点击回调内——这正是下文"启动音频"一节所强调的浏览器自动播放策略约束下的标准写法。

三、Hello Tone:第一段声音

README 给出的入门示例只有三行,却是理解 Tone.js 对象模型的钥匙:

// create a synth and connect it to the main output (your speakers) const synth = new Tone.Synth().toDestination(); // play a middle 'C' for the duration of an 8th note synth.triggerAttackRelease("C4", "8n");

关键动作拆解:

  1. new Tone.Synth():创建合成器实例。从源码看(Tone/instrument/Synth.ts),构造函数内部创建了OmniOscillator与AmplitudeEnvelope,并通过this.oscillator.chain(this.envelope, this.output)将振荡器串联包络后接入输出;
  2. .toDestination():把合成器的输出接到主输出(扬声器)。这是 Tone.js 连接系统的便捷方法,等价于手动connect到Destination;
  3. triggerAttackRelease("C4", "8n"):以"音符音高 + 时值"的方式触发一个完整音符。

3.1 Tone.Synth 的组成与默认参数

Tone.Synth是"一个振荡器 + 一个 ADSR 包络"的基本合成器。其默认参数在 Tone/instrument/Synth.ts 的getDefaults()中定义:

  • oscillator:默认type: "triangle"(三角波);
  • envelope:默认attack: 0.005(秒)、decay: 0.1、sustain: 0.3、release: 1;
  • 继承自Monophonic的detune: 0、portamento: 0、onsilence空回调(见 Tone/instrument/Monophonic.ts)。

这些默认值构成了"按一下就有声"的实用起点,也解释了为什么上面的入门示例无需任何配置即可发声。

3.2 triggerAttack / triggerRelease:手动控制音符起止

triggerAttack开始音符(幅度上升),triggerRelease使幅度回落到 0(即note off)。README 给出的分离控制示例:

const synth = new Tone.Synth().toDestination(); const now = Tone.now(); // trigger the attack immediately synth.triggerAttack("C4", now); // wait one second before triggering the release synth.triggerRelease(now + 1);

从源码层面看(Tone/instrument/Monophonic.ts),两个方法都会先把时间参数经this.toSeconds()换算为 AudioContext 秒,然后分别调用内部钩子_triggerEnvelopeAttack(seconds, velocity)与_triggerEnvelopeRelease(seconds)。在Synth的实现中(Tone/instrument/Synth.ts):

  • attack 时:envelope.triggerAttack(time, velocity)启动包络,同时oscillator.start(time)启动振荡器;若sustain === 0,会在 attack+decay 之后自动停止振荡器;
  • release 时:envelope.triggerRelease(time)触发释放,并在release时长结束后停止振荡器。

triggerAttack还支持第三个可选参数velocity(0–1 的力度),例如synth.triggerAttack("C4", "+0.5", 0.5)表示半秒后以一半力度起音(见 Tone/instrument/Monophonic.ts 中的源码示例注释)。

3.3 triggerAttackRelease:起止一体

triggerAttackRelease是triggerAttack与triggerRelease的组合,也是日常最常用的方法。README 对其三个参数给出了精确定义:

  • 第一个参数:音符。可以是赫兹频率(如440),也可以是"音高-八度"记法(如"D#2");
  • 第二个参数:音符保持的时长。可以是秒数,也可以是拍速相对值(如"8n",详见下文"时间系统"一节);
  • 第三个可选参数:音符在 AudioContext 时间轴上播放的时刻,用于在未来调度事件。

README 的排程示例(以八分音符间隔依次触发 C 大三和弦的三音):

const synth = new Tone.Synth().toDestination(); const now = Tone.now(); synth.triggerAttackRelease("C4", "8n", now); synth.triggerAttackRelease("E4", "8n", now + 0.5); synth.triggerAttackRelease("G4", "8n", now + 1);

四、时间系统:从 AudioContext 秒到音乐时值

Web Audio 具备采样级精度的调度能力。AudioContext 时间从页面加载时开始计数、以秒为单位递增,是 Web Audio API 调度事件所使用的时间基准。

4.1 Tone.now():获取当前 AudioContext 时间

Tone.now()返回 AudioContext 的当前时间。README 示例用setInterval周期性打印:

setInterval(() => console.log(Tone.now()), 100);

Tone.js 将 AudioContext 时间做了抽象:任何接受时间作为参数的方法,既可以传数字(秒),也可以传字符串(音乐时值)。例如:

  • "4n":四分音符(quarter-note);
  • "8t":八分音符三连音(eighth-note triplet);
  • "1m":一个小节(one measure)。

4.2 时间解析的源码实现

这套时间编码体系的实现位于 Tone/core/type/Time.ts,核心是TimeClass。从源码可以看到两类重要的表达式解析:

  • 相对时间:以+开头表示"从现在起多少"(正则regexp: /^\+(.+)/),例如"+1"表示一秒后、"+4n"表示一个四分音符后;
  • 量化时间:以@开头表示"量化到下一个指定细分"(正则regexp: /^@(.+)/),例如"@4n"表示对齐到下一个四分音符边界。

TimeClass还提供quantize(subdiv, percent)方法,可把任意时间值按给定细分量化,例如Tone.Time(0.6).quantize("4n", 0.5)返回0.55(源码注释示例,见 Tone/core/type/Time.ts)。这些能力使得"将事件精确对齐到音乐网格"成为可能。

五、启动音频:浏览器自动播放策略(务必遵守)

重要约束:浏览器在用户产生点击等交互行为之前不会播放任何音频。必须把 Tone.js 代码放在由用户动作(如"click"、"keydown")触发的事件监听器中执行,并先调用Tone.start()。

Tone.start()返回一个 Promise,只有在该 Promise resolve 之后音频才真正就绪。在 AudioContext 运行之前进行调度或播放,将导致静音或错误的调度结果。README 的标准写法:

//attach a click listener to a play button document.querySelector("button")?.addEventListener("click", async () => { await Tone.start(); console.log("audio is ready"); });

这一约束同样解释了仓库示例中为何几乎所有可运行页面(如 examples/simpleSynth.html、examples/simpleHtml.html)都把发声动作封装在按钮回调里。

六、调度:Transport 与 Loop

6.1 Transport:DAW 式全局时间轴

Tone.getTransport()返回全局主时间管理器。与 AudioContext 时钟不同,它可以被启动、停止、循环,并在运行中调整速度——可以把它想象成 DAW 中的编排视图(arrangement view)。从源码看(Tone/core/clock/Transport.ts),Transport 支持"速度曲线与拍速变化",并且与浏览器计时器(setInterval、requestAnimationFrame)不同,回调会收到被调度事件的精确时间参数,同时会发出"start"、"stop"、"pause"、"loop"等事件。

多个事件与部件可以沿着 Transport 排布并同步。Tone.Loop是一种创建可调度启停的循环回调的简单方式。README 的经典示例——两个合成器交替演奏、整体速度斜坡加速:

// create two monophonic synths const synthA = new Tone.FMSynth().toDestination(); const synthB = new Tone.AMSynth().toDestination(); //play a note every quarter-note const loopA = new Tone.Loop((time) => { synthA.triggerAttackRelease("C2", "8n", time); }, "4n").start(0); //play another note every off quarter-note, by starting it "8n" const loopB = new Tone.Loop((time) => { synthB.triggerAttackRelease("C4", "8n", time); }, "4n").start("8n"); // all loops start when the Transport is started Tone.getTransport().start(); // ramp up to 800 bpm over 10 seconds Tone.getTransport().bpm.rampTo(800, 10);

这段代码同时展示了两个关键机制:

  1. 回调时间参数:(time) => {...}中的time是采样级精确的事件时间,必须用它来调度事件——因为 JavaScript 回调本身不是精确定时的;
  2. 偏移启动:loopB.start("8n")以八分音符为偏移启动,使两个循环形成"错拍"(off quarter-note)的切分效果;
  3. bpm 是信号:Tone.getTransport().bpm.rampTo(800, 10)说明拍速本身是可平滑自动化的信号量,10 秒内从默认 120 BPM 斜坡到 800 BPM。

Transport 的更多能力(scheduleRepeat、scheduleOnce、事件系统、loop 区间等)可进一步阅读 Tone/core/clock/Transport.ts 与配套测试 Tone/core/clock/Transport.test.ts。

七、乐器:单音合成器与复音 PolySynth

7.1 可选合成器

README 列举了多个可直接选用的合成器:Tone.FMSynth、Tone.AMSynth、Tone.NoiseSynth,加上前文详述的Tone.Synth。它们对应的源码分别位于 Tone/instrument/FMSynth.ts、Tone/instrument/AMSynth.ts、Tone/instrument/NoiseSynth.ts。

所有这些乐器默认都是单音(monophonic)的,即同一时刻只能演奏一个音符。

7.2 PolySynth:一键获得复音能力

要创建**复音(polyphonic)**合成器,使用Tone.PolySynth。它接受一个单音乐器作为第一个参数,自动处理音符分配(voice allocation),因此可以同时传入多个音符。其 API 与单音乐器类似,区别在于triggerRelease必须传入一个音符或音符数组。

从源码看(Tone/instrument/PolySynth.ts),PolySynth 本身并不是合成器,它只是管理"另一种合成器类型"的声音实例(voice),并支持maxPolyphony(最大复音数)等选项,以及通过synth.set({ detune: -1200 })跨所有 voice 批量设置参数。

README 的琶音示例——依次叠加 D 大调五声音阶的五个音,最后统一释放:

const synth = new Tone.PolySynth(Tone.Synth).toDestination(); const now = Tone.now(); synth.triggerAttack("D4", now); synth.triggerAttack("F4", now + 0.5); synth.triggerAttack("A4", now + 1); synth.triggerAttack("C5", now + 1.5); synth.triggerAttack("E5", now + 2); synth.triggerRelease(["D4", "F4", "A4", "C5", "E5"], now + 4);

注意triggerRelease接收了包含全部五个音符的数组,一次性释放整个和弦。

八、采样回放:Tone.Player 与 Tone.Sampler

声音生成不限于合成。Tone.js 也支持加载并回放音频采样文件。

8.1 Tone.Player:加载并播放单个音频文件

const player = new Tone.Player( "https://tonejs.github.io/audio/berklee/gong_1.mp3" ).toDestination(); Tone.loaded().then(() => { player.start(); });

Tone.loaded()返回一个 Promise,在所有音频文件加载完成后 resolve。它是等待每个音频 buffer 的onload事件的便捷替代。仓库 examples/player.html 展示了 Player 的完整可交互用法。

8.2 Tone.Sampler:按音符组织的采样乐器

多个采样可以组合成一件乐器。如果音频文件按音符组织,Tone.Sampler会对采样做变调(pitch shift)以填补音符之间的空缺——例如只采样了钢琴每三个音,就能补全成一架完整钢琴。

与其他合成器不同,Sampler 天生是复音的,无需再包一层 PolySynth。README 示例使用 Salamander 钢琴采样库:

const sampler = new Tone.Sampler({ urls: { C4: "C4.mp3", "D#4": "Ds4.mp3", "F#4": "Fs4.mp3", A4: "A4.mp3", }, release: 1, baseUrl: "https://tonejs.github.io/audio/salamander/", }).toDestination(); Tone.loaded().then(() => { sampler.triggerAttackRelease(["Eb4", "G4", "Bb4"], 4); });

参数说明:

  • urls:音符名到采样文件名的映射(键为音符名,如C4、"D#4";注意升号写法Ds4.mp3是文件命名上的转义);
  • release:释放时长(秒);
  • baseUrl:所有urls中文件名的公共基础路径,用于拼接完整 URL;
  • 播放时同样支持传入音符数组(这里是 E 大三和弦["Eb4", "G4", "Bb4"])与时长(4 秒)。

九、效果器与灵活的路由系统

前面所有示例都把声源直接连到Destination,但合成器的输出同样可以先经过一个或多个效果器再进扬声器。

9.1 串联一个效果器

const player = new Tone.Player({ url: "https://tonejs.github.io/audio/berklee/gurgling_theremin_1.mp3", loop: true, autostart: true, }); //create a distortion effect const distortion = new Tone.Distortion(0.4).toDestination(); //connect a player to the distortion player.connect(distortion);

这里Tone.Distortion(0.4)的 0.4 是失真强度(NormalRange 0–1),对应源码 Tone/effect/Distortion.ts。player.connect(distortion)将声源路由进效果器,效果器再输出到主输出。

9.2 并联多个效果器

连接路由是灵活的,既可以串联也可以并联:

const player = new Tone.Player({ url: "https://tonejs.github.io/audio/drum-samples/loops/ominous.mp3", autostart: true, }); const filter = new Tone.Filter(400, "lowpass").toDestination(); const feedbackDelay = new Tone.FeedbackDelay(0.125, 0.5).toDestination(); // connect the player to the feedback delay and filter in parallel player.connect(filter); player.connect(feedbackDelay);

Tone.Filter(400, "lowpass")创建截止频率 400 Hz 的低通滤波器;Tone.FeedbackDelay(0.125, 0.5)创建延迟时间 0.125 秒、反馈量 0.5 的反馈延迟。两者并联:Player 的输出同时进入滤波器和反馈延迟,得到"干湿并行"的混合效果。

9.3 共享效果与 Gain 工具节点

多个节点可以连接到同一个输入,使多个声源共享效果器。Tone.Gain是构建复杂路由时非常有用的工具节点。这一设计理念在仓库中有大量实例——例如 examples/buses.html 展示了"总线"式路由,examples/mixer.html 展示了混音台式的多轨路由。

十、信号:一切皆可音频速率自动化

与底层 Web Audio API 一脉相承,Tone.js 几乎所有参数都由音频速率(audio-rate)的信号控制。这意味着参数的采样级精确同步与调度成为可能,也是 Tone.js 与普通"setValue 一次性赋值"式音频库的核心区别。

Signal属性自带若干用于创建自动化曲线的内建方法。README 用振荡器频率做例子——Oscillator的frequency参数就是一个 Signal:

const osc = new Tone.Oscillator().toDestination(); // start at "C4" osc.frequency.value = "C4"; // ramp to "C2" over 2 seconds osc.frequency.rampTo("C2", 2); // start the oscillator for 2 seconds osc.start().stop("+3");

要点:

  • osc.frequency.value = "C4":直接给频率信号赋一个音符值(时间系统会自动换算为 Hz);
  • osc.frequency.rampTo("C2", 2):在 2 秒内从 C4 平滑斜降到 C2(指数斜坡,符合音高感知);
  • osc.start().stop("+3"):立即启动、三秒后停止,"+"前缀即前文提到的相对时间表达式。

信号系统在仓库中的实现位于 Tone/signal/ 目录,包含Signal、Add、Multiply、Scale、WaveShaper等一系列可组合的信号算子(见 Tone/signal/index.ts),测试覆盖见 Tone/signal/Signal.test.ts。Transport 的bpm.rampTo(800, 10)正是这一体系的又一实例。

十一、AudioContext 管理

Tone.js 在加载时会自动创建一个 AudioContext,并通过 standardized-audio-context 对其进行 shim,以最大化浏览器兼容性——这一依赖声明在 package.json 中可以看到("standardized-audio-context": "^25.3.70")。

  • 获取:Tone.getContext()返回当前 AudioContext 封装;
  • 自定义:Tone.setContext(audioContext)可注入你自己的 AudioContext。

从源码看(Tone/core/context/Context.ts),Context类封装了原生BaseAudioContext,并额外管理 Ticker(可靠的定时回调)、常量 AudioBufferSourceNode 缓存、超时事件时间线,以及每个 Context 专属的 Transport / Listener / Destination / Draw 单例。Context也支持Offline渲染模式(离线音频处理,见 Tone/core/context/Offline.ts 与 examples/offline.html),可用于离线渲染与测试。

十二、MIDI、性能与测试

MIDI

要使用 MIDI 文件,需先用工具将其转换为 Tone.js 可理解的 JSON 格式。仓库本身并不直接解析.mid二进制文件,而是依赖转换后的 JSON 结构,相关类型支持见 Tone/core/type/Midi.ts(MIDI 音符到频率的转换)与 Tone/core/type/Midi.test.ts。

性能

Tone.js 大量使用原生 Web Audio 节点(如 GainNode、WaveShaperNode)完成所有信号处理,这一设计使它在桌面与移动浏览器上都能良好运行。README 建议关注性能相关的 wiki 文章以获得最佳实践。

测试与版本

仓库使用 mocha 与 chai 运行大规模测试套件,宣称接近 100% 覆盖率。dev 分支通过构建的产物会发布到 npm 的tone@next。在 package.json 中可以找到对应的脚本:

  • npm test:运行 TypeScript 编译检查与 web-test-runner 测试套件(配置见 test/web-test-runner.config.js);
  • npm test:examples、npm test:html、npm test:integrations、npm test:readme:分别验证示例页面、HTML 页面、各打包方式集成与 README 代码块的可运行性;
  • npm run lint:tsc --noEmit+ ESLint 静态检查。

测试辅助代码与音频对比文件位于 test/ 目录,例如 test/helper/CompareToFile.ts 实现了"与参考 WAV 逐采样对比"的回归测试机制,参考音频存放在 test/audio/compare/。

十三、快速开始清单:从零到可交互音乐应用

综合全文,搭建一个最小可用的 Tone.js 交互应用需要以下步骤:

  1. 引入:npm 安装后import * as Tone from "tone",或通过 unpkg CDN 在 HTML 中引入(参考 examples/simpleHtml.html);
  2. 等待用户手势:在 click / keydown 回调中await Tone.start(),之后音频才可用;
  3. 创建声源:如new Tone.Synth().toDestination(),或Tone.Player/Tone.Sampler加载采样(加载完成可依赖Tone.loaded());
  4. 可选路由效果器:用player.connect(effect)串联/并联Tone.Filter、Tone.FeedbackDelay、Tone.Distortion等效果器,复杂路由可用Tone.Gain;
  5. 调度:单音用triggerAttack/triggerRelease/triggerAttackRelease(时间参数支持秒或"8n"等音乐时值);循环事件用Tone.Loop+Tone.getTransport().start();精确对齐用Tone.now()作为基准时刻;
  6. 自动化:利用 Signal 能力,如osc.frequency.rampTo("C2", 2)或Tone.getTransport().bpm.rampTo(800, 10)。

每一步的完整可运行参考都可在 examples/ 目录找到对应页面(如 examples/simpleSynth.html、examples/player.html、examples/stepSequencer.html 等),它们共同构成了阅读源码 Tone/ 之前最直观的实践入口。

  • 音频处理
  • 前端

【免费下载链接】Tone.js

A Web Audio framework for making interactive music in the browser.

项目地址:https://gitcode.com/gh_mirrors/to/Tone.js
点击查看免费下载

相关推荐

上一篇:Cosmos 仓库 Dice Simulation 动态规划题解:基于三维 DP + 记忆化搜索的连续次数约束计数
下一篇:Shields 徽章服务退役(RetiredService)完整实践指南:当上游服务关闭时如何优雅下线徽章

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询