hetrickcv icon indicating copy to clipboard operation
hetrickcv copied to clipboard

Polyphony request and a suggestion for ADC/DAC

Open biggnome opened this issue 3 years ago • 7 comments

I spent a little time last year whacking polyphony into some of these modules for my own use (naively, and where I could make sense of how--disclaimer: I am not a programmer!) I think it's an especially useful addition to Flip Pan, Boolean, and 2-to-4 in particular (each my go-to modules for what they do!), and I would love to see this implemented in the standard release.

Exponent and Contrast can get a little out of hand with CPU consumption with multiple voices, although I was able to keep this much more manageable by swapping out the powf() and sinf() functions, respectively, with much faster approximations I found around the internet:

// faster powf() approximation cribbed from martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/
double fastPow(double a, double b) {
    if (a == 0) {
    	return 0;
    }
    else {
    	union {
	        double d;
	        int x[2];
	    } u = { a };
	    u.x[1] = (int)(b * (u.x[1] - 1072632447) + 1072632447);
	    u.x[0] = 0;
	    return u.d;
	}
}
//fast sine approximation pinched from gist.github.com/geraldyeo/988116
//1.27323954  = 4/pi
//0.405284735 =-4/(pi^2)
float fastSin(float x) {
	float fsin = 0;
	if (x < -3.14159265) x += 6.28318531;
	else if (x >  3.14159265) x -= 6.28318531;
	if (x < 0) {
	    fsin = 1.27323954 * x + 0.405284735 * x * x;
	    if (fsin < 0) fsin = .225 * (fsin *-fsin - fsin) + fsin;
	    else fsin = .225 * (fsin * fsin - fsin) + fsin;
	}
	else {
	    fsin = 1.27323954 * x - 0.405284735 * x * x;
	    if (fsin < 0) fsin = .225 * (fsin *-fsin - fsin) + fsin;
	    else fsin = .225 * (fsin * fsin - fsin) + fsin;
	}
	return fsin;
}

I suspect simd would perform better than the crude for (int c = 0; c < channels; c++) approach I used to poly-fy these; I also kinda wanted to keep the original, legit operation available via an "HQ" menu option or something, but both were a little beyond my ken. Like I said, I'm a tinkerer at best...

For giggles, I hacked in optional A-law and mu-law compansion to the ADC and DAC modules. It's kinda fun, especially for off-the-wall applications like, say, using these to generate gate sequences from LFOs in which case they can produce a funky accelerating/decelerating behavior, bouncing ball effects, etc.; or mixing & matching normal and companded modes as a crude form of waveshaping. The relevant code is:

int sgn(float v) {
    return (v > 0) - (v < 0);
}

const float mu = 255.0;
const float A = 87.6;

// Compression (pre A2D)
if (compMode == 1) {
    float absx = std::abs(input);
    if (absx < (1 / A)) input = std::abs((A * absx) / (1 + log(A))) * sgn(input);
    else input = std::abs((1 + log(A * absx)) / (1 + log(A))) * sgn(input);
}
else if (compMode == 2) {
    input = std::abs((log(1 + mu * std::abs(input))) / (log(mu + 1))) * sgn(input);
}

// Expansion (post D2A)
if (compMode == 1) {
    float absy = std::abs(mainOutput);
    if (absy < (1/(1 + log(A)))) mainOutput = std::abs((absy * (1 + log(A))) / A) * sgn(mainOutput);
    else mainOutput = std::abs(std::exp(absy * (1 + log(A)) - 1) / A) * sgn(mainOutput);
}
else if (compMode == 2) {
    mainOutput =  std::abs((1 / mu) * (powf(1 + mu, std::abs(mainOutput)) - 1)) * sgn(mainOutput);
}

I also found it really useful to expand the range of the Scale parameter on these to +/-2x since so much of what makes these modules special involves wrangling tiny signals and differing polarities.

By all means feel free to poke around my (admittedly rather messy) fork for particulars.

biggnome avatar Oct 30 '21 15:10 biggnome

"I am not a programmer!"

