Skip to content

Add multi-track advanced example #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ The [media-source-buffer](https://github.com/mdn/webaudio-examples/tree/main/med

The [multi-track](https://github.com/mdn/webaudio-examples/tree/main/multi-track) directory contains an example of connecting separate independently-playable audio tracks to a single [`AudioDestinationNode`](https://developer.mozilla.org/en-US/docs/Web/API/AudioDestinationNode) interface. [Run the example live](http://mdn.github.io/webaudio-examples/multi-track/).

### Multi track advanced

The [multi-track-advanced](https://github.com/mdn/webaudio-examples/tree/main/multi-track-advanced) directory contains an enhanced version of the original multi-track example. This version introduces a [`GainNode`](https://developer.mozilla.org/en-US/docs/Web/API/GainNode) for each track, providing precise control over individual audio levels through volume faders. It also includes solo buttons, enabling the isolation of a specific track by muting all other tracks. [Run the example live](http://mdn.github.io/webaudio-examples/multi-track-advanced/).

### Offline audio context

The [offline-audio-context](https://github.com/mdn/webaudio-examples/tree/main/offline-audio-context) directory contains a simple example to show how a Web Audio API [`OfflineAudioContext`](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext) interface can be used to rapidly process/render audio in the background to create a buffer, which can then be used in any way you please. For more information, see [https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext). [Run example live](http://mdn.github.io/webaudio-examples/offline-audio-context/).
Expand Down
Binary file added multi-track-advanced/bassguitar.mp3
Binary file not shown.
Binary file added multi-track-advanced/clav.mp3
Binary file not shown.
Binary file added multi-track-advanced/drums.mp3
Binary file not shown.
Binary file added multi-track-advanced/horns.mp3
Binary file not shown.
271 changes: 271 additions & 0 deletions multi-track-advanced/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Web Audio API Mixer - Advanced</title>
<meta
name="description"
content="A way to make sure files have loaded before playing them" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<!--
Some browsers' autoplay policy requires that an AudioContext be initialized
during an input event in order to correctly synchronize.

So provide a simple button to get things started.
-->
<button
id="startbutton"
class="top-left-button"
aria-label="Start loading tracks">
Press to load tracks
</button>

<div class="wrapper">
<section id="tracks" role="region" aria-labelledby="tracks-title">
<ul role="list">
<li data-loading="true" role="listitem">
<a href="leadguitar.mp3" class="track" tabindex="0">Lead Guitar</a>
<input
type="range"
class="fader"
min="0"
max="1"
step="0.01"
value="0.8"
aria-label="Volume control for Lead Guitar" />
<p class="loading-text" aria-live="polite">Loading...</p>
<button
class="playbutton"
aria-describedby="guitar-play-label"
aria-pressed="false">
<span id="guitar-play-label">Play</span>
</button>
<button
class="solobutton"
aria-describedby="guitar-solo-label"
aria-pressed="false">
<span id="guitar-solo-label">Solo</span>
</button>
</li>
<li data-loading="true" role="listitem">
<a href="bassguitar.mp3" class="track" tabindex="0">Bass Guitar</a>
<input
type="range"
class="fader"
min="0"
max="1"
step="0.01"
value="0.8"
aria-label="Volume control for Bass Guitar" />
<p class="loading-text" aria-live="polite">Loading...</p>
<button
class="playbutton"
aria-describedby="bass-play-label"
aria-pressed="false">
<span id="bass-play-label">Play</span>
</button>
<button
class="solobutton"
aria-describedby="bass-solo-label"
aria-pressed="false">
<span id="bass-solo-label">Solo</span>
</button>
</li>
<li data-loading="true" role="listitem">
<a href="drums.mp3" class="track" tabindex="0">Drums</a>
<input
type="range"
class="fader"
min="0"
max="1"
step="0.01"
value="0.8"
aria-label="Volume control for Drums" />
<p class="loading-text" aria-live="polite">Loading...</p>
<button
class="playbutton"
aria-describedby="drums-play-label"
aria-pressed="false">
<span id="drums-play-label">Play</span>
</button>
<button
class="solobutton"
aria-describedby="drums-solo-label"
aria-pressed="false">
<span id="drums-solo-label">Solo</span>
</button>
</li>
<li data-loading="true" role="listitem">
<a href="horns.mp3" class="track" tabindex="0">Horns</a>
<input
type="range"
class="fader"
min="0"
max="1"
step="0.01"
value="0.8"
aria-label="Volume control for Horns" />
<p class="loading-text" aria-live="polite">Loading...</p>
<button
class="playbutton"
aria-describedby="horns-play-label"
aria-pressed="false">
<span id="horns-play-label">Play</span>
</button>
<button
class="solobutton"
aria-describedby="horns-solo-label"
aria-pressed="false">
<span id="horns-solo-label">Solo</span>
</button>
</li>
<li data-loading="true" role="listitem">
<a href="clav.mp3" class="track" tabindex="0">Clavi</a>
<input
type="range"
class="fader"
min="0"
max="1"
step="0.01"
value="0.8"
aria-label="Volume control for Clavi" />
<p class="loading-text" aria-live="polite">Loading...</p>
<button
class="playbutton"
aria-describedby="clavi-play-label"
aria-pressed="false">
<span id="clavi-play-label">Play</span>
</button>
<button
class="solobutton"
aria-describedby="clavi-solo-label"
aria-pressed="false">
<span id="clavi-solo-label">Solo</span>
</button>
</li>
</ul>
<p class="sourced">
All tracks sourced from <a href="http://jplayer.org/">jplayer.org</a>
</p>
</section>
</div>

<script>
let audioCtx = null;
let soloedButton = null;

// Provide a start button so demo can load tracks from an event handler for cross-browser compatibility
const startButton = document.querySelector("#startbutton");

// Select all list elements
const trackEls = document.querySelectorAll("li");

// Loading function for fetching the audio file and decode the data
async function getFile(filepath) {
const response = await fetch(filepath);
const arrayBuffer = await response.arrayBuffer();
return await audioCtx.decodeAudioData(arrayBuffer);
}

function createGainNode() {
const gainNode = audioCtx.createGain();
gainNode.connect(audioCtx.destination);
return gainNode;
}

// Create a buffer, plop in data, connect and play -> modify graph here if required
function playTrack(audioBuffer, gainNode, playButton) {
const source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
source.connect(gainNode);
source.start();
playButton.classList.add("playing");
playButton.setAttribute("aria-pressed", "true");
source.onended = () => {
playButton.classList.remove("playing");
playButton.setAttribute("aria-pressed", "false");
};
}

function toggleSolo(button) {
if (soloedButton === button) {
button.classList.remove("active");
button.setAttribute("aria-pressed", "false");
soloedButton = null;
} else {
if (soloedButton) {
soloedButton.classList.remove("active");
soloedButton.setAttribute("aria-pressed", "false");
}
button.classList.add("active");
button.setAttribute("aria-pressed", "true");
soloedButton = button;
}
updateFadersAndMute();
}

function updateFadersAndMute() {
// Get children
trackEls.forEach((el) => {
const fader = el.querySelector(".fader");
// Retrieve the gain node
const gainNode = el.gainNode;
const isSoloed = el.contains(soloedButton);

if (soloedButton) {
// Mute non-soloed tracks
gainNode.gain.value = isSoloed ? fader.value : 0;
fader.classList.toggle("disabled", !isSoloed);
} else {
// Restore all tracks if no solo is active
gainNode.gain.value = fader.value;
fader.classList.remove("disabled");
}
});
}

startButton.addEventListener("click", () => {
if (audioCtx) return;
audioCtx = new AudioContext();
startButton.hidden = true;

trackEls.forEach((el) => {
const anchor = el.querySelector("a");
const playButton = el.querySelector(".playbutton");
const soloButton = el.querySelector(".solobutton");
const loadText = el.querySelector(".loading-text");
const fader = el.querySelector(".fader");

// Create a gain node
const gainNode = createGainNode();
// Store the gain node in the element
el.gainNode = gainNode;

fader.addEventListener("input", (e) => {
gainNode.gain.value = e.target.value;
});

getFile(anchor.href).then((track) => {
loadText.style.display = "none";
playButton.style.display = "inline-block";
soloButton.style.display = "inline-block";

playButton.addEventListener("click", () => {
if (audioCtx.state === "suspended") {
audioCtx.resume();
}
playTrack(track, gainNode, playButton);
});

soloButton.addEventListener("click", () => toggleSolo(soloButton));
});
});
});
</script>
</body>
</html>
Binary file added multi-track-advanced/leadguitar.mp3
Binary file not shown.
Loading
Loading