Source: input/yin.js

const DEFAULT_THRESHOLD = 0.07;

function difference(data) {
  const n = data.length;
  const results = new Float32Array(n);
  let difference;
  let summation;
  const windowSize = Math.floor(n * 0.5);

  for (let tau = 0; tau <= windowSize; tau++) {
    summation = 0;
    for (let j = 0; j < windowSize; j++) {
      difference = data[j] - data[j + tau];
      summation += difference * difference;
    }
    results[tau] = summation;
  }
  return results;
}

function cumulativeMeanNormalizedDifference(data) {
  const n = data.length;
  const results = new Float32Array(n);
  let summation;

  for (let tau = 0; tau < n; tau++) {
    summation = 0;
    for (let j = 0; j <= tau; j++) {
      summation += data[j];
    }
    results[tau] = data[tau] / (summation / tau);
  }
  return results;
}

function absoluteThreshold(data, threshold) {
  let k = Number.POSITIVE_INFINITY;
  let tau;

  for (let i = 0, n = data.length; i < n; i++) {
    const x = data[i];
    if (x < threshold) {
      return i;
    }
    if (x < k) {
      k = x;
      tau = i;
    }
  }
  return tau;
}

function bestLocalEstimate(data, tau) {
  let i = tau + 1;
  const n = data.length;
  let k = data[tau];

  while (i < n && data[i] < k) {
    k = data[i];
    i++;
  }
  return i - 1;
}

// Function to calculate RMS for volume estimation
function calculateRMS(data) {
  let sumSquares = 0;
  for (let i = 0; i < data.length; i++) {
    sumSquares += data[i] * data[i];
  }
  return Math.sqrt(sumSquares / data.length);
}

/**
 * Enhanced YIN algorithm to estimate fundamental frequency, duration, and volume of audio signal
 * @param {Float32Array} data The time-domain data for the audio signal
 * @param {Number} sampleRate The sample rate
 * @param {Number} [threshold = 0.07] The threshold
 * @returns {Object} An object with frequency, volume, and chunk duration
 */
export function yin(data, sampleRate, aThreshold = DEFAULT_THRESHOLD) {
  // Step 1: Calculate the difference function
  const diff = difference(data);

  // Step 2: Calculate the cumulative mean normalized difference function
  const cmnd = cumulativeMeanNormalizedDifference(diff);

  // Step 3: Apply the threshold to find the first dip
  const tau = absoluteThreshold(cmnd, aThreshold);

  if (tau === -1) {
    return { frequency: NaN, duration: 0, volume: 0 }; // If no valid tau is found, return NaN
  }

  // Step 4: Refine the estimate using parabolic interpolation
  const refinedTau = bestLocalEstimate(cmnd, tau);

  // Step 5: Convert the refined lag into a frequency
  const frequency = sampleRate / refinedTau;

  // Step 6: Calculate the chunk's volume using RMS
  const volume = calculateRMS(data);

  // Step 7: Calculate the chunk duration in seconds
  const duration = data.length / sampleRate;

  return { frequency, volume, duration };
}