Implementing Random Probability in Tone.js Note Triggers

Adding randomness to your web audio projects creates more dynamic, generative, and organic musical patterns. This article explains how to implement a probability factor for note triggers in Tone.js using JavaScript’s conditional logic within scheduling blocks, allowing you to control the exact likelihood of a note playing.

The Core Concept of Probability

To implement random chance in Tone.js, you compare a threshold value (the probability) against a randomly generated decimal between 0 and 1 using Math.random().

If the generated number is lower than your target probability, the note triggers. Otherwise, the step is skipped.


Method 1: Global Probability in a Sequence

If you want a uniform probability applied to every note in a sequence, you can place a simple conditional check inside the Tone.Sequence callback function.

// Initialize a synth and connect it to the main output
const synth = new Tone.Synth().toDestination();

// Set a global probability (e.g., 70% chance)
const playProbability = 0.7;

// Create a sequence of notes
const sequence = new Tone.Sequence((time, note) => {
    // Generate a random number and compare it to the probability
    if (Math.random() < playProbability) {
        synth.triggerAttackRelease(note, "8n", time);
    }
}, ["C4", "E4", "G4", "B4"], "8n");

// Start the sequence and transport
sequence.start(0);
Tone.Transport.start();

Method 2: Per-Note Probability

For more complex compositions, you might want certain notes to trigger more often than others (such as a strong downbeat vs. a weak syncopated note). You can achieve this by passing objects containing both the pitch and the specific probability value into the sequence.

const synth = new Tone.Synth().toDestination();

// Define steps with individual note names and playback probabilities
const pattern = [
    { note: "C4", probability: 1.0 }, // Always plays on the downbeat
    { note: "E4", probability: 0.4 }, // Occasional accent
    { note: "G4", probability: 0.8 }, // Highly likely to play
    { note: "B4", probability: 0.2 }  // Rare variation
];

const sequence = new Tone.Sequence((time, step) => {
    if (step && Math.random() < step.probability) {
        synth.triggerAttackRelease(step.note, "8n", time);
    }
}, pattern, "8n");

sequence.start(0);
Tone.Transport.start();

Method 3: Randomizing Note Selection (Weighted Random)

Instead of choosing whether or not to play a single note, you can use probability to choose which note plays from an array of options.

const synth = new Tone.Synth().toDestination();

const chords = ["C4", "E4", "G4"];
const occasionalNote = "B4";

const loop = new Tone.Loop((time) => {
    // 80% chance to play a standard chord note, 20% chance to play the accent note
    const chosenNote = Math.random() < 0.8 
        ? chords[Math.floor(Math.random() * chords.length)]
        : occasionalNote;

    synth.triggerAttackRelease(chosenNote, "8n", time);
}, "4n");

loop.start(0);
Tone.Transport.start();