Old 07-02-2019, 01:34 PM   #1
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default Waveshaper Code Not Working

Hi

Ive been trying to make an Asymetric Waveshaper. I started with the curves in Desmos and then translated that into code in jsfx. But somethings cleary wrong. Its not behaving as expected.

Where did I go wrong with the code?

/thanks

Heres the Desmos curve
https://www.desmos.com/calculator/wjwzuyw44s

Heres the jsfx-code

desc:Asymetric Waveshaper
slider1:0<-0.3,0.3,0.01>parameter

@init
bpos=0;

//@slider

@block
blah+=samplesblock;

@sample
spl0= slider1 * sin((spl0*pi)+spl0);
spl1= slider1 * sin((spl1*pi)+spl1);
danerius is offline   Reply With Quote
Old 07-02-2019, 01:43 PM   #2
sai'ke
Human being with feelings
 
sai'ke's Avatar
 
Join Date: Aug 2009
Location: NL
Posts: 650
Default

You probably want $pi. Also, you might want to clamp the input between -1 and 1, so that it doesn't exceed the peak of the sine wave unless that is the kind of sound you are going for.
__________________
[Tracker Plugin: Thread|Github|Reapack] | [Routing Plugin: Thread|Reapack] | [Filther: Thread|Github|Reapack] | [More JSFX: Thread|Reapack]
sai'ke is offline   Reply With Quote
Old 07-02-2019, 01:55 PM   #3
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by sai'ke View Post
You probably want $pi. Also, you might want to clamp the input between -1 and 1, so that it doesn't exceed the peak of the sine wave unless that is the kind of sound you are going for.
Awesome. Thanks It kinda behaves more like it should now. But I just realized that Im alos zeroing out the formula with the slider. The range -0.3 to 0.3 works fine in Desmos but not in jsfx...

How do I get around that?
danerius is offline   Reply With Quote
Old 07-02-2019, 01:58 PM   #4
sai'ke
Human being with feelings
 
sai'ke's Avatar
 
Join Date: Aug 2009
Location: NL
Posts: 650
Default

Ah, I must have been sleepy

The equation is different from what you plotted.

You wrote:
Code:
spl0= slider1 * sin((spl0*$pi)+spl0);
spl1= slider1 * sin((spl1*$pi)+spl1);
While the formula you graphed is:
Code:
spl0= slider1 * sin(spl0*$pi)+spl0;
spl1= slider1 * sin(spl1*$pi)+spl1;
__________________
[Tracker Plugin: Thread|Github|Reapack] | [Routing Plugin: Thread|Reapack] | [Filther: Thread|Github|Reapack] | [More JSFX: Thread|Reapack]
sai'ke is offline   Reply With Quote
Old 07-02-2019, 02:05 PM   #5
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by sai'ke View Post
Ah, I must have been sleepy

The equation is different from what you plotted.

You wrote:
Code:
spl0= slider1 * sin((spl0*$pi)+spl0);
spl1= slider1 * sin((spl1*$pi)+spl1);
While the formula you graphed is:
Code:
spl0= slider1 * sin(spl0*$pi)+spl0;
spl1= slider1 * sin(spl1*$pi)+spl1;
Sleepy is fine. I have no excuse other than that Im learning

It was the parenthesises... So simple... Thanks a million for the help
danerius is offline   Reply With Quote
Old 07-02-2019, 02:18 PM   #6
sai'ke
Human being with feelings
 
sai'ke's Avatar
 
Join Date: Aug 2009
Location: NL
Posts: 650
Default

No prob. Happy to help
__________________
[Tracker Plugin: Thread|Github|Reapack] | [Routing Plugin: Thread|Reapack] | [Filther: Thread|Github|Reapack] | [More JSFX: Thread|Reapack]
sai'ke is offline   Reply With Quote
Old 07-05-2019, 12:46 AM   #7
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 765
Default

Also, instead of clamping to +/- 1 you could do something like....

Code:
desc:Danerius Waveshaper
slider1:0.1<-0.3,0.3,0.01>parameter 

@init

bpos=0;


@slider

sliderpi = (1 - slider1 * $pi);


@sample

