# Strand B — Sampling & Looping Fundamentals Rulebook

> **Charter context:** This rulebook governs the mechanical layer of the Foundry
> loop-finder (Phase 2). It covers everything the machine is allowed to decide
> autonomously: seam physics, bar alignment, stem-type policies, and SuperDirt
> file conventions. Human creative judgment is explicitly out of scope here —
> see the design philosophy table in `PHASE2_LOOPFINDER.md`.
>
> **Critical constraint throughout:** source material is livecoded (TidalCycles,
> no click track). Tempo drifts within and between tracks. Every rule that would
> assume a fixed global BPM is annotated with [DRIFT HAZARD].

---

## Section 1 — Seam Physics: Making the Boundary Inaudible

### 1.1 Zero-Crossing Alignment

**Rule B-1.1a — Both endpoints must land on a zero crossing.**

A "click" at a loop seam is an amplitude discontinuity: the waveform at the
end of loop iteration N abruptly jumps to a different amplitude at the start of
iteration N+1. The physical cause is the digital sample value changing
instantaneously by an amount audible as a broadband click. Snapping both the
loop-out and loop-in markers to zero-amplitude samples eliminates the worst-case
discontinuity.

> "Do look for crossings in the same direction (i.e. negative to positive) on
> both ends to help reduce slope discontinuity."
> — KVR Audio DSP Forum, "Avoiding Clicks In Loops" [1]

**Rule B-1.1b — Direction-matched zero crossings only.**

Snap to a zero crossing where the waveform slope is in the same direction on
both the loop-out and loop-in marker (both ascending, or both descending). A
slope mismatch at zero amplitude still produces an audible first-derivative
discontinuity (a softer pop, but audible in quiet material).

**Rule B-1.1c — Search window of ±10 ms around the candidate beat position.**

At 44.1 kHz that is ±441 samples — large enough to find a matching zero
crossing in most material, small enough not to wander into the next beat.
Tighten to ±5 ms (221 samples) for drums where transients are dense. If no
zero crossing is found in the window, fall back to Rule B-1.4 (crossfade).

Implementation sketch (Python/numpy):
```python
# find nearest zero crossing to sample index `pos`, within `window` samples
def nearest_zc(signal, pos, window=441, direction=None):
    lo = max(0, pos - window)
    hi = min(len(signal) - 1, pos + window)
    indices = np.where(
        (np.diff(np.sign(signal[lo:hi])) != 0)
    )[0] + lo
    if direction is not None:
        # filter by slope sign
        indices = [i for i in indices
                   if np.sign(signal[i+1] - signal[i]) == direction]
    if not indices:
        return pos  # no crossing found — caller must crossfade
    return indices[np.argmin(np.abs(np.array(indices) - pos))]
```

### 1.2 DC Offset Removal

**Rule B-1.2a — Remove DC offset before any loop-point search.**

DC offset shifts the entire waveform away from zero amplitude. A sample with
even a small DC component has no true zero crossings; every apparent zero
crossing is actually a non-zero sample. Result: every loop seam clicks or pops.

> "Playing back a sound effect with DC offset will produce a click at the
> beginning or end of the file. Since the waveform of a problematic DC offset
> file is shifted, the audio no longer intersects with the zero crossing line."
> — Digido.com, "DC Offset: Clicks and Pops" [2]

> "If you have a DC offset problem that's large enough to cause an audible pop,
> the most successful method is to apply an extremely-high quality linear phase
> high pass filter."
> — Digido.com [2]

**Rule B-1.2b — Apply a gentle linear-phase high-pass filter at ≤10 Hz.**

A very-low-cutoff HP filter removes DC and near-DC content without audibly
affecting bass frequencies.

> "Every highpass filter has infinite attenuation at 0 Hz (DC), you don't need
> a steep one. Steep filters will do more harm, because they introduce
> significant phase shift which blurs transients."
> — Digido.com [2]

