I have been trying for the past two days to figure out why a stereo VU meter emulating the analog style is not responding on the designated right channel. Tests in Chromium and Firefox show the same result.
When the page loads, both meters should be in the ready state with the needles in similar positions, but the right channel doesn't behave as intended. Also, channel 2 does not respond at all to the sound being played.
Here is the complete code, which can be copied to an HTML page and tested:
window.addEventListener('DOMContentLoaded', () => {
const audio = document.getElementById('audio');
const vuMeter1 = document.getElementById('vu-meter1');
const vuMeter2 = document.getElementById('vu-meter2');
const needle1 = document.querySelector('#vu-meter1 .needle');
const needle2 = document.querySelector('#vu-meter2 .needle');
// Create an audio context
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Create a media element source node from the audio element
const sourceNode = audioContext.createMediaElementSource(audio);
// Create a stereo analyser node
const analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 2048;
// Connect the nodes
sourceNode.connect(analyserNode);
analyserNode.connect(audioContext.destination);
// Create an array to store the frequency data
const frequencyData = new Uint8Array(analyserNode.frequencyBinCount);
// Function to update the VU meters
function updateVUMeters() {
// Get the frequency data
analyserNode.getByteFrequencyData(frequencyData);
// Calculate the average volume for each channel
const numChannels = analyserNode.fftSize / 2;
let totalVolume1 = 0;
let totalVolume2 = 0;
/*for (let i = 0; i < numChannels; i++) {
totalVolume1 += frequencyData[i];
totalVolume2 += frequencyData[i + numChannels];
}*/
for (let i = 0; i < numChannels; i++) {
totalVolume1 += frequencyData[i];
totalVolume2 += frequencyData[i + numChannels - 1]; // Subtract 1 to account for the zero-based indexing
}
const averageVolume1 = totalVolume1 / numChannels;
const averageVolume2 = totalVolume2 / numChannels;
//console.log(numChannels);
// Calculate the angle for the needle rotation
const angle1 = (averageVolume1 / 255) * 140 - 140 + 90;
const angle2 = (averageVolume2 / 255) * 140 - 140 + 90;
// Rotate the needles
/*needle1.style.transform = `translateX(-50%) rotate(${angle1}deg)`;
needle2.style.transform = `translateX(-50%) rotate(${angle2}deg)`;*/
needle1.style.transform = `rotate(${angle1}deg)`;
needle2.style.transform = `rotate(${angle2}deg)`;
// Schedule the next update
requestAnimationFrame(updateVUMeters);
}
// Start updating the VU meters
updateVUMeters();
// Play audio
//audio.play();
});
#vu-meter-container {
display: flex;
justify-content: center;
}
.vu-meter {
width: 200px;
height: 150px;
position: relative;
margin: 0 10px;
}
.vu-meter .scale {
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
perspective: 1000px;
}
.vu-meter .scale .scale-arc {
width: 100%;
height: 100%;
border-radius: 50%;
position: absolute;
box-sizing: border-box;
border: 2px solid lightgray;
transform-origin: center;
transform-style: preserve-3d;
}
.vu-meter .scale .scale-arc::before {
content: "";
position: absolute;
top: 0;
left: 50%;
width: 2px;
height: 100%;
background-color: lightgray;
transform-origin: center;
transform: translateX(-50%);
}
.vu-meter .scale .scale-range {
width: 100%;
height: 100%;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
transform-origin: center;
transform-style: preserve-3d;
}
.vu-meter .scale .scale-range--normal {
background-color: green;
transform: rotateX(0deg);
}
.vu-meter .scale .scale-range--peak {
background-color: red;
transform: rotateX(14deg);
}
.needle-container {
width: 100%;
height: 100%;
position: relative;
background: #dfc68d;
}
.needle {
width: 2px;
height: 90px;
background-color: red;
position: absolute;
bottom: 5px;
left: 50%;
transform-origin: bottom center;
transform: translateX(-50%) rotate(-140deg);
transition: transform 0.2s;
}
.minus {
position: absolute;
top: 5px;
left: 5px;
color: black;
font-weight: bold;
}
.plus {
position: absolute;
top: 5px;
right: 5px;
color: red;
font-weight: bold;
}
.vu-meter-label {
text-align: center;
margin-top: 10px;
font-weight: bold;
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<title>VU Meter</title>
<head>
</head>
<body>
<header>
<h1>VU Meter</h1>
</header>
<main>
<div id="vu-meter-container">
<div id="vu-meter1" class="vu-meter">
<div class="scale">
<div class="scale-arc">
<div class="scale-range scale-range--normal"></div>
<div class="scale-range scale-range--peak"></div>
</div>
</div>
<div class="needle-container">
<div class="minus">-</div>
<div class="plus">+</div>
<div class="needle"></div>
</div>
<div class="vu-meter-label">Channel 1</div>
</div>
<div id="vu-meter2" class="vu-meter">
<div class="scale">
<div class="scale-arc">
<div class="scale-range scale-range--normal"></div>
<div class="scale-range scale-range--peak"></div>
</div>
</div>
<div class="needle-container">
<div class="minus">-</div>
<div class="plus">+</div>
<div class="needle"></div>
</div>
<div class="vu-meter-label">Channel 2</div>
</div>
</div>
<div class="player">
<audio id="audio" controls>
<source src="https://rulerandcompass.net/music/BourreeInEMinor.mp3" type="audio/mpeg" id="src" />
</audio>
</div>
</main>
<footer></footer>
</body>
</html>