s0 = abs(spl0);
s0 > 1 ? ( s0 = sliderpi*(s0 - 1) + 1 ) : s0 = slider1 * sin(s0*$pi)+s0;
spl0 = s0 * sign(spl0);

s1 = abs(spl1);
s1 > 1 ? ( s1 = sliderpi*(s1 - 1) + 1 ) : s1 = slider1 * sin(s1*$pi)+s1;
spl1 = s1 * sign(spl1);
...No hard clipping, large signal values are handled gracefully. Also, this isn't really asymmetric, it has rotational symmetry. This makes it an odd function and you can check on a spectrogram, it is only creating odd harmonics. I like SPAN, I use it all the time, but of course there is a very handy JSFX spectrogram as well, you can find it if you look for gfx_analyzer.

Like, seriously though, test this code against the original. See what happens when you push a signal through that is greater than 0 dB. Compare it to what happens with this version.

https://www.desmos.com/calculator/wcdhdiixvp


(note that cos(pi*1) is -1, the cos() is there to show that I was taking the derivative of sine where x = 1 etc)

Last edited by SaulT; 07-05-2019 at 01:40 AM.
SaulT is offline   Reply With Quote
Old 07-05-2019, 09:13 AM   #8
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by SaulT View Post
Also, instead of clamping to +/- 1 you could do something like....

Code:
desc:Danerius Waveshaper
slider1:0.1<-0.3,0.3,0.01>parameter 

@init

bpos=0;


@slider

sliderpi = (1 - slider1 * $pi);


@sample

s0 = abs(spl0);
s0 > 1 ? ( s0 = sliderpi*(s0 - 1) + 1 ) : s0 = slider1 * sin(s0*$pi)+s0;
spl0 = s0 * sign(spl0);

s1 = abs(spl1);
s1 > 1 ? ( s1 = sliderpi*(s1 - 1) + 1 ) : s1 = slider1 * sin(s1*$pi)+s1;
spl1 = s1 * sign(spl1);
...No hard clipping, large signal values are handled gracefully. Also, this isn't really asymmetric, it has rotational symmetry. This makes it an odd function and you can check on a spectrogram, it is only creating odd harmonics. I like SPAN, I use it all the time, but of course there is a very handy JSFX spectrogram as well, you can find it if you look for gfx_analyzer.

Like, seriously though, test this code against the original. See what happens when you push a signal through that is greater than 0 dB. Compare it to what happens with this version.

https://www.desmos.com/calculator/wcdhdiixvp


(note that cos(pi*1) is -1, the cos() is there to show that I was taking the derivative of sine where x = 1 etc)
First of all. Thanks a million for looking through and fixing my attempt at learning to code

The point of this is to make a console-ish emulation. Imitating the hysterisis of a transformer. With one waveshape for channels and one for the summing. (With the added slider for adjustment.) And then those waveshapes not being mirror images. Hence me calling it an Asymetric Waveshaper. Maybe Asymetric Console Emulation would be more appropriate?

I believe you when you say there'll be no overs. Ive only tested my version on two songs and it clipped a bit. Yours did not. Again... thanks

Looking through the code. Am I correct in seeing an if/else thing with the " ? : ".? Also. Youre making all values positive with the (abs) and then processing them?

Best regards /Bo
danerius is offline   Reply With Quote
Old 07-05-2019, 09:07 PM   #9
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 765
Default

Quote:
Looking through the code. Am I correct in seeing an if/else thing with the " ? : ".? Also. Youre making all values positive with the (abs) and then processing them?
Bingo. JS handles conditionals similarly but differently than other C-based languages, it's pretty compact but can be hard to read sometimes. I thought for a while about how to write it out in a way that made sense but there was no way around using conditionals, so instead of at least three conditionals for each channel I only used the one. Conditionals are expensive, CPU-wise, so fewer is better.

If I was to do it again I would probably put it in a function so it could be tweaked. For example (this function would be declared in the @init section)

Code:
function waveshape(in,shape)
local(sliderpi,s0)
{
sliderpi = (1 - shape * $pi);
s0 = abs(in);
s0 > 1 ? ( s0 = sliderpi*(s0 - 1) + 1 ) : s0 = slider1 * sin(s0*$pi)+s0;
s0 *= sign(in);
}
This way you could treat the positive values differently than the negative values, for example,

