tonejs

ساخت صدای تعاملی و انیمیشن‌های هماهنگ با ضرب با Tone.js

این مهارت برای افزودن موسیقی، افکت صوتی، سینت‌سایزر و صدای تولیدشده با کد به پروژه‌های وب طراحی شده است. با Tone.js می‌توانید پخش فایل‌های صوتی، زمان‌بندی بر اساس BPM و زنجیره‌های افکت مانند reverb و delay را پیاده‌سازی کنید.

همچنین داده‌های waveform و frequency را به انیمیشن‌های DOM و SVG متصل می‌کند و با استفاده از Tone.getDraw() و GSAP، هماهنگی دقیق و بدون لرزش میان صدا و تصویر می‌سازد.

چه چیزی می‌گیرید

  • پخش و زمان‌بندی موسیقی، VO و SFX با Tone.js
  • ساخت انیمیشن‌های beat-locked و برش‌های هماهنگ با BPM
  • ایجاد audio visualizer با داده‌های waveform و frequency
  • تولید صدای برنامه‌ای با Synth و Oscillator
  • اتصال افکت‌هایی مانند reverb، delay و distortion به منابع صوتی

برای چه کاری خوب است

  • وب‌سایت‌ها و تجربه‌های تعاملی که صدا و انیمیشن را هم‌زمان اجرا می‌کنند
  • پروژه‌های موشن گرافیک، launch page و visualizerهای موسیقی
  • اپلیکیشن‌هایی که به صدای UI، افکت‌های تعاملی یا موسیقی تولیدشده با کد نیاز دارند

برای چه کاری نیست

  • تدوین کامل فایل‌های صوتی یا میکس حرفه‌ای خارج از محیط مرورگر
  • پروژه‌هایی که به صدا، زمان‌بندی موسیقی یا انیمیشن audio-reactive نیاز ندارند

دربارهٔ این اسکیل

Tone.js

When to Use This Skill

Apply when any of the following are needed:

  • Playing audio files (SFX, VO, music) alongside GSAP animations
  • Beat-synchronized or BPM-locked animation (e.g. scene cuts on the beat)
  • Audio-reactive visuals: DOM/SVG elements animated by waveform or frequency data
  • Programmatic sound generation (synths, oscillators, UI sounds)
  • Adding audio effects (reverb, delay, distortion) to any source

Related skills: For timeline sequencing use gsap-timeline, for performance use gsap-performance, and for scroll-sync use gsap-scrolltrigger.

⚠️ Browser Autoplay Policy: Tone.start() MUST be called inside a user-gesture handler (click, keydown, etc.). Audio context will not resume otherwise. Always gate initialization behind an interaction.


Setup

CDN (vanilla HTML projects):

<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>

npm / bun:

bun add tone
import * as Tone from 'tone';

Required initialization (always wrap in user gesture):

document.getElementById('start-btn').addEventListener('click', async () => {
  await Tone.start();           // Resume AudioContext
  Tone.getTransport().start();  // Start master clock
}, { once: true });

Core Architecture

Tone.js signal graph
  Source  →  Effect(s)  →  Destination
  Synth       Reverb        toDestination()
  Player      Delay
  Analyser    Distortion

All nodes connect via .connect() or .toDestination() shorthand.


Quick Reference

TaskAPI
Play a tonenew Tone.Synth().toDestination().triggerAttackRelease("C4", "8n")
Load + play filenew Tone.Player(url).toDestination(); player.start()
Add reverbconst rev = new Tone.Reverb(2).toDestination(); synth.connect(rev)
Schedule on beatTone.getTransport().scheduleRepeat(cb, "4n")
Read waveformnew Tone.Analyser("waveform", 64).getValue()
Read frequencynew Tone.Analyser("fft", 32).getValue()
Set BPMTone.getTransport().bpm.value = 120
Sync visual to audioTone.getDraw().schedule(() => { /* gsap here */ }, time)
Stop everythingTone.getTransport().stop(); Tone.getTransport().cancel()

GSAP Sync: The Right Way

Problem: Tone callbacks run on the audio thread. Calling gsap.to() directly inside them causes jitter.

Solution: Always use Tone.getDraw().schedule() to bridge audio events to the visual frame.

// Correct: no jitter
Tone.getTransport().scheduleRepeat((time) => {
  Tone.getDraw().schedule(() => {
    gsap.fromTo('#element', { scale: 1 }, { scale: 1.2, duration: 0.1 });
  }, time);
}, '4n');

// Wrong: triggers on audio thread, causes jitter
Tone.getTransport().scheduleRepeat((time) => {
  gsap.to('#element', { scale: 1.2 }); // DO NOT do this
}, '4n');

Audio-Reactive Animation

Wire a Tone.Analyser into the GSAP ticker to drive DOM elements in real time:

const analyser = new Tone.Analyser('waveform', 64);
const synth    = new Tone.Synth().connect(analyser).toDestination();

// Pull audio data every animation frame via GSAP ticker
gsap.ticker.add(() => {
  const waveform = analyser.getValue(); // Float32Array, values in [-1, 1]
  const bars = document.querySelectorAll('.bar');

  bars.forEach((bar, i) => {
    const amp = Math.abs(waveform[i] ?? 0);
    gsap.set(bar, { scaleY: 1 + amp * 15 }); // gsap.set: no tween overhead
  });
});

synth.triggerAttackRelease('C3', '1n');

Use gsap.set() (not gsap.to()) inside ticker.add() because creating new tweens every frame is expensive.


BPM-Locked Scene Cuts

Tone.getTransport().bpm.value = 120;

// Fire visual on every measure
Tone.getTransport().scheduleRepeat((time) => {
  Tone.getDraw().schedule(() => {
    gsap.fromTo('#scene', { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.3 });
  }, time);
}, '1m'); // 1m = 1 measure

Tone.getTransport().start();

Playing Audio Files (SFX / VO)

const player = new Tone.Player({
  url:       'assets/audio/whoosh.mp3',
  autostart: false,
}).toDestination();

// Trigger on a GSAP event
gsap.to('#card', {
  y: -200,
  onStart: () => player.start(),
});

Effects Chain

const reverb = new Tone.Reverb({ decay: 3, wet: 0.4 }).toDestination();
const delay  = new Tone.FeedbackDelay('8n', 0.3).connect(reverb);
const synth  = new Tone.Synth().connect(delay);

synth.triggerAttackRelease('A3', '4n');

Common Mistakes

MistakeFix
Audio does not startCall await Tone.start() inside a click or keydown handler first
Jittery visualsNever call gsap.to() directly in Tone callbacks. Use Tone.getDraw().schedule()
gsap.to() inside tickerUse gsap.set() inside gsap.ticker.add() instead of creating tweens per frame
Transport events fire twiceCall Tone.getTransport().cancel() before re-scheduling
Player not readyWrap player.start() in the player.load() promise or use onsuccess
High CPU from analyserUse a small buffer size (32 to 64) and avoid FFT when waveform is enough

Minimal Working Example (vanilla HTML)

<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
<script src="assets/vendor/gsap.min.js"></script>
<script>
  document.body.addEventListener('click', async () => {
    await Tone.start();

    const analyser = new Tone.Analyser('waveform', 32);
    const synth    = new Tone.Synth().connect(analyser).toDestination();

    gsap.ticker.add(() => {
      const data = analyser.getValue();
      document.querySelectorAll('.bar').forEach((bar, i) => {
        gsap.set(bar, { scaleY: 1 + Math.abs(data[i] ?? 0) * 10 });
      });
    });

    synth.triggerAttackRelease('C2', '2n');
  }, { once: true });
</script>

دانلود ZIP