Skip to content

Sync

Align recordings from different devices by their transient envelopes.

One session, many gadgets, every clock slightly wrong: this estimates the start-time offset between two recordings of the same scene from the cross-correlation of their band-passed onset envelopes. Use the result to fill ambiscape's calibration.json clock_offsets_s or to trim video against a separate audio recorder.

align_recordings

align_recordings(file_a, file_b, band=(200.0, 4000.0), env_fs=200, max_lag_s=None)

Offset between two recordings of the same scene.

Returns {"lag_s": s, "peak": p} where lag_s is positive when file_b starts after file_a. peak is the normalized correlation peak; below ~0.3 the alignment is unreliable (little shared audio). max_lag_s restricts the search when a rough offset is known.

Source code in musicalgestures/_sync.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def align_recordings(file_a, file_b, band=(200.0, 4000.0), env_fs=200,
                     max_lag_s=None):
    """Offset between two recordings of the same scene.

    Returns ``{"lag_s": s, "peak": p}`` where ``lag_s`` is positive when
    *file_b starts after file_a*. ``peak`` is the normalized correlation
    peak; below ~0.3 the alignment is unreliable (little shared audio).
    ``max_lag_s`` restricts the search when a rough offset is known.
    """
    from scipy import signal

    ea = _envelope(file_a, band, env_fs)
    eb = _envelope(file_b, band, env_fs)
    a = (ea - ea.mean()) / (ea.std() + 1e-12)
    b = (eb - eb.mean()) / (eb.std() + 1e-12)
    c = signal.correlate(a, b, mode="full")
    lags = signal.correlation_lags(len(a), len(b), mode="full")
    if max_lag_s is not None:
        keep = np.abs(lags) <= max_lag_s * env_fs
        c, lags = c[keep], lags[keep]
    k = int(np.argmax(c))
    return {"lag_s": float(lags[k] / env_fs),
            "peak": float(c[k] / min(len(a), len(b)))}