Fix pitch shifter phase wrapping#228
Conversation
📝 WalkthroughWalkthroughPitchShifter's phase normalization is refactored to replace unbounded while-loop wrapping with O(1) std::fmod-based wrapping. A ChangesPhase Wrapping Optimization
🎯 2 (Simple) | ⏱️ ~8 minutes Suggested labels: Suggested reviewers:
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Coverage Report 📊Line Coverage: 81.3% ✅ Coverage meets threshold: 81.3% >= 60% Full Coverage Summary |
PR Preview RemovedThe GitHub Pages preview for this PR has been removed because the PR was closed. |
|
@donglrd shouldn't we have some test coverage? |
|
Added a regression test for the pitch shifter phase wrapping path. It exercises an extreme positive shift/fine-tune case, processes a full buffer, and asserts the output remains finite with signal energy. Validated locally:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/test_effects.cpp (1)
1343-1347: ⚡ Quick winHarden the regression setup so extreme params are guaranteed active.
Set params after
reset()(or assert values after reset) so this test can’t silently stop exercising the extreme-wrap path if reset behavior changes.Proposed tweak
TEST(pitch_shifter_extreme_shift_wraps_phase_without_instability) { PitchShifter ps; ps.set_sample_rate(48000); + ps.reset(); ps.params()[0].value = 120.0f; ps.params()[1].value = 500.0f; ps.params()[2].value = 1.0f; - ps.reset(); + + ASSERT_NEAR(ps.params()[0].value, 120.0f, 1e-6f); + ASSERT_NEAR(ps.params()[1].value, 500.0f, 1e-6f); + ASSERT_NEAR(ps.params()[2].value, 1.0f, 1e-6f);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_effects.cpp` around lines 1343 - 1347, Move the parameter assignments so they occur after calling ps.reset() (or alternatively add assertions that ps.params()[i].value equals the intended values immediately after reset) to ensure the extreme values are active regardless of reset behavior; specifically modify the test to call ps.reset() first and then set ps.params()[0].value = 120.0f, ps.params()[1].value = 500.0f, ps.params()[2].value = 1.0f (or assert those values after ps.reset()) so the extreme-wrap path is enforced reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/test_effects.cpp`:
- Around line 1343-1347: Move the parameter assignments so they occur after
calling ps.reset() (or alternatively add assertions that ps.params()[i].value
equals the intended values immediately after reset) to ensure the extreme values
are active regardless of reset behavior; specifically modify the test to call
ps.reset() first and then set ps.params()[0].value = 120.0f,
ps.params()[1].value = 500.0f, ps.params()[2].value = 1.0f (or assert those
values after ps.reset()) so the extreme-wrap path is enforced reliably.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/test_effects.cpp (1)
1368-1383: ⚡ Quick winExtend this regression to include the sample-rate-change trigger path.
This test covers extreme phase stepping well, but the reported repro also includes runtime sample-rate changes. Adding a second pass after changing sample rate will lock in that path too.
Proposed test extension
TEST(pitch_shifter_extreme_shift_wraps_phase_without_instability) { PitchShifter ps; ps.set_sample_rate(48000); ps.params()[0].value = 120.0f; ps.params()[1].value = 500.0f; ps.params()[2].value = 1.0f; ps.reset(); std::vector<float> buf(4096); fill_sine(buf.data(), static_cast<int>(buf.size()), 440.0f, 48000); ps.process(buf.data(), static_cast<int>(buf.size())); ASSERT_TRUE(buffer_is_finite(buf.data(), static_cast<int>(buf.size()))); ASSERT_GT(rms(buf.data(), static_cast<int>(buf.size())), 0.001f); + + ps.set_sample_rate(96000); + ps.reset(); + fill_sine(buf.data(), static_cast<int>(buf.size()), 440.0f, 96000); + ps.process(buf.data(), static_cast<int>(buf.size())); + ASSERT_TRUE(buffer_is_finite(buf.data(), static_cast<int>(buf.size()))); + ASSERT_GT(rms(buf.data(), static_cast<int>(buf.size())), 0.001f); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_effects.cpp` around lines 1368 - 1383, The test pitch_shifter_extreme_shift_wraps_phase_without_instability needs to exercise the sample-rate-change trigger path too: after the first process pass, call ps.set_sample_rate(...) with a different rate (e.g., 44100), call ps.reset() if appropriate, then run a second ps.process(...) on the same buffer and assert finiteness and non-zero rms again; update the TEST (and use the same PitchShifter instance and functions set_sample_rate, reset, process, buffer_is_finite, rms) so the regression includes the runtime sample-rate change path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/test_effects.cpp`:
- Around line 1368-1383: The test
pitch_shifter_extreme_shift_wraps_phase_without_instability needs to exercise
the sample-rate-change trigger path too: after the first process pass, call
ps.set_sample_rate(...) with a different rate (e.g., 44100), call ps.reset() if
appropriate, then run a second ps.process(...) on the same buffer and assert
finiteness and non-zero rms again; update the TEST (and use the same
PitchShifter instance and functions set_sample_rate, reset, process,
buffer_is_finite, rms) so the regression includes the runtime sample-rate change
path.
Summary
whileloops inPitchShifter::process()with a constant-time wrapperread_linear()so phase normalization stays consistentFixes #218
Testing
git diff --check HEAD~1 HEADcmake --build build --target amplitron-tests -j10./build/amplitron-tests --gtest_filter='*pitch_shifter*'(274 passed, 0 failed)Summary by CodeRabbit