///|
/// Multichannel output on the same timeline; no automatic peak normalization.
pub struct Audio {
  priv samples : Array[Array[Double]]
  sample_rate : Int32
  latency_samples : Int32
}

///|
/// Uses the existing MoonSpectrum FFT. Returns the complete linear-convolution tail.
pub fn convolve(
  response : Response,
  dry : Array[Double],
) -> Result[Audio, String] {
  if dry.is_empty() || dry.length() > 262144 {
    return Err("dry signal must contain 1..262144 samples")
  }
  for v in dry {
    if !(v >= -1.0e6 && v <= 1.0e6) {
      return Err("dry samples must be finite and abs <= 1000000")
    }
  }
  let length = dry.length() + response.scene.output_frames() - 1
  if length > 262144 || length * response.samples.length() > 2000000 {
    return Err("convolution output budget exceeded")
  }
  let channels : Array[Array[Double]] = []
  for h in response.samples {
    let wet = @spectrum.fft_convolve(dry[:], h[:]) catch {
      _ => return Err("FFT convolution failed")
    }
    if wet.length() != length {
      return Err("FFT backend returned an unexpected length")
    }
    channels.push(wet)
  }
  Ok({
    samples: channels,
    sample_rate: response.scene.sample_rate,
    latency_samples: response.scene.half_width,
  })
}

///|
pub fn Audio::channel(
  self : Audio,
  index : Int32,
) -> Result[Array[Double], String] {
  if index < 0 || index >= self.samples.length() {
    Err("audio channel index out of range")
  } else {
    Ok(self.samples[index].copy())
  }
}

///|
pub fn Audio::channel_count(self : Audio) -> Int32 {
  self.samples.length()
}

///|
pub fn Audio::frames(self : Audio) -> Int32 {
  self.samples[0].length()
}