# Loop seam lab: measure, edit, recheck

A small, reproducible, standard-library Python lab for inspecting mono/stereo PCM16 loops. Every WAV is an original deterministic synthetic fixture. The lab does not generate music, download assets, start audio playback, call an engine, or claim that a metric predicts audibility.

## Run

Use Python 3.10+; this release was exercised on Python 3.12. The core lab needs no install step or third-party dependencies.

```sh
python3 -m unittest -v test_loop_lab.py
python3 build_lab.py
python3 loop_lab.py inspect discontinuous-ramp.wav
python3 loop_lab.py inspect stereo-one-bad-channel.wav
python3 loop_lab.py crossfade discontinuous-ramp.wav my-edited-loop.wav --overlap-frames 480 --law linear
python3 loop_lab.py inspect my-edited-loop.wav
```

Run from the extracted lab directory. `build_lab.py` regenerates the named fixture WAVs, SVGs, reports, manifest and ZIP beside itself. It deliberately replaces these generated lab outputs. It does not include unrelated files in the ZIP. The `crossfade` command refuses to overwrite an existing destination. Keep originals; choose a new output path.

### Optional PNG versions

The article PNGs are drawn from the same WAV samples and gain functions by `render_png.py`. That optional renderer needs Pillow and the DejaVu Sans fonts already installed; it is not required to build or test the core lab. Run `python3 render_png.py` after `python3 build_lab.py` if those dependencies are available. The ZIP includes this optional renderer but uses the standard-library-generated SVGs as its reproducible figures; PNGs are not bundled.

## Input and safety limits

- Uncompressed WAV containing signed, little-endian PCM16 samples, mono or stereo only
- Sample rates 8,000–192,000 Hz; 4–1,000,000 frames; at most 60 seconds; file size at most 8,000,000 bytes
- Crossfade input needs at least 6 frames; integer overlap M must be 2 through floor((N−1)/2)
- No compressed audio, floating-point WAV, higher-bit-depth WAV, streaming, resampling, engine metadata or loop-tag interpretation
- Invalid/truncated/oversized inputs are rejected before normal sample processing; no silent clipping or automatic normalization
- Crossfades compute in floating point, round back to PCM16, and reject out-of-range rounded samples before opening the output file

The sample-count bound makes this an intentionally small offline inspector, not a production media parser or a security sandbox. Files are read locally; no network use is made. Python's WAV support may differ across versions for optional WAV container variants.

## What the measurements mean

Each channel is inspected independently. Normalized amplitude is PCM16 count / 32,768. dBFS uses a 32,768-count reference; JSON `null` denotes a zero-amplitude dB value or a ratio with a zero denominator.

- Wrap step: first sample minus last sample; also reported as absolute count and normalized amplitude
- Internal baseline: absolute differences of adjacent samples *inside* the file, excluding the wrap; median, nearest-rank p95, maximum
- Wrap/p95 ratio: a scale comparison, not a pass threshold; p95 can be zero for flat/sparse material
- Endpoint delta difference: (second − first) − (last − penultimate); a two-sample slope proxy, not a robust derivative estimate
- Enter/leave-wrap delta changes: compare the wrap step with the immediately neighboring internal differences
- Mean, sample peak, RMS and short head/tail RMS windows: clues for further inspection, not perceptual ratings
- Longest exact-zero run: only literal zero samples; does not detect all quiet passages, leading encoder delay, or intentional/unintentional silence reliably
- Rail sample count: occurrences of −32,768 or +32,767; rail values alone do not prove clipping

No universal threshold is encoded. A periodic sampled sine can have unequal endpoint values and still have a wrap transition equal to an ordinary neighboring sample step. Conversely, endpoint equality can coexist with a slope change or a long internal zero run. Matching this slope proxy is also not proof that the whole loop is good. Inspect the waveform, listen at a safe level, and test the actual playback path.

## The circular-overlap algorithm, exactly

For input x with N frames and an overlap M, define a head H=x[0:M], middle C=x[M:N−M], and tail T=x[N−M:N]. All ranges are half-open. For i=0…M−1, use t=i/(M−1) and blend Z[i]=a(t)T[i]+b(t)H[i]. The output is C followed by Z.