Sure seem like a programmer to me ;) Believe me, we all start somewhere.

This is awesome! Yes, I probably should replace the trig functions with lower-cost approximations. The DSP library that we use at Unfiltered and that I just popped into HetrickCV for the v2 modules has a ton of templated appox functions. Templates are a C++ feature that all me to a use a function for any supported data type. As an example, in the functions you have above, they are defined for float. With the templated functions, I can use VCV's SIMD data type, float4.

Honestly, I'm still learning SIMD, and my first usage of it is in the Chaotic Attractors module where I used SIMD to process three channels in the DC Filter simultaneously. Now that I have it figured out, it will be pretty easy to optimize these modules.

The a-law and mu-law stuff is a great idea. I'm wondering if I should tuck that into ADC/DAC or if I should make a separate compansion encoder/decode module (like the XY-Polar and Mid-Side modules). I'll experiment and see what I can come up with.

mhetrick avatar Oct 31 '21 03:10 mhetrick

Okay Waveshaper and Contrast are now fully SIMD optimized (and much harder to read!), so test those out if you get a chance. I'm in bed but I plan on doing more of these this week.

In Contrast, you'll see that I replaced sinf with the seemingly identical sin and not an approximation. However, behind the scenes, Rack's simd/functions.hpp file defines an optimized version for float_4. So now Contrast's single-channel operation should be faster as well.

mhetrick avatar Oct 31 '21 04:10 mhetrick

Gee whiz, that was fast! These are great, and indeed the SIMD-optimized modules are really lightweight. I hadn't intended that as a critique, by the way--I only brought up approximations since I essentially came here begging for polyphony, and in my own efforts with just enclosing the works in a by-rote '1 to n channels' loop, running those functions on multiple voices at high sample rates quickly got pretty unmanageable. Thanks for the note about the optimized sin--that would have had me scratching my head for sure.

The a-law and mu-law stuff is a great idea. I'm wondering if I should tuck that into ADC/DAC or if I should make a separate compansion encoder/decode module

Thanks! I hadn't considered it as a separate module, but that could be really cool--I had popped them into the converter modules originally (initially in your wonderful Euro Reakt collection) with the notion of using the discrete implementation à la computer multimedia in the 90's, but I got befuddled--digital math is definitely not my strong suit--and ended up using the continuous form instead; which I suppose is more broadly applicable beyond 8-bit anyway, so your idea might well be more practical. Either way, I'd be tickled to see something like that added in one way or another. Let me know if there's anything I can do to help out.

biggnome avatar Oct 31 '21 11:10 biggnome

Hey! No critique taken. We use approximations in the Unfiltered Audio code, and I should have used them here. I suppose the reason that I didn't is that the first version of HetrickCV was intended to be as readable as possible for new programmers since it's MIT-licensed and useful for learning and hacking. However, that was before polyphony and many other features were added to VCV, so at this point it makes sense to optimize and turn them into better, more performant tools. Also, now that I added Gamma to the v2 branch, everything is going to be less readable hahah.

mhetrick avatar Oct 31 '21 17:10 mhetrick

2-to-4 Mix Matrix is now up with SIMD polyphony. About to go trick-or-treating, but more to come.

mhetrick avatar Oct 31 '21 21:10 mhetrick

Wow! Spent a lot of time playing with all the new poly stuff today. Thanks for all these! (Hope you were able to squeeze in a nice evening of trick or treating amidst all this...) By the way, I really appreciate the care you clearly do put into keeping your code neat and readable--dabbling with HetrickCV was my introduction to the VCV API and I learned a lot perusing your work.

One tiny issue to report--the description of Gingerbread Chaos is identical to Gate Junction's.

biggnome avatar Nov 01 '21 09:11 biggnome

Just added the Data Compander module based on your idea! Also, thanks for the heads up on the Gingerbread description. That's fixed.

mhetrick avatar Nov 02 '21 04:11 mhetrick

Closing this issue now that most of HetrickCV is polyphonic and and now the Data Compander module exists. Thanks again for the great ideas.

mhetrick avatar Jul 02 '23 18:07 mhetrick