How to set integer param from a custom IControl?

Hi there!

I have a custom IControl where I want to have an activeId parameter that can be set by the IControl to determine the “active element” I am working with.

The issue is that the standard way to set a parameter in an IControl, i.e. SetValue only seems to set the value to between 0 and 1, then when I try and read it back afterwards using GetValue() or GetParam(id)->Value() the result is no longer the integer I wanted to set.

i.e.

inside the custom IControl:

SetValue(double(11), paramId);

Then later:

int val = static_cast<int>(GetValue(paramId));

same with

int val = GetParam(paramId)->Int();

doesn’t return 11, it returns 1.

The weirdest thing is, I’ve looked through the source code, and I have absolutely no idea how it is doing this. There doesn’t seem to be any evidence of clipping the value, yet it is.

The only thing I can see is that:

void IControl::SetDirty(bool triggerAction, int valIdx)

Seems to (IMO incorrectly) clip the value to be between 0 and 1 when it executes. Is that a bug? Why isn’t the value instead clipped to what it’s original definition is like it should be?

Ah… I see that in ISwitchControlBase there is a “hack” to get around this:

int GetSelectedIdx() const { return int(0.5 + GetValue() * (double) (mNumStates - 1)); }

But honestly, that feels pretty ugly and inefficient. Is there a better way to do this?

I.e. is there the concept in iPlug2 of a parameter that assists with storing a UI modal state. I.e. if I want a UI that stores the last interaction to highlight some region, I don’t want that parameter to be visible at all to the host, I just want it to be accessible to the UI and be updated as the user interacts with it, but store an integer id. And then have multiple other controls linked to it to also then update their UI state.

I found something that kind of works, but I’d like a seasoned pro to help me understand if this is the right way to go about things, since I think that maybe it’s not…

So in my custom control, to read the value I can do:

      // get the active eq band id
      const IParam* activeBandParam = GetParam(kActiveBand);
      const int activeBandId = static_cast<int>(activeBandParam->FromNormalized(GetValue(kActiveBand)));

And to set it I can do:

SetValue(GetParam(kActiveBand)->ToNormalized(double(i)), kActiveBand);

And this does what I think I want, but I still don’t like it.

To ensure everything works correctly, is it true that within an IControl you should only use GetValue and SetValue, and avoid using GetParam since that goes back to the delegate? Will this cause any issues with updates/multi-threading/concurrency etc?

What are the rules here?

So IControl parameters are ALWAYS 0-1 values? If so, why? Is that something that could be changed in future?

I’m just curious as to the design philosophy here.

Thanks!

IControls store their state (IControl::mVals) using a normalized values. Parameters store their state using real, non-normalized values (IParam::mValue), both are double precision floating point.

IControl::To/FromNormalized() are const conversion methods. They should generally be safe to use like this:

  GetParam(kGain)->InitInt("Gain", 0, 0, 2);
class TestIntegerControl : public IControl
{
public:
  TestIntegerControl(const IRECT& bounds, int paramIdx)
  : IControl(bounds, paramIdx)
  {}
  
  void Draw(IGraphics& g) override
  {
    g.FillRect(COLOR_RED, mRECT.SubRectHorizontal(3, 0));
    g.FillRect(COLOR_GREEN, mRECT.SubRectHorizontal(3, 1));
    g.FillRect(COLOR_BLUE, mRECT.SubRectHorizontal(3, 2));
    g.DrawRect(COLOR_BLACK, mRECT.SubRectHorizontal(3, GetParam()->Int()));
  }
  
  void OnMouseDown(float x, float y, const IMouseMod& mod) override 
  {
    if (mRECT.SubRectHorizontal(3, 0).Contains(x, y))
    {
      SetValue(GetParam()->ToNormalized(0));
    }
    else if (mRECT.SubRectHorizontal(3, 1).Contains(x, y))
    {
      SetValue(GetParam()->ToNormalized(1));
    }
    else if (mRECT.SubRectHorizontal(3, 2).Contains(x, y))
    {
      SetValue(GetParam()->ToNormalized(2));
    }

    SetDirty(true);
  }
};

The design philosophy is this is how it was in iPlug1 back in 2008. In order to maintain a degree of backwards compatibility, we didn’t change such things in iPlug2.

1 Like

Thanks @olilarkin the main thing you just clarified for me is that it’s safe to call GetParam from an IControl, even though it goes back to the delegate. That’s what I was worried about. Your above example is how I’ve solved it in my own implementation, so thank you for confirming!