Skip to content

Fix stereo compressed (FLAC/OGG) samples truncated to half length#150

Merged
git-moss merged 2 commits into
git-moss:mainfrom
douglas-carmichael:fix-stereo-compressed-sample-truncation
Jun 22, 2026
Merged

Fix stereo compressed (FLAC/OGG) samples truncated to half length#150
git-moss merged 2 commits into
git-moss:mainfrom
douglas-carmichael:fix-stereo-compressed-sample-truncation

Conversation

@douglas-carmichael

Copy link
Copy Markdown
Contributor

Problem

AudioFileUtils.decompressToWav computes the number of frames as:

final int numFrames = audioDataBytes.length / (convertFormat.getFrameSize () * channels);

AudioFormat.getFrameSize() already accounts for all channels (bytes-per-sample × channels), so multiplying by channels again divides by the channel count twice. For stereo samples this yields half the real frame count, so the AudioInputStream is told it is half as long and AudioSystem.write emits only the first half of the audio. Mono samples (channels = 1) are unaffected, which is why this went unnoticed.

Impact

Any conversion from a format that stores stereo compressed audio (FLAC/OGG) to a destination written as uncompressed WAV loses the second half of every sample. Because some creators store loop positions as a fraction of the sample length (e.g. Waldorf Quantum/Iridium), the loop end then lands at the wrong position in the now-shorter sample, which is audible as a click at the loop point.

Concrete example: converting a Renoise instrument (XRNI, which stores stereo FLAC) to Waldorf Quantum/Iridium produced 8.03 s patches from 16.05 s sources, and a sustained-note loop clicked on the hardware.

Fix

Drop the redundant * channels:

final int numFrames = audioDataBytes.length / convertFormat.getFrameSize ();

Verification

For a stereo 48 kHz source resampled to 44.1 kHz / 16-bit:

  • Output frame count: 353,991 (before) → 707,981 (after), matching an independent reference resample (707,963).
  • Loop-seam discontinuity at the device loop point: 1.45–1.65 % full-scale (before) → 0.000 % (after).

AudioFileUtils.decompressToWav computed the frame count as
audioDataBytes.length / (frameSize * channels). AudioFormat.getFrameSize()
already accounts for all channels (bytes-per-sample * channels), so the extra
"* channels" halved the frame count for stereo samples and truncated them to
their first half. For mono samples the result was correct, which is why this
went unnoticed.

This corrupted any conversion from a format that stores stereo compressed
audio (e.g. a Renoise XRNI, which stores FLAC) to a destination written as
uncompressed WAV: the second half of every sample was dropped and, because
loop positions can be stored as a fraction of the sample length, the loop
point landed in the wrong position and produced an audible click at the loop
seam.
@douglas-carmichael

Copy link
Copy Markdown
Contributor Author

A couple of notes for merging:

1. Standalone fix, but especially relevant to #149 (Renoise XRNI support). This is a pre-existing bug independent of Renoise, but reading an XRNI's stereo FLAC samples into an uncompressed destination was being halved by it — so this PR and #149 complement each other.

2. Small overlap with #149 in AudioFileUtils.decompressToWav. Both PRs touch that method but on different lines — #149 adds the mark/reset stream wrap near the top of the method, while this PR fixes the frame-count line ~20 lines below. Each is clean against main individually; whichever lands second may need a trivial one-line rebase. CHANGELOG entries also sit under different version headings (18.1.2 here, 18.2.0 in #149).

@git-moss git-moss merged commit cdafb0d into git-moss:main Jun 22, 2026
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.

2 participants