Skip to content

Commit

Permalink
Add multi-track advanced example
Browse files Browse the repository at this point in the history
  • Loading branch information
mattfsourcecode committed Oct 14, 2024
1 parent 32e9c8c commit efe3bae
Show file tree
Hide file tree
Showing 8 changed files with 403 additions and 0 deletions.
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

0 comments on commit efe3bae

Please sign in to comment.