Repository navigation
Add WSOLA pitch compensation to timewarp - #1357
Conversation
Add a wsola module with a "wsola" audio filter: a port of Chromium's WSOLA time stretcher (media/filters/audio_renderer_algorithm and wsola_internals, BSD licensed). The algorithm, constants and index arithmetic are kept as upstream; only Chromium's support types were replaced. The filter takes the audio that timewarp labels at frequency * speed, time stretches it by 1/speed and returns it at the original frequency, so the duration is unchanged and the pitch is restored. It has no external dependencies. timewarp gets a new property, warp_pitch_method. "wsola" selects the new filter for pitch compensation when warp_pitch is on; any other value, or a build without the module, keeps using rbpitch as before. Add test_wsola. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bmatherly
left a comment
There was a problem hiding this comment.
What is the typical latency for this service? Also, can you check what the typical latency is for rubberband? I wonder how they compare?
The filter has no external dependencies, so it does not need a module of its own. The Chromium port goes to src/modules/plus/wsola/, like ebur128, and the MOD_WSOLA option is removed. The test now depends on MOD_PLUS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The filter expects the audio that timewarp delivers: the original samples labeled with the frequency multiplied by the speed. Used on any other audio, it labeled the output with a wrong frequency, which the resampler then compounded frame after frame. timewarp now marks its audio with _timewarp_audio_speed, and the filter passes any audio without that mark through unchanged, with a warning. Add OtherAudioPassesThrough to test_wsola. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port media/base/sinc_resampler from Chromium, next to the WSOLA port and under the same BSD license, so that the wsola filter can change the pitch without an external library. The algorithm is unchanged; the Chromium support types were replaced and the AVX2 path, which needs run time CPU detection, was left out. It is built in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make wsola a pitch change filter that can be used on its own and is interchangeable with rbpitch: it takes octaveshift, pitchscale and stretch, and keeps the number of samples and the frequency. The audio is first resampled by pitchscale with the sinc resampler, which changes the pitch, and WSOLA then time stretches it back to the requested length. Resampling first means WSOLA always works on audio whose real sample rate is the labeled frequency, also inside timewarp. This replaces the speed parameter and the mark that timewarp put on its audio: timewarp now sets pitchscale = 1 / speed for both filters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review, and sorry for the delay. I've pushed changes for all three points: plus module. The filter is now in Same parameters as rubberband. wsola is now a regular pitch change filter with the same interface as rbpitch: Latency. Measured end to end with
Through timewarp: at 0.5x, wsola 63 ms and rubberband 25 ms; at 2x, wsola 58 ms and rubberband between 91 and 126 ms (it varies). So they are in the same range. wsola's delay grows with the pitch scale, because the resampling step stretches WSOLA's own look-ahead, while rbpitch tends to go the other way. wsola's delay is also constant over time. On a 1 kHz tone, wsola keeps the energy outside the tone more than 100 dB down at all of the scales above, where rbpitch measured 7–53 dB. Both hit the right pitch. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved sample-accounting, overlap-selection, and pitch-range defects can produce silence or incorrect audio, while filter destruction leaks resources and the seek test fails to exercise nonzero seeks.
5 open issues
What changed in this PR
Adds a dependency-free WSOLA pitch filter to MLT’s plus module and makes it selectable for timewarp pitch compensation, while retaining Rubber Band as the default.
Changes:
- Adds Chromium-derived sinc resampling and WSOLA time stretching.
- Introduces
warp_pitch_methodand registers the new filter with its metadata. - Adds Qt tests for pitch shifts, timewarp compensation, sample counts, audio gaps and seeking.
| File | Description |
|---|---|
src/tests/test_wsola/test_wsola.cpp |
Adds standalone filter and timewarp audio tests. |
src/tests/CMakeLists.txt |
Enables WSOLA tests when plus is built. |
src/modules/plus/wsola/wsola_algorithm.h |
Declares audio buffers and time-stretching interfaces. |
src/modules/plus/wsola/wsola_algorithm.cpp |
Implements WSOLA with optimized similarity searches. |
src/modules/plus/wsola/sinc_resampler.h |
Declares the sinc resampler interface. |
src/modules/plus/wsola/sinc_resampler.cpp |
Implements sample-rate conversion for pitch shifting. |
src/modules/plus/filter_wsola.yml |
Documents filter parameters and latency reporting. |
src/modules/plus/filter_wsola.cpp |
Integrates resampling and time stretching into an MLT filter. |
src/modules/plus/factory.c |
Registers WSOLA and its metadata. |
src/modules/plus/CMakeLists.txt |
Builds WSOLA sources and installs metadata. |
src/modules/core/producer_timewarp.yml |
Documents pitch-compensation method selection. |
src/modules/core/producer_timewarp.c |
Selects and switches pitch-compensation filters. |
Review effort: Balanced
Without warp_pitch_method, timewarp uses rubberband if it is available and wsola otherwise. An explicit method also falls back to the other filter when the selected one is not available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Accept a pitchscale down to 0.05, like rbpitch, so that timewarp at 20x is compensated correctly. - Close the parent service when the filter is closed. - Let FullSearch() pick a candidate with a negative similarity. - Pass the audio through only at a rate of exactly 1: near 1, the output did not have the requested number of samples and the queue drifted. - The seek test now plays from the position it seeks to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This adds a WSOLA pitch change filter, interchangeable with
rbpitchand with no external dependencies, and makes it a second pitch compensation method fortimewarp, as discussed.What it does
wsolain theplusmodule. It takes the same parameters asrbpitch(octaveshift,pitchscale, animatable, andstretch) and, like it, keeps the number of samples and the frequency, so it works on its own and the two can be swapped.pitchscale, which changes the pitch, and WSOLA then time stretches it back to the requested length. Both are ports from Chromium (media/base/sinc_resamplerandmedia/filters/audio_renderer_algorithmwithwsola_internals, BSD licensed; the license is kept insrc/modules/plus/wsola/). The algorithms, constants and index arithmetic are unchanged; only Chromium's support types were replaced, and the resampler's AVX2 path was left out.producer_timewarp: new propertywarp_pitch_method. Withwarp_pitch=1,wsolaselects the new filter. Any other value usesrbpitchexactly as before, so existing projects are not affected. Both filters getpitchscale = 1/speed.Testing
test_wsola(Qt test): pitch preserved through timewarp at 0.5x, 2x and 3x; the filter on its own shifts a tone bypitchscale0.5, 1.5, 2 andoctaveshift-1, with the requested number of samples and no gaps; a seek does not change the pitch.blipflashblip, with and without the filter): 22 ms atpitchscale0.5 to 72 ms at 2 (rbpitch: 25–67 ms). Through timewarp: about 60 ms at 0.5x and 2x. The delay is constant over time..ymlfiles validate againstmetaschema.yaml.ctest: everything passes exceptTestProperties::RadixRespondsToLocale, which fails the same way on master on this machine (no comma-decimal locale).How to listen
Any clip with speech or music works (close the melt window to stop):
To run the automated test:
ctest -R wsola --output-on-failure.Kdenlive has said it wants to offer this; a matching merge request there will select the method per clip.
This was written with the help of Claude (AI).
🤖 Generated with Claude Code