Get Tone.js Transport Progress as Percentage or Beats
Tracking the playback position of the Tone.Transport is
essential for building interactive audio applications, visualizers, or
custom timeline progress bars in Tone.js. This article explains how to
retrieve the current playback progress of the Tone.js transport both as
a precise musical beat count and as a normalized percentage (0% to
100%), using built-in Tone.js helper classes and simple mathematical
calculations.
Capturing Progress as a Beat Count
To get the exact current position as a decimal beat count, the most
reliable method is to use the Tone.Time utility. While
Tone.Transport.position returns a formatted string (such as
"bars:beats:sixteenths"), passing this value into
Tone.Time() allows you to convert it into a raw numerical
beat format.
import * as Tone from 'tone';
function getCurrentBeats() {
// Converts the current transport position string into a decimal beat count
const position = Tone.Transport.position;
const beatCount = Tone.Time(position).toBeats();
return beatCount;
}This method is highly accurate because it accounts for any changes in the transport’s tempo (BPM) or time signature dynamically.
Capturing Progress as a Percentage
To calculate playback progress as a percentage, you must compare the current transport time against a defined total duration or a loop end point.
If your application uses a looping transport, you can calculate the
percentage by dividing the current elapsed seconds by the
loopEnd value:
function getPlaybackPercentage() {
const currentSeconds = Tone.Transport.seconds;
const totalSeconds = Tone.Transport.loopEnd;
if (totalSeconds === 0) return 0; // Prevent division by zero
// Calculate percentage (0 to 100)
const percentage = (currentSeconds / totalSeconds) * 100;
// Clamp the value between 0 and 100
return Math.min(Math.max(percentage, 0), 100);
}If you are not using a loop but have a fixed song duration (for
example, 16 bars), you can define that duration as a
Tone.Time object and convert it to seconds for the
calculation:
const songDurationSeconds = Tone.Time("16:0:0").toSeconds();
function getFixedPlaybackPercentage() {
const currentSeconds = Tone.Transport.seconds;
const percentage = (currentSeconds / songDurationSeconds) * 100;
return Math.min(Math.max(percentage, 0), 100);
}Implementing Real-Time Tracking
Because audio timing runs on a separate web audio thread, polling the
transport inside a standard browser rendering loop is the best way to
update your user interface. Using requestAnimationFrame
allows you to query the transport properties continuously without
degrading performance.
function updateUI() {
const beats = getCurrentBeats().toFixed(2);
const percentage = getPlaybackPercentage().toFixed(1);
// Update DOM elements
document.getElementById('beat-display').innerText = `Beats: ${beats}`;
document.getElementById('progress-bar').style.width = `${percentage}%`;
// Continue the loop if the transport is actively playing
if (Tone.Transport.state === "started") {
requestAnimationFrame(updateUI);
}
}
// Start tracking when the transport starts
function playAudio() {
Tone.Transport.start();
requestAnimationFrame(updateUI);
}