webdevpuneet.com

Tone.js Synth Pad — Free Web Audio Note Grid Snippet

Tone.js Synth Pad · Media · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

Requires
<div class="tp-wrap">
  <div class="tp-head">
    <h1>Synth Pad</h1>
    <p id="tpStatus">Tap a pad to start audio and play a note.</p>
  </div>
  <div class="tp-grid" id="tpGrid">
    <button class="tp-pad" data-note="C4">C4</button>
    <button class="tp-pad" data-note="D4">D4</button>
    <button class="tp-pad" data-note="E4">E4</button>
    <button class="tp-pad" data-note="F4">F4</button>
    <button class="tp-pad" data-note="G4">G4</button>
    <button class="tp-pad" data-note="A4">A4</button>
    <button class="tp-pad" data-note="B4">B4</button>
    <button class="tp-pad" data-note="C5">C5</button>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Correct autoplay handling
Tone.start() runs inside the first gesture only.
Single PolySynth
One instance voices all eight pads.
Overlap-safe
Fast repeated presses don't cut notes off.
Short pluck envelope
Fast attack, quick decay for a clean note.
Visual flash per pad
Glow feedback independent of audio state.
C major scale mapping
Eight pads span C4 through C5.
Pointer events
Works with touch, mouse, and pen input.
Single dependency
Only Tone.js is required.

About this UI Snippet

Tone.js Synth Pad — Click-to-Play Notes With Correct Autoplay Handling

Screenshot of the Tone.js Synth Pad snippet rendered live

Tone.js wraps the Web Audio API in a musician-friendly API — synths, envelopes, effects, and scheduling — so a click-to-play instrument doesn't require hand-building an oscillator graph. This snippet is a small eight-pad grid, each pad mapped to a note in a C major scale, where pressing a pad calls synth.triggerAttackRelease(note, '8n') and flashes a glow so the interaction reads clearly even with the sound off.

The autoplay rule, handled correctly

Browsers refuse to let audio play until a real user gesture unlocks the page's AudioContext — starting Tone (or any Web Audio graph) on page load simply does nothing silently. This snippet calls await Tone.start() from inside the first pointerdown handler, not from a page-load script, which is the one place browsers reliably honor as a qualifying gesture. A started flag makes sure it only runs once; every pad press after that goes straight to triggerAttackRelease.

One PolySynth, many notes

Rather than creating a synth per pad, a single Tone.PolySynth(Tone.Synth, {...}) handles all eight — PolySynth manages voice allocation internally, so overlapping presses (or a fast double-tap) don't cut each other off. The triangle oscillator and short envelope (fast attack, quick decay, low sustain, moderate release) give each note a soft pluck rather than a harsh buzz or a synth-pad drone.

Visual feedback independent of audio

flash() adds an is-active class for 220ms regardless of whether sound is actually audible (muted tab, no speakers) — a glowing ring and background shift on the CSS side, not tied to any Tone.js callback. That separation matters: the pad always looks pressed, even in edge cases where audio output isn't available.

Where this fits

For animating elements on scroll or hover rather than triggering sound, see scroll reveal grid or interactive hover button; for a celebratory click effect with confetti instead of audio, see confetti button. The pattern of "unlock on first gesture, feed a flag" generalizes to any Web Audio-based interaction, not just Tone.js.

Customizing it

Swap Tone.Synth for Tone.FMSynth/Tone.MembraneSynth for a different timbre, change the note list to a different scale or chord voicing, or route the synth through a Tone.Reverb/Tone.FeedbackDelay before toDestination() for a fuller sound. Change '8n' to a different note duration for longer or shorter notes.

Step by step

How to Use

  1. 1
    Add the Tone.js CDNInclude Tone.js from the CDN panel.
  2. 2
    Paste HTML, CSS, and JSAn 8-pad grid renders, silent until the first tap.
  3. 3
    Tap any padTone.start() unlocks audio; the note plays.
  4. 4
    Tap other padsEach plays instantly, no further unlock needed.
  5. 5
    Watch the glowEvery press flashes regardless of audio output.
  6. 6
    Change the soundSwap Tone.Synth for a different synth type.

Real-world uses

Common Use Cases

Playable music widgets
Embed a small eight-pad instrument in a page, voiced by a single PolySynth so fast repeated presses never cut earlier notes off.
Interactive product demos
Tie sound feedback to a grid interaction, with a visual flash on each pad confirming exactly which note was triggered.
Onboarding easter eggs
Add a discoverable audio moment for curious users, with Tone.start() correctly called inside the first gesture to satisfy autoplay rules.
Sound design prototyping
Swap synth types and envelopes to audition timbres, using a fast attack and quick decay for a clean pluck on every pad.
Audio feedback pairing
Combine sound with a notification bell to explore how an audible cue complements a visual alert.

Got questions?

Frequently Asked Questions

Browsers enforce an autoplay policy that blocks any AudioContext from producing sound until the user has performed a genuine gesture like a click or tap on the page. Calling Tone.start() (which resolves Tone's shared AudioContext) has to happen inside a real event handler — attempting it on page load or in a setTimeout without a preceding gesture silently fails to unlock audio in most browsers.

Once the AudioContext has been successfully resumed, it stays running for the rest of the page's lifetime — calling Tone.start() again is harmless but unnecessary. The started flag just avoids an extra await on every subsequent press, so notes after the first one trigger with no perceptible delay.

Tone.PolySynth internally manages a pool of voices for a single synth definition, allocating and releasing them as notes are triggered and released. That means one PolySynth instance can play all eight pads' notes, including overlapping ones from a fast double-tap, without needing to manage eight separate synth objects or worry about one note cutting another off.

It combines two steps — starting a note's envelope (attack) and releasing it (release) — into one call with a specified note and duration, here "8n" (an eighth note at the current tempo). That's the right method for a single tap-and-release pad interaction; triggerAttack and triggerRelease as separate calls are more useful when you want a note to sustain for as long as a button stays held down.

No — flash() runs purely by adding and removing a CSS class on a timer, completely independent of Tone.js's playback state or whether the browser tab is muted or has no audio output device. That separation means the pad always gives clear visual confirmation of a press, even in situations where sound isn't actually audible.