Skip to content

Commit efe3bae

Browse files
Add multi-track advanced example
1 parent 32e9c8c commit efe3bae

File tree

8 files changed

+403
-0
lines changed

8 files changed

+403
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ The [media-source-buffer](https://github.com/mdn/webaudio-examples/tree/main/med
6969

7070
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/).
7171

72+
### Multi track advanced
73+
74+
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/).
75+
7276
### Offline audio context
7377

7478
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/).

multi-track-advanced/bassguitar.mp3

631 KB
Binary file not shown.

multi-track-advanced/clav.mp3

654 KB
Binary file not shown.

multi-track-advanced/drums.mp3

654 KB
Binary file not shown.

multi-track-advanced/horns.mp3

654 KB
Binary file not shown.

multi-track-advanced/index.html

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Web Audio API Mixer - Advanced</title>
6+
<meta
7+
name="description"
8+
content="A way to make sure files have loaded before playing them" />
9+
<meta
10+
name="viewport"
11+
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
12+
<link rel="stylesheet" type="text/css" href="style.css" />
13+
</head>
14+
<body>
15+
<!--
16+
Some browsers' autoplay policy requires that an AudioContext be initialized
17+
during an input event in order to correctly synchronize.
18+
19+
So provide a simple button to get things started.
20+
-->
21+
<button
22+
id="startbutton"
23+
class="top-left-button"
24+
aria-label="Start loading tracks">
25+
Press to load tracks
26+
</button>
27+
28+
<div class="wrapper">
29+
<section id="tracks" role="region" aria-labelledby="tracks-title">
30+
<ul role="list">
31+
<li data-loading="true" role="listitem">
32+
<a href="leadguitar.mp3" class="track" tabindex="0">Lead Guitar</a>
33+
<input
34+
type="range"
35+
class="fader"
36+
min="0"
37+
max="1"
38+
step="0.01"
39+
value="0.8"
40+
aria-label="Volume control for Lead Guitar" />
41+
<p class="loading-text" aria-live="polite">Loading...</p>
42+
<button
43+
class="playbutton"
44+
aria-describedby="guitar-play-label"
45+
aria-pressed="false">
46+
<span id="guitar-play-label">Play</span>
47+
</button>
48+
<button
49+
class="solobutton"
50+
aria-describedby="guitar-solo-label"
51+
aria-pressed="false">
52+
<span id="guitar-solo-label">Solo</span>
53+
</button>
54+
</li>
55+
<li data-loading="true" role="listitem">
56+
<a href="bassguitar.mp3" class="track" tabindex="0">Bass Guitar</a>
57+
<input
58+
type="range"
59+
class="fader"
60+
min="0"
61+
max="1"
62+
step="0.01"
63+
value="0.8"
64+
aria-label="Volume control for Bass Guitar" />
65+
<p class="loading-text" aria-live="polite">Loading...</p>
66+
<button
67+
class="playbutton"
68+
aria-describedby="bass-play-label"
69+
aria-pressed="false">
70+
<span id="bass-play-label">Play</span>
71+
</button>
72+
<button
73+
class="solobutton"
74+
aria-describedby="bass-solo-label"
75+
aria-pressed="false">
76+
<span id="bass-solo-label">Solo</span>
77+
</button>
78+
</li>
79+
<li data-loading="true" role="listitem">
80+
<a href="drums.mp3" class="track" tabindex="0">Drums</a>
81+
<input
82+
type="range"
83+
class="fader"
84+
min="0"
85+
max="1"
86+
step="0.01"
87+
value="0.8"
88+
aria-label="Volume control for Drums" />
89+
<p class="loading-text" aria-live="polite">Loading...</p>
90+
<button
91+
class="playbutton"
92+
aria-describedby="drums-play-label"
93+
aria-pressed="false">
94+
<span id="drums-play-label">Play</span>
95+
</button>
96+
<button
97+
class="solobutton"
98+
aria-describedby="drums-solo-label"
99+
aria-pressed="false">
100+
<span id="drums-solo-label">Solo</span>
101+
</button>
102+
</li>
103+
<li data-loading="true" role="listitem">
104+
<a href="horns.mp3" class="track" tabindex="0">Horns</a>
105+
<input
106+
type="range"
107+
class="fader"
108+
min="0"
109+
max="1"
110+
step="0.01"
111+
value="0.8"
112+
aria-label="Volume control for Horns" />
113+
<p class="loading-text" aria-live="polite">Loading...</p>
114+
<button
115+
class="playbutton"
116+
aria-describedby="horns-play-label"
117+
aria-pressed="false">
118+
<span id="horns-play-label">Play</span>
119+
</button>
120+
<button
121+
class="solobutton"
122+
aria-describedby="horns-solo-label"
123+
aria-pressed="false">
124+
<span id="horns-solo-label">Solo</span>
125+
</button>
126+
</li>
127+
<li data-loading="true" role="listitem">
128+
<a href="clav.mp3" class="track" tabindex="0">Clavi</a>
129+
<input
130+
type="range"
131+
class="fader"
132+
min="0"
133+
max="1"
134+
step="0.01"
135+
value="0.8"
136+
aria-label="Volume control for Clavi" />
137+
<p class="loading-text" aria-live="polite">Loading...</p>
138+
<button
139+
class="playbutton"
140+
aria-describedby="clavi-play-label"
141+
aria-pressed="false">
142+
<span id="clavi-play-label">Play</span>
143+
</button>
144+
<button
145+
class="solobutton"
146+
aria-describedby="clavi-solo-label"
147+
aria-pressed="false">
148+
<span id="clavi-solo-label">Solo</span>
149+
</button>
150+
</li>
151+
</ul>
152+
<p class="sourced">
153+
All tracks sourced from <a href="http://jplayer.org/">jplayer.org</a>
154+
</p>
155+
</section>
156+
</div>
157+
158+
<script>
159+
let audioCtx = null;
160+
let soloedButton = null;
161+
162+
// Provide a start button so demo can load tracks from an event handler for cross-browser compatibility
163+
const startButton = document.querySelector("#startbutton");
164+
165+
// Select all list elements
166+
const trackEls = document.querySelectorAll("li");
167+
168+
// Loading function for fetching the audio file and decode the data
169+
async function getFile(filepath) {
170+
const response = await fetch(filepath);
171+
const arrayBuffer = await response.arrayBuffer();
172+
return await audioCtx.decodeAudioData(arrayBuffer);
173+
}
174+
175+
function createGainNode() {
176+
const gainNode = audioCtx.createGain();
177+
gainNode.connect(audioCtx.destination);
178+
return gainNode;
179+
}
180+
181+
// Create a buffer, plop in data, connect and play -> modify graph here if required
182+
function playTrack(audioBuffer, gainNode, playButton) {
183+
const source = audioCtx.createBufferSource();
184+
source.buffer = audioBuffer;
185+
source.connect(gainNode);
186+
source.start();
187+
playButton.classList.add("playing");
188+
playButton.setAttribute("aria-pressed", "true");
189+
source.onended = () => {
190+
playButton.classList.remove("playing");
191+
playButton.setAttribute("aria-pressed", "false");
192+
};
193+
}
194+
195+
function toggleSolo(button) {
196+
if (soloedButton === button) {
197+
button.classList.remove("active");
198+
button.setAttribute("aria-pressed", "false");
199+
soloedButton = null;
200+
} else {
201+
if (soloedButton) {
202+
soloedButton.classList.remove("active");
203+
soloedButton.setAttribute("aria-pressed", "false");
204+
}
205+
button.classList.add("active");
206+
button.setAttribute("aria-pressed", "true");
207+
soloedButton = button;
208+
}
209+
updateFadersAndMute();
210+
}
211+
212+
function updateFadersAndMute() {
213+
// Get children
214+
trackEls.forEach((el) => {
215+
const fader = el.querySelector(".fader");
216+
// Retrieve the gain node
217+
const gainNode = el.gainNode;
218+
const isSoloed = el.contains(soloedButton);
219+
220+
if (soloedButton) {
221+
// Mute non-soloed tracks
222+
gainNode.gain.value = isSoloed ? fader.value : 0;
223+
fader.classList.toggle("disabled", !isSoloed);
224+
} else {
225+
// Restore all tracks if no solo is active
226+
gainNode.gain.value = fader.value;
227+
fader.classList.remove("disabled");
228+
}
229+
});
230+
}
231+
232+
startButton.addEventListener("click", () => {
233+
if (audioCtx) return;
234+
audioCtx = new AudioContext();
235+
startButton.hidden = true;
236+
237+
trackEls.forEach((el) => {
238+
const anchor = el.querySelector("a");
239+
const playButton = el.querySelector(".playbutton");
240+
const soloButton = el.querySelector(".solobutton");
241+
const loadText = el.querySelector(".loading-text");
242+
const fader = el.querySelector(".fader");
243+
244+
// Create a gain node
245+
const gainNode = createGainNode();
246+
// Store the gain node in the element
247+
el.gainNode = gainNode;
248+
249+
fader.addEventListener("input", (e) => {
250+
gainNode.gain.value = e.target.value;
251+
});
252+
253+
getFile(anchor.href).then((track) => {
254+
loadText.style.display = "none";
255+
playButton.style.display = "inline-block";
256+
soloButton.style.display = "inline-block";
257+
258+
playButton.addEventListener("click", () => {
259+
if (audioCtx.state === "suspended") {
260+
audioCtx.resume();
261+
}
262+
playTrack(track, gainNode, playButton);
263+
});
264+
265+
soloButton.addEventListener("click", () => toggleSolo(soloButton));
266+
});
267+
});
268+
});
269+
</script>
270+
</body>
271+
</html>

multi-track-advanced/leadguitar.mp3

654 KB
Binary file not shown.

0 commit comments

Comments
 (0)