0

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>
Eflyax
  • 5,307
  • 2
  • 19
  • 29
Mark Lee
  • 163
  • 11
  • Try this https://stackoverflow.com/questions/31697427/getting-l-r-data-with-analysernode-and-channelsplitter – Cody Tookode Jun 09 '23 at 06:31
  • That seems to be splitting the users mic input. How does it fit this situation? – Mark Lee Jun 09 '23 at 11:39
  • Does this answer your question? [AudioContext stereo output from MediaStreamDestination in Chrome M34](https://stackoverflow.com/questions/23093723/audiocontext-stereo-output-from-mediastreamdestination-in-chrome-m34) – Cody Tookode Jun 09 '23 at 11:51

0 Answers0