Use a 1st-order or gentle 2nd-order Butterworth HP at 5–10 Hz. Avoid steep
brick-wall filters for this purpose. Alternative: compute the mean of the
signal and subtract it (DC-mean subtraction) — this is zero-phase and
equivalent for short blocks.

**Rule B-1.2c — DC offset threshold: flag stems where |mean(signal)| > 1e-4.**

Stems from demucs rarely have significant DC (demucs normalizes internally),
but re-encoded intermediates or analog-chain captures can acquire it. Flag and
auto-correct before loop-point search.

### 1.3 Equal-Power Crossfade at the Seam

Even with perfect zero-crossing alignment, a "hard splice" can still produce
a click if slope discontinuity remains. A short crossfade is the universal
fallback and should always be applied as insurance.

**Rule B-1.3a — Apply an equal-power (constant-power) crossfade, not a linear one.**

A linear crossfade causes a perceivable volume dip at the midpoint because
power is not preserved. An equal-power curve maintains perceived loudness:

```
gain_in(t)  = sqrt(t)          # fade in,  t ∈ [0, 1]
gain_out(t) = sqrt(1 - t)      # fade out, t ∈ [0, 1]
```

At the midpoint (t = 0.5): `gain_in = gain_out = 0.707`, so power is
`0.707² + 0.707² = 1.0`. [3]

The sin/cos variant is mathematically equivalent (sin²+cos²=1) and slightly
smoother at the edges but more expensive to compute [4]:

```
gain_in(t)  = sin(t * π/2)
gain_out(t) = cos(t * π/2)
```

For the foundry's batch offline context, either variant is fine; prefer sqrt
for simplicity.

**Rule B-1.3b — Crossfade seam length: 10–30 ms (default: 20 ms) for loops;
1–5 ms for drum one-shots.**

- 20 ms at 44.1 kHz = 882 samples. This is the minimum that reliably hides
  seam discontinuity in sustained material (pads, bass, chords).
- KVR forum consensus puts the minimum at 128–256 samples (~3–6 ms at 44.1 kHz)
  for structurally significant material; 1 ms (44 samples) with an equal-power
  curve eliminates the pop even at shorter crossfades [1].
- For material with audible pitch/timbre structure at the seam, keep the
  crossfade short (≤10 ms) to prevent comb-filtering artifacts from the two
  overlapping sections.
- For drones and pads, longer crossfades (up to 50 ms) are transparent; use
  them to mask any harmonic mismatch at the boundary.

**Rule B-1.3c — Crossfade implementation: overlap-add, not mix-in-place.**

The crossfade region must borrow `fade_len` samples of "pre-roll" before the
loop-out point and `fade_len` samples of "post-roll" after the loop-in point.
Both must exist in the source audio. If the source audio has no pre/post room
(tight transient at bar 1 beat 1), fall back to a shorter fade or hard splice
at the zero crossing.

### 1.4 Loopability Score

**Rule B-1.4a — Compute a seam-discontinuity score; reject candidates above threshold.**

The primary loopability metric is the RMS of the amplitude difference vector
across the overlap region, normalized to the RMS of the loop body:

```
seam_error = rms(signal[loop_in : loop_in + K] - signal[loop_out - K : loop_out])
loop_rms   = rms(signal[loop_in : loop_out])
loopability = 1 - clip(seam_error / (loop_rms + ε), 0, 1)
```

Higher `loopability` (→1.0) means the two ends are nearly identical in
waveform shape — the seam will be inaudible. A correlation-based variant
(Pearson r on the overlap region) is equivalent and sometimes more stable for
pitched material.

> "Calculate a score based on what provides the least harmonic distortion...
> apply frequency bias weighting to penalize lower frequency mismatches more
> heavily."
> — DSPRelated thread, "Calculating optimal loop point" [5]

**Rule B-1.4b — Secondary loopability metric: spectral continuity.**

Compute the spectral centroid difference between the last 50 ms of the loop
body and the first 50 ms. Timbre jumps (e.g., the loop out-point is in a
sparse section and the in-point is in a dense section) are audible even with
perfect amplitude matching. Flag candidates where central centroid delta
exceeds 20% of the loop's mean centroid.