Code:
spl0 >= 0 ? spl0 = waveshape(spl0,slider1) : spl0 = waveshape(spl0,slider1*0.5);
This is what I thought you meant when you said asymmetric, now we no longer have rotational symmetry and it will produce a mixture of even and odd harmonics. Squishing the positive values a little more than the negative values is a characteristic of many analog gain stages btw, like tubes and JFETs. Depends on how they're biased, but that's a different topic. Anyways, that's what asymmetric usually refers to. If you incorporate asymmetry into your plugin you can call it that, or just refer to it as a console emulation if you don't.

It can help to see what the function does. In an empty channel put three effects - the first should create a sine wave, the second is this plugin, the third is a plugin like gfx_analyzer. Set the plugin to desired degree of clipping then start raising the level of the sine wave. As it begins to clip you will see new harmonics appear in gfx_analyzer. It is very cool. E.g. as is you see the plugin create odd harmonics... so if the sine wave is at 1 kHz you start to see 3 kHz, 5 kHz etc appear.

I like to put things in functions because it helps keep the code clean when it comes time to do something like oversampling. Strictly speaking you probably don't need oversampling since you aren't clipping super hard, but it's always something you can explore.

If you are interested in learning more about coding I recommend checking out Tale's plugin pack (I don't know if it's in ReaPack but you can download it by searching "tale jsfx"). I've found his code to general be pretty easy to understand and I've been inspired by it quite a bit. There are a lot of good plugin authors here, lots of resources, it's a great community.

The type of saturation you're using tends to be very clean, although it does need to be tweaked a little to handle large values gracefully. Another saturation function is tanh(), which is used because it naturally converges to +/- 1. Use Desmos to graph it out so you see what I mean. There has been a lot of discussion in the forum about how to use it, approximate it, optimize it, even anti-alias it without oversampling. Just searching for "tanh" in this sub forum, or using google "jsfx tanh" should land you a bunch of posts about it. On a few of them I have links to other posts, I was really into the topic for a while.

Sry for wall of text, hope some of this helped.
SaulT is offline   Reply With Quote
Old 07-07-2019, 06:22 AM   #10
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by SaulT View Post
Bingo. JS handles conditionals similarly but differently than other C-based languages, it's pretty compact but can be hard to read sometimes. I thought for a while about how to write it out in a way that made sense but there was no way around using conditionals, so instead of at least three conditionals for each channel I only used the one. Conditionals are expensive, CPU-wise, so fewer is better.

If I was to do it again I would probably put it in a function so it could be tweaked. For example (this function would be declared in the @init section)

Code:
function waveshape(in,shape)
local(sliderpi,s0)
{
sliderpi = (1 - shape * $pi);
s0 = abs(in);
s0 > 1 ? ( s0 = sliderpi*(s0 - 1) + 1 ) : s0 = slider1 * sin(s0*$pi)+s0;
s0 *= sign(in);
}
This way you could treat the positive values differently than the negative values, for example,

Code:
spl0 >= 0 ? spl0 = waveshape(spl0,slider1) : spl0 = waveshape(spl0,slider1*0.5);
This is what I thought you meant when you said asymmetric, now we no longer have rotational symmetry and it will produce a mixture of even and odd harmonics. Squishing the positive values a little more than the negative values is a characteristic of many analog gain stages btw, like tubes and JFETs. Depends on how they're biased, but that's a different topic. Anyways, that's what asymmetric usually refers to. If you incorporate asymmetry into your plugin you can call it that, or just refer to it as a console emulation if you don't.

It can help to see what the function does. In an empty channel put three effects - the first should create a sine wave, the second is this plugin, the third is a plugin like gfx_analyzer. Set the plugin to desired degree of clipping then start raising the level of the sine wave. As it begins to clip you will see new harmonics appear in gfx_analyzer. It is very cool. E.g. as is you see the plugin create odd harmonics... so if the sine wave is at 1 kHz you start to see 3 kHz, 5 kHz etc appear.

I like to put things in functions because it helps keep the code clean when it comes time to do something like oversampling. Strictly speaking you probably don't need oversampling since you aren't clipping super hard, but it's always something you can explore.

