VCVRack / Fundamental

https://vcvrack.com/Fundamental
Other
235 stars 76 forks source link

**SOLUTION** Fundamental VCO Pops and Clicks from PWM #140

Open tone-roche opened 2 years ago

tone-roche commented 2 years ago

Most VCOs in VCV will produce bothersome clicks and pops when modulating the pulse width because they used the Fundemental VCO as a reference. The developer of Slime Child Audio found the solution to this issue:

https://community.vcvrack.com/t/clicks-and-pops-from-pwm/15824/5

From Slime Child Audio: VCV VCO’s MinBLEP implementation will sometimes insert a second discontinuity immediately after the first, which results in a pop. Many developers–myself included–have used that implementation as reference, and have inadvertently duplicated the bug, which is why you’re seeing it in other modules too.

The fix is pretty simple: only insert discontinuities for the single sample where the pre-antialiased value has changed (e.g. the exact sample where the value jumps from -1 → +1 or +1 → -1).

Sample SIMD implementation:

// ...

// Calculate waveform value = rack::simd::ifelse(_phase < _width, 1.0f, -1.0f);

// Check for discontinuity int change_mask = rack::simd::movemask(value != _last); _last = value; if (change_mask != 0) { // Insert discontinuity where phase crosses 0 rack::simd::float_4 zero_cross = (delta_phase - _phase) / delta_phase; int zero_mask = change_mask & rack::simd::movemask((0.0f < zero_cross) & (zero_cross <= 1.0f)); if (zero_mask) { for (int i = 0; i < channels; i++) { if (zero_mask & (1 << i)) { rack::simd::float_4 mask = rack::simd::movemaskInverse(1 << i); float p = zero_cross[i] - 1.0f; rack::simd::float_4 x = mask & static_cast(2.0f); _minblep_generator.insertDiscontinuity(p, x); } } }

// Insert discontinuity where phase crosses pulse width
rack::simd::float_4 pulse_cross = (_width - (_phase - delta_phase)) / delta_phase;
int pulse_mask = change_mask & rack::simd::movemask((0.0f < pulse_cross) & (pulse_cross <= 1.0f));
pulse_mask = pulse_mask & ~zero_mask;  // Don't double-insert! This is probably overkill but it's cheap
if (pulse_mask) {
    for (int i = 0; i < channels; i++) {
        if (pulse_mask & (1 << i)) {
            rack::simd::float_4 mask = rack::simd::movemaskInverse<rack::simd::float_4>(1 << i);
            float p = pulse_cross[i] - 1.0f;
            rack::simd::float_4 x = mask & static_cast<rack::simd::float_4>(-2.0f);
            _minblep_generator.insertDiscontinuity(p, x);
        }
    }
}

}

// Add MinBLEP result value += _minblep_generator.process();

// ...


See attachment:

minblep_diff.txt