Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
tone-roche opened this issue Jan 10, 2022 · 0 comments
Open

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

tone-roche opened this issue Jan 10, 2022 · 0 comments

Comments

@tone-roche
Copy link

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::movemaskInverserack::simd::float_4(1 << i);
float p = zero_cross[i] - 1.0f;
rack::simd::float_4 x = mask & static_castrack::simd::float_4(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


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

No branches or pull requests

1 participant