///|
/// Counts for this call, in complete frames. Retry unconsumed input.
pub(all) struct Progress {
consumed_frames : Int
produced_frames : Int
needs_input : Bool
needs_output : Bool
finished : Bool
}
///|
/// Future input needed by the centered filter; no frames are prepended to output.
pub(all) struct Latency {
input_frames : Int
output_frames : Int
seconds : Double
}
///|
/// Current stream counters and fixed storage budgets (Double slots, not RSS).
pub(all) struct Stats {
input_frames : Int64
output_frames : Int64
buffered_frames : Int
peak_history_frames : Int
history_capacity_samples : Int
coefficient_samples : Int
internal_samples : Int
}
///|
enum StreamState {
Active
Finishing
Finished
Failed
}
///|
/// A single PCM stream. Internal sample storage has a fixed upper bound.
pub struct Resampler {
priv config : Config
priv coefficients : FixedArray[FixedArray[Double]]
priv history : FixedArray[Double]
priv capacity : Int
priv radius : Int
priv mut input_count : Int64
priv mut output_count : Int64
priv mut position : Int64
priv mut phase : Int
priv mut peak : Int
priv mut state : StreamState
}
///|
/// Construct a bounded filter and ring. Config can only be created by validation.
pub fn Resampler::new(config : Config) -> Resampler {
let identity = config.input_rate == config.output_rate
let radius = if identity { 0 } else { @filter.FILTER_RADIUS }
let ratio = config.output_rate.to_double() / config.input_rate.to_double()
let coefficients = if identity {
FixedArray::make(0, FixedArray::make(0, 0.0))
} else {
@filter.make_filter(
config.numerator,
0.94 * (if ratio < 1.0 { ratio } else { 1.0 }),
).unwrap()
}
let capacity = if identity { 0 } else { 2 * radius + 8 }
{
config,
coefficients,
history: FixedArray::make(capacity * config.channels, 0.0),
capacity,
radius,
input_count: 0L,
output_count: 0L,
position: 0L,
phase: 0,
peak: 0,
state: Active,
}
}
///|
/// Lookahead in input/output frames, and seconds. Identity has zero latency.
pub fn Resampler::latency(self : Resampler) -> Latency {
{
input_frames: self.radius,
output_frames: (
self.radius * self.config.numerator + self.config.denominator - 1
) /
self.config.denominator,
seconds: self.radius.to_double() / self.config.input_rate.to_double(),
}
}
///|
/// True only when finish has drained all timestamped output frames.
pub fn Resampler::is_finished(self : Resampler) -> Bool {
self.state is Finished
}
///|
fn Resampler::live_frames(self : Resampler) -> Int {
if self.radius == 0 || self.state is Finished {
return 0
}
let oldest = (self.position - self.radius.to_int64()).max(0L)
(self.input_count - oldest).max(0L).to_int()
}
///|
/// Storage counts exclude caller buffers, object headers and allocator overhead.
pub fn Resampler::stats(self : Resampler) -> Stats {
let coefficient_samples = self.coefficients.length() * (2 * self.radius + 1)
{
input_frames: self.input_count,
output_frames: self.output_count,
buffered_frames: self.live_frames(),
peak_history_frames: self.peak,
history_capacity_samples: self.history.length(),
coefficient_samples,
internal_samples: self.history.length() + coefficient_samples,
}
}
///|
/// Explicitly start an unrelated stream while reusing all allocated tables/ring.
pub fn Resampler::reset(self : Resampler) -> Unit {
self.input_count = 0L
self.output_count = 0L
self.position = 0L
self.phase = 0
self.peak = 0
self.state = Active
}
///|
fn Resampler::advance(self : Resampler) -> Unit {
let next = self.phase + self.config.denominator
self.position += (next / self.config.numerator).to_int64()
self.phase = next % self.config.numerator
}
///|
fn Resampler::emit(
self : Resampler,
output : FixedArray[Double],
offset : Int,
) -> Result[Unit, ResampleError] {
let row = self.coefficients[self.phase]
for channel in 0..= 0L && frame < self.input_count {
let index = (frame % self.capacity.to_int64()).to_int() *
self.config.channels +
channel
sum += row[tap] * self.history[index]
}
}
if sum.is_nan() || sum.is_inf() {
self.state = Failed
return Err(NumericOverflow)
}
output[offset * self.config.channels + channel] = sum
}
self.output_count += 1L
Ok(())
}
///|
fn Resampler::progress(
self : Resampler,
consumed : Int,
produced : Int,
capacity : Int,
) -> Progress {
{
consumed_frames: consumed,
produced_frames: produced,
needs_input: self.state is Active &&
(
self.radius == 0 ||
self.position + self.radius.to_int64() >= self.input_count
),
needs_output: produced == capacity && !(self.state is Finished),
finished: self.state is Finished,
}
}
///|
/// Validate the entire supplied view, then consume only reported frames.
/// Zero output capacity consumes nothing. Invalid/misaligned/nonfinite inputs
/// leave stream state unchanged. NumericOverflow requires reset before reuse.
pub fn Resampler::process(
self : Resampler,
input : ArrayView[Double],
output : FixedArray[Double],
) -> Result[Progress, ResampleError] {
if !(self.state is Active) {
return Err(InvalidState)
}
let channels = self.config.channels
if input.length() % channels != 0 {
return Err(MisalignedInput)
}
if output.length() % channels != 0 {
return Err(MisalignedOutput)
}
for i in 0.. 9223372036854775614L - offered.to_int64() {
return Err(FrameOverflow)
}
match self.config.output_frames(self.input_count + offered.to_int64()) {
Err(error) => return Err(error)
Ok(_) => ()
}
let capacity = output.length() / channels
if self.radius == 0 {
let count = offered.min(capacity)
for i in 0..<(count * channels) {
output[i] = input[i]
}
self.input_count += count.to_int64()
self.output_count += count.to_int64()
return Ok(self.progress(count, count, capacity))
}
let mut consumed = 0
let mut produced = 0
while produced < capacity {
if self.position + self.radius.to_int64() < self.input_count {
match self.emit(output, produced) {
Err(error) => return Err(error)
Ok(_) => ()
}
produced += 1
self.advance()
} else if consumed < offered {
let slot = (self.input_count % self.capacity.to_int64()).to_int() *
channels
for channel in 0.. Result[Progress, ResampleError] {
if output.length() % self.config.channels != 0 {
return Err(MisalignedOutput)
}
if self.state is Failed {
return Err(InvalidState)
}
if self.state is Active {
self.state = Finishing
}
let target = self.config.output_frames(self.input_count).unwrap()
let capacity = output.length() / self.config.channels
let mut produced = 0
while self.output_count < target && produced < capacity {
match self.emit(output, produced) {
Err(error) => return Err(error)
Ok(_) => ()
}
produced += 1
if self.output_count < target {
self.advance()
}
}
if self.output_count == target {
self.state = Finished
}
Ok(self.progress(0, produced, capacity))
}