If you are interested in learning more about coding I recommend checking out Tale's plugin pack (I don't know if it's in ReaPack but you can download it by searching "tale jsfx"). I've found his code to general be pretty easy to understand and I've been inspired by it quite a bit. There are a lot of good plugin authors here, lots of resources, it's a great community.

The type of saturation you're using tends to be very clean, although it does need to be tweaked a little to handle large values gracefully. Another saturation function is tanh(), which is used because it naturally converges to +/- 1. Use Desmos to graph it out so you see what I mean. There has been a lot of discussion in the forum about how to use it, approximate it, optimize it, even anti-alias it without oversampling. Just searching for "tanh" in this sub forum, or using google "jsfx tanh" should land you a bunch of posts about it. On a few of them I have links to other posts, I was really into the topic for a while.

Sry for wall of text, hope some of this helped.
No problem. You have been most helpful

Im still at a point where Im wrapping my head around the first bit of code you provided. The abs makes all samples positive and then s0*sign(spl0) reverses the negative ones back to negative again?

s0 = abs(spl0);
spl0 = s0 * sign(spl0);

And this bit handles samples going over 1. I tried plotting sliderpi*(s0 - 1) + 1 ) in Desmos but just got a straight line at a low angle.
s0 > 1 ? ( s0 = sliderpi*(s0 - 1) + 1 ) : s0 = slider1 * sin(s0*$pi)+s0;

Best regards /Bo

Last edited by danerius; 07-07-2019 at 10:59 AM.
danerius is offline   Reply With Quote
Old 07-07-2019, 08:00 PM   #11
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 765
Default

Quote:
Im still at a point where Im wrapping my head around the first bit of code you provided. The abs makes all samples positive and then s0*sign(spl0) reverses the negative ones back to negative again?

s0 = abs(spl0);
spl0 = s0 * sign(spl0);
Yes!

If you rotate a function and it looks the same then it has rotational symmetry - it's an odd function, only produces odd harmonics, and you can treat the input the same as long as you apply the right sign at the end.

I guess I should probably apologize, I should be posting code that is more readable rather than optimized. Maybe this will make more sense - it is functionally equivalent:

Code:
sliderpi = (1 - slider1 * $pi);
s0 = spl0;

spl0 >= 1 ? ( s0 = sliderpi*(spl0 - 1) + 1 );
spl0 <= -1 ? ( s0 = sliderpi*(spl0 + 1) - 1 );

spl0 > -1 && s0 < 1 ? ( s0 = slider1 * sin(spl0*$pi)+spl0);
spl0 = s0;
So if our sample is between 1 and -1 then we do our normal function, the one you came up with. If it's greater than or equal to 1 we do a linear function, if it's less than or equal to -1 we do the other linear function.

Now as to why... alright, so bear with me, I don't know if this is going to make sense or not but I'm going to try and explain the other two functions.

When you look at the original function you can see that it is wavy as it progresses, right? Analog clipping doesn't look like that, in the analog realm our transfer functions almost always look more like tanh() or atan() (that's arctan in Desmos) or some variation of e^x - 1. So instead of the weird sounding waviness you could clamp your output to +/- 1 as Sai'ke suggested, but that would create a discontinuity at f(1) and f(-1). Instead of that abrupt shift from a curve to a flat line, I suggested a straight line leading away from the function at the same slope the function is at f(1) and f(-1). Linear segments don't add distortion, so this means that large input values would be handled gracefully. So, I took the derivative of the function at 1 and -1 and wrote a line function that extended that slope.

Hmmm, that was still a little mathy. Maybe I'll try again...
SaulT is offline   Reply With Quote
Old 07-08-2019, 12:59 AM   #12
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 765
Default

Alright, so maybe this is a little crazy, I don't even know, but I whipped together this little plugin to demonstrate what I'm talking about. When you load up the plugin you will see a slider - move it to the left to increasingly linearize the function, move it to the right to... de-linearize? As you play something through it, listen and watch the display. Green is incoming audio, red is processed. What you will notice is that you will only see red as long as the function is linear. The more green you see, the more the function is being saturated and the more noticeable the effect should be. Boost to exaggerate this effect. Linearize it to undo it, etc.

