Pressed duck

Just tried out the Pressed duck test.

It seems to use a lot of CPU. On my machine:

Pressed Duck: 1.5% VCV mixer: 0.1% VCV VCO: .3%

Also, as I’m sure you know, using Tanh distortion with no alias mitigation is going to alias a lot. but no worse that the Instruo Tanh module, of course.

Are you planning on addressing either issue before release?

1 Like

@Squinky Why is it using so much CPU in your case? PressedDuck only uses 0.8% on my computer fully patched (at 96khz), compared to 1.6% for the MixMaster (in the same configuration), which is the mixer I was typically using before. VCV mixer is so bare bones it’s not exactly a fair comparison, since the compression/saturation is where all the CPU is being spent. The question is, does this mixer sound good enough to warrant using it over a more basic mixer given the CPU cost. Ah well, there are probably some ways to optimize it a bit more still.

So the Tanh, yes, I wasn’t sure how to easily get rid of aliasing, and saturation seems to create this problem no matter what function you use. I’m willing to give a different saturation function a go if you think it’ll sound better. At 96k it didn’t seem too scratchy, I thought the most universal solution to aliasing was to just crank the sample rate a bit. The PressedDuck doesn’t skip any process cycles, so this might be why it is CPU hungry. How badly does it alias?

The feedback distortion isn’t as good sounding as I had hoped anyways, so maybe this is the aliasing at work. I thought about adding a bandpass filter to the Feedback circuit, so the low/highs won’t feedback, maybe that would be cleaner (and more CPU expensive!). I also considered adding a high-pass to the inputs/output to make the mixer more like an AC-coupled one.

But basically the answer is yes, I’d like to improve it on both fronts!

Two ideas to start:

First, I was thinking of implementing minBlep or polyBlep to band-limit the waveforms, but I was concerned about how CPU intensive this would become. I think polyBlep might be a good option, but it’s not a thing I’m super familiar with. Is it something I need to do to all 14 input channels to be effective, or can I do it just on the two processed ouput channels before I apply the saturating effect. Clearly applying the band-limiting just once would save CPU.

Another approach I was thinking of was to use a low-pass on the inputs, to eliminate DC offsets before the saturation is applied. And possibly again on the outputs. The thinking is that eliminating DC offsets could reduce inharmonic effects during waveshaping. Also it would make this more of an AC-coupled mixer, which is something we have in Eurorack a lot, but I don’t see in the Library.

Also, I’d like to somehow quantify just how bad the aliasing is to start, and then I can weigh how much CPU it costs to get any useful gains by additional processing.

Now upon studying minBlep and polyBlep a bit more. I would need to know where the discontinuities in the waveform are (or detect them somehow). So to use it I would make some sort of slope detector, and if the waveform slope exceeded that then I call minBlep and make a correction. I’m not sure I understand this correctly though. Does this mean I should make a buffer of some number of samples, and this would then add delay to the mix output (currently it does not have any delay beyond 1 sample, I think).

On the other hand, making like a high-pass filter to remove the DC, and a brick-wall like low-pass filter to remove >20khz stuff. Maybe that’s the easier option.

Disclaimer: I don’t know what pressed duck is.

For wave shapers in surge we use Antideriv Anti Aliasing in many cases, and find it works great, especially on more “sharp” edged shapers like the wave folders. Jatin wrote a nice blog about it

if you are using tanh directly you can use the identity for the anitderiv of log(cosh) but if you are using one of the polynomial tanh-like which are popular you can just do the integral analytically.

For us in the VST, 2x oversampling plus first order ADAA works great.

Hope that helps!

1 Like

Thanks for the suggestions. I saw that article in another thread here on anti aliasing. Very interesting and well documented! I don’t think I fully understood it the first time around, but I re-read it again just now and I think it’s making more sense. I’m very tempted to give it a try. It doesn’t look very obvious to me how I implement it correctly, so I could use some pointers in that respect. My program just calls Tanh directly, so that means I can use the log(cosh) solution for a first-order ADAA.