---

## Section 2 — Musical Alignment: Bar and Beat Grid

### 2.1 The Core Formula

**Rule B-2.1a — Sample length for N bars at tempo T (BPM) and S (sample rate):**

```
samples = (N * 4 * 60 / T) * S          # 4/4 time
seconds = N * 4 * 60 / T
```

Example: 2 bars at 130 BPM, 44100 Hz → `2 * 4 * 60 / 130 * 44100 = 81415 samples ≈ 1.846 s`.

Candidate loop lengths should be multiples of one beat: 1, 2, 4, 8 bars.
Half-bar loops (2 beats) are acceptable for percussive one-shots that repeat
at the bar.

**Rule B-2.1b — Quantize candidate endpoints to the nearest detected beat.**

Beat-synchronous cutting aligns the loop with the musical phrase. Even a 5 ms
misalignment (220 samples at 44.1 kHz) at 130 BPM causes the loop to drift one
full beat within ~37 repeats. [6]

**Rule B-2.1c [DRIFT HAZARD] — Never assume a global constant BPM.**

For livecoded material:

> "When running a beat tracker on signals with varying tempo directly, the
> result does not follow well because the beat tracker is assuming a single
> (average) tempo across the entire signal."
> — librosa documentation, "Beat tracking with time-varying tempo" [7]

Mitigation: use librosa's dynamic beat tracker or madmom's DBN beat tracker
(which models per-region local tempo). Compute a per-region BPM for each loop
candidate window rather than a global tempo. Treat any region where the beat
tracker confidence is below a threshold as "tempo-uncertain" and widen the zero-
crossing search window accordingly.

**Rule B-2.1d [DRIFT HAZARD] — Report estimated BPM as a range, not a point.**

A livecoded 130 BPM track may drift ±3 BPM within a single 8-bar phrase. Store
`bpm_mean`, `bpm_min`, `bpm_max` in the `LoopCandidate` model. Flag any
candidate with a range exceeding ±5 BPM as "tempo-unstable" in the UI — the
human must decide whether the drift is audible in context.

### 2.2 Phrase and Downbeat Alignment

**Rule B-2.2a — Prefer loop-in points at downbeats (beat 1 of a bar).**

