Skip to content

spike(rust/snr): port validate_peak to Rust #98

Description

@natashaannn

User story

As a coding agent implementing the Rust FFT spike (tracked in #93), I want a validate_peak function in spike/audio-sync/src/lib.rs that exactly replicates the JS SNR reliability check, so that the Rust binary surfaces a "LOW CONFIDENCE" flag for unreliable offsets in the same cases the JS would.

Background

RFC 0001 (#90) proposes rewriting the editing pipeline in Rust. To validate the agent-driven Rust dev loop before committing, the team is running a spike: port the FFT cross-correlation algorithm from scripts/sync/AudioSyncer.js to a standalone Rust binary.

This issue ports validatePeak (scripts/sync/AudioSyncer.js L218–235). It takes the correlation array and the lag already computed by find_best_lag (#97) and computes an SNR metric: how many standard deviations above the mean is the peak value? If the SNR is below RELIABILITY_SNR_THRESHOLD = 3.0, the result is flagged as unreliable. This is a quality-of-result signal, not an error — the lag is still returned and used; the flag tells the operator to verify the sync manually.

Acceptance criteria

Happy path

Given a correlation array with a sharp, high-amplitude peak and a lag computed by find_best_lag
When validate_peak(correlation, lag_seconds, sample_rate) is called
Then it returns (snr, true) where snr >= 3.0

Given a near-silence correlation array (all values close to zero with tiny noise) and any lag
When validate_peak is called
Then it returns (snr, false) where snr < 3.0

Error path / edge case

Given a correlation array where all values are exactly equal (std = 0)
When validate_peak is called
Then it returns (0.0, false) without a division-by-zero panic — matching the JS snr = std > 0 ? ... : 0 guard

Out of scope

Technical context

JS source to replicate (scripts/sync/AudioSyncer.js L218–235):

validatePeak(correlation, lagSeconds) {
  const N = correlation.length;
  let sum = 0, sumSq = 0;
  for (let i = 0; i < N; i++) { sum += correlation[i]; sumSq += correlation[i] ** 2; }
  const mean = sum / N;
  const std = Math.sqrt(sumSq / N - mean ** 2);

  // convert frame-quantized lag back to sample index for lookup
  const lagFrames = Math.round(lagSeconds * SYNC_FRAME_RATE);
  const lagSamples = Math.round(lagFrames * this.sampleRate / SYNC_FRAME_RATE);
  const idx = ((lagSamples % N) + N) % N;  // modular wrap to handle negative lags

  const signalValue = correlation[idx];
  const snr = std > 0 ? Math.abs(signalValue - mean) / std : 0;

  return { snr, isReliable: snr >= RELIABILITY_SNR_THRESHOLD };
}

Constants (already declared in lib.rs from #97):

const SYNC_FRAME_RATE: f64 = 30.0;
const RELIABILITY_SNR_THRESHOLD: f64 = 3.0;

Signature:

pub fn validate_peak(correlation: &[f64], lag_seconds: f64, sample_rate: u32) -> (f64, bool)

Returns (snr, is_reliable).

Index reconstruction subtlety: the JS reconstructs the sample index from the already-quantized lagSeconds (not from raw lag samples) using lagFrames = round(lagSeconds * SYNC_FRAME_RATE) then lagSamples = round(lagFrames * sampleRate / SYNC_FRAME_RATE). The modular wrap ((lagSamples % N) + N) % N handles negative lags correctly — replicate this exactly.

Variance formula: the JS uses the biased population variance (sumSq / N - mean²), not Bessel-corrected. Rust's iterator .map(|x| x*x).sum::<f64>() / n - mean² replicates this.

Implementation details

  1. Add pub fn validate_peak(correlation: &[f64], lag_seconds: f64, sample_rate: u32) -> (f64, bool) to spike/audio-sync/src/lib.rs.
  2. Add #[test] for high-SNR case: build a correlation array that is all zeros except one large value at a known index; assert is_reliable == true.
  3. Add #[test] for low-SNR case: build a flat correlation array (all same value); assert is_reliable == false and snr == 0.0.
  4. Add #[test] for zero-std guard: all-equal values; assert no panic and snr == 0.0.
  5. Run cargo test --manifest-path spike/audio-sync/Cargo.toml — all tests pass.
  6. Commit spike/audio-sync/src/lib.rs.

Additional test scenarios

  • Negative lag (index reconstructed via modular wrap): assert result is the same as positive lag of equal magnitude against a symmetric correlation array
  • Correlation array of length 1: assert function does not panic

Hard constraints

  • RELIABILITY_SNR_THRESHOLD = 3.0 must not be changed
  • Population variance (divide by N, not N-1) must be used — the JS uses this form
  • No changes to files outside spike/audio-sync/src/lib.rs

Dependency issues

Depends on #95 (Cargo scaffold) and that SYNC_FRAME_RATE / RELIABILITY_SNR_THRESHOLD constants are declared (introduced in #97 — can be added here instead if #97 is not yet merged, but only one issue should own the constants).
Can be developed in parallel with #96 and #97.
#99 (CLI + round-trip test) depends on this issue being complete.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions