Skip to content

Conversation

@JanWilczek
Copy link
Collaborator

No description provided.

@reuk reuk self-requested a review November 5, 2025 13:15
Copy link
Member

@reuk reuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comments are all fairly minor, the code looks generally good.

One thing I noticed while testing is that the LFO seems to "remember" its phase during bypass, so when the bypass is disabled the LFO resumes from its last position. I'm not sure if that's desirable behaviour - normally I'd expect bypass to behave as though the plugin is still active in the background, i.e. the LFO phase would continue to update.

}

void setModulationRate(float rateHz) noexcept {
void setModulationRate(float rateHz, bool force = false) noexcept {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this matches the JUCE API, I don't think we've done a very good job here. Bool parameters like this (auxiliary flags that modify the "main" behaviour of the function) aren't very readable at the call site. Consider:

lfo.setModulationRate (5.0f, true);

Unless we go and look at the docs, it's not immediately clear what this function will do. Using an enum class ApplySmoothing { no, yes }; might improve readability.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not convinced of this change, but after implementing it, I agree 🙂

What I would do in a plugin is to create a separate struct Tremolo::Parameters with all plugin parameters, and possibly a force field. Then there could be just a single setParameters() function and using designated initializers would get the message across, i.e.,

tremolo.setParameters({
  //...
  .force = bypassedAndNotTransitioning
});

This would allow us avoiding the SerializableParameters struct as well.


void setModulationRate(float rateHz) noexcept {
void setModulationRate(float rateHz, bool force = false) noexcept {
for (auto& lfo : lfos) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this again, maybe this should be named setModulationRateHz so that the units are slightly more obvious

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

For more clarity, I'd use the ClosedRangeValue<float> or Frequency classes from my wolfsound_dsp_utils library. Then, you could write

tremolo.setModulationRate(Frequency{p.rate});

or even

tremolo.setModulationRate(10_Hz);

I don't like using floats as parameters in general!

tremolo.setModulationRate(parameters.rate);
// force updates (=skip transitions) if fully bypassed to avoid peculiar
// behavior when parameters change under bypass ON
tremolo.setModulationRate(parameters.rate, bypassedAndNotTransitioning);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to describe exactly the kind of behaviours that we're trying to avoid here. If this comment is here to act as a warning ("something subtle is happening here") then, as a reader/maintainer, I'd want to know what behaviours to test when making changes in this area. The same goes for the comment later on in the file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to clarify, but it's difficult to explain waveform shapes in words! 😄

// offset the phase by pi/2 to return 0 if phase equals 0
// (otherwise, the waveform starts at 1)
phase -= juce::MathConstants<float>::halfPi;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider instead introducing a new variable with a different name (maybe just offsetPhase), so that phase always refers to the initial parameter value. I expect this will be marginally clearer

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@JanWilczek
Copy link
Collaborator Author

The inline comments are all fairly minor, the code looks generally good.

One thing I noticed while testing is that the LFO seems to "remember" its phase during bypass, so when the bypass is disabled the LFO resumes from its last position. I'm not sure if that's desirable behaviour - normally I'd expect bypass to behave as though the plugin is still active in the background, i.e. the LFO phase would continue to update.

I see three options for the LFO phase on toggling bypass ON:

  1. Phase of the LFO “freezes” and after toggling bypass OFF, the LFO picks up exactly where it left off when bypass was turned on
  2. Phase resets
  3. Phase is continuously updated even if bypass is ON (what you suggested)

Attached is the image of these approaches.

TBH, I asked a fellow sound engineer, because I have no intuition on which approach is right.

Image-1

@JanWilczek
Copy link
Collaborator Author

The inline comments are all fairly minor, the code looks generally good.

One thing I noticed while testing is that the LFO seems to "remember" its phase during bypass, so when the bypass is disabled the LFO resumes from its last position. I'm not sure if that's desirable behaviour - normally I'd expect bypass to behave as though the plugin is still active in the background, i.e. the LFO phase would continue to update.

I believe this is a minor thing that requires major changes. I have put that on my TODO list. Let's deal with that once all lessons are published.

@JanWilczek JanWilczek merged commit 6630cd0 into main Nov 7, 2025
4 checks passed
@JanWilczek JanWilczek deleted the jan/fix-forcing-lfo-change branch November 7, 2025 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants