///| A bounded histogram of block energy indexed by LUFS.

///| Buckets cover -120..24 LUFS at 0.01 LU resolution; per-bucket energy sums

///|
/// retain exact mean-square values for the integrated gate calculation.
priv struct LoudnessHistogram {
  counts : Array[Double]
  energy_sums : Array[Double]
  mut total_count : Double
  mut above_range_count : Int32
}

///|
/// Lowest loudness represented without clamping.
const HISTOGRAM_MIN_LUFS : Double = -120.0

///|
/// Highest loudness represented without clamping.
const HISTOGRAM_MAX_LUFS : Double = 24.0

///|
/// Number of quantization buckets per loudness unit.
const HISTOGRAM_BUCKETS_PER_LU : Int32 = 100

///|
/// Creates fixed-size arrays for the declared range and resolution.
fn LoudnessHistogram::new() -> LoudnessHistogram {
  let bucket_count = ((HISTOGRAM_MAX_LUFS - HISTOGRAM_MIN_LUFS) *
    HISTOGRAM_BUCKETS_PER_LU.to_double()).to_int() +
    1
  let counts : Array[Double] = []
  let energy_sums : Array[Double] = []
  for _ in 0.. Int32 {
  let lufs = energy_to_lufs(energy)
  let scaled = (lufs - HISTOGRAM_MIN_LUFS) *
    HISTOGRAM_BUCKETS_PER_LU.to_double()
  if scaled < 0.0 {
    0
  } else if scaled >= self.counts.length().to_double() {
    self.counts.length() - 1
  } else {
    scaled.to_int()
  }
}

///|
/// Adds one complete gating block without retaining an unbounded block list.
fn LoudnessHistogram::push_energy(
  self : LoudnessHistogram,
  energy : Double,
) -> Unit {
  self.total_count = self.total_count + 1.0
  if energy_to_lufs(energy) > HISTOGRAM_MAX_LUFS {
    self.above_range_count = self.above_range_count + 1
    return
  }
  let bucket = self.bucket_for_energy(energy)
  self.counts[bucket] = self.counts[bucket] + 1.0
  self.energy_sums[bucket] = self.energy_sums[bucket] + energy
}

///|
/// Finds the first bucket whose lower edge is at or above a loudness gate.
fn LoudnessHistogram::first_bucket_at_or_above(
  self : LoudnessHistogram,
  threshold_lufs : Double,
) -> Int32 {
  let scaled = (threshold_lufs - HISTOGRAM_MIN_LUFS) *
    HISTOGRAM_BUCKETS_PER_LU.to_double()
  if scaled < 0.0 {
    0
  } else if scaled >= self.counts.length().to_double() {
    self.counts.length()
  } else {
    let base = scaled.to_int()
    let fraction = scaled - base.to_double()
    // Ignore floating-point residue at a mathematically exact bucket edge.
    if fraction > 0.000000001 {
      base + 1
    } else {
      base
    }
  }
}

///|
/// Returns the exact mean energy of occupied buckets above a gate.
fn LoudnessHistogram::mean_energy_at_or_above(
  self : LoudnessHistogram,
  threshold_lufs : Double,
) -> Double? {
  let start = self.first_bucket_at_or_above(threshold_lufs)
  if start >= self.counts.length() {
    return None
  }
  let mut count = 0.0
  let mut energy = 0.0
  for bucket in start.. Double {
  let start = self.first_bucket_at_or_above(threshold_lufs)
  let mut count = 0.0
  for bucket in start.. Double? {
  let start = self.first_bucket_at_or_above(threshold_lufs)
  let count = self.count_at_or_above(threshold_lufs)
  if count == 0.0 {
    return None
  }
  // Use the nearest zero-based order-statistic index, with half-up ties.
  let target = @math.floor((count - 1.0) * percentile.to_double() / 100.0 + 0.5)
  let mut cumulative = 0.0
  for bucket in start.. target {
      return Some(
        HISTOGRAM_MIN_LUFS +
        bucket.to_double() / HISTOGRAM_BUCKETS_PER_LU.to_double(),
      )
    }
  }
  Some(HISTOGRAM_MAX_LUFS)
}

///|
/// Clears all bins while retaining their allocated, fixed capacity.
fn LoudnessHistogram::clear(self : LoudnessHistogram) -> Unit {
  for bucket in 0.. LoudnessHistogram {
  let counts : Array[Double] = []
  let energy_sums : Array[Double] = []
  for bucket in 0.. Unit {
  for bucket in 0..