-
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
Topic/impulse rewrite #4150
Topic/impulse rewrite #4150
Conversation
36cadad
to
c76a2ea
Compare
Update: this PR has been updated with clang-format. |
c76a2ea
to
7ea859b
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.
The logic is a massive improvement. Let's please get this into 3.11. There are concrete bugs with this UGen that are fixed here.
I'm going to say "comment" for now rather than "approve" because my comments are questions, not "things to change":
- How many calculation functions do we need?
- What should be the behavior of a 0-frequency Impulse with a moving phase offset?
unit->mPhase = initPhase; | ||
|
||
UnitCalcFunc func; | ||
switch (INRATE(0)) { |
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 don't know how to address it, but when this reaches the stage of c++ style review, I expect there to be a comment about the number of calculation functions. There have been some discussions lately about the topic of handling the permutations.
If you remove all the i
functions, then we go from 9 down to 4 -- reducing the maintenance load by a factor of 2.22, while the performance gain of the i
functions would be minuscule. So I'd start there.
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.
Yes, 9 is overkill. I've trimmed it to 7 (dropping a-rate phase offset for k- and i-rate frequencies). If needed we could also drop a
-rate phase offset altogether. I kept it because I recall a user on the scsynth forums being surprised it wasn't supported.
I agree we could drop i
-rate altogether, falling back on a k
-rate minimum for both args. Although one of the goals of this PR was support more rates. If maintenance is the issue though, i-rate is the easiest to introspect.
server/plugins/LFUGens.cpp
Outdated
} | ||
|
||
// Note: In calc functions, |
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 logic is sound, but I admit, I really had to scratch my head to understand it. Could this comment perhaps be expanded?
A particular sticking point is that it isn't immediately obvious why both phase and phaseOffset are initialized to the phase input. I get it now -- the phase adjustment is based on input phase - previous phase (so, it's 0 if the phase input is constant).
Users may expect to get triggers from Impulse.ar(0, Phasor.ar(...))
(in the same way that we expect a sine wave from SinOsc.ar(0, Phasor.ar(...))
) but in my testing, that doesn't happen. The workaround of course would be HPZ1.ar(Phasor.ar(...)) < 0
instead of Impulse but I guess some users may be surprised by this.
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 logic is sound, but I admit, I really had to scratch my head to understand it. Could this comment perhaps be expanded?
Agreed. I've updated the comment to read more clearly.
A particular sticking point is that it isn't immediately obvious why both phase and
phaseOffset are initialized to the phase input.
Part of the confusion may come from the fact that the UGen argument for
phase offset on the sclang side is just called phase
. Whereas internally, phase
is
the actual phase state value. Throughout the UGen I've renamed local vars
phaseInc
and phaseOff
to simply inc
and off
, hopefully disambiguating
it from phase
.
Users may expect to get triggers from Impulse.ar(0, Phasor.ar(...)) ...
Great point, I was also surprised the Impulse doesn't support driving by the
phase
(offset). Based on your comment I've added this to the Discussion in
the Help file, as well as suggesting the HPZ
solution for generating an
impulse train with a Phasor
.
Perhaps related, the pattern of Impulse.ar(0) producing a single impulse (e.g. to excite a resonator) is fairly well established IME. Don't know if this changes that, but if so it could break code. But maybe that's not an issue here? |
I didn't test it (I should -- this is a very important use case!) but I believe it will be fine, based on lines 1023-1025 in the revision:
EDIT: though I believe |
Cool. Sorry if I'm adding noise, just thought it good to check! |
Thanks @jamshark70 and @muellmusik for having a look at this. Briefly,
I do remember testing this, and based on a quick glance at the additions to the help docs, this is a supported case. But, I'd have to rebuild this and test with the case that @jamshark70 brought up:
I'll try to get back into this headspace and test soon. And yes, c++ style review is welcomed! This was a long time ago, before the style guidelines were set, and some of the style here was based on the previous |
3.11 beta should be released on Tuesday; this will probably go into 3.12+. |
I just (accidentally) discovered some fallout from this:
These changes to Impulse mean that the Ctor initializes the "pre-sample" to 1 when the Impulse is starting at phase 0. I've argued elsewhere that this is correct -- e.g. It also makes sense that inputs expecting a trigger should initialize their But many trigger inputs, e.g. So we have two choices:
(When I raised this initialization issue before, the decision at that time was to avoid breaking existing code. But that has a maintenance cost too, which is now coming due -- if we had fixed it properly back when I brought it up, then this PR would be straightforward.) |
Thanks @jamshark70 for keeping an eye on this.
I strongly agree!
I'm strongly against this! I'll get blue in the face about it too ;)
This is the right idea. I've documented the initialization problem and explained in detail how I think this kind of thing should be resolved: |
I guess this is related to #4976 |
FWIW: I just grepped 'plugins/' for the string TRand_Ctor ... these 4 include lines like Also checking for |
151c015
to
14d5d9b
Compare
Back at it again, and hopefully this time for the win :) I've reviewed all comments and updated the OP with clarifications and updates. ^^^ Inline reviews are addressed above ^^^ Some other notable updates:
No problem, this works. :)
This is actually a problem with
The first 2 polls happen on the initialization sample (which it ultimately shouldn't), and the second is the first sample. In any case I'm confident
Great, this is a useful approach in addressing the init sample issue. I'll reference this in that thread. |
Impulse: remove calc functions for 2 input rate combinations
Impulse_next_ak: consolidate prev_phaseInc and phaseInc vars Throughout: phaseInc -> inc, phaseOff -> off
Update TestFilterUGens:test_time_invariance to accommodate initialization sample bugs that pervade FilterUGens
docs: Impulse: Describe phase offset behavior and supported input rates.
14d5d9b
to
fcd97da
Compare
Just a ping on this that this PR is ready for final review ☝️ |
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 looks good to me! Can you please add documentation for our change log to pull in that describes any breakage from the past, and how to migrate old code in case of breakage? If that is no longer a concern, please let me know and I'll check this to approve.
Thanks @joshpar ! Given that this fix could affect the initial state of a number of UGen configurations, we could say something like: "
These are intended and documented behaviors, but which failed in various UGen configurations previously. Therefore, users may observe changes to the initial state of synth graphs that use Impulse. (Especially triggered UGens.) If it's not too verbose, we could also consider providing one concise example of changed/fixed behavior:
... or if that's not appropriate for a changelog, we could just link to this detailed list of resolved behaviors (subsection of this PR, which lists numerous examples): #4150 (comment) Is that helpful? |
@mtmccrea - I think adding it to the changelog wiki is good idea. thanks! |
Purpose and Motivation
This PR addresses a number of longstanding issues with
Impulse
that I've aggregated from issues, comments, etc. which are highlighted below.The primary goal was to initialize the UGen properly and support new frequency and phase argument ranges.
Part fo the initialization sample fix project, specifically in the LFUGens subset.
Types of changes
Added features
Fixes
#4752, #4075, likely more trigger-related issues
Implementation notes to review
Added a wrapping function.
I chose to use a function to wrap the phase, taking advantage of the fact that the range is [0,1].
This could be useful for other UGens with a phasor in this range.
https://github.com/mtmccrea/supercollider/blob/36cadad0eef499a8122a9136fe2f17acace566ca/server/plugins/LFUGens.cpp#L915-L937
Trigger logic:
freq
drives impulse triggering, not phase offset.Phase offset is applied, then phase is wrapped, then phase increment (freq) is applied, then the condition is checked for whether an impulse fires. In this way, phase changes are "instantaneous" and always in-range internally. So phase increment (the frequency control) primarily determines when impulses fire.
In other words, if you advance the phase offset by 3 periods, it's a relative phase offset of 0, so that won't cause an impulse to fire.
Argument input rates aren't limited.
You can read in audio rate arguments even if the UGen running at control rate.
If it's advisable to limit kr ugens to kr arguments, that can be added to the class.
A k-rate frequency argument is now interpolated when running at a-rate.
This wasn't the case before. My understanding is that all k-rate args should be interpolated when used at a-rate, unless there's a particular reason not to.
phOffChanged
flag added toImpulse_next_ak
.This flag checks if k-rate
phase
offset input changes, and if not, skips adding a slope increment of0
in the sample loop.Are these kind of optimizations worth it?... benchmarks seem to indicate yes.
https://github.com/mtmccrea/supercollider/blob/36cadad0eef499a8122a9136fe2f17acace566ca/server/plugins/LFUGens.cpp#L963-L973
Aside: we might consider adding support for this kind of optimization in
SCUnit::SlopeSignal
Variable names are changed
To more precisely reflect what they are. E.g.
unit->mFreq
wasn't actually a frequency, but rather a phase increment (unit->mPhaseIncrement
).Bug fixes #
One of the primary issues resolved is the Ctor set the initialization sample to 0, incorrectly setting the initial state of downstream UGens. This bug is expressed in the initial state of, e.g.,
ToggleFF
,RLPF
, etc.Phase offset isn't wrapped - fixed.
Ex: Phase is out of range for 3 k-periods, so erroneously outputs 1s for those 3 control samples.
plugins: Impulse Ctor initial output = 1 for iphase = 0 #2864 (comment)
Ex: A phase offset of -1 is equivalent to phase offset of 1, which should trigger an impulse (should output 1 on first sample).
See Impulse UGEN sets the presample to the wrong value when frequency equals zero #4075
Ex: There's no impulse after first sample:
See: plugins: Impulse Ctor initial output = 1 for iphase = 0 #2864 (comment)
This behaves properly now.
plugins: Impulse Ctor initial output = 1 for iphase = 0 #2864 (comment)
Note, the original IR in the above comment is "erroneously correct".
This correction to
Impulse
appeared to "break" this RLPF case, but the problem is to do with an initialization bug inRLPF
(#4127), causing the appearance of an impulse double in size and off in phase (coefficients are improperly initialized inRLPF
). Once this is corrected, it works again, which coincidentally matches the erroneous IR.Tests
UPDATE: Newly added unit tests will confirm the above issues are fixed as well as the rate-equivalence tests below.
I made numerous tests for the above changes, though here's a basic test comparing the output from all variations of argument rates and output rates:
Checklist
TestCoreUGens:test_ugen_generator_equivalences
), all relating toTrig
andTrig1
. After applying init sample bug fixes to both UGens, the tests pass.TestFilterUGens:test_time_invariance
to accommodate initialization sample bugs that pervade FilterUGens