Skip to content

Commit 7a074bb

Browse files
Fix pitch shifter phase wrapping (#228)
* Fix pitch shifter phase wrapping * Add pitch shifter phase wrap regression test --------- Co-authored-by: SUDIP MONDAL <sudmondal2002@gmail.com>
1 parent ab54d95 commit 7a074bb

2 files changed

Lines changed: 27 additions & 8 deletions

File tree

src/audio/effects/pitch_shifter.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ static constexpr int P_MIX = 2;
1414
// Grain window size in seconds (~23 ms at 48kHz = 1024 samples)
1515
static constexpr float GRAIN_WINDOW_SEC = 0.023f;
1616

17+
static float wrap_phase(float phase, int buf_size) {
18+
phase = std::fmod(phase, static_cast<float>(buf_size));
19+
if (phase < 0.0f) phase += static_cast<float>(buf_size);
20+
return phase;
21+
}
22+
1723
PitchShifter::PitchShifter() {
1824
params_ = {
1925
{"Shift", 0.0f, -12.0f, 12.0f, 0.0f, "st",
@@ -35,9 +41,7 @@ void PitchShifter::set_sample_rate(int sample_rate) {
3541
}
3642

3743
float PitchShifter::read_linear(float phase) const {
38-
// Wrap phase into [0, buf_size_)
39-
phase = std::fmod(phase, static_cast<float>(buf_size_));
40-
if (phase < 0.0f) phase += buf_size_;
44+
phase = wrap_phase(phase, buf_size_);
4145

4246
int pos0 = static_cast<int>(phase);
4347
int pos1 = (pos0 + 1) % buf_size_;
@@ -77,11 +81,9 @@ void PitchShifter::process(float* buffer, int num_samples) {
7781
read_phase_a_ += drift;
7882
read_phase_b_ += drift;
7983

80-
// Wrap phases into [0, buf_size_)
81-
while (read_phase_a_ < 0.0f) read_phase_a_ += buf_size_;
82-
while (read_phase_a_ >= buf_size_) read_phase_a_ -= buf_size_;
83-
while (read_phase_b_ < 0.0f) read_phase_b_ += buf_size_;
84-
while (read_phase_b_ >= buf_size_) read_phase_b_ -= buf_size_;
84+
// Wrap phases into [0, buf_size_) in constant time.
85+
read_phase_a_ = wrap_phase(read_phase_a_, buf_size_);
86+
read_phase_b_ = wrap_phase(read_phase_b_, buf_size_);
8587

8688
// Compute absolute read positions in the buffer
8789
float pos_a = static_cast<float>(write_pos_) - read_phase_a_;

tests/test_effects.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,23 @@ TEST(pitch_shifter_with_mix_and_shift_differs_from_dry) {
13651365
ASSERT_GT(mag_shifted, mag_440 * 0.5f);
13661366
}
13671367

1368+
TEST(pitch_shifter_extreme_shift_wraps_phase_without_instability) {
1369+
PitchShifter ps;
1370+
ps.set_sample_rate(48000);
1371+
ps.params()[0].value = 120.0f;
1372+
ps.params()[1].value = 500.0f;
1373+
ps.params()[2].value = 1.0f;
1374+
ps.reset();
1375+
1376+
std::vector<float> buf(4096);
1377+
fill_sine(buf.data(), static_cast<int>(buf.size()), 440.0f, 48000);
1378+
1379+
ps.process(buf.data(), static_cast<int>(buf.size()));
1380+
1381+
ASSERT_TRUE(buffer_is_finite(buf.data(), static_cast<int>(buf.size())));
1382+
ASSERT_GT(rms(buf.data(), static_cast<int>(buf.size())), 0.001f);
1383+
}
1384+
13681385
// ============================================================
13691386
// MultiBandCompressor tests
13701387
// ============================================================

0 commit comments

Comments
 (0)