https://stash.reaper.fm/v/36680/sault-linear-tanh.txt

This is basically what I'm doing to your function - I'm linearizing part of it, literally turning part of the function into a line, both to keep it musically usable and to keep it from hard clipping. I tried to not optimize it too much, I hope the source code is useful.
SaulT is offline   Reply With Quote
Old 07-08-2019, 03:43 AM   #13
sai'ke
Human being with feelings
 
sai'ke's Avatar
 
Join Date: Aug 2009
Location: NL
Posts: 650
Default

What SaulT says is right of course. Discontinuities in waveshapers are typically evil and his way is a good way of ensuring continuity

Though, depending on the purpose of the waveshaper, hitting a wall at 1 might be what's desired.

One thing that can also be done is to make a smooth transition between two shapers using a smooth interpolation function.

Computationally cheap examples are:
Code:
xi = min(abs(x), 1);
x2 = xi*xi;
x3 = x2*xi;
interp = 3*x2-2*x3;
Or smoother yet:

Code:
xi = min(abs(x), 1);
x2 = xi*xi;
x3 = x2*xi;
x4 = x2*x2;
x5 = x3*x2;
interp = 6*x5-15*x4+10*x3;
Code:
clipper = sign(x);
out = b*sin($pi*x)+x;

result = out * (1-interp) + clipper * interp;
You can also shift these things around if you want the interpolation function to start later. They're relatively cheap ways of piecing things together

Edit: One thing to keep in mind with these is that the interpolation weight should be zero for any discontinuous parts of the shapers being interpolated between (which is the case for the sign shaper, since it has its discontinuity at zero).
__________________
[Tracker Plugin: Thread|Github|Reapack] | [Routing Plugin: Thread|Reapack] | [Filther: Thread|Github|Reapack] | [More JSFX: Thread|Reapack]

Last edited by sai'ke; 07-08-2019 at 04:05 AM.
sai'ke is offline   Reply With Quote
Old 07-08-2019, 12:28 PM   #14
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by SaulT View Post
Alright, so maybe this is a little crazy, I don't even know, but I whipped together this little plugin to demonstrate what I'm talking about. When you load up the plugin you will see a slider - move it to the left to increasingly linearize the function, move it to the right to... de-linearize? As you play something through it, listen and watch the display. Green is incoming audio, red is processed. What you will notice is that you will only see red as long as the function is linear. The more green you see, the more the function is being saturated and the more noticeable the effect should be. Boost to exaggerate this effect. Linearize it to undo it, etc.

https://stash.reaper.fm/v/36680/sault-linear-tanh.txt

This is basically what I'm doing to your function - I'm linearizing part of it, literally turning part of the function into a line, both to keep it musically usable and to keep it from hard clipping. I tried to not optimize it too much, I hope the source code is useful.
Duude.... You just whipped together a plugin...?

Thanks for simplifying the code. Ive tried the plugin on a couple of different things. Mostly with sine waves and checking a spectrum analyzer. I understand what youre saying now and I can very much see the nonlinearities. And the anti-aliasing. And the above 1 peculiarties.

Im definitely gonna try some other formulas with this. Is there a JS with a simple anti-aliasing filter I can look at?

One other thing Ive been looking at and youve touched on is treating positive and negative values differently. But for the purpose of making a mono-to-stereoizer. So essentially splitting the mono signal in +/- and processing them differently in the left channel. Then doing the exakt opposite of that for the right channel.

Thanks + best regards /Bo
danerius is offline   Reply With Quote
Old 07-08-2019, 12:48 PM   #15
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by sai'ke View Post
What SaulT says is right of course. Discontinuities in waveshapers are typically evil and his way is a good way of ensuring continuity

Though, depending on the purpose of the waveshaper, hitting a wall at 1 might be what's desired.

One thing that can also be done is to make a smooth transition between two shapers using a smooth interpolation function.

Computationally cheap examples are:
Code:
xi = min(abs(x), 1);
x2 = xi*xi;
x3 = x2*xi;
interp = 3*x2-2*x3;
Or smoother yet:

