Skip to content

Commit

Permalink
Parameter Attachment Updates (#513)
Browse files Browse the repository at this point in the history
* Allowing manual parameter attachment trigger

* Setting up slider choice attachment

* More custom attachment tests

* Apply clang-format

* Fix SONAR warning

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Mar 20, 2024
1 parent fba69f8 commit 293048b
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ struct StringLiteral
: actual_size (sl_detail::num_str_len (int_value))
{
// N is not large enough to hold this number!
#if __cplusplus >= 202002L || _MSVC_LANG >= 202002L
jassert (N >= actual_size);
#endif

if constexpr (std::is_signed_v<IntType>)
sl_detail::sint_to_str (chars.data(), actual_size, static_cast<int64_t> (int_value));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ class EnumChoiceParameter : public ChoiceParameter
EnumType defaultChoice,
const std::initializer_list<std::pair<char, char>>& charMap = { { '_', ' ' } })
: ChoiceParameter (
parameterID,
parameterName,
EnumHelpers::createStringArray<EnumType> (charMap),
static_cast<int> (*magic_enum::enum_index (defaultChoice)))
parameterID,
parameterName,
EnumHelpers::createStringArray<EnumType> (charMap),
static_cast<int> (*magic_enum::enum_index (defaultChoice)))
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ template <typename Param, typename Callback>
ParameterAttachment<Param, Callback>::ParameterAttachment (Param& parameter,
ParameterListeners& listeners,
Callback&& callback)
: param (&parameter)
: param (&parameter),
updateCallback (std::move (callback))
{
valueChangedCallback = listeners.addParameterListener (*param,
ParameterListenerThread::MessageThread,
[this, c = std::move (callback)]() mutable
[this]() mutable
{
if (param != nullptr)
c (ParameterTypeHelpers::getValue (*param));
updateCallback (ParameterTypeHelpers::getValue (*param));
});
}

Expand Down Expand Up @@ -73,6 +74,13 @@ void ParameterAttachment<Param, Callback>::setValueAsPartOfGesture (ParamElement
});
}

template <typename Param, typename Callback>
void ParameterAttachment<Param, Callback>::manuallyTriggerUpdate() const
{
if (param != nullptr)
updateCallback (ParameterTypeHelpers::getValue (*param));
}

template <typename Param, typename Callback>
template <typename Func>
void ParameterAttachment<Param, Callback>::callIfParameterValueChanged (ParamElementType newValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class ParameterAttachment
Callback&& callback);

ParameterAttachment() = default;
ParameterAttachment (const ParameterAttachment&) = default;
ParameterAttachment& operator= (const ParameterAttachment&) = default;
ParameterAttachment (ParameterAttachment&&) noexcept = default;
ParameterAttachment& operator= (ParameterAttachment&&) noexcept = default;

Expand Down Expand Up @@ -51,16 +53,20 @@ class ParameterAttachment
*/
void setValueAsPartOfGesture (ParamElementType newValue);

/** Manually triggers an update as though the parameter has changed. */
void manuallyTriggerUpdate() const;

ParamType* param = nullptr;
PluginState* pluginState = nullptr;

private:
template <typename Func>
void callIfParameterValueChanged (ParamElementType newValue, Func&& func);

Callback updateCallback {};
ScopedCallback valueChangedCallback;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterAttachment)
JUCE_LEAK_DETECTOR (ParameterAttachment)
};
} // namespace chowdsp

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ namespace ParameterAttachmentHelpers
template <typename Attachment>
struct SetValueCallback
{
explicit SetValueCallback (Attachment& a) : attach (a) {}
SetValueCallback() = default;
explicit SetValueCallback (Attachment& a) : attach (&a) {}
SetValueCallback (const SetValueCallback&) = default;
SetValueCallback& operator= (const SetValueCallback&) = default;
SetValueCallback (SetValueCallback&&) noexcept = default;
SetValueCallback& operator= (SetValueCallback&&) noexcept = default;

template <typename T>
void operator() (T val)
{
attach.setValue (val);
attach->setValue (val);
}

Attachment& attach;
Attachment* attach = nullptr;
};

