grame-cncm / faustlibraries

The Faust libraries
https://faustlibraries.grame.fr
183 stars 59 forks source link

add ahdsrfqqq, adsrfqqq, asrfqqq, and derivatives #159

Closed marchingband closed 8 months ago

josmithiii commented 9 months ago

Very nice! Thanks for the contribution

DBraun commented 9 months ago

This is great! I've messing around in the IDE now.

  1. It seems ok to do ratio = pow(k, 5) / 100000 : max(ma.EPSILON); instead of ratio = pow(k, 5) / 100000 + 0.001; but what is the justification for 5 and 100000?
  2. Should there be another version that has three k, one for each of the attack, decay, and release curves?
  3. Can you make another version that has a "hold" section too in between attack and decay? 😄
marchingband commented 9 months ago

max(ma.EPSILON)

Ok I will use that thank you!

what is the justification for 5 and 100000

I don't have a math background, so I used a graphing website to fiddle around to find a good algorithm that produced values I liked when using the whole numbers for q. Open to a better formula here for sure. Another thing that works is ratio = pow(k, 4) * 5 + 0.001; for q=[0..1] which gives useful results for the 0.1 incriments of q.

Should there be another version that has three k, one for each of the attack, decay, and release curves?

sure, I'll work on that

Can you make another version that has a "hold" section too in between attack and decay? 😄

yes I will work on that too.

DBraun commented 9 months ago

I think pow(k, 5) / 100000 for k [0,20] is effectively the same as pow(q,5)*32 for q [0,1]. It's just a substitution of variables. The second one is probably more interpretable, but the new constants are 5 and 32.

marchingband commented 9 months ago

It seems ok to do ratio = pow(k, 5) / 100000 : max(ma.EPSILON);

The issue comes up when q==0, the envelope never reaches 1.0, I do not have a precise understanding of why, I assume it is some floating point hijinx. using ma.EPSILON does not work :( 0.001 does however, so I will use pow(q,5)*32 : max(0.001) unless you have another idea.

I also feel like it may not be idiomatic in faust to add this computation at all, where it is just to improve DX. Perhaps I should just put a note in the docs on how to pick a good number, and leave it up to users? What do you think?

marchingband commented 9 months ago

OK I have added hold, added optional independent q's for each segment, and changed the q input parameter to [0..1]. I decided that since it is in the "UI thread" it's ok to have these pow(q,5)*32 : max(0.001) computations in the code. Let me know if I should change that!

Thanks for the feedback :)

DBraun commented 9 months ago

I'm messing around with this code:

k_power = hslider("k_power", 8., 1., 16., .001);
ratio_max = hslider("ratio_max", 15., 8., 32., .001);
to_ratio(k) = pow(k, k_power) * ratio_max : max(0.001);

Then in these plots pow in the legend is k_power and max is ratio_max. The other pars: attack: 1.0 sec decay: 0.5 sec sustain: 0 release: 0.2 sec

image

This looks good. However, if I change the sustain level to 0.5, the linear mode (k close to 1.0) doesn't work well. The line appears to be on the same trajectory. You can rapidly switch between the two images and see the similarity.

image

So I think it would be better if the linear lines (high k values) didn't reach the sustain value so much sooner than other settings. Do you think there's a fix for this? Here's the notebook https://colab.research.google.com/drive/1fnbSr36BTNtzWkPcdCoBiVppGQLHxVLA?usp=sharing

DBraun commented 9 months ago

Here's the graph with the 5 and 32 numbers from earlier. image

marchingband commented 9 months ago

@DBraun thanks for those awesome graphs! I noticed this problem as well.
The formula for the coefficient from the blog posts is calc_coef(rate, target) = exp((-1 * log((1.0 + target) / target)) / rate);
I imagine that there must be an implicit 1 in this formula, where it always assumes the distance in the y axis to be +/-1. I tried to figure out how to adapt the formula to work for different distances in the y axis (and I am noticing this variable magnitude found its way into my PR here), but I was not able to ... again, no math background :(
Is there any chance you could help me here?

DBraun commented 9 months ago

I did some guessing and changed the middle portion to this

         calc_coef(d, rate, target) = exp((-1 * log((d + target) / target)) / rate);

         // calculate our "move" distances
         d1 = 1.-fin;
         d2 = 1.-sus;
         d3 = sus-fin;

         att_ratio = to_ratio(k_att);
         att_rate = ma.SR * att;
         att_coef = calc_coef(d1, att_rate, att_ratio);
         att_base = (1.0 + att_ratio) * (1.0 - att_coef);

         dec_ratio = to_ratio(k_dec);
         dec_rate = ma.SR * dec;
         dec_coef = calc_coef(d2, dec_rate, dec_ratio);
         dec_base = (sus - dec_ratio) * (1.0 - dec_coef);

         rel_ratio = to_ratio(k_rel);
         rel_rate = ma.SR * rel;
         rel_coef = calc_coef(d3, rel_rate, rel_ratio);
         rel_base = (fin - rel_ratio) * (1.0 - rel_coef);

Then the graph for attack=1., decay=0.5, sustain=0.5, release=0.5 and a note held for 1.5 sec looks like this image

For sustain =0.4 it looks like this image

I wasn't able to get it working for fin values other than zero, and I would be fine if it were dropped as a parameter and hard-coded to zero.

marchingband commented 9 months ago

eek that's embarrassing, I feel like I must have guessed that as well? Thank you! I am using your formula with a variety of final values and it appears to be working ... what results are you getting that seem wrong? I am happy to remove final if it is not working and not needed.

DBraun commented 9 months ago

Yes you hinted at it, thanks.

Here's a graph with final set to -1. attack=1., decay=0.5, sustain=0.5, release=0.5 and a note held for 1.5 sec looks like this

image

It's wrong because it initializes at 0 somehow, and also all the curves seem to reach their peak too soon.

marchingband commented 9 months ago

I see now thanks. I wasn't able to find much info on this final parameter online so I assumed it was meant to be a positive integer. So the envelope never gets to 0 but rather drones quietly all the time instead, which does seem useful. Using a positive integer, adsrfq does SEEM to work correctly for me.

DBraun commented 9 months ago

Ah yes final>=0 seems ok.

Here I set final to 0.2 and sustain to 0.6==(1.+0.2)/2 so that the second portion is a straight line.

image

DBraun commented 9 months ago

We also must enforce that final <= sustain or else you get stuff like this (final: 0.2, sustain: 0.1) image

marchingband commented 9 months ago

Ok I have added the distance parameter, and added the checks. It seems pretty good now.

DBraun commented 9 months ago

One more thing: Can we refactor this:

calc_coef(rate, target, magnitude) = exp((-1 * log((magnitude + target) / target)) / rate);

into

calc_coef(rate, target, magnitude) = (target/(magnitude + target))^(1/rate); 

The first one's c++ is

output0[i0] = FAUSTFLOAT(std::exp((0.0f - std::log((fTemp0 + float(input2[i0])) / fTemp0)) / float(input0[i0])));

but the second's is

output0[i0] = FAUSTFLOAT(std::pow(fTemp0 / (fTemp0 + float(input2[i0])), 1.0f / float(input0[i0])));
marchingband commented 9 months ago

That's awesome. I've pushed this change. Thanks!

DBraun commented 9 months ago

I haven't benchmarked this, but I think by performing the select operations earlier we can avoid more duplicate operations.

IDE demo.

marchingband commented 9 months ago

ahhh ok fantastic, that makes sense, should I make this change?

DBraun commented 9 months ago

Actually the attack slider isn't working in that example. I also tried optimizing the ba.selector into select3 primitives which turn out to be faster in C++. Here's that example but unfortunately the attack slider is not working in the same way as before.

DBraun commented 9 months ago

I fixed it. The coef_pars were ordered incorrectly. Here's the update. I think you should push it with any other edits you'd like to make.

DBraun commented 8 months ago

I think I don't like the output in an edge case. Try these settings for adsrfqqq

When you release the gate at a high value, the actual time to release is much more than 0.1 sec. This is why I ended up keeping tracking of the y value at the moment of release (y_at_release) in the adsr_bias functions.

Last, there are more places where you could refactor the code so that you re-use functions, like what you did for arqq(att, rel, k_att, k_rel, gate) = asrfqq(att, 1.0, rel, 0.0, k_att, k_rel, gate);

It would be good to use the select3 as late as possible to get faster C++ code (lower priority for now..).

sletz commented 8 months ago

I was thinking the @DBraun bias versions (juste merged...) were more general? So is there any need to continue working here ?

marchingband commented 8 months ago

I am pretty sure the bias functions completely replace all these so we can close it.

sletz commented 8 months ago

Replaced by more general xx_biasversions.