Most musical phrases start on the downbeat. A loop that begins mid-bar will
feel rhythmically displaced when played with `s "<kit>"` in a Tidal pattern
that cycles at bar boundaries. Downbeat detection is a separate algorithm layer
(madmom's downbeat tracker is the state of the art); it is not the same as beat
tracking.

**Rule B-2.2b — Enumerate candidates at 1-, 2-, 4-, and 8-bar multiples.**

Starting from each detected downbeat, emit one candidate per standard loop
length. This gives the human a fast chooser grid without forcing a specific
length. [8]

**Rule B-2.2c — Flag loops shorter than 0.5 s as one-shot candidates, not loop candidates.**

See Section 4 (one-shot vs. loop policy).

---

## Section 3 — Stem-Specific Rules

### 3.1 Bass / Sub (bass.wav)

**Rule B-3.1a — Always mono-sum bass stems.**

Sub-bass energy below ~100 Hz must be mono-summed before export. Stereo width
in the sub creates phase cancellation on mono playback systems (club soundsystems,
phone speakers) and can cancel the sub entirely.

> "The low-end of your music, around 0–100 Hz, is the most problematic
> frequency range when it comes to mono compatibility... when summed to mono
> there's a huge risk of phase cancellation."
> — Black Ghost Audio, "How to Improve Mono Compatibility" [9]

> "In mono is assumed when loading sound files."
> — SuperDirt README [10]

Since SuperDirt loads all samples as mono by default, outputting a stereo bass
WAV is wasted data at best and phase-cancelled at worst. Export `bass.wav`
as a 1-channel (mono) file. Demucs `bass.wav` outputs are typically stereo —
apply `np.mean(audio, axis=0)` to collapse before writing.

**Rule B-3.1b — Validate: check correlation between stereo channels before summing.**

If the stereo bass stem has inter-channel correlation < 0.7, the channels
contain significantly different information; flag for human review before
collapsing. (Correlation < 0.5 indicates likely phase inversion or heavy
out-of-phase processing — warn strongly.)

**Rule B-3.1c — Loop points for bass: prefer cycle-aligned cuts at the sub fundamental.**

The sub-bass waveform period at, e.g., 50 Hz is 882 samples at 44.1 kHz.
Cutting mid-cycle produces a click that a 20 ms crossfade will mask, but the
seam will still audibly thump if the loop-out and loop-in are at opposite
phases of the sub cycle. After the bar-aligned beat snap, find the nearest
full-cycle zero crossing of the low-passed (100 Hz) signal. This is the
"sub-cycle snap" refinement.

### 3.2 Drums (drums.wav)

**Rule B-3.2a — Preserve transients: loop-in must be at or before the kick/snare
transient, not inside its attack.**

A transient truncated at the loop-in point sounds like a click AND destroys
the punch of the first beat. The loop-in point must be at a zero crossing
that precedes the first transient by at least 5 ms (220 samples), so the full
attack envelope is inside the loop.

> "Loop boundary crossfading may blur correctly captured transients very close
> to the loop beginning."
> — Loopy Pro Wiki, "Troubleshooting Transients" [11]

If the zero-crossing snap moves the loop-in past a transient, pull it back to
the previous zero crossing before the transient onset.

**Rule B-3.2b — Apply a shorter crossfade for drums: 1–5 ms (default: 3 ms).**

A 20 ms crossfade on a drum loop smears the first hit of each repetition. Use
3 ms equal-power crossfade unless the seam discontinuity score (B-1.4a) demands
more; cap at 10 ms for drums. [1]

**Rule B-3.2c — Onset-detect the drums stem to validate beat positions.**

Use librosa's `onset_detect` on the drum stem as a ground-truth beat grid. If
the detected onsets differ from the general beat tracker positions by >15 ms,
prefer the stem-derived onsets for drum-loop boundaries.

### 3.3 Pads / Other (other.wav)

**Rule B-3.3a — Allow longer crossfades for pads: up to 50 ms.**

Pad material has no transients to protect and often has significant spectral
evolution at phrase boundaries. A 30–50 ms equal-power crossfade is transparent
and effectively hides any timbral mismatch at the seam.

**Rule B-3.3b — Export pads as stereo if the source is stereo and the material
has genuine stereo width (mid-side correlation < 0.95).**

SuperDirt announces "mono is assumed" but SuperCollider can play stereo files —
it will upmix/distribute them. Since demucs `other.wav` often carries stereo
reverb tails and wide synth pads, exporting as stereo preserves that width.
Test the inter-channel correlation: if > 0.95 (nearly mono), collapse to mono.

**Rule B-3.3c — Pad loop ends must include the reverb tail.**

If the loop-out point clips a reverb tail, the next repetition starts with
an abrupt reverb restart that sounds unnatural. Detect tail activity by
tracking when the signal envelope falls below -40 dBFS, and snap the loop-out
point to include the full tail (or crossfade it into the in-point of the next
loop if the tail bleeds into the next bar).

---

## Section 4 — One-Shot vs. Loop Convention

**Rule B-4.1 — Duration threshold: < 0.75 s → classify as one-shot candidate.**

In the existing custom sample library the bimodal split is sub-second one-shots
vs. multi-bar loops. A stem or clip shorter than 0.75 s has no room for a
meaningful loop crossfade and should be exported as a one-shot (no loop
metadata, plays once on trigger).

> "One-shot sample... short in duration, ranging from a fraction of a second
> to a few seconds at max."
> — The Sample Lab [12]

**Rule B-4.2 — Loop candidates must be at least 1 bar at the estimated BPM.**

Below one bar, the loop repeats faster than the Tidal pattern cycle and
produces a stutter effect. The minimum loop length is therefore:
```
min_loop_s = 4 * 60 / bpm    # 1 bar in 4/4
```
At 130 BPM: 1.846 s minimum. At 90 BPM: 2.667 s minimum.

**Rule B-4.3 — Naming convention: one-shots use plain index (`0.wav`, `1.wav`);
loops may use descriptive suffixes but must still sort to predictable indices.**

SuperDirt indexes samples within a kit folder alphabetically by filename
(behavior is OS-dependent — see note below). Name files so that alphabetical
order equals intended playback order:

```
00_kick_dry.wav
01_kick_room.wav
02_snare.wav
```

Zero-pad the numeric prefix to 2 digits minimum (3 if > 99 files per kit).
Do not rely on filename alone without the numeric prefix for ordering.

> [SuperDirt issue #131]: "Linux-based systems sort the former first, and
> others sort the latter first" — the sort order is OS-dependent and an
> unresolved issue in the (now-archived) SuperDirt repo. [13]
>
> Mitigation: use numeric-prefix names so that both alphabetical-by-name and
> alphabetical-by-full-path sorting produce the same order. This is the
> safest cross-platform convention.

**Rule B-4.4 — One-shots: zero-pad the tail to the next bar boundary.**

SuperDirt plays one-shots in full. If a kick sample ends abruptly, adding
20 ms of silence padding prevents the click from the buffer release. For
loops, no tail padding is needed (the loop crossfade handles it).

---

## Section 5 — SuperDirt File Format Requirements

### 5.1 Accepted Formats

**Rule B-5.1a — Export as 24-bit PCM WAV.**

SuperDirt/SuperCollider uses libsndfile for sample loading. libsndfile supports
16-bit and 24-bit PCM WAV, AIFF, and other formats. 24-bit gives 16 dB more
headroom than 16-bit (144 dB theoretical dynamic range vs. 96 dB) at no
practical storage penalty for loops. [14]

Do not export as 32-bit float WAV unless you specifically need bit-perfect
preservation of the demucs output (demucs emits 32-bit float internally);
SuperCollider handles 32-bit float WAV but it is wasteful for distribution
into a kit.

**Rule B-5.1b — Sample rate: match the SuperCollider server rate (typically 44100 Hz).**

SuperDirt requires the audio device to run at 44100 Hz or 48000 Hz. [15]
SuperCollider resamples on load if the file sample rate differs from the
server rate, but resampling costs CPU and degrades quality at the loop seam
(the resampled positions no longer land on the snapped zero crossings).
Export all loop WAVs at the same rate as the SuperCollider server — which is
almost always 44100 Hz in a standard TidalCycles setup.

> "Resampling is enabled by default in SuperCollider, so SuperCollider can run
> at a different sample rate than the hardware." [15]
> — but prefer matching rates to keep zero-crossing snaps exact.

Demucs outputs at the input file's sample rate. Most YouTube/streaming sources
are 44100 Hz; verify with `soundfile.info()` before processing.

**Rule B-5.1c — Channel count:**
- Bass stem → 1 channel (mono, always — see B-3.1a).
- Drums stem → 1 channel (mono) by default; stereo if the drum bus was
  intentionally widened (rare in demucs output, correlation check per B-3.1b).
- Other/pads stem → 2 channels (stereo) unless correlation > 0.95.
- Vocals stem → 2 channels (stereo) for natural room/reverb.

### 5.2 Folder Layout and Indexing

**Rule B-5.2a — One kit = one flat directory under `~/Work/Sound/Samples/<kit>/`.**

The publish pipeline (`engine/publish.py`) symlinks this directory into
Dirt-Samples so that `s "<kit>"` resolves. No subdirectories inside a kit
folder (SuperDirt does not recurse).

**Rule B-5.2b — Files are indexed by SuperDirt in alphabetical filename order (with
OS-dependent caveats per B-4.3). The `n` parameter in Tidal selects by this index.**

```tidal
d1 $ s "mykit"             -- plays index 0
d1 $ s "mykit" # n 3       -- plays index 3
d1 $ s "mykit" <| n (run 4) -- cycles through 0 1 2 3
```

The Foundry must write the kit manifest (which index maps to which loop) so
the human knows which `n` value to audition.

### 5.3 Normalization

**Rule B-5.3a — Normalize to -1 dBFS peak (not LUFS) for sample kit export.**

Kits are played by SuperDirt at whatever amplitude they are stored; Tidal's
`gain` parameter then adjusts at playback. Normalizing to near-0 dBFS peak
(-1 dBFS headroom for inter-sample peaks) ensures headroom is not wasted and
that the Tidal `gain` control has maximum range.

> "If you want to maximize level, normalize to peak, and set the value to
> somewhere just below 0 dBFS (maybe -0.7 dBFS)." [16]

Do NOT loudness-normalize to -14 LUFS for sample kits: that target is for
streaming masters, not raw sample material. Sample kits are not metered by
the streaming algorithms.

**Rule B-5.3b — Apply peak normalization per-file, not per-kit.**

Each WAV in the kit is normalized independently. Kit-level normalization is
the human's job via Tidal's `gain` — the machine just ensures no file is
clipping or excessively quiet.

**Rule B-5.3c — True-peak limit: -0.1 dBTP.**

Use a true-peak limiter (or numpy clip + 1 sample lookahead) rather than
sample-peak normalization. Inter-sample peaks above 0 dBFS cause clipping
on D/A conversion even when the sample values themselves are below 0 dBFS.

---

## Section 6 — Livecoded / Tempo-Drift Specific Rules

**Rule B-6.1 [DRIFT HAZARD] — Use a sliding-window BPM estimate, not a global one.**

Compute BPM in overlapping windows of 4–8 bars. For each candidate loop
region, use the local BPM from its own window to compute the expected bar
length. Drift of even 1 BPM over 4 bars causes a 30 ms timing error —
larger than the ±10 ms zero-crossing search window.

**Rule B-6.2 [DRIFT HAZARD] — Drift-tolerant bar-length estimate.**

Rather than computing `bar_length = 4 * 60 / bpm_global`, use the onset
positions directly:

```python
beats = librosa.beat.beat_track(y, sr, trim=False)[1]
bar_starts = beats[::4]   # every 4th beat is a downbeat (4/4 assumption)
bar_lengths = np.diff(bar_starts)  # in samples, variable across the piece
```

Loop candidates should span an integer number of actual inter-onset intervals,
not an integer number of mathematically computed bar lengths.

**Rule B-6.3 [DRIFT HAZARD] — Flag and score tempo-stable regions preferentially.**

A loop cut from a region where the local BPM variance is low (σ < 1 BPM over
the loop duration) will repeat more reliably in a Tidal pattern than one from
a high-drift region. Include `bpm_stability` (inverse of σ) as a positive
factor in the loopability score.

**Rule B-6.4 — Loops from livecoded stems are "free" loops, not tempo-locked.**

Unlike a sample-pack loop tagged with `120bpm`, these loops have an inherent
tempo that may not match the current Tidal session. Document the estimated BPM
in the Foundry's kit manifest (a `meta.json` alongside the WAV files) so the
human knows whether to stretch/pitch the loop or just play it as-is.

> TidalCycles does not auto-stretch samples to fit the current tempo; `speed`
> and `slow` modify playback rate but also shift pitch. There is no native
> tempo-sync stretching in SuperDirt. The human's judgment governs how to deploy
> a free loop. [17]

---

## Section 7 — Conflict Table and Trade-Offs

| Rule A | Rule B | Conflict | Resolution |
|--------|--------|----------|------------|
| B-1.1c: ±10 ms ZC search window | B-2.2a: snap to downbeat | If the nearest ZC is 12 ms from the downbeat, we breach one constraint | Prefer the musically-aligned downbeat; apply crossfade (B-1.3) to hide the seam. Never move more than 25 ms from the beat. |
| B-1.3b: 20 ms crossfade | B-3.2b: 3 ms crossfade for drums | Drums stem fed through the default pad crossfade | Per-stem defaults override the global default. Dispatch on stem type. |
| B-3.1a: mono-sum bass | B-3.3b: stereo pads | No conflict if stem type dispatches correctly | Identity check: bass.wav always mono, other.wav conditionally stereo. |
| B-1.4a: seam discontinuity score | B-2.2b: enumerate all bar-multiple candidates | Exhaustive enumeration is slow; scoring every candidate at 1/2/4/8 bars from every downbeat is O(N²) | Prune: only score candidates within a configurable "region of interest" around the detected high-activity section. Default: first 4 minutes. |
| B-5.1b: match SC server rate (44100) | Source at 48000 Hz | Resampling degrades ZC snap accuracy | Resample the whole stem to 44100 before loop-point search AND before export. Do not export at source rate. |
| B-6.2: drift-tolerant bar lengths | B-4.2: 1-bar minimum duration | At low BPM + high drift, `bar_lengths` array is noisy; minimum may not hold | Use median bar length, not mean, for minimum-duration check. Reject only if all detected bar lengths are < 0.5 s. |

---

## Sources

[1] KVR Audio DSP Forum, "Avoiding Clicks In Loops":
https://www.kvraudio.com/forum/viewtopic.php?t=325574

[2] Digido.com, "DC Offset: Clicks and Pops":
https://www.digido.com/ufaqs/dc-offset-clicks-pops/
(mirror/summary via integraudio.com: https://integraudio.com/how-to-remove-dc-offset/)

[3] Teedstuff blog, "Implementing a Constant Power Crossfade":
https://teedteed.wordpress.com/2019/05/06/implementing-a-constant-power-crossfade/

[4] KVR Audio, "I'm confused about equal power crossfading":
https://www.kvraudio.com/forum/viewtopic.php?t=347151

[5] DSPRelated forum, "Calculating optimal loop point in audio sample":
https://www.dsprelated.com/thread/15880/calculating-optimal-loop-point-in-audio-sample
(The music-dsp Columbia thread [https://music-dsp.music.columbia.narkive.com/C8IESiuZ/algorithms-for-finding-seamless-loops-in-audio]
covers equivalent ground but was inaccessible during research; the archived
version at web.archive.org is blocked from automated fetch.)

[6] CMUSE, "Loop Length Calculator for BPM, Bars, and Samples":
https://www.cmuse.org/loop-length-calculator

[7] librosa 0.11.0 documentation, "Beat tracking with time-varying tempo":
http://librosa.org/doc/0.11.0/auto_examples/plot_dynamic_beat.html

[8] Freesound Loop Dataset paper (Fonseca et al.):
https://arxiv.org/pdf/2008.11507

[9] Black Ghost Audio, "How to Improve Mono Compatibility":
https://www.blackghostaudio.com/blog/how-to-improve-mono-compatibility

[10] SuperDirt GitHub README (yaxu/SuperDirt):
https://github.com/yaxu/SuperDirt
("load samples (path can be passed) mono is assumed.")

[11] Loopy Pro Wiki, "Troubleshooting Transients":
https://wiki.loopypro.com/Troubleshooting_Transients

[12] The Sample Lab, "What are One-Shot Samples?":
https://thesamplelab.com/blogs/news/what-are-one-shot-samples

[13] SuperDirt GitHub issue #131, "Inconsistent sorting of files":
https://github.com/musikinformatik/SuperDirt/issues/131
(repo archived 2025-12-06; issue unresolved)

[14] Sound on Sound / general audio engineering: 24-bit vs 16-bit PCM.
https://blog.landr.com/sample-rate-bit-depth/

[15] TOPLAP forum, "Sample Rate Mismatch, server won't boot":
https://forum.toplap.org/t/sample-rate-mismatch-server-wont-boot/2048

[16] Normalization and peak targets for sample packs:
https://nolabelnoproducernolimits.com/indie/production/reaper/normalize/

[17] TidalCycles documentation, Custom Samples:
https://tidalcycles.org/docs/configuration/AudioSamples/audiosamples/
