AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

ModulatableSlider.cpp (13382B)


      1 #include "ModulatableSlider.h"
      2 #include "../PluginProcessor.h"
      3 
      4 void ModulatableSlider::attachToParameter (juce::RangedAudioParameter* param)
      5 {
      6     if (param == nullptr)
      7     {
      8         attachment.reset();
      9         modParameter = nullptr;
     10         stopTimer();
     11         return;
     12     }
     13 
     14     attachment = std::make_unique<juce::SliderParameterAttachment> (*param, *this, nullptr);
     15     modParameter = dynamic_cast<chowdsp::FloatParameter*> (param);
     16     modulatedValue = modParameter->getCurrentValue();
     17 }
     18 
     19 double ModulatableSlider::getModulatedPosition()
     20 {
     21     if (modParameter == nullptr)
     22         return valueToProportionOfLength (getValue());
     23 
     24     return jlimit (0.0, 1.0, valueToProportionOfLength ((double) modParameter->getCurrentValue()));
     25 }
     26 
     27 void ModulatableSlider::mouseDown (const MouseEvent& e)
     28 {
     29     if (e.mods.isPopupMenu())
     30     {
     31         auto popupMenu = getContextMenu();
     32         if (popupMenu.containsAnyActiveItems())
     33             popupMenu.showMenuAsync (juce::PopupMenu::Options());
     34 
     35         return;
     36     }
     37 
     38     foleys::AutoOrientationSlider::mouseDown (e);
     39 }
     40 
     41 void ModulatableSlider::setPluginEditorCallback (PluginEditorCallback&& newCallback)
     42 {
     43     pluginEditorCallback = std::move (newCallback);
     44 }
     45 
     46 juce::PopupMenu ModulatableSlider::getContextMenu()
     47 {
     48     if (pluginEditorCallback != nullptr)
     49     {
     50         if (auto* pluginEditor = pluginEditorCallback())
     51         {
     52             if (auto* hostContext = pluginEditor->getHostContext())
     53             {
     54                 if (auto menu = hostContext->getContextMenuForParameter (modParameter))
     55                     return menu->getEquivalentPopupMenu();
     56             }
     57         }
     58     }
     59 
     60     return {};
     61 }
     62 
     63 void ModulatableSlider::timerCallback()
     64 {
     65     const auto newModulatedValue = modParameter->getCurrentValue();
     66     if (std::abs (modulatedValue - newModulatedValue) < 0.01)
     67         return;
     68 
     69     modulatedValue = newModulatedValue;
     70     repaint();
     71 }
     72 
     73 void ModulatableSlider::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float modSliderPos)
     74 {
     75     int diameter = (width > height) ? height : width;
     76     if (diameter < 16)
     77         return;
     78 
     79     juce::Point<float> centre ((float) x + std::floor ((float) width * 0.5f + 0.5f), (float) y + std::floor ((float) height * 0.5f + 0.5f));
     80     diameter -= (diameter % 2 == 1) ? 9 : 8;
     81     float radius = (float) diameter * 0.5f;
     82     x = int (centre.x - radius);
     83     y = int (centre.y - radius);
     84 
     85     const auto bounds = juce::Rectangle<int> (x, y, diameter, diameter).toFloat();
     86 
     87     auto b = sharedAssets->pointer->getBounds().toFloat();
     88     sharedAssets->pointer->setTransform (AffineTransform::rotation (MathConstants<float>::twoPi * ((sliderPos - 0.5f) * 300.0f / 360.0f),
     89                                                                     b.getCentreX(),
     90                                                                     b.getCentreY()));
     91 
     92     const auto alpha = isEnabled() ? 1.0f : 0.4f;
     93     auto knobBounds = (bounds * 0.75f).withCentre (centre);
     94     sharedAssets->knob->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, alpha);
     95     sharedAssets->pointer->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, alpha);
     96 
     97     static constexpr auto rotaryStartAngle = MathConstants<float>::pi * 1.2f;
     98     static constexpr auto rotaryEndAngle = MathConstants<float>::pi * 2.8f;
     99     const auto toAngle = rotaryStartAngle + modSliderPos * (rotaryEndAngle - rotaryStartAngle);
    100     constexpr float arcFactor = 0.9f;
    101 
    102     juce::Path valueArc;
    103     valueArc.addPieSegment (bounds, rotaryStartAngle, rotaryEndAngle, arcFactor);
    104     g.setColour (Colour (0xff595c6b).withMultipliedAlpha (alpha));
    105     g.fillPath (valueArc);
    106     valueArc.clear();
    107 
    108     valueArc.addPieSegment (bounds, rotaryStartAngle, toAngle, arcFactor);
    109     g.setColour (Colour (0xff9cbcbd).withMultipliedAlpha (alpha));
    110     g.fillPath (valueArc);
    111 }
    112 
    113 void ModulatableSlider::drawLinearSlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float modSliderPos)
    114 {
    115     const auto horizontal = isHorizontal();
    116     auto trackWidth = juce::jmin (6.0f, horizontal ? (float) height * 0.25f : (float) width * 0.25f);
    117 
    118     juce::Point startPoint (horizontal ? (float) x : (float) x + (float) width * 0.5f,
    119                             horizontal ? (float) y + (float) height * 0.5f : (float) (height + y));
    120 
    121     juce::Point endPoint (horizontal ? (float) (width + x) : startPoint.x,
    122                           horizontal ? startPoint.y : (float) y);
    123 
    124     juce::Path backgroundTrack;
    125     backgroundTrack.startNewSubPath (startPoint);
    126     backgroundTrack.lineTo (endPoint);
    127 
    128     const auto alphaMult = isEnabled() ? 1.0f : 0.4f;
    129     g.setColour (Colour (0xff595c6b).withAlpha (alphaMult));
    130     g.strokePath (backgroundTrack, { trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded });
    131 
    132     juce::Path valueTrack;
    133     const auto maxPoint = [&]
    134     {
    135         auto kx = horizontal ? sliderPos : ((float) x + (float) width * 0.5f);
    136         auto ky = horizontal ? ((float) y + (float) height * 0.5f) : sliderPos;
    137         return juce::Point { kx, ky };
    138     }();
    139     const auto modPoint = [&]
    140     {
    141         auto kmx = horizontal ? modSliderPos : ((float) x + (float) width * 0.5f);
    142         auto kmy = horizontal ? ((float) y + (float) height * 0.5f) : modSliderPos;
    143         return juce::Point { kmx, kmy };
    144     }();
    145 
    146     valueTrack.startNewSubPath (startPoint);
    147     valueTrack.lineTo (modPoint);
    148     g.setColour (Colour (0xff9cbcbd).withAlpha (alphaMult));
    149     g.strokePath (valueTrack, { trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded });
    150 
    151     auto thumbWidth = getLookAndFeel().getSliderThumbRadius (*this);
    152     auto thumbRect = juce::Rectangle<float> (static_cast<float> (thumbWidth),
    153                                              static_cast<float> (thumbWidth))
    154                          .withCentre (maxPoint);
    155     sharedAssets->knob->drawWithin (g, thumbRect, juce::RectanglePlacement::stretchToFit, alphaMult);
    156 }
    157 
    158 void ModulatableSlider::paint (Graphics& g)
    159 {
    160     if (modParameter == nullptr)
    161         return;
    162 
    163     auto& lf = getLookAndFeel();
    164     auto layout = lf.getSliderLayout (*this);
    165     const auto sliderRect = layout.sliderBounds;
    166 
    167     modulatedValue = modParameter->getCurrentValue();
    168     if (isRotary())
    169     {
    170         const auto sliderPos = (float) valueToProportionOfLength (getValue());
    171         const auto modSliderPos = (float) jlimit (0.0, 1.0, valueToProportionOfLength (modulatedValue));
    172         drawRotarySlider (g,
    173                           sliderRect.getX(),
    174                           sliderRect.getY(),
    175                           sliderRect.getWidth(),
    176                           sliderRect.getHeight(),
    177                           sliderPos,
    178                           modSliderPos);
    179     }
    180     else
    181     {
    182         const auto normRange = NormalisableRange { getRange() };
    183         const auto sliderPos = getPositionOfValue (getValue());
    184         const auto modSliderPos = getPositionOfValue (modulatedValue);
    185         drawLinearSlider (g,
    186                           sliderRect.getX(),
    187                           sliderRect.getY(),
    188                           sliderRect.getWidth(),
    189                           sliderRect.getHeight(),
    190                           sliderPos,
    191                           modSliderPos);
    192     }
    193 }
    194 
    195 //====================================================================
    196 ModSliderItem::ModSliderItem (foleys::MagicGUIBuilder& builder, const juce::ValueTree& node) : GuiItem (builder, node)
    197 {
    198     setColourTranslation (
    199         { { "slider-background", juce::Slider::backgroundColourId },
    200           { "slider-thumb", juce::Slider::thumbColourId },
    201           { "slider-track", juce::Slider::trackColourId },
    202           { "rotary-fill", juce::Slider::rotarySliderFillColourId },
    203           { "rotary-outline", juce::Slider::rotarySliderOutlineColourId },
    204           { "slider-text", juce::Slider::textBoxTextColourId },
    205           { "slider-text-background", juce::Slider::textBoxBackgroundColourId },
    206           { "slider-text-highlight", juce::Slider::textBoxHighlightColourId },
    207           { "slider-text-outline", juce::Slider::textBoxOutlineColourId } });
    208 
    209     addAndMakeVisible (slider);
    210 }
    211 
    212 void ModSliderItem::update()
    213 {
    214     if (const auto* plugin = dynamic_cast<const ChowtapeModelAudioProcessor*> (magicBuilder.getMagicState().getProcessor()))
    215     {
    216         if (plugin->supportsParameterModulation())
    217             slider.startTimerHz (24);
    218     }
    219 
    220     slider.setPluginEditorCallback ([this]
    221                                     { return magicBuilder.getMagicState().getProcessor()->getActiveEditor(); });
    222 
    223     slider.setTitle (magicBuilder.getStyleProperty (foleys::IDs::name, configNode));
    224     defaultHeight = magicBuilder.getStyleProperty (foleys::IDs::defaultHeight, configNode);
    225 
    226     auto type = getProperty (pSliderType).toString();
    227     slider.setAutoOrientation (type.isEmpty() || type == pSliderTypes[0]);
    228 
    229     if (type == pSliderTypes[1])
    230         slider.setSliderStyle (juce::Slider::LinearHorizontal);
    231     else if (type == pSliderTypes[2])
    232         slider.setSliderStyle (juce::Slider::LinearVertical);
    233     else if (type == pSliderTypes[3])
    234         slider.setSliderStyle (juce::Slider::Rotary);
    235     else if (type == pSliderTypes[4])
    236         slider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    237     else if (type == pSliderTypes[5])
    238         slider.setSliderStyle (juce::Slider::IncDecButtons);
    239 
    240     auto textbox = getProperty (pSliderTextBox).toString();
    241     sliderTextBoxHeight = getProperty (pSliderTextBoxHeight);
    242     if (textbox == pTextBoxPositions[0])
    243         textBoxPosition = juce::Slider::NoTextBox;
    244     else if (textbox == pTextBoxPositions[1])
    245         textBoxPosition = juce::Slider::TextBoxAbove;
    246     else if (textbox == pTextBoxPositions[3])
    247         textBoxPosition = juce::Slider::TextBoxLeft;
    248     else if (textbox == pTextBoxPositions[4])
    249         textBoxPosition = juce::Slider::TextBoxRight;
    250     else
    251         textBoxPosition = juce::Slider::TextBoxBelow;
    252 
    253     double minValue = getProperty (pMinValue);
    254     double maxValue = getProperty (pMaxValue);
    255     if (maxValue > minValue)
    256         slider.setRange (minValue, maxValue);
    257 
    258     auto valueID = configNode.getProperty (pValue, juce::String()).toString();
    259     if (valueID.isNotEmpty())
    260         slider.getValueObject().referTo (getMagicState().getPropertyAsValue (valueID));
    261 
    262     auto paramID = getControlledParameterID ({});
    263     if (paramID.isNotEmpty())
    264         slider.attachToParameter (getMagicState().getParameter (paramID));
    265     else
    266         slider.attachToParameter (nullptr);
    267 
    268     resized();
    269 }
    270 
    271 void ModSliderItem::resized()
    272 {
    273     const auto sliderTextHeightToUse = [this]
    274     {
    275         if (defaultHeight == 0)
    276             return sliderTextBoxHeight;
    277 
    278         return proportionOfHeight ((float) sliderTextBoxHeight / (float) defaultHeight);
    279     }();
    280 
    281     slider.setTextBoxStyle (textBoxPosition, false, proportionOfWidth (0.75f), sliderTextHeightToUse);
    282 
    283     foleys::GuiItem::resized();
    284 }
    285 
    286 std::vector<foleys::SettableProperty> ModSliderItem::getSettableProperties() const
    287 {
    288     std::vector<foleys::SettableProperty> props;
    289 
    290     props.push_back ({ configNode, foleys::IDs::parameter, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenuLambda() });
    291     props.push_back ({ configNode, pSliderType, foleys::SettableProperty::Choice, pSliderTypes[0], magicBuilder.createChoicesMenuLambda (pSliderTypes) });
    292     props.push_back ({ configNode, pSliderTextBox, foleys::SettableProperty::Choice, pTextBoxPositions[2], magicBuilder.createChoicesMenuLambda (pTextBoxPositions) });
    293     props.push_back ({ configNode, pValue, foleys::SettableProperty::Choice, 1.0f, magicBuilder.createPropertiesMenuLambda() });
    294     props.push_back ({ configNode, pMinValue, foleys::SettableProperty::Number, 0.0f, {} });
    295     props.push_back ({ configNode, pMaxValue, foleys::SettableProperty::Number, 2.0f, {} });
    296     props.push_back ({ configNode, pSliderTextBoxWidth, foleys::SettableProperty::Number, 85.0f, {} });
    297     props.push_back ({ configNode, pSliderTextBoxHeight, foleys::SettableProperty::Number, 17.0f, {} });
    298 
    299     return props;
    300 }
    301 
    302 juce::String ModSliderItem::getControlledParameterID (juce::Point<int>)
    303 {
    304     return configNode.getProperty (foleys::IDs::parameter, juce::String()).toString();
    305 }
    306 
    307 juce::Component* ModSliderItem::getWrappedComponent()
    308 {
    309     return &slider;
    310 }
    311 
    312 void ModSliderItem::mouseDrag (const juce::MouseEvent& e)
    313 {
    314     auto numSources = juce::Desktop::getInstance().getNumDraggingMouseSources();
    315     if (numSources > 1)
    316         return;
    317 
    318     GuiItem::mouseDrag (e);
    319 }
    320 
    321 const juce::Identifier ModSliderItem::pSliderType { "slider-type" };
    322 const juce::StringArray ModSliderItem::pSliderTypes { "auto", "linear-horizontal", "linear-vertical", "rotary", "rotary-horizontal-vertical", "inc-dec-buttons" };
    323 const juce::Identifier ModSliderItem::pSliderTextBox { "slider-textbox" };
    324 const juce::StringArray ModSliderItem::pTextBoxPositions { "no-textbox", "textbox-above", "textbox-below", "textbox-left", "textbox-right" };
    325 const juce::Identifier ModSliderItem::pSliderTextBoxWidth { "slidertext-width" };
    326 const juce::Identifier ModSliderItem::pSliderTextBoxHeight { "slidertext-height" };
    327 const juce::Identifier ModSliderItem::pValue { "value" };
    328 const juce::Identifier ModSliderItem::pMinValue { "min-value" };
    329 const juce::Identifier ModSliderItem::pMaxValue { "max-value" };