Code:
xi = min(abs(x), 1);
x2 = xi*xi;
x3 = x2*xi;
x4 = x2*x2;
x5 = x3*x2;
interp = 6*x5-15*x4+10*x3;
Code:
clipper = sign(x);
out = b*sin($pi*x)+x;

result = out * (1-interp) + clipper * interp;
You can also shift these things around if you want the interpolation function to start later. They're relatively cheap ways of piecing things together

Edit: One thing to keep in mind with these is that the interpolation weight should be zero for any discontinuous parts of the shapers being interpolated between (which is the case for the sign shaper, since it has its discontinuity at zero).
Hi and thanks This is a bit beyond me. Im gonna need to try and code this slowly.

Best regards /Bo
danerius is offline   Reply With Quote
Old 07-08-2019, 04:28 PM   #16
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 765
Default

Quote:
Im definitely gonna try some other formulas with this. Is there a JS with a simple anti-aliasing filter I can look at?
So there are a few ways to anti-alias, one is doing what I showed you above, partially linearizing a waveshaper. There are a few different techniques for anti-aliasing. One of those is oversampling (quite possibly one of my favorite topics ever), and another is using continuous time convolution, which I saw sai'ke talk about sometime recently. I am a newcomer to that, I'm all about the oversampling using a FIR (aka Nyquist, L-th band, or quarterband/halfband depending on the specific type).

The super quick explanation to oversampling is that more distortion creates more harmonics. Too many harmonics and they "fold over" the Nyquist limit and this is what causes aliasing. Higher samplerates mean more frequency space for those harmonics before they fold over. So, oversampling temporarily raises the samplerate while you're doing your nonlinear stuff so you can filter out the unwanted stuff when you return to your normal samplerate. It can mean double, quadruple, or even more CPU load, but it is a valid technique as long as your methods are good and your functions aren't too CPU-heavy.

If you want to go that direction we can, I don't want to overwhelm you when you're just getting the hang of things. If you look up Tale's plugin pack you can look at his oversampling code and that might help, I have plugins where I've used a similar methodology. The continuous time convolution stuff is super cool, but it's most effective when the two methods are used together.

Anyways, here's the link to Tale's pack, you can look at the source code. I've basically taken his approach and run with it... I actually included oversampling in the plugin I just wrote, the transient x lowpass plug, so that should be more than enough to go by, both a simple example and a "real world" example.

https://www.taletn.com/reaper/mono_synth/#os

https://stash.reaper.fm/v/36681/translow.txt (still in beta)

Quote:
One other thing Ive been looking at and youve touched on is treating positive and negative values differently. But for the purpose of making a mono-to-stereoizer. So essentially splitting the mono signal in +/- and processing them differently in the left channel. Then doing the exakt opposite of that for the right channel.
I don't think waveshapers alone are going to make very potent stereoizers because they don't really change anything about the underlying sound, they just have different harmonics added. Someone posted a technique recently that I was looking at that seemed really interesting, it was mono-compatible even... might have been sai'ke, I'd have to look to figure it out. Something about opposing allpass filters or some such. That's the direction I'd look if you're thinking about creating fake stereo.
SaulT is offline   Reply With Quote
Old 07-09-2019, 01:00 PM   #17
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by sai'ke View Post
What SaulT says is right of course. Discontinuities in waveshapers are typically evil and his way is a good way of ensuring continuity

Though, depending on the purpose of the waveshaper, hitting a wall at 1 might be what's desired.

One thing that can also be done is to make a smooth transition between two shapers using a smooth interpolation function.

Computationally cheap examples are:
Code:
xi = min(abs(x), 1);
x2 = xi*xi;
x3 = x2*xi;
interp = 3*x2-2*x3;
Or smoother yet:

Code:
xi = min(abs(x), 1);
x2 = xi*xi;
x3 = x2*xi;
x4 = x2*x2;
x5 = x3*x2;
interp = 6*x5-15*x4+10*x3;
Code:
clipper = sign(x);
out = b*sin($pi*x)+x;

result = out * (1-interp) + clipper * interp;
You can also shift these things around if you want the interpolation function to start later. They're relatively cheap ways of piecing things together