- Linear: a=1−t; b=t
- Equal power: a=cos(πt/2); b=sin(πt/2)
- Identical gains and frame positions apply to both channels
- Fade endpoints are exactly (1,0) and (0,1)
- Output length is **N−M**, and its first sample comes from original frame **M**
- With gain 1, output ends at x[M−1], so its wrap transition is x[M−1]→x[M], an existing adjacent pair
- The transition from middle into blend is x[N−M−1]→x[N−M], also an existing adjacent pair

This is a circular overlap-and-shortening edit, not a fade-out plus fade-in on an unchanged-length file. It reduces the example's 4,800 frames to 4,320 frames: 100 ms becomes 90 ms at 48 kHz. It shifts the cycle's starting position and changes repeat timing. It can smear attacks or alter phase, energy and derivatives inside the blend. A small relocated wrap step does not establish that those other changes are acceptable.

For tempo-locked material, a fixed-duration requirement, a protected onset, or engine loop points, choose an appropriate authoring method instead of applying this blindly. Update duration/loop metadata after edits. This script neither preserves timing nor implements Web Audio loop-point interpolation. A separate head/tail handle workflow, a carefully selected cut, or an arrangement edit may fit better.

`--gain` is an explicit global linear gain in (0,1], applied to the complete output. It can create headroom but changes the level everywhere. The script never chooses it for you. Recheck the full output. Sample-peak headroom is not a true-peak or codec-transcode guarantee.

## Why a curve name is not enough

For fixed gains a,b, equal input RMS σ, and normalized cross-product ρ, output power is σ²(a²+b²+2abρ). For zero-mean signals this agrees with correlation-based reasoning; this lab's report explicitly uses the uncentered normalized cross-product. If the windows contain offsets or unequal energy, interpret them accordingly. A time-varying crossfade has time-varying weights, so a single whole-window correlation does not guarantee a flat envelope.

At the midpoint, linear gains are 0.5/0.5 and equal-power gains are √0.5/√0.5. The report's four-sample floating-point controls hold these midpoint weights fixed:

- Identical signals: linear preserves amplitude; equal power raises it by √2, about 3.01 dB
- Opposite signals: both midpoint sums cancel
- Orthogonal equal-RMS controls: linear loses about 3.01 dB RMS; equal power preserves RMS, but its sample peaks can still exceed full scale

The corresponding whole WAV headroom fixture is constant at 29,491 PCM16 counts. A 481-frame equal-power edit at gain 1 is rejected because its midpoint exceeds PCM16 range. No clipped output is shipped. This constant signal is an arithmetic test, not representative music or an audible recommendation.

## Included evidence

- `report.json`: full numeric fixture and midpoint-control results
- `report.md`: compact human-readable results and limits
- `seam-measurements.svg`: actual last/first sample plots and measured wrap steps
- `equal-endpoints-counterexamples.svg`: actual cusp and internal-zero-run samples
- `crossfade-gains.svg`: the implemented gain functions and midpoint arithmetic
- Nine small synthetic WAVs, including original and edited ramp, periodic sine, equal-endpoint counterexamples, stereo fault traps and correlated headroom input
- `test_loop_lab.py`: diagnostics, length/rotation, gain, headroom, WAV rejection and CLI tests
- `sources.json`: primary background references, access date and specific provenance scope
- `manifest.json`: SHA-256 hashes for bundled files (excluding the manifest and ZIP itself)
- `audio-loop-evidence.zip`: fixed-order, fixed-timestamp package; excludes unrelated illustration files

WAV quantization and expected counts are deterministic on the tested interpreter. Floating-point math or ZIP implementations can differ slightly across platforms; compare metrics and tests before expecting byte-identical rebuilds elsewhere. On the tested environment, two unchanged builds should produce identical hashes.

## What has not been tested

No listening study, generated or real music, game-engine playback, browser playback, codec encode/decode, platform resampling, gapless decoder behavior, streaming buffer scheduling, loudness weighting or reconstructed true peaks. These need separate verification on the final exported asset and target runtime. Numeric findings here support only the stated synthetic cases.
