Skip to content

Log a note for loops which audibly click at their wrap-around point - #340

Open
douglas-carmichael wants to merge 2 commits into
git-moss:mainfrom
douglas-carmichael:add-loop-click-warning
Open

Log a note for loops which audibly click at their wrap-around point#340
douglas-carmichael wants to merge 2 commits into
git-moss:mainfrom
douglas-carmichael:add-loop-click-warning

Conversation

@douglas-carmichael

Copy link
Copy Markdown
Contributor

Sample libraries ship loops whose boundaries land on non-matching sample values surprisingly often. Converting three commercial Ensoniq libraries (DSF ASR/MR/ZR, 152 selected presets), 17 presets click audibly at their loop wrap - steps of up to 63% of the sample level, a tick on every repeat of a held note. Nothing in the log points at them today: the first hint is the converted preset ticking on the destination hardware, and finding the culprits means listening to every preset one by one.

This adds one aggregated note per affected multi-sample:

Note: 1 of the 3 loops of 'Dull Vinyl' wrap with an audible step, the largest is 54% of the
sample level ('SURFACE NOISE'). The loop is written as the source authored it - the 'Snap loops
to zero-crossings' or loop cross-fade processing option removes such clicks.

How it decides. After the processing of each source, every forward loop without a cross-fade is checked: the step from the last played frame back to the loop start is compared with the median frame-to-frame movement of the waveform around the loop boundaries. A step of at least 8 times that movement and at least 2% of the local level is reported. The thresholds were calibrated against 3527 looped samples of the three libraries - loud enough to be heard on sustained material, quiet enough that none of the ~1900 clean loops is flagged.

What it deliberately does not do:

  • Nothing is changed - the loop is written exactly as the source authored it. The note names the two processing options which remove such clicks, and the choice stays with the user.
  • A loop which already has a cross-fade (from the source or from the processing option) is not reported - the cross-fade masks the wrap.
  • The measurement reuses the discontinuity function of LoopZeroSnapper (now package-visible), so the note and the snap option can never disagree about a loop: a loop the snap option fixed is not reported. Converting with snapping enabled shrinks the notes to exactly the loops snapping could not fix (the pitch-critical short loops it skips), which tells the user those need a cross-fade instead.
  • It runs for conversion and for Analyse, but not for the contents scan, which stays fast.

Cost: the audio of a zone is only read when it has an uncrossfaded forward loop, and once per zone. Converting the full MR library (514 presets, 5095 samples) measures the same wall time with and without the check (8.5s vs 9.6s across runs - inside run-to-run noise, since decoding is a fraction of the resample/write work the conversion does anyway).

The check has already caught a real bug once: while validating it against a build without #338, it flagged 'Eastern EPno' at 92% - the wrong-library sample that PR fixes. With the note in place, that class of problem is visible in the log instead of on the hardware.

Sample libraries ship loops whose boundaries land on non-matching sample
values surprisingly often - 17 of 152 presets across three commercial
Ensoniq libraries - and without a note the first hint is the converted
preset ticking on the destination device, where finding the culprit
means listening to every preset.

After the processing of each source, every forward loop without a
cross-fade is now checked: the step from the last played frame back to
the loop start is compared with the median frame-to-frame movement of
the waveform around the loop boundaries. A step of at least eight times
that movement and at least 2% of the local level - thresholds calibrated
on 3527 looped samples of the three libraries - is heard as a tick on
every repeat and logs one aggregated note per multi-sample, with the
worst zone and its step as a percentage of the level.

Nothing is changed: the loop is written as the source authored it, and
the note points to the snap-to-zero-crossing and loop cross-fade
processing options which remove such clicks. The measurement reuses the
discontinuity function of the loop snapper, so the note and the snap
option can never disagree about a loop; a loop the snap option fixed is
therefore not reported. The check runs for conversion and analysis but
not for the contents scan, and converting a full library of 514 presets
measures the same wall time with and without it.
The step at the wrap was compared with the median frame-to-frame
movement of the waveform around the loop boundaries. That comparison
depends on the sample rate of the source: the same waveform stored at
22 kHz moves twice as much per frame as at 44.1 kHz, so a step which
audibly sticks out once the destination format resamples the audio to a
higher rate could hide below the threshold in the source file. Across
821 loops converted to 44.1 kHz, the source-rate check missed 4 of the
16 loops which click in the written files - all of them low-rate
sources (16-30 kHz).

The median movement is now scaled to a 44.1 kHz reference before the
comparison, which makes the check independent of the source rate and
catches 14 of the 16. Resampling itself does not need to be simulated:
measured across the same 821 loops, the step as a part of the level
stays within one percent point through resampling - only the per-frame
yardstick was changing, never the step.
@douglas-carmichael

Copy link
Copy Markdown
Contributor Author

One refinement after measuring the check against what actually lands in the written files: the movement comparison is now made per unit of time instead of per frame (second commit).

The step at the wrap was compared with the median frame-to-frame movement around the loop boundaries, and that yardstick depends on the source sample rate - the same waveform stored at 22 kHz moves twice as much per frame as at 44.1 kHz. A step which audibly sticks out after the destination resamples to a higher rate could therefore hide below the threshold in a low-rate source. Comparing all 821 loops of the three Ensoniq libraries in both domains (source audio vs the written 44.1 kHz files):

  • Resampling never changes the step itself - it stays within one percent point of the sample level in every one of the 821 loops. So the destination format does not need to be known or simulated.
  • But the per-frame yardstick made the source-rate check miss 4 of the 16 loops which click in the written files, all from 16-30 kHz sources.

With the median movement scaled to a 44.1 kHz reference the check catches 14 of the 16; the three additional reports are loops whose source step is large (up to 47% of the level) but which happen to land cleanly at this particular destination rate - they are broken as authored, so reporting them seems right. The two remaining misses are wraps which only become steep through the frame relocation of resampling, which is not predictable from the source audio.

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