Edit: One thing to keep in mind with these is that the interpolation weight should be zero for any discontinuous parts of the shapers being interpolated between (which is the case for the sign shaper, since it has its discontinuity at zero).
Hi. Since Im a visual thinker I did a Desmos version of the formulas. Is this correct?

https://www.desmos.com/calculator/epdhbubkph

/Thanks + best regards
danerius is offline   Reply With Quote
Old 07-09-2019, 02:32 PM   #18
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by SaulT View Post
I actually included oversampling in the plugin I just wrote, the transient x lowpass plug, so that should be more than enough to go by, both a simple example and a "real world" example.

https://stash.reaper.fm/v/36681/translow.txt (still in beta)
Hi. Youre being more that generous Wich bit of the code is the oversampling?

Best regards /Bo
danerius is offline   Reply With Quote
Old 07-10-2019, 01:44 AM   #19
sai'ke
Human being with feelings
 
sai'ke's Avatar
 
Join Date: Aug 2009
Location: NL
Posts: 650
Default

Quote:
Originally Posted by danerius View Post
Hi. Since Im a visual thinker I did a Desmos version of the formulas. Is this correct?

https://www.desmos.com/calculator/epdhbubkph

/Thanks + best regards
Yeah, you plotted the interpolation function; but forgot the abs part and the min part. The abs part symmetrizes it, and the min part makes sure it doesn't go beyond one.

https://www.desmos.com/calculator/mjg9dxfebu

As you can see, it's a smooth transition between 0 and 1 now.

It can be used to smoothly interpolate between two shapers by multiplying one with this function, and the other with 1 - this function. Something like this:

https://www.desmos.com/calculator/mar6owuct1

Here I interpolated between your original shaper and sign(x) (which is non-continuous). As you can see though, it can still overshoot, since your original shaper could. It will however be smooth as long as the non-zero parts of both shapers are smooth.

As for the "continuous interpolation method" to avoid aliasing SaulT mentioned, I would argue that it performs quite a bit better at lower CPU cost than oversampling in most cases. At least in all the tests I've run. It has plenty of downsides too though. It requires a different set of functions for every waveshaper, which is annoying. But more tricky is that it is a pain to set up once there is any sort of feedback involved. Waveshapers in feedback paths require careful consideration. In addition, very sudden extreme gain changes can cause issues (although, in practice, I've never really had an issue with this in any project, just in test benches). Anti-aliasing by oversampling is much easier to understand and is easier to generalize (write once, use everywhere).

As for widening, yeah, I did write a pseudo stereoizer at some point. It's here: https://raw.githubusercontent.com/Jo...0Bub%20II.jsfx

It is actually surprisingly effective for how simple it is. It adds a delayed copy of the center signal to the left and subtracts a delayed copy of the signal to the right channel. This means it's always 100% mono compatible (since -1 + 1 = 0) while still widening everything because of a perceived difference in left and right. I think it works pretty well when used in subtle ways.

If you analyze this setup, what you will see is that this basically acts as a comb filter. Where the left channel will have its notches in exactly the peaks of the right channel. You can very clearly see this on the spectrum (as long as you look at a separate right and left spectrum) if you dial up the strength.

The rest of the plugin code is just some stuff that allows you to select which frequency region to apply this to and process the original side signal a bit as well as GUI stuff.
__________________
[Tracker Plugin: Thread|Github|Reapack] | [Routing Plugin: Thread|Reapack] | [Filther: Thread|Github|Reapack] | [More JSFX: Thread|Reapack]

Last edited by sai'ke; 07-10-2019 at 02:12 AM. Reason: added some blahblah about widening
sai'ke is offline   Reply With Quote
Old 07-10-2019, 01:13 PM   #20
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by sai'ke View Post
Yeah, you plotted the interpolation function; but forgot the abs part and the min part. The abs part symmetrizes it, and the min part makes sure it doesn't go beyond one.

https://www.desmos.com/calculator/mjg9dxfebu

As you can see, it's a smooth transition between 0 and 1 now.

It can be used to smoothly interpolate between two shapers by multiplying one with this function, and the other with 1 - this function. Something like this:

https://www.desmos.com/calculator/mar6owuct1

