Repository navigation
Test suite, bug fixes, embedded loop tags, lossless trim and sample-level loop alignment - #1
Merged
Merged
Conversation
Tests generate their own audio: an intro followed by a repeated 8-note pattern, so the correct loop points are known in advance and loop detection can be checked for correctness, not just for running. Covers audio loading, loop detection and scoring, zero-crossing snapping, split/extend/tag/txt exports, the interactive picker, batch file discovery and every CLI command. Known bugs are marked as strict xfail, so each fix is flagged until its marker is removed. Also adds pytest as a dev dependency and a CI workflow running the tests on Ubuntu and Windows with Python 3.10 and 3.13.
- Mono tracks were played and exported louder than the original: the analysis-only normalization was applied in place to the audio array that is also used for playback, since to_mono returns its input as-is for mono audio. - Longer loops were never preferred among near-identical scores: _prioritize_duration ran before loop_start/loop_end were set, so every duration it compared was 0. - Loops starting in the first seconds of a track were underscored: the truncated look-behind window was zero-padded on the side nearest the loop point, where the weights are heaviest. - Interactive mode discarded the choice made after 'more', 'all' or 'reset' and prompted again. - extend with fade_length=0 crashed (x[-0:] selects the whole array). - export_tags() without output_dir used the file path as the directory. - The txt export message named loop.txt instead of loops.txt.
…mmand Embedded loop tags: - Loop points already stored in a file's metadata (LOOP_START/LOOP_END, LOOPSTART/LOOPLENGTH and the other known tag names) are now returned first by find_loop_pairs, ahead of the detected candidates. They are still returned if detection finds no loop, and are skipped when an approximate loop position is given or when they are out of range. - LoopPair gains a from_metadata flag; interactive mode shows these points as "from tags" and play reports them as such. - New --ignore-tags option to skip them. trim command: - Cuts the audio a given number of samples (--keep-after) after the loop end, keeping the original format, bit depth and tags. - WAV and FLAC are rewritten bit-exact from their native sample format. - Ogg Vorbis is cut at the container level without re-encoding: the audio packets after the one containing the end sample are dropped and the granule position of the final page is lowered to the exact end, so decoders that only trim the last packet (e.g. FFmpeg) also end on the exact sample. Falls back to a page-level cut if the packet durations cannot be determined. - Other formats are rejected, since trimming them would require lossy re-encoding. Tests cover both features; an optional test checks trimmed Ogg files with FFmpeg as an independent decoder when it is available.
Loop points were located on STFT frames (512 samples) and each was then moved to its own nearest zero crossing, without checking that the waveforms at the two points line up. Loops were often off by a few hundred samples, which can be heard as a faint flam or phase smear at the jump. Each candidate is now refined by: - moving the loop end (by up to 2 frames) to where the waveform around it best matches the waveform around the loop start, using normalized cross-correlation over 75 ms on each side; - shifting both points by the same amount (keeping the loop length) to where the waveforms differ the least within +/-5 ms, since the difference at the jump is what causes clicks. When the correlation is below 0.3, the audio at the two points does not match well enough for this to be reliable, and each point is moved to its nearest zero crossing as before. The threshold was chosen by comparing both methods on 181 candidates from real tracks. On game tracks with official loop tags, all top detected loops now have exactly the official loop length (or a multiple of it). The added cost is about 0.5 ms per candidate. Also declares scipy as a direct dependency (already required by librosa).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four commits, each passing its own test suite:
more/all/resetdiscarded the choice made afterwards.extend --fade-length 0crashed.export_tags()withoutoutput_dirused the file path as the directory.loop.txtinstead ofloops.txt.trimcommandLOOP_START/LOOP_END,LOOPSTART/LOOPLENGTH, …) are now the first choice in every command, ahead of the detected ones. New--ignore-tagsoption.trimcommand: cuts the audio--keep-after Nsamples after the loop end, keeping the original format, bit depth and tags. WAV and FLAC are rewritten bit-exact; Ogg Vorbis is cut at the container level without re-encoding, ending on the exact sample in both libvorbis and FFmpeg. Other formats are rejected.scipyas a direct dependency (already required by librosa).Test plan
uv run pytest: 83 passedtrimverified bit-exact on real WAV/FLAC files, and on real Ogg Vorbis files with libvorbis and FFmpeg 7.0.2 / 9.0.2