template <typename ParamType>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace chowdsp
{
SliderChoiceAttachment::SliderChoiceAttachment (ChoiceParameter& param,
PluginState& pluginState,
juce::Slider& paramSlider)
: SliderChoiceAttachment (param, pluginState.getParameterListeners(), paramSlider, pluginState.undoManager)
{
}

SliderChoiceAttachment::SliderChoiceAttachment (ChoiceParameter& param,
ParameterListeners& listeners,
juce::Slider& paramSlider,
juce::UndoManager* undoManager)
: slider (&paramSlider),
attachment (param, listeners, ParameterAttachmentHelpers::SetValueCallback { *this }),
um (undoManager)
{
slider->valueFromTextFunction = [&p = static_cast<juce::RangedAudioParameter&> (param)] (const juce::String& text)
{
return (double) p.convertFrom0to1 (p.getValueForText (text));
};
slider->textFromValueFunction = [&p = static_cast<juce::RangedAudioParameter&> (param)] (double value)
{
return p.getText (p.convertTo0to1 ((float) value), 0);
};
slider->setDoubleClickReturnValue (true, param.getDefaultIndex());

slider->setRange (0.0, static_cast<double> (param.choices.size() - 1), 1.0);

setValue (param.getIndex());
slider->valueChanged();
slider->addListener (this);
}

SliderChoiceAttachment::~SliderChoiceAttachment()
{
if (slider != nullptr)
slider->removeListener (this);
}

void SliderChoiceAttachment::setValue (int newValue)
{
if (slider != nullptr)
{
juce::ScopedValueSetter svs { skipSliderChangedCallback, true };
slider->setValue (static_cast<double> (newValue), juce::sendNotificationSync);
}
}

void SliderChoiceAttachment::sliderValueChanged (juce::Slider*)
{
if (skipSliderChangedCallback)
return;

attachment.setValueAsPartOfGesture (static_cast<int> (slider->getValue()));
}

void SliderChoiceAttachment::sliderDragStarted (juce::Slider*)
{
valueAtStartOfGesture = attachment.param->getIndex();
attachment.beginGesture();
}

void SliderChoiceAttachment::sliderDragEnded (juce::Slider*)
{
if (um != nullptr)
{
um->beginNewTransaction();
um->perform (
new ParameterAttachmentHelpers::ParameterChangeAction<ChoiceParameter> (
*attachment.param,
valueAtStartOfGesture,
attachment.param->getIndex()));
}

attachment.endGesture();
}
} // namespace chowdsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

namespace chowdsp
{
class SliderChoiceAttachment : private juce::Slider::Listener
{
public:
/** Creates an attachment for a given parameter, using the undo manager from the plugin state. */
SliderChoiceAttachment (ChoiceParameter& param,
PluginState& pluginState,
juce::Slider& paramSlider);

/** Creates an attachment for a given parameter. */
SliderChoiceAttachment (ChoiceParameter& param,
ParameterListeners& listeners,
juce::Slider& paramSlider,
juce::UndoManager* undoManager);

SliderChoiceAttachment() = default;
SliderChoiceAttachment (SliderChoiceAttachment&&) noexcept = default;
SliderChoiceAttachment& operator= (SliderChoiceAttachment&&) noexcept = default;

~SliderChoiceAttachment() override;

/** Sets the initial value of the slider */
void setValue (int newValue);

/** Returns the attached parameter */
[[nodiscard]] const ChoiceParameter* getParameter() const { return attachment.param; }

private:
void sliderValueChanged (juce::Slider*) override;
void sliderDragStarted (juce::Slider*) override;
void sliderDragEnded (juce::Slider*) override;

juce::Slider* slider = nullptr;
ParameterAttachment<ChoiceParameter,
ParameterAttachmentHelpers::SetValueCallback<SliderChoiceAttachment>>
attachment;
juce::UndoManager* um = nullptr;

bool skipSliderChangedCallback = false;
int valueAtStartOfGesture = 0;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderChoiceAttachment)
};
} // namespace chowdsp
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
#include "Backend/chowdsp_ParameterListeners.cpp"

#include "Frontend/chowdsp_SliderAttachment.cpp"
#include "Frontend/chowdsp_SliderChoiceAttachment.cpp"
#include "Frontend/chowdsp_ComboBoxAttachment.cpp"
#include "Frontend/chowdsp_ButtonAttachment.cpp"
1 change: 1 addition & 0 deletions modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4324) // struct was padded warning

#include "Frontend/chowdsp_ParameterAttachment.h"
#include "Frontend/chowdsp_SliderAttachment.h"
#include "Frontend/chowdsp_SliderChoiceAttachment.h"
#include "Frontend/chowdsp_ComboBoxAttachment.h"
#include "Frontend/chowdsp_ButtonAttachment.h"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,97 @@ TEST_CASE ("Slider Attachment Test", "[plugin][state][attachments]")
}
}