Here I interpolated between your original shaper and sign(x) (which is non-continuous). As you can see though, it can still overshoot, since your original shaper could. It will however be smooth as long as the non-zero parts of both shapers are smooth.
Hi. Im moved by all the help Im getting on this forum. Im not understanding all of it. One day I probably will. Big thank you

Its gonna take me awhile to get that code in place. Meanwhile theres a couple of thing I dont get with DSP?

- Zero dB is basically either +1 or -1 in sample value? Correct?
- Lets assume a digital mixer can handle +6 dB? How much is that in sample value?

Thanks + best regards /Bo
danerius is offline   Reply With Quote
Old 07-10-2019, 02:28 PM   #21
sai'ke
Human being with feelings
 
sai'ke's Avatar
 
Join Date: Aug 2009
Location: NL
Posts: 650
Default

Decibels are specified with respect to a reference I0.

dB = 20 log10(I/I0)

If your peak level reference is 1 (which is usually the case for audio stuff), then yes, 1 is at 0 dB.

For question two, we can just invert the formula.

dB = 20 log10(I/I0)

(dB/20) = log10(I/I0)

10^(dB/20) = I/I0

I0*10^(dB/20) = I

With I0 being 1, this is 10^(6/20) = 1.9953.

Also, just as a sidenote, the every doubling equates to 6 dB thing is a rule of thumb. It's not exactly 6 dB as you can see now
__________________
[Tracker Plugin: Thread|Github|Reapack] | [Routing Plugin: Thread|Reapack] | [Filther: Thread|Github|Reapack] | [More JSFX: Thread|Reapack]
sai'ke is offline   Reply With Quote
Old 07-11-2019, 11:43 AM   #22
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by sai'ke View Post

With I0 being 1, this is 10^(6/20) = 1.9953.

Also, just as a sidenote, the every doubling equates to 6 dB thing is a rule of thumb. It's not exactly 6 dB as you can see now
Aaahhh... cool. Close to what I was guessing

Now Im gonna google what levels over 0 dB Reaper is able to handle. And maybe test it myself

/Big big thanks
danerius is offline   Reply With Quote
Old 07-11-2019, 10:58 PM   #23
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 765
Default

Sample values of 1 or -1 correlate to an absolute amplitude of 1, which is 0 dBfs (decibel full scale).

It's probably worth mentioning that internally Reaper is set to automute at +20 dB but once it's rendered then anything that went over 0 dB is clipped. That's an icky sound, so that's why we spend a lot of time figuring out how to limit the sound to 0 dB and not have it sound like poo (technical term).

dB = decibel = a ratio. It's important to know that decibels can be found in all sorts of contexts - voltage, pressure, loudness, signal, etc... so dBfs, dBu, dBmV, they're all different. A mixer that says it can do +6 dB... dB what? That number and the numbers you see in your DAW are probably different and mean different things.

http://daniellesden.com/blog/all/the...els-explained/
SaulT is offline   Reply With Quote
Old 07-12-2019, 04:54 AM   #24
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 43
Default

Quote:
Originally Posted by SaulT View Post
Sample values of 1 or -1 correlate to an absolute amplitude of 1, which is 0 dBfs (decibel full scale).

It's probably worth mentioning that internally Reaper is set to automute at +20 dB but once it's rendered then anything that went over 0 dB is clipped. That's an icky sound, so that's why we spend a lot of time figuring out how to limit the sound to 0 dB and not have it sound like poo (technical term).

dB = decibel = a ratio. It's important to know that decibels can be found in all sorts of contexts - voltage, pressure, loudness, signal, etc... so dBfs, dBu, dBmV, they're all different. A mixer that says it can do +6 dB... dB what? That number and the numbers you see in your DAW are probably different and mean different things.

http://daniellesden.com/blog/all/the...els-explained/
I was literally googling this subject yesterday...

As a bit of a backdrop. I used to work fulltime as an audio engineer way, way back in the analog days. Some of the things I learned back then works and some doesnt work at all. Theres a fair bit of relearning.

Writing code is still weird for me. I need way more practice to get even remotely fluent at it

Thanks for your help /Bo

Last edited by danerius; 07-12-2019 at 06:42 AM.
danerius is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 03:07 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2019, vBulletin Solutions Inc.