-
Notifications
You must be signed in to change notification settings - Fork 757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EnvGen gate < -1 now respects curve #4793
Conversation
My one comment for now is that we need to be clear what EnvGen forced-release really means. Today, I found a commit from 6-7 years ago that fixed a bug, but also accidentally changed the meaning of forced-release. Let's be careful that we are not about to change its meaning again. I'm sorry to be persnickety about this, but we've gone in a matter of hours from "oh, I didn't expect that behavior" to a proposal to change the behavior, with almost no discussion about what the behavior should be. Slow down. Not so fast. Let's figure out what it should do before casually dropping something into the product that we might regret later. |
All right, thanks for your meaningful input, I'm sorry to have rushed. Of course I'm open for discussion, which I welcome, and never expected my code to be integrated right away. Still figuring out how the contribution process works, I thought the discussion could have been easier with a concrete code proposal. So, shall we continue discussing it on the issue, or shall I open a RFC? I initially thought an RFC would have been too much for this: I admit I thought this was a bug or a feature that was never fully developed, instead of understanding its intended use as an emergency cut for envs with longer release phases. I also admit that I have been using |
I think it would be good to understand the use cases before making further changes. It's slippery: just this morning I found a bugfix that accidentally changed the behavior, non-trivially, outside the scope of the reported bug. This kind of thing happens when the feature is not well-understood (a Rorschach feature, if you will -- everybody looks at it and sees what they want to see in it). I understand the wish to override release duration, perhaps without overriding other release properties. I have a feeling that passing a single float to the server isn't an articulate enough expression for this. If you want to override some properties but not others, I tend to think this should be built into the SynthDef, so that you are in control of the override, rather than relying on magic behavior of special float values. Overloading that one float to do perhaps too much is potentially confusing. For me, early-release should fade out the envelope immediately, no matter what phase the envelope is in (this latter point was broken in the commit I mentioned) -- for me, non-negotiable. The curve, I'm not really concerned about, as long as it's clear and justified. Final segment curve is probably the least objectionable option, though I'd like to think a little more about cases where that might be wrong. |
After I understood this use case (to immediately cut an envelope even if it has a complex release phase), I changed the code to jump to the last node instead of to release node. So, that functionality is preserved, the change is that shape is read from the env's last node, instead of forcing a linear ramp.
Envelopes that end curved will still end curved, while linear ending envelopes will still end linearly. While preserving the 'old' use-case, this would improve it for others where a curved envelope is more appropriate. That said, I'm open to wait for use cases of a forced linear ramp to show up.
My input to this important question is that for now there is a source of confusion in having forced and normal release packed into the same method. These two functions have in fact different meanings:
So, maybe I see the point now. Making it work for basic envelopes will blur its meaning as an emergency measure even more, shifting it towards a stretched release. I can't help thinking that this is much more what .release(releaseTime) suggests, than a shortcut release. Again, I'm open to wait for use cases of a forced linear ramp to show up.
Would you mind posting some links, I think it would be relevant to the current discussion to understand how the meaning of release vs forced release is shifting :) |
--> #753 If I'm reading it right, the bug was: if an EnvGen was oscillating between releasing and non-releasing states in some specific way, it would infloop. Tim fixed it by preventing a forced-release during any release, but he missed the fact that a forced-release is legal (and desirable) during a normal release. That is IMO not shifting semantics, but a mistaken understanding of the feature's purpose, which introduced the bug that you now can't interrupt a normal release.
In hindsight, the bugfix should have had two flags:
And a bit below would check (I think... I haven't tried it. Or maybe there's another, better way to implement it.)
That's a fair point. It's a long standing problem in SC that it isn't clear what is a convenience method and what is a robust "the right way" solution. I won't object further to the change here, but I still think, if you want something specific to happen when overriding release properties, the best "can't-fail" way is to write exactly what you want into the SynthDef. |
Thanks, very interesting. Out of curiosity I tried the following condition for forced release:
and I still can't reproduce the crashes from #753. This looks nice to me because it would allow a forced release of a normal release, and even of a previous forced release. I'll definitely study the case more. The check I removed has nothing to do with this, it was still a leftover from a previous commit of mine |
@elgiano thank you for taking it on and looking at it carefully, very welcome indeed. |
I dug into it. It looks like the bug was simpler (but hideous) and didn't require a check for m_released. Tim's fix was this: It turns out the bug was not fixed by I don't know how acceptable these empiric proofs are, but I checked the following:
This made me think Now, moving on: Let's say someone calls Would it be best to keep |
@elgiano I'm convinced by your analysis, that's great sleuthing!
My gut feeling is not to do it that way. A gate opening or closing is analogous to a trigger (note that a gate opening isn't "not equal to previous," but rather "previous was <= 0, current is > 0"). The analogous trigger-like expression for below -1 would be exactly "previous >= -1, current < -1." It definitely doesn't make sense to reset a forced release on every sample.
It could, if the gate signal dipped below -1, then rose above -1, and fell below it again. That strikes me as sensible behavior. So I'd go with |
Thanks @jamshark70, I agree. |
2ab0e49
to
936396d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good overall! i don't think you need to add more tests. just a few code comments for what you've added
server/plugins/LFUGens.cpp
Outdated
dur = overrideDur; | ||
} else { | ||
dur = *envPtr[1] * ZIN0(kEnvGen_timeScale); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you don't need dur
; you can just name your function parameter to dur
and set it here if it's nonnegative
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! Thanks
server/plugins/LFUGens.cpp
Outdated
@@ -2379,33 +2379,126 @@ void EnvGen_Ctor(EnvGen* unit) { | |||
EnvGen_next_k(unit, 1); | |||
} | |||
|
|||
static bool EnvGen_initSegment(EnvGen* unit, int& counter, double level, float overrideDur = -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document the meaning of these parameters please, also change float overrideDur
to double dur
as in the comment below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function was a goto in nextSegment. I changed it to a function to be able to call it also in check_gate and use a supplied dur instead of the one defined by the segment. What do you think? Shall I add a comment explaining that unit, counter and level are from nextSegment and check_gate, while dur allows dur overriding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mainly, i would like to see the behavior of dur
documented since that behavior, while expedient, is not immediately obvious. explaining the purpose of the other parameters would be helpful too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put a comment there, please see if it's ok
server/plugins/LFUGens.cpp
Outdated
// first ZIN0 gets the last envelope node's level, then apply levelScale and levelBias | ||
unit->m_endLevel = ZIN0(unit->mNumInputs - 4) * ZIN0(kEnvGen_levelScale) + ZIN0(kEnvGen_levelBias); | ||
unit->m_grow = (unit->m_endLevel - level) / counter; | ||
unit->m_stage = (int)ZIN0(kEnvGen_numStages) - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in general prefer the most restrictive cast -- that would be static_cast
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, maybe you saw that, but I just copied the previous implementation, assigning directly to m_stage instead of a new variable. Anyway, I can change that cast! How about the one right above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh i see! i was trying to be careful to style-review only code that you wrote, because you shouldn't be responsible for the code you just move around. i missed that you had copied the expression from a few lines above.
if you have the time and interest, please fix this one, but i'd prefer if you didn't do the one above, simply because that line wasn't modified in this diff and so modifying it now creates extra clutter. my practice is: if a line is modified in a commit, freely update its syntax/formatting for unambiguously better idioms, otherwise save those kinds of changes for separate commits, preferably making several identical changes in many places at once. in my experience, that makes it a lot easier to resolve merge conflicts and to browse git history.
along similar lines, ideally this PR should consist of a single squashed/amended commit, i.e. no extra commits addressing review comments. we don't go to the trouble of enforcing this or recommending it zealously, but that's my thoughts since we're on the topic. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I thought so too, just wanted to make sure :) Thanks!
About squashing: shall I squash also the tests so that we end up with only one commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no problem. i personally like to see code change + corresponding test change in a single commit, yes :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
1148b99
to
a8fc81f
Compare
server/plugins/LFUGens.cpp
Outdated
// first ZIN0 gets the last envelope node's level, then apply levelScale and levelBias | ||
unit->m_endLevel = ZIN0(unit->mNumInputs - 4) * ZIN0(kEnvGen_levelScale) + ZIN0(kEnvGen_levelBias); | ||
unit->m_grow = (unit->m_endLevel - level) / counter; | ||
unit->m_stage = static_cast<int> ZIN0(kEnvGen_numStages) - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static_cast is written as static_cast<int>(value)
. i think this only works here because ZIN0 expands to something surrounded by parens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ops!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed, thanks and sorry for that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks!
Purpose and Motivation
Fixes #3782
Types of changes
To-do list