-
Notifications
You must be signed in to change notification settings - Fork 433
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
32e9c8c
commit d2752b7
Showing
8 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
<!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"> | ||
Press to load tracks | ||
</button> | ||
|
||
<div class="wrapper"> | ||
<section id="tracks"> | ||
<ul> | ||
<li data-loading="true"> | ||
<a href="leadguitar.mp3" class="track">Lead Guitar</a> | ||
<input | ||
type="range" | ||
class="fader" | ||
min="0" | ||
max="1" | ||
step="0.01" | ||
value="0.8" /> | ||
<p class="loading-text">Loading...</p> | ||
<button class="playbutton" aria-describedby="guitar-play-label"> | ||
<span id="guitar-play-label">Play</span> | ||
</button> | ||
<button class="solobutton" aria-describedby="guitar-solo-label"> | ||
<span>Solo</span> | ||
</button> | ||
</li> | ||
<li data-loading="true"> | ||
<a href="bassguitar.mp3" class="track">Bass Guitar</a> | ||
<input | ||
type="range" | ||
class="fader" | ||
min="0" | ||
max="1" | ||
step="0.01" | ||
value="0.8" /> | ||
<p class="loading-text">Loading...</p> | ||
<button class="playbutton" aria-describedby="bass-play-label"> | ||
<span id="bass-play-label">Play</span> | ||
</button> | ||
<button class="solobutton" aria-describedby="bass-solo-label"> | ||
<span>Solo</span> | ||
</button> | ||
</li> | ||
<li data-loading="true"> | ||
<a href="drums.mp3" class="track">Drums</a> | ||
<input | ||
type="range" | ||
class="fader" | ||
min="0" | ||
max="1" | ||
step="0.01" | ||
value="0.8" /> | ||
<p class="loading-text">Loading...</p> | ||
<button class="playbutton" aria-describedby="drums-play-label"> | ||
<span id="drums-play-label">Play</span> | ||
</button> | ||
<button class="solobutton" aria-describedby="drums-solo-label"> | ||
<span>Solo</span> | ||
</button> | ||
</li> | ||
<li data-loading="true"> | ||
<a href="horns.mp3" class="track">Horns</a> | ||
<input | ||
type="range" | ||
class="fader" | ||
min="0" | ||
max="1" | ||
step="0.01" | ||
value="0.8" /> | ||
<p class="loading-text">Loading...</p> | ||
<button class="playbutton" aria-describedby="horns-play-label"> | ||
<span id="horns-play-label">Play</span> | ||
</button> | ||
<button class="solobutton" aria-describedby="horns-solo-label"> | ||
<span>Solo</span> | ||
</button> | ||
</li> | ||
<li data-loading="true"> | ||
<a href="clav.mp3" class="track">Clavi</a> | ||
<input | ||
type="range" | ||
class="fader" | ||
min="0" | ||
max="1" | ||
step="0.01" | ||
value="0.8" /> | ||
<p class="loading-text">Loading...</p> | ||
<button class="playbutton" aria-describedby="clavi-play-label"> | ||
<span id="clavi-play-label">Play</span> | ||
</button> | ||
<button class="solobutton" aria-describedby="clavi-solo-label"> | ||
<span>Solo</span> | ||
</button> | ||
</li> | ||
</ul> | ||
<p class="sourced"> | ||
All tracks sourced from <a href="http://jplayer.org/">jplayer.org</a> | ||
</p> | ||
</section> | ||
</div> | ||
|
||
<script type="text/javascript"> | ||
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"); | ||
source.onended = () => playButton.classList.remove("playing"); | ||
} | ||
|
||
function toggleSolo(button) { | ||
if (soloedButton === button) { | ||
button.classList.remove("active"); | ||
soloedButton = null; | ||
} else { | ||
if (soloedButton) soloedButton.classList.remove("active"); | ||
button.classList.add("active"); | ||
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
body { | ||
background-color: #121212; | ||
color: white; | ||
font-family: Arial, sans-serif; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
a { | ||
color: #8cb4ff; | ||
} | ||
|
||
.top-left-button { | ||
position: absolute; | ||
top: 20px; | ||
left: 20px; | ||
background-color: #4caf50; | ||
border: none; | ||
padding: 10px 15px; | ||
border-radius: 5px; | ||
cursor: pointer; | ||
transition: background-color 0.3s; | ||
} | ||
|
||
.top-left-button:hover { | ||
background-color: #45a049; | ||
} | ||
|
||
.wrapper { | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
height: 100vh; | ||
padding: 20px; | ||
box-sizing: border-box; | ||
} | ||
|
||
#tracks { | ||
display: flex; | ||
flex-direction: row; | ||
gap: 20px; | ||
} | ||
|
||
ul { | ||
display: flex; | ||
gap: 20px; | ||
padding: 0; | ||
margin: 0; | ||
list-style: none; | ||
} | ||
|
||
li { | ||
background-color: #1e1e1e; | ||
padding: 20px; | ||
padding-top: 30px; | ||
border-radius: 8px; | ||
width: 150px; | ||
height: 370px; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
gap: 10px; | ||
} | ||
|
||
.track { | ||
font-weight: bold; | ||
font-size: 18px; | ||
text-align: center; | ||
} | ||
|
||
.playbutton { | ||
background-color: #ff5050; | ||
border: none; | ||
padding: 8px 12px; | ||
border-radius: 5px; | ||
cursor: pointer; | ||
transition: background-color 0.3s; | ||
display: none; | ||
} | ||
|
||
.playbutton:hover, | ||
.playbutton.playing { | ||
background-color: #45a049; | ||
} | ||
|
||
.solobutton { | ||
background-color: gray; | ||
border: none; | ||
padding: 8px 12px; | ||
border-radius: 5px; | ||
cursor: pointer; | ||
transition: background-color 0.3s; | ||
display: none; | ||
} | ||
|
||
.solobutton.active { | ||
background-color: yellow; | ||
} | ||
|
||
.fader { | ||
-webkit-appearance: slider-vertical; | ||
width: 30px; | ||
height: 220px; | ||
margin-top: 10px; | ||
cursor: pointer; | ||
} | ||
|
||
.fader.disabled { | ||
background-color: #555; | ||
pointer-events: none; | ||
} | ||
|
||
.loading-text { | ||
font-size: 14px; | ||
color: #bbb; | ||
} | ||
|
||
.sourced { | ||
position: absolute; | ||
bottom: 20px; | ||
right: 20px; | ||
font-size: 14px; | ||
color: #bbb; | ||
} | ||
|
||
button:focus { | ||
outline: 2px solid #2196f3; | ||
} |