Tone.js: A Web Audio Framework That Treats Time as a Musical Object
Project brief: A Web Audio framework for making interactive music in the browser.
At a glance
- What is it?
- Tone.js wraps the raw AudioContext clock in a transport, tempo-relative time strings, and a DAW-like scheduling model. This review covers its core mechanisms, setup, and the trade-offs of adopting it for browser-based music.
- Who is it for?
- Adopt Tone.js if you are building interactive music tools, sequencers, or generative audio in the browser and want a higher-level abstraction over the Web Audio API. Skip it if your project needs only a single oscillator or a simple playback button, or if you must avoid the overhead of learning its time and transport model.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What Tone.js Solves and Who It Is For
Tone.js solves a specific problem: the Web Audio API gives you a low-level clock and audio nodes, but nothing that feels like a musical sequencer. If you want to schedule notes at musical intervals, synchronize loops, or build a synth with an envelope, you end up writing a lot of glue code. Tone.js is for developers who are comfortable with JavaScript but want DAW-like concepts: a global transport, tempo-relative durations, and prebuilt instruments. It is not for someone who just needs to play an MP3 on a button click; that is a few lines of vanilla Web Audio. The README positions it as familiar to both musicians and audio programmers, which is accurate. The target user is someone building an interactive music application, not a one-off sound effect.
The Transport and the Time Abstraction
The core mechanism is the Transport, obtained via `Tone.getTransport()`. It is a separate clock from the AudioContext, and it can be started, stopped, looped, and adjusted while running. The README compares it to the arrangement view in a DAW. The key abstraction is time: any method that takes a time argument can accept a number in seconds, a string like `"4n"` for a quarter note, `"8t"` for an eighth-note triplet, or `"1m"` for a measure. This is a significant departure from raw Web Audio, where you must convert musical durations to seconds yourself. The Transport also passes a sample-accurate time value into loop callbacks, which is critical because JavaScript callbacks are not precisely timed. You must use that passed time to schedule events, not `Date.now()` or `performance.now()`. This design is what makes the framework feel musical, but it also means you have to think in two time domains: the AudioContext clock and the Transport's musical time.
Synths, Polyphony, and the Monophonic Trap
Tone.js ships with several prebuilt synthesizers: `Tone.Synth`, `Tone.FMSynth`, `Tone.AMSynth`, and `Tone.NoiseSynth`. The README is explicit that these are all monophonic, meaning they play one note at a time. That is a real constraint. If you try to play a chord with a single `Tone.Synth`, the notes will cut each other off. The intended solution is `Tone.PolySynth`, which takes a monophonic synth as its first parameter and handles note allocation. The example shows a five-note chord with `triggerAttack` and a single `triggerRelease` that takes an array of notes. This is a clean design, but it means you must plan for polyphony from the start. If you write a simple melody with `Tone.Synth` and later decide you want chords, you will need to swap the instrument and adjust your note-release logic. The API difference is small, but the mental model changes: with `PolySynth`, `triggerRelease` needs to know which notes to release.
Sample Playback and the Loaded Promise
For sample-based work, `Tone.Player` loads an audio file and plays it back. The README shows a player constructed from a URL and connected to the destination. The important utility is `Tone.loaded()`, which returns a promise that resolves when all audio files in the project are loaded. This is a convenience over waiting on each individual buffer's `onload` event. There is also `Tone.Sampler`, which combines multiple samples into an instrument, though the README is truncated at that point, so details on mapping notes to samples are not available here. The `loaded()` promise is a good example of how Tone.js reduces boilerplate, but it also implies a loading phase. If you start playback before the promise resolves, you will get silence or an error. The documentation does not specify what happens on a failed network request, so you should assume you need to handle errors yourself. This is a practical limitation for applications that rely on remote sample files.
Getting Started: Installation and the Click-to-Start Rule
Installation is straightforward via npm: `npm install tone` for the latest stable version, or `npm install tone@next` for the development version. You can also load it from unpkg with a script tag, as long as it precedes your own scripts. The Hello Tone example is minimal: create a `Tone.Synth`, connect it to the destination, and call `triggerAttackRelease("C4", "8n")`. The second argument is a tempo-relative duration, which defaults to a specific BPM on the Transport. The one rule you cannot ignore is that browsers will not play audio until a user gesture. The README is emphatic: call `Tone.start()` from a click or keydown listener, and wait for the promise to resolve before scheduling anything. If you schedule before the AudioContext is running, you get silence or incorrect timing. This is a Web Audio constraint, not a Tone.js quirk, but the framework makes it easy to forget because the API looks synchronous. The `Tone.start()` promise is your gate.
Limitations and When Tone.js Is the Wrong Tool
The most obvious limitation is that the README shows a truncated example for `Tone.getTransport().bp`, which suggests the API is still evolving or the documentation is incomplete. Another limitation is the monophonic nature of the basic synths; if you need dense polyphony, you must use `Tone.PolySynth`, which adds overhead and a different release API. Tone.js is also the wrong tool if you need precise sample-level control over a custom audio graph; the framework abstracts away the AudioContext time, and if you want to write your own DSP in a ScriptProcessor or AudioWorklet, you will fight the abstraction. The README does not mention any built-in support for audio analysis or visualization, so if your goal is a spectrum analyzer, you are better off with raw Web Audio. Finally, the release history shows a gap from 2019 to 2020, then a jump to 15.x in 2026. That suggests the project had a long dormant period, which is a maintenance risk. You should check the changelog for breaking changes between 14.x and 15.x before upgrading.
The Real Alternative: Raw Web Audio and Its Own Costs
The alternative to Tone.js is using the Web Audio API directly. With raw Web Audio, you create an `AudioContext`, an `OscillatorNode`, and a `GainNode`, then connect them. You control time in seconds, and you write your own scheduling logic with `setTimeout` or `requestAnimationFrame`, which is not sample-accurate. The difference in approach is fundamental: raw Web Audio gives you nodes and a clock, but no transport, no musical time, and no polyphony management. You would have to build a scheduler, a note allocation system, and a time conversion utility yourself. For a simple beep, raw Web Audio is simpler. For a sequence of notes at a changing tempo, Tone.js saves you hours. The trade-off is that raw Web Audio has no abstraction layer to learn, and it gives you full control over every node. Tone.js hides the AudioContext time, which is good for musical thinking but bad if you need to sync with external audio or video at the sample level. Choose based on whether your problem is musical or technical.
Maintenance, License, and Upgrade Cost
Tone.js is MIT licensed, which means you can use it in commercial and open-source projects without paying a fee, and you can modify it. The license does not come with warranty or support obligations, so you are on your own for bugs. The repository is not archived and has recent activity, with version 15.1.22 pushed in July 2026. However, the gap between 13.8.25 (2019) and 14.7.39 (2020) and then 15.1.22 (2026) is notable. That is a long stretch with no releases, which could mean the project was stable or abandoned and then revived. The upgrade cost from 14.x to 15.x is unknown from the README alone. You should read the release notes for 15.x to see if the API changed, especially around the Transport and time encodings. The README examples use `Tone.getTransport()` and `Tone.Loop`, which are present in 15.x, so the core API appears stable. But the README also shows a truncated line for `Tone.getTransport().bp`, which might indicate a new feature or a documentation error. Before adopting, check the GitHub issues for any known breaking changes.
Editorial conclusion
Adopt Tone.js if you are building interactive music tools, sequencers, or generative audio in the browser and want a higher-level abstraction over the Web Audio API. Skip it if your project needs only a single oscillator or a simple playback button, or if you must avoid the overhead of learning its time and transport model. Before committing, verify that the version you install (15.x) matches the API you find in tutorials, since the repository shows a long gap between releases. Check that your target browsers support the Web Audio features Tone.js relies on, and confirm that the 'next' tag is what you want for development. The library is MIT licensed, so you can integrate it freely, but you are responsible for testing against your own audio graph.
Community notes