Jamba Views / Step Pad

Step Pad

Step Pad

A step pad lets you step through the values of a parameter by click dragging vertically or horizontally with the mouse. This control behaves very similarly to a knob with the difference that the visual representation is like a button (held or released).

This view works for any parameter (both Vst and Jmb) that is (or can be interpreted as) a discrete parameter.

In addition to the attributes exposed by CustomDiscreteControlView, this class exposes the following attributes:

Attribute Description
step-increment Value by which this pad will increment (positive) or decrement (negative) the parameter.
shift-step-increment Value by which this pad will increment (positive) or decrement (negative) the parameter when the shift key modifier is being held. This allows to have bigger steps (or smaller steps) when shift is used.
drag-factor Defines how many pixels the mouse needs to move to go through the range of values. The bigger this number the “slower” the value will change.
shift-drag-factor Defines how many pixels the mouse needs to move to go through the range of values when shift is being held. The bigger this number the “slower” the value will change.
held-color Color to use when the pad is held and no image is provided (note that the “released” color is simply the back color).
disabled-color When no image is provided and the control is disabled, this color is used instead.
pad-image The image to use to draw the pad (2 or 3 frames).
pad-image-has-disabled-state Flag to determine whether the image contains a disabled state (3 frames) or not (2 frames).
inverse Inverses the meaning of “held” and “released” in regards to drawing the view/image.
wrap Defines what happens when the value reaches its end of range after being incremented (resp. decremented). When set to true it will wrap around, otherwise it will remain at its max (resp. min)
positive-direction Defines the direction (up, down, left or right) in which the mouse needs to be dragged in order to increase the value. The opposite direction will decrease.

The image above shows an example of a vst parameter representing a number of slices which can vary between 1.0 and 64.0. A ParamDisplayView shows the actual number. Since a ParamDisplayView does not do anything on its own, a StepPadView is layered on top of it to add the click and drag behavior. The back-color is set to fully transparent so that when not clicked, the ParamDisplayView is shown, thus revealing the number of slices. The held-color is set to a transparent shade of the green used by the LCD screen so that, a) you can still see the number below, and b) there is a visual feedback that something is happening when you click.

Example

The XML defining the display (number) and transparent pad (Source)

<view back-color="~ TransparentCColor" class="jamba::ParamDisplay" control-tag="Param_NumSlices" font="~ NormalFontSmaller" font-color="LCD Foreground" origin="533, 18" size="39, 18" .../>
<view back-color="~ TransparentCColor" class="jamba::StepPad" control-tag="Param_NumSlices" drag-factor="100" held-color="LCD Active" origin="531, 18" positive-direction="up" shift-drag-factor="10000" shift-step-increment="1" size="40, 18" step-count="63000" step-increment="125" wrap="false" .../>
Note
  • Note how the pad is layered on top of the display (origin and size overlapping).
  • Note how the number of slices is not a discrete parameter but is being interpreted as one with 63000 steps with a step increment of 125 and a shift step increment of 1 (which will make the number of slices change a lot slower)

The vst param is defined this way (Source)

struct NumSlice
{
  using real_type = double;
  using int_type = int32;

  NumSlice() = default;
  explicit NumSlice(real_type iValue) : fRealValue{iValue}, fIntValue{static_cast<int_type>(std::ceil(iValue))} {}
  constexpr explicit NumSlice(int_type iValue) : fRealValue{static_cast<real_type>(iValue)}, fIntValue{iValue} {}

  constexpr real_type realValue() const { return fRealValue; }
  constexpr int_type intValue() const { return fIntValue; }

private:
  real_type fRealValue{};
  int_type fIntValue{};
};

//------------------------------------------------------------------------
// NumSlicesParamConverter: Number of slices is between 1 and NUM_SLICES
// mapping [0.0, 1.0] to [1.0, NUM_SLICES]
//------------------------------------------------------------------------
class NumSlicesParamConverter : public IParamConverter<NumSlice>
{
public:
  inline ParamValue normalize(ParamType const &iValue) const override
  {
    return Utils::mapValueDP(iValue.realValue(), 1, NUM_SLICES, 0.0, 1.0);
  }

  inline ParamType denormalize(ParamValue iNormalizedValue) const override
  {
    return NumSlice{Utils::mapValueDP(iNormalizedValue, 0.0, 1.0, 1.0, NUM_SLICES)};
  }

  inline void toString(ParamType const &iValue, String128 oString, int32 /* iPrecision */) const override
  {
    Steinberg::UString wrapper(oString, str16BufferSize (String128));
    wrapper.printFloat(iValue.realValue(), 2);
  }
};

The vst param is initialized this way (Source)

  // the number of slices the sample will be split into
  fNumSlices =
    vst<NumSlicesParamConverter>(ESampleSplitterParamID::kNumSlices, STR16("Num Slices"))
      .defaultValue(DEFAULT_NUM_SLICES)
      .shortTitle(STR16("Slices"))
      .add();