TEST_CASE ("Slider Choice Attachment Test", "[plugin][state][attachments]")
{
struct Params : chowdsp::ParamHolder
{
Params()
{
add (param);
}
chowdsp::ChoiceParameter::Ptr param { "choice", "Choice", juce::StringArray { "Zero", "One", "Two" }, 1 };
};
using State = chowdsp::PluginStateImpl<Params>;

SECTION ("Setup Test")
{
State state;
auto& param = state.params.param;

juce::Slider slider;
chowdsp::SliderChoiceAttachment attach { param, state, slider };

REQUIRE_MESSAGE (static_cast<int> (slider.getValue()) == param->getIndex(), "Incorrect slider value after setup!");
REQUIRE_MESSAGE (static_cast<int> (slider.getRange().getStart()) == 0, "Slider range start is incorrect!");
REQUIRE_MESSAGE (static_cast<int> (slider.getRange().getEnd()) == param->choices.size() - 1, "Slider range end is incorrect!");

REQUIRE_MESSAGE (slider.textFromValueFunction (param->getIndex()) == param->getCurrentValueAsText(), "Slider text from value function is incorrect!");
REQUIRE_MESSAGE (static_cast<int> (slider.valueFromTextFunction (param->getCurrentValueAsText())) == param->getIndex(), "Slider value from text function is incorrect!");
}

SECTION ("Slider Change Test")
{
State state;
const auto& param = state.params.param;

juce::Slider slider;
chowdsp::SliderChoiceAttachment attach { state.params.param, state, slider };

static constexpr int newValue = 0;

{
juce::Slider::ScopedDragNotification scopedDrag { slider };
slider.setValue ((double) newValue, juce::sendNotificationSync);
}

REQUIRE_MESSAGE (param->getIndex() == newValue, "Parameter value after slider drag is incorrect!");
}

SECTION ("Host Change Test")
{
State state;
auto& param = state.params.param;

juce::Slider slider;
chowdsp::SliderChoiceAttachment attach { param, state, slider };

static constexpr int newValue = 2;

chowdsp::ParameterTypeHelpers::setValue (newValue, *param);
state.getParameterListeners().updateBroadcastersFromMessageThread();
REQUIRE_MESSAGE (static_cast<int> (slider.getValue()) == newValue, "Slider value after parameter change is incorrect!");
}

SECTION ("With Undo/Redo Test")
{
juce::UndoManager um { 100 };

State state { &um };
const auto& param = state.params.param;

juce::Slider slider;
chowdsp::SliderChoiceAttachment attach { state.params.param, state, slider };

const auto originalValue = param->getIndex();
static constexpr int newValue = 0;

{
juce::Slider::ScopedDragNotification scopedDrag { slider };
slider.setValue ((double) newValue, juce::sendNotificationSync);
}

REQUIRE_MESSAGE (param->getIndex() == newValue, "Parameter value after slider drag is incorrect!");
REQUIRE_MESSAGE (um.canUndo(), "Slider drag is not creating undoable action!");

um.undo();
REQUIRE_MESSAGE (param->getIndex() == originalValue, "Parameter value after undo action is incorrect!");
REQUIRE_MESSAGE (um.canRedo(), "Slider drag undo is not creating redoable action!");

um.redo();
REQUIRE_MESSAGE (param->getIndex() == newValue, "Parameter value after redo action is incorrect!");
}
}

TEST_CASE ("ComboBox Attachment Test", "[state][attachments]")
{
struct Params : chowdsp::ParamHolder
Expand Down Expand Up @@ -258,3 +349,34 @@ TEST_CASE ("Button Attachment Test", "[state][attachments]")
REQUIRE_MESSAGE (param->get() == newValue, "Parameter value after redo action is incorrect!");
}
}

TEST_CASE ("Custom Parameter Attachment Test", "[state][attachments]")
{
struct Params : chowdsp::ParamHolder
{
Params()
{
add (param);
}

chowdsp::BoolParameter::Ptr param { "bool", "Bool", true };
};

using State = chowdsp::PluginStateImpl<Params>;

State state;
auto& param = state.params.param;

juce::Component comp;
chowdsp::ParameterAttachment<chowdsp::BoolParameter> attach { param, state, [&comp] (bool val)
{ comp.setVisible (val); } };

REQUIRE (! comp.isVisible());

attach.manuallyTriggerUpdate();
REQUIRE (comp.isVisible());

chowdsp::ParameterTypeHelpers::setValue (false, *param);
state.getParameterListeners().updateBroadcastersFromMessageThread();
REQUIRE (! comp.isVisible());
}

0 comments on commit 293048b

Please sign in to comment.