はじめに
「Webサイトに音を付けたい」と思ってWeb Audio APIのドキュメントを開き、AudioContext、OscillatorNode、GainNode……と連なる低レベルAPIの数々にそっとタブを閉じた経験はありませんか?
ブラウザには本格的なオーディオ処理エンジンが標準搭載されているのに、それを直接扱うのはかなり骨が折れます。「ドの音を0.5秒鳴らす」だけでも、ノードの生成・接続・スケジューリングを自前で書く必要があるのです。
そこで登場するのがTone.jsです。Web Audio APIの複雑さを覆い隠し、「シンセを作って、音符を鳴らして、エフェクトをかける」という音楽的な語彙でコードが書けるフレームワークです。この記事では、最初の1音を鳴らすところから、ループやエフェクトを使った実践例まで順に見ていきます。
とはいえ、読むより実際に音を鳴らした方が早いと思います。ボタンをクリックするとTone.jsのシンセが実際に発音するサンプルを用意したので、先に挙動を確かめたい方はこちらからどうぞ。
Tone.jsとは
Tone.jsは、ブラウザ上でインタラクティブな音楽を作るためのWeb Audioフレームワークです。GitHubで14,000を超えるスターを集めており、Webオーディオ分野では事実上の定番ライブラリになっています。ライセンスはMITで、2026年7月現在もv15系として活発にメンテナンスされています。
単なるWeb Audio APIのラッパーにとどまらず、DAW(音楽制作ソフト)でおなじみの概念——グローバルなトランスポート(再生ヘッド)、BPM同期、イベントスケジューリング——をそのままJavaScriptに持ち込んでいるのが最大の特徴です。
主な特徴
- 音楽的な記法 - 周波数を
440と書く代わりに"A4"、秒数の代わりに"8n"(8分音符)のように、楽譜の語彙でコードが書けます - プリセットのシンセとエフェクト -
Synth、FMSynth、PolySynthなどの楽器と、ReverbやFeedbackDelayなどのエフェクトが最初から揃っています - 強力なスケジューリング -
TransportがDAWの再生ヘッドのように機能し、BPMに同期したループや正確なタイミングでのイベント発火を実現します - サンプル再生にも対応 -
PlayerやSamplerで音声ファイルを楽器として扱えます
インストール
npmを使う場合は次のコマンドでインストールします。
npm install tone
CDN経由でサクッと試すこともできます。
<script src="https://unpkg.com/tone"></script>
TypeScriptの型定義も同梱されているため、追加のパッケージは不要です。
Tone.jsのサンプルを動かす
以下はTone.jsのTone.Synthをブラウザ上で実際に鳴らせるサンプルです。ボタンをクリックするとTone.start()でAudioContextを起動し、triggerAttackRelease()でド(C4)の音を発音します。ブラウザの自動再生制限により、Tone.jsの音はクリックなどのユーザー操作なしには鳴らせない点に注意してください。
要点だけを抜き出すと、次のようになります。
document.getElementById("play").addEventListener("click", async () => {
await Tone.start(); // ユーザー操作を起点にAudioContextを起動
const synth = new Tone.Synth().toDestination();
synth.triggerAttackRelease("C4", "8n"); // ド(C4)を8分音符の長さで再生
});
実際に動かせるサンプルが下です。ボタンを押すとTone.Synthが発音し、コンソールにTone.context.state(AudioContextの状態)が出力されます。
triggerAttackRelease()の第一引数を"E4"や"G4"に書き換えると音の高さが変わり、第二引数を"2n"(2分音符)にすると音の長さが伸びます。new Tone.Synth()の部分をnew Tone.AMSynth()やnew Tone.FMSynth()に差し替えると、音色そのものが変化することも確認できます。
基本的な使い方
まずは1音鳴らす
Tone.jsの「Hello, World」はわずか2行です。
import * as Tone from "tone";
const synth = new Tone.Synth().toDestination();
synth.triggerAttackRelease("C4", "8n");
toDestination()はスピーカー(出力先)への接続、triggerAttackRelease("C4", "8n")は「ドの音(C4)を8分音符の長さで鳴らす」という意味です。Web Audio APIを生で書けば数十行になる処理が、音楽の言葉で素直に表現できます。
ユーザー操作で音を開始する
ここで1つ重要な注意点があります。ブラウザの自動再生ポリシーにより、オーディオはユーザー操作をきっかけに開始する必要があります。実際のアプリでは、ボタンクリックなどのイベント内でTone.start()を呼びます。
document.getElementById("play-button").addEventListener("click", async () => {
await Tone.start(); // AudioContextを起動(初回のみ必要)
const synth = new Tone.Synth().toDestination();
synth.triggerAttackRelease("C4", "8n");
});
「コードは正しいのに音が出ない」ときは、まずこのTone.start()を疑ってください。Tone.js初心者が最初につまずくポイントです。
和音を鳴らす(PolySynth)
Synthは単音専用ですが、PolySynthでラップすると和音が扱えます。
const poly = new Tone.PolySynth(Tone.Synth).toDestination();
// Cメジャーコードを2分音符の長さで
poly.triggerAttackRelease(["C4", "E4", "G4"], "2n");
エフェクトをかける
エフェクトはconnect()でチェーンするだけです。ギターのエフェクターを直列につなぐ感覚ですね。
const reverb = new Tone.Reverb({ decay: 4, wet: 0.5 }).toDestination();
const delay = new Tone.FeedbackDelay("8n", 0.4).connect(reverb);
const synth = new Tone.Synth().connect(delay);
synth.triggerAttackRelease("E5", "8n");
これだけで、シンセ → ディレイ → リバーブ → スピーカーという信号経路が完成します。
実践的なユースケース
BPM同期のループを作る
Tone.jsの真価はスケジューリングにあります。Transport(v15ではTone.getTransport()で取得)はDAWの再生ボタンに相当し、すべてのイベントをBPMに同期させられます。
const synth = new Tone.MembraneSynth().toDestination(); // キック系の音色
// 4分音符ごとにキックを鳴らすループ
const loop = new Tone.Loop((time) => {
synth.triggerAttackRelease("C1", "8n", time);
}, "4n");
Tone.getTransport().bpm.value = 120;
loop.start(0);
Tone.getTransport().start();
コールバックが受け取るtime引数をそのままtriggerAttackReleaseに渡すのがポイントです。これによりJavaScriptのタイマーのブレに影響されない、サンプル精度のタイミングで発音されます。
実際にブラウザ上でTone.TransportとTone.Loopを動かして確かめてみましょう。ボタンでループの開始・停止を切り替えられ、スライダーでBPM(テンポ)をリアルタイムに変更できます。
Tone.getTransport().bpm.valueをスライダーで書き換えると、再生中のループのテンポが即座に変わることが確認できます。Tone.Loopの第二引数を"8n"にすればループの間隔が倍の細かさになり、Tone.MembraneSynthをTone.MetalSynthに差し替えるとキック音がハイハット風の音色に変わります。
ステップシーケンサー風のメロディ
Tone.Sequenceを使うと、配列でメロディパターンを定義できます。簡易的なシーケンサーがこれだけで作れます。
const synth = new Tone.Synth().toDestination();
const sequence = new Tone.Sequence(
(time, note) => {
if (note !== null) {
synth.triggerAttackRelease(note, "16n", time);
}
},
["C4", "E4", "G4", null, "B3", "D4", "F4", null], // nullは休符
"8n" // 8分音符間隔で進行
);
sequence.start(0);
Tone.getTransport().start();
配列の値を差し替えるだけでパターンが変わるので、UIと組み合わせればブラウザ上のドラムマシンやメロディエディタに発展させられます。
Tone.Sequenceを実際に動かせるサンプルです。ボタンをクリックすると、配列で定義したメロディがTone.Transportに同期して再生されます。
配列['C4', 'E4', 'G4', null, 'B3', 'D4', 'F4', null]の音名を書き換えれば別のメロディに変わり、nullの位置を増減すれば休符のリズムが変わります。第二引数の'8n'を'16n'にすると、同じ音符数でも倍のテンポ感で駆け抜けるように聴こえます。
エフェクトチェーンをリアルタイムに調整する
Tone.jsのエフェクトはconnect()でつないだあとも、パラメータをいつでも書き換えられます。ここではTone.FeedbackDelayとTone.Reverbをチェーンし、スライダーでwet(エフェクトのかかり具合)とdelayTime(ディレイの間隔)を操作します。
const reverb = new Tone.Reverb({ decay: 4, wet: 0.5 }).toDestination();
const delay = new Tone.FeedbackDelay(0.2, 0.4).connect(reverb);
const synth = new Tone.Synth().connect(delay);
// wetを書き換えるとリバーブのかかり具合が変わる
reverb.wet.value = 0.8;
// delayTimeを書き換えるとエコーの間隔が変わる
delay.delayTime.value = 0.3;
実際に動かせるサンプルはこちらです。ボタンで音を鳴らしながら、2つのスライダーでエフェクトの効き具合を変えられます。
Reverb wetスライダーを0に近づけると乾いた音に、1に近づけると残響たっぷりの音になります。Delay timeを長くすると、こだまのように音が繰り返される間隔が広がります。Tone.FeedbackDelayの第二引数(フィードバック量)を0.4から0.7に上げると、エコーがより長く尾を引くようになります。
UIのフィードバック音に使う
音楽アプリだけでなく、操作音のような小さな用途にも便利です。たとえば、タスク完了時に軽やかな効果音を鳴らすならこうなります。
const synth = new Tone.Synth({
oscillator: { type: "triangle" },
envelope: { attack: 0.005, decay: 0.1, sustain: 0, release: 0.1 },
}).toDestination();
function playSuccessSound() {
const now = Tone.now();
synth.triggerAttackRelease("C5", "16n", now);
synth.triggerAttackRelease("G5", "16n", now + 0.1);
}
音声ファイルを用意せずにコードだけで効果音を定義できるため、アセット管理が不要になり、パラメータ調整も自由自在です。
まとめ
Tone.jsの魅力を振り返ってみましょう。
- Web Audio APIの低レベルな複雑さを隠し、音楽の語彙でコードが書ける
- シンセ・エフェクト・サンプラーがプリセットで揃っている
TransportとLoop/SequenceによるBPM同期の正確なスケジューリング- 音楽アプリからUI効果音まで、用途の幅が広い
「ブラウザで音を扱う」というと特殊な領域に感じられるかもしれませんが、Tone.jsを使えば普段のJavaScriptの延長で音楽的な表現が手に入ります。まずはTone.Synthで1音鳴らすところから、ぜひ手を動かして試してみてください。公式サイト(https://tonejs.github.io/)には豊富なサンプルとAPIドキュメントが揃っています。
