Source: input/wavProcessor.js

// wavProcessor.js
import {
  analyzeFrequencies,
  analyzeSmallChunkFrequencies,
  instantaneousFrequency,
} from "../input/fft.js";
import { ProgressID } from "../dom/dom.js";
var ProcessorProgress = 0;
/**
 * Reads a WAV file from the provided URL, parses it, and processes the audio data.
 * @param {string} url - The URL of the WAV file to read.
 */
function readWavFile(url, callback) {
  fetch(url)
    .then((response) => response.arrayBuffer())
    .then((buffer) => {
      const wavData = parseWav(buffer); // Parse the WAV file
      const sampleRate = wavData.sampleRate;
      const audioData = wavData.samples;

      //console.log("WAV file parsed successfully");
      //console.log("Sample Rate: ", sampleRate);
      //console.log("Number of samples: ", audioData.length);

      const allVoiceFrequencies = processAudioData(audioData, sampleRate, 2);
      //console.log("Voice F : ", voiceFrequencies);

      if (callback) {
        callback(allVoiceFrequencies); // Pass voiceFrequencies to the callback
      }
    })
    .catch((err) => {
      console.error("Error loading WAV file: ", err);
    });
}

/**
 * Parses WAV file data from an ArrayBuffer.
 * @param {ArrayBuffer} buffer - The WAV file buffer.
 * @returns {Object} Parsed WAV data including sample rate and audio samples.
 */
function parseWav(buffer) {
  const view = new DataView(buffer);
  const numChannels = view.getUint16(22, true);
  const sampleRate = view.getUint32(24, true);
  const bitsPerSample = view.getUint16(34, true);
  const dataOffset = 44; // WAV header size (assumes no additional sub-chunks)
  const bytesPerSample = bitsPerSample / 8;
  const numSamples = Math.floor(
    (view.byteLength - dataOffset) / (numChannels * bytesPerSample),
  );

  //console.log("Num Channels: ", numChannels);
  //console.log("Sample Rate: ", sampleRate);
  //console.log("Bits Per Sample: ", bitsPerSample);
  //console.log("Number of Samples: ", numSamples);

  const samples = new Float32Array(numSamples);

  for (let i = 0; i < numSamples; i++) {
    let sample = 0;
    const sampleIndex = dataOffset + i * numChannels * bytesPerSample;

    // Ensure we are not reading beyond the available buffer length
    if (sampleIndex >= buffer.byteLength) {
      console.error("Sample index exceeds buffer length at sample", i);
      break;
    }

    // If stereo, take only the left channel (instead of averaging)
    if (numChannels === 2) {
      const left = view.getInt16(sampleIndex, true);
      sample = left;
    } else {
      sample = view.getInt16(sampleIndex, true);
    }

    // Normalize to [-1, 1] range
    samples[i] = sample / 32768;
  }

  return {
    sampleRate: sampleRate,
    samples: samples,
  };
}

/**
 * @param {Float32Array} audioData - The audio data samples.
 * @param {number} sampleRate - The sample rate of the audio data.
 */
function processAudioData(audioData, sampleRate, n) {
  const chunkSize = 11025; // Adjusted chunk size for more effective analysis
  const numChunks = Math.ceil(audioData.length / chunkSize);

  // First loop: Process chunks from 0, 44100, 88200, ...
  const results = [];
  var ProcessorProgressCounter = 0;

  for (let offsetMultiplier = 0; offsetMultiplier < n; offsetMultiplier++) {
    const offset = 4410 * offsetMultiplier;
    const voiceFrequencies = new Map();

    for (let i = 0; i < Math.ceil(audioData.length / chunkSize); i++) {
      const start = i * chunkSize + offset;
      const end = Math.min(start + chunkSize, audioData.length);
      const chunk = audioData.slice(start, end);

      const result = analyzeFrequencies(chunk, sampleRate);
      ProcessorProgressCounter++;
      ProcessorProgress =
        (ProcessorProgressCounter /
          (n * Math.ceil(audioData.length / chunkSize))) *
        100;
      console.log(`Processor P ${ProcessorProgress}`);
      if (!isNaN(result.dominantFrequency)) {
        //console.log(`Chunk (offset ${offset}): Frequency = ${result.dominantFrequency} Hz, Volume = ${result.volume}, Duration = ${result.duration}`);
        voiceFrequencies.set(i, [
          result.dominantFrequency,
          result.volume,
          result.duration,
        ]);
      } else {
        //console.log(`Chunk (offset ${offset}): Frequency could not be determined (NaN)`);
      }
    }
    results.push(voiceFrequencies);
  }
  return results;
}

function updateProgressBarById(id, value) {
  const progressBar = document.getElementById(id);
  progressBar.style.width = `${value}%`;
}

// Export functions and data for CommonJS
export { readWavFile, parseWav, processAudioData, ProcessorProgress };