First, how do you implement ADAA? If I understand correctly, instead of computing tanh of the signal, it seems like we are supposed to compute the antiderivative log(cosh) of the signal, and then measure the difference between that value each process update (taking the derivative of the antiderivative basically)? But then next, how do you do oversampling? (I think I’m maybe misunderstanding what oversampling is, if the VCV is set to 44khz, then the process only updates at 44khz and can’t run at 2x sample rate). I could compute the antiderivative every 2nd process, and then interpolate between those samples. In this case, is that really better than just computing the Tanh directly every process?

BTW PressedDuck is a mixer module I started developing that has some built-in compression, saturation and ducking. It sprang out of my wanting 6 stereo inputs, and then having issues with the signals running out of headroom, and then wishing the mixer would just auto-compress for me. i might just be in over my head a little with this concept! ha ha

1 Like

I think tanh is a good function to use for “soft clipping”, and it doesn’t alias any more than any non-linear function that’s going to generate those nice distortion harmonics.

It’s just a fact of life that almost any non-linear audio processing is going to alias like crazy unless you do something to reduce it. As you noted, minBLEP isn’t obviously applicable. Oversampling is the classic way to reduce it. As @baconpaul is suggesting there are now more advanced ways that (afaik) give better alias suppression vs CPU usage.

Using a higher sample rate for your session is not the best way to reduce aliasing. this is explained in a popular video.

What system are you measuring CPU usage on? I’m on windows. Last I looked (long ago), windows was much slower than mac for doing transcendental functions like sin, cos, tanh, etc… I’ll do more measurements tonight against some other, more reasonable modules.

btw, I have written a lot about there issues over here.. I think the VCV plugin manual has a lot of excellent info on efficiency and DSP programming.

Yeah as the jatin blog says basically you transform your code from

  out = tanh(in)

to keep an ‘in_previous’ initialized at zero then do

   if (fabs(in_previous-in) > 1e-6)
      out = (ln(cosh(in))-ln(cosh(in_previous)))/(in - in_previous);
   else
      out = tanh(in)

that will be very CPU inefficient but it will suppress your aliasing. You then have to make it CPU efficient, which is an exercise left to the reader, as they say in old E&M text books. Three approaches you could use are (1) lookup tables, (2) algebraic approximations of the functions or (3) use a similar simpler form and take the anti-derivative.

A good similar form is

z = x * 0.55
out = 1.5 * z - 0.5 * z * z *z  | -1 < z < 1

which you can see tracks tanh very nicely. Integrating this function is also an exercise left to the reader, but its just polynomials so not that hard.

As to ‘how to oversample’, the “short” version is “you have a set of filters which go from sample rate to nx sample rate, and another set of filers which go from nx sample rate to sample rate, and you do your calculation in the higher space”. But for something like a mixer in rack this (1) adds latency and (2) requires you to have a set of filters. In surge we have a tried and tested half rate filter, but there’s lots of literature on this.

My guess is if you just do ADAA on tanh or tan-like curve you will get a good result though.

2 Likes

@Squinky I’m on MacM1 it’s using ARM. Maybe it has some cool shortcut for these functions? I dunno, it seems to run fast enough, if you give it the benefit of also being an effect module. Of course, I’m very happy to try and optimize it better if you have some ideas.

Yes, I’ve read most of the threads on these topics here, and tutorials too. But of course none of it sticks until it gets to be more relevant. For example, how exactly does the oversampling work? I’m looking at your shaper.cpp code and not really getting the point, it just calling oversamplingshaper.h. And … okay in there it runs a loop n times for the oversampling factor. So basically every process it compute some sub-samples to fill in the gaps. Then these are just used for the filtering stage? The inputs will still come at the normal sample rate, so we could at best compute interpolated input points by linear or maybe some splines.

In a previous thread you mentioned using a brick wall-like filter to block out the higher harmonics, and I wanted to know if that would be maybe a simpler option or if it doesn’t work well enough.

Wow thanks for the help @baconpaul ! That function looks honestly quite close to the tanh. That’s a good lead, I’ll do some research / and math and see if I can make any improvements to my design.

1 Like

oooooohhhh wait. VCV 2.5.1 totally screwed up my build environment. My compiled plugins are not updating in the library post update! Arg. I don’t know how to develop for this new version, I think I have to downgrade to 2.4. arg!!!

edit: Ah, I figured out how to proceed. I just have to manually copy the plugin.dylib to the new directory. Kinda annoying…

I ended up using this:

	float applyADAA(float input, float lastInput, float sampleRate) {
		float delta = input - lastInput;
		if (fabs(delta) > 1e-6) {
			return (antiderivative(input) - antiderivative(lastInput)) / delta;
		} else {
			return tanh(input);
		}
	}

	float antiderivative(float x) {
		float x2 = x * x;
		float x4 = x2 * x2;
		float x6 = x4 * x2;
		float x8 = x4 * x4;
		return x2 / 2.0f - x4 / 12.0f + x6 / 45.0f - 17.0f * x8 / 2520.0f;
	}

It seems to work! I think. As a bonus it seems like the CPU usage went down as well.

@Squinky I made an update of the module on Git. I think I have addressed both points you brought up. It’s hovering at about 0.4% CPU now at 96k sample rate, and I think the compression/saturation controls are actually working correctly for once. I also figured out that another source of harmonics was the feedback from the envelope follower, so I added some smoothing and that seemed to make it more stable. I’d really like your opinion again.

Also, now I’m thinking it needs some output lights to show info for each of the three knobs on the right. Top should show the sum of the envelope followers, middle should show how much the signal is being distorted, and bottom shows the LR output levels. somehow… I’ll sort that out next I guess. Any idea how to measure distortion?

That is not the anti derivative of tang so in the else you should replace tang with the polynomial also but otherwise looks good

1 Like

The CPU usage is still not great. Here I’ve put it up next to some other modules. The AS mixer is one that I know uses sin and cos every process and is terrible. My 8 channel “Mixer 8” is based on that, but made more efficient. It also has a lot of features.

Shaper is doing a tanh, but with 4X oversampling for pretty low aliasing.

Comp is a pretty popular compressor. It of course has a smoothed envelope follower and the full decibel compression ratio, which requires a “log” and an “exp”.

1 Like

Aliasing looks better, I think.

1 Like

@Squinky Okay, I made a whole lotta updates to this thing. Thanks again for testing on PC for me, and for all the optimization tips!

1- See the CPU usage went down a little bit. Okay, not nearly as efficient as your mixer, but I’ll take 0.5% over 1.3% from my other favorite fancy mixer. I replaced all trig functions with some polynomial stuff, even the sin and cos (used for panning, so who cares), and then I only call them when the parameter changes. Replacing the Tanh with polynomials did not improve performance on my ARM computer, but I think it might be faster on older machines without special trig stuff in the processors.

2- Fixed the Press function, it expands/compresses now. It’ll amplify a low signal to take up the full range of 5V, or it’ll squash all signals so they fit in 5V. Basically it’ll make sausage out of your signals if you want it.

3- Fixed the Feed feedback/saturation distortion. Feed just amplifies the signal pre-saturation, but it functions as a saturation knob. I adjusted it so the knob scales X^3, this gives you a bit more range before everything turns to square wave.

4- Smoothing of ducking envelopes. Smoothing these out prevents harmonics from being re-injected into the signal. I realized it was a BIG source of noise, but I think I found a space where it is responsive but not making a buzzy noise.

5- SampleRate - After all that testing, it sounded worse and worse at higher sample rates. I realized that I need to adjust my smoothing functions based on the sample rate, and at higher sample rates it was doing the buzzing thing again. Adding a compensation based on sampleRate seems to fix it, mostly.

3 Likes

New Update. Clickless Mutes. Also, dark themed jacks in dark mode.

2 Likes