Skip to content

Add opt-in "snap loops to zero-crossings" processing option#162

Open
douglas-carmichael wants to merge 1 commit into
git-moss:mainfrom
douglas-carmichael:add-loop-zero-snap
Open

Add opt-in "snap loops to zero-crossings" processing option#162
douglas-carmichael wants to merge 1 commit into
git-moss:mainfrom
douglas-carmichael:add-loop-zero-snap

Conversation

@douglas-carmichael

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in Snap loops to zero-crossings processing option.

Some sample libraries ship forward loops whose start and end land on non-matching sample values, so the loop audibly clicks at the wrap-around on every repeat. This is common with auto-sampled instruments whose loop was not designed to be click-free — they rely on the host applying a cross-fade at play time. When the destination format has no loop cross-fade (and none is baked), that click is exported as-is.

This option moves both loop boundaries to a nearby rising zero-crossing so the loop end and start meet near zero, removing the click without resampling the audio — only the stored loop positions change. It complements the existing Set fixed loop-crossfade option (which bakes a cross-fade) for the cases where you would rather not alter the audio at all.

How it works

A new core/algorithm/LoopZeroSnapper decodes each zone to a mono mix and, for every forward loop, searches a small window around the existing start and end for the nearest rising zero-crossing. It is deliberately conservative:

  • only forward loops are touched;
  • very short (single-cycle) loops are skipped so their pitch is unchanged (< 4096 frames);
  • a boundary is moved by at most an eighth of the loop length, capped at 512 frames;
  • the new boundaries are applied only when they actually reduce the discontinuity at the wrap;
  • a loop end of -1 ("loop to sample end") is materialised to an explicit zero-crossing only when snapping helps.

It runs after the existing resample / bit-depth stage in ConverterBackend.processSamples, so it always sees the final audio.

Wiring

  • New Snap loops to zero-crossings check-box in the processing dialog (ProcessingDialog / MainFrame), persisted like the other processing options.
  • New CLI flag -Zs (gated by -Ze, like the others).
  • DetectSettings.snapLoopsToZero + needsProcessing() gate.
  • Off by default.

Example

A real 12-zone Synthstrom Deluge pad whose loops wrap with an audible step. The numbers below are the absolute sample-value jump at each loop wrap, measured on the source audio, before and after enabling the option (16-bit samples, full scale 32768):

zone jump before jump after
D#3 381 19
F#3 448 21
A3 405 81
C4 223 223
D#4 382 174
F#4 872 226
A4 825 285
C5 328 169
D#5 1517 1
F#5 1747 0
A5 1398 567
C6 85 1
total 8611 1767

11 of 12 loops improved (−79% total); the C4 zone is left exactly as it was, because no nearby crossing reduced its jump — the conservative guard in action. No loop is ever made worse.

Notes

Happy to adjust the naming, the window / threshold constants, or move the algorithm elsewhere if you'd prefer.

Some sample libraries ship forward loops whose start and end land on
non-matching sample values, so the loop audibly clicks at the wrap-around
on every repeat (common with auto-sampled instruments whose loop was not
designed to be click-free). This adds a processing option that moves both
loop boundaries to a nearby zero-crossing so the loop end and start meet
near zero, removing the click without resampling the audio - only the
stored loop positions change.

The adjustment is conservative: it only touches forward loops, leaves
very short (single-cycle) loops untouched so their pitch is unchanged,
moves a boundary by at most an eighth of the loop length (capped at 512
frames), and only applies when it actually reduces the discontinuity at
the wrap. A loop end of -1 ("loop to sample end") is materialised to an
explicit zero-crossing when snapping helps.

Enabled with the "Snap loops to zero-crossings" check-box in the
processing dialog or -Zs on the command line; off by default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant