Closed marchingband closed 8 months ago
This is great! I've messing around in the IDE now.
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?k
, one for each of the attack, decay, and release curves?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.
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.
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?
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 :)
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
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.
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
Here's the graph with the 5 and 32 numbers from earlier.
@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?
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
For sustain =0.4 it looks like this
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.
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.
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
It's wrong because it initializes at 0 somehow, and also all the curves seem to reach their peak too soon.
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.
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.
We also must enforce that final <= sustain or else you get stuff like this (final: 0.2, sustain: 0.1)
Ok I have added the distance parameter, and added the checks. It seems pretty good now.
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])));
That's awesome. I've pushed this change. Thanks!
I haven't benchmarked this, but I think by performing the select operations earlier we can avoid more duplicate operations.
ahhh ok fantastic, that makes sense, should I make this change?
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.
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.
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..).
I was thinking the @DBraun bias versions (juste merged...) were more general? So is there any need to continue working here ?
I am pretty sure the bias functions completely replace all these so we can close it.
Replaced by more general xx_bias
versions.
Very nice! Thanks for the contribution