Hello Tone.js

Learning notes from first time trying Tone.js

On This Page

Official documentation: https://tonejs.github.io

Back story: After skimming through Web Audio + MIDI API stuff, I decided to start from tone.js. While they've got an extensive API documentation, I am learning the basics from the readme to avoid getting overwhelmed.

What?

  • "Web Audio framework for creating interactive music in the browser"
  • Has common DAW features, such as:
    • "transport" for synchronizing and scheduling events
    • synths and effects

From this readme, it seems we do most things by creating a new instance of Tone.js classes. The ones discussed here:

  • new Tone.Synth()
    • same with other instruments, eg. Tone.FMSynth(), Tone.AMSynth(), Tone.Polysynth()
  • new Tone.Loop()
  • new Tone.Player()
  • new Tone.Sampler()
  • new Tone.Distortion()
    • same with other effects, eg. Tone.Filter(), Tone.FeedbackDelay()

Important methods:

  • toDestination()
  • triggerAttack(), triggerRelease(), triggerAttackRelease()
    • attack = start the note (amplitude rises)
    • release = ends the note (amplitude goes to 0)
    • attackRelease = combination of both methods above. It takes 3 arguments:
      1. note (either Hz frequency in int or note name in string, eg. "D#2")
      2. duration in second or "tempo-relative value" ("8n" is an 1/8 note)
      3. (optional) when to play "along the AudioContext time"
        • eg. now, now + 0.5, now + 1
  • now() -- gets current AudioContext time
    • WebAudio API uses AudioContext time to schedule events, counting in seconds. Tone.js abstracts it away so we don't need to calculate/convert seconds to our audio based on the tempo. Thus we can use like 4n to create a ¼ note.
  • start() -- returns a promise. Resolved = ready.
  • loaded() -- returns a promise. Resolved = all files have finished loading.

Tone.Transport is an object(?)--not a class or a method--where we can call several methods, eg. start() and bpm.rampTo(800, 10).

  • "like the arrangement view in a Digital Audio Workstation or channels in a Tracker"
  • "can be started, stopped, looped and adjusted on the fly"
  • I'm not sure exactly how it works yet but seems vital.

We can pass these types of tempo values to any methods which take time argument:

  • "4n" -- 1/4 note
  • "1m" -- 1 measure (not sure what this means)
  • "8t" -- 1/8 note triplet

I have not fully understood Signal, but basically they are multiple properties that "have a few built in methods for creating automation curves." The example given is the frequency property in an Oscillator object.

Usage

Create an instance of the (mono) synth object.

// Create a new instance of the synth object.
// We can loop, combine, or simply play it.
const mySynth = new Tone.Synth().toDestination();

Play our synth.

// Play something--in this case a synth object created elsewhere.
mySynth.triggerAttackRelease("C4", "8n");

Create an instance of the polyphonic synth object.

// Create a new instance of the poly synth object.
// The parameter can be any mono synth.
const myPolySynth = new Tone.PolySynth(Tone.Synth).toDestination();

Play the poly synth. triggerRelease must be given a note or array of notes.

❓ I think we can simply replace triggerRelease below with triggerAttackRelease with the same parameters. TODO: Try to rewrite the code below and confirm.

const now = Tone.now()
myPolySynth.triggerAttack("D4", now);
myPolySynth.triggerAttack("F4", now + 0.5);
myPolySynth.triggerAttack("A4", now + 1);
myPolySynth.triggerRelease(["D4", "F4", "A4"], now + 4);

Create an instance of the player object to play a sample (an audio file).

const mySnare = new Tone.Player("https://foo.com/snare.mp3").toDestination();

Play multiple samplers.

const mySampler = new Tone.Sampler(myObj).toDestination();
 
const myObj = {
	urls: {
		"C4": "C4.mp3",
		"F#4": "Fs4.mp3",
		"A4": "A4.mp3",
	},
	release: 1,
	baseUrl: "https://tonejs.github.io/audio/salamander/",
}

Play the sample (or do other things with it) after all files finish loading.

// Play a single sample from Tone.player().
Tone.loaded().then(() => { mySnare.start() });
 
// Play multiple samplers from Tone.Sampler().
// Even if we only supply notes for C4, F#4, and A4, Tonejs converts the samples to other pitches.
Tone.loaded().then(() => { mySampler.triggerAttackRelease(["Eb4", "G4", "Bb4"], 4) });

Create (but not play) loops.

// Loop A: play a note every quarter-note.
const loopA = new Tone.Loop(loopFn, "4n").start(0);
 
// Loop B: play another note every off quarter-note--see start() param.
const loopB = new Tone.Loop(loopFn, "4n").start("8n");
 
// Loops take a function that plays a sound as the first argument.
// The 3rd argument ("time") for the delay is passed from the loop.
const loopFn = (time) => { mySynth.triggerAttackRelease("C2", "8n", time) }
 

Play the loops.

❓ I'm not sure whether we use Tone.Transport.start() to start all types of sounds (loops, synths, samplers?). Neither am I sure about the the exact use case of Tone.Transport.start() vs Tone.start().

// Play our loops defined above.
Tone.Transport.start();

Create an instance of an effect object (eg. Distortion) and connect a player object to it.

const myDistortion = new Tone.Distortion(0.4).toDestination();
 
// Connect the player we made above.
mySnare.connect(myDistortion);
 
// I haven't tried this but we should be able to do the same with a sampler object.
mySampler.connect(myDistortion);

We can connect to multiple effects (eg. Distortion, Filter).

// Assume myDistortion and myFilter have been instantiated.
mySampler.connect(myDistortion);
mySampler.connect(myFilter);

Create an oscillator object that smoothly ramps between two frequencies.

const myOsc = new Tone.Oscillator().toDestination();
 
// Define starting frequency and _ramp_ it up over 2 seconds.
myOsc.frequency.value = "C4";
myOsc.frequency.rampTo("C2", 2);
 
// Start the oscillator for 2 seconds
myOsc.start().stop("+3");

Links to this note

  • Est ex deserunt esse ut pariatur quis fugiat id velit commodo

    Ut anim fugiat laboris et eiusmod aliquip.

  • Est ex deserunt esse ut pariatur quis fugiat id velit commodo

    Ut anim fugiat laboris et eiusmod aliquip.

  • Est ex deserunt esse ut pariatur quis fugiat id velit commodo

    Ut anim fugiat laboris et eiusmod aliquip.

  • Est ex deserunt esse ut pariatur quis fugiat id velit commodo

    Ut anim fugiat laboris et eiusmod aliquip.

  • Est ex deserunt esse ut pariatur quis fugiat id velit commodo

    Ut anim fugiat laboris et eiusmod aliquip.

  • Est ex deserunt esse ut pariatur quis fugiat id velit commodo

    Ut anim fugiat laboris et eiusmod aliquip.

  • Est ex deserunt esse ut pariatur quis fugiat id velit commodo

    Ut anim fugiat laboris et eiusmod aliquip.