diff --git a/Source/EffectsComponent.h b/Source/EffectsComponent.h index 581008a4..64522e44 100644 --- a/Source/EffectsComponent.h +++ b/Source/EffectsComponent.h @@ -23,7 +23,7 @@ private: EffectsListBoxModel listBoxModel; DraggableListBox listBox; - EffectComponent frequency = EffectComponent(*audioProcessor.frequencyEffect, false); + EffectComponent frequency = EffectComponent(audioProcessor, *audioProcessor.frequencyEffect, false); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsComponent) }; \ No newline at end of file diff --git a/Source/ObjComponent.h b/Source/ObjComponent.h index 72f8c0f3..f25418a5 100644 --- a/Source/ObjComponent.h +++ b/Source/ObjComponent.h @@ -17,11 +17,11 @@ private: OscirenderAudioProcessor& audioProcessor; OscirenderAudioProcessorEditor& pluginEditor; - EffectComponent focalLength{*audioProcessor.focalLength, false}; - EffectComponent rotateX{*audioProcessor.rotateX, false}; - EffectComponent rotateY{*audioProcessor.rotateY, false}; - EffectComponent rotateZ{*audioProcessor.rotateZ, false}; - EffectComponent rotateSpeed{*audioProcessor.rotateSpeed, false}; + EffectComponent focalLength{audioProcessor, *audioProcessor.focalLength, false}; + EffectComponent rotateX{audioProcessor, *audioProcessor.rotateX, false}; + EffectComponent rotateY{audioProcessor, *audioProcessor.rotateY, false}; + EffectComponent rotateZ{audioProcessor, *audioProcessor.rotateZ, false}; + EffectComponent rotateSpeed{audioProcessor, *audioProcessor.rotateSpeed, false}; juce::TextButton resetRotation{"Reset Rotation"}; juce::ToggleButton mouseRotate{"Rotate with Mouse (Esc to disable)"}; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index f6b0e4ba..2fed7ebd 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -74,10 +74,15 @@ void OscirenderAudioProcessorEditor::resized() { } else { collapseButton.setBounds(0, 0, 0, 0); } - effects.setBounds(area.removeFromRight(getWidth() / sections)); - main.setBounds(area.removeFromTop(getHeight() / 2)); - lua.setBounds(area); - obj.setBounds(area); + auto effectsSection = area.removeFromRight(1.2 * getWidth() / sections); + main.setBounds(area); + if (lua.isVisible() || obj.isVisible()) { + auto altEffectsSection = effectsSection.removeFromBottom(juce::jmin(effectsSection.getHeight() / 2, 300)); + lua.setBounds(altEffectsSection); + obj.setBounds(altEffectsSection); + } + effects.setBounds(effectsSection); + } void OscirenderAudioProcessorEditor::addCodeEditor(int index) { diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index ec31daac..5ea915c0 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -46,26 +46,26 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() )); toggleableEffects.push_back(std::make_shared( std::make_shared(), - new EffectParameter("2D Rotate Speed", "rotateSpeed", 0.0, 0.0, 1.0) + new EffectParameter("2D Rotate", "rotateSpeed", 0.0, 0.0, 1.0) )); toggleableEffects.push_back(std::make_shared( std::make_shared(), - new EffectParameter("Vector Cancelling", "vectorCancelling", 0.0, 0.0, 1.0) + new EffectParameter("Vector Cancel", "vectorCancelling", 0.0, 0.0, 1.0) )); toggleableEffects.push_back(std::make_shared( std::make_shared(false), - new EffectParameter("Horizontal Distort", "horizontalDistort", 0.0, 0.0, 1.0) + new EffectParameter("X Distort", "horizontalDistort", 0.0, 0.0, 1.0) )); toggleableEffects.push_back(std::make_shared( std::make_shared(true), - new EffectParameter("Vertical Distort", "verticalDistort", 0.0, 0.0, 1.0) + new EffectParameter("Y Distort", "verticalDistort", 0.0, 0.0, 1.0) )); toggleableEffects.push_back(std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { input.x += values[0]; input.y += values[1]; return input; - }, std::vector{new EffectParameter("Translate x", "translateX", 0.0, -1.0, 1.0), new EffectParameter("Translate y", "translateY", 0.0, -1.0, 1.0)} + }, std::vector{new EffectParameter("Translate X", "translateX", 0.0, -1.0, 1.0), new EffectParameter("Translate Y", "translateY", 0.0, -1.0, 1.0)} )); toggleableEffects.push_back(std::make_shared( std::make_shared(), @@ -97,7 +97,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() permanentEffects.push_back(rotateZ); permanentEffects.push_back(focalLength); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 26; i++) { addLuaSlider(); } @@ -106,8 +106,11 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() effects.insert(effects.end(), luaEffects.begin(), luaEffects.end()); for (auto effect : effects) { - for (auto parameter : effect->parameters) { - addParameter(parameter); + for (auto effectParameter : effect->parameters) { + auto parameters = effectParameter->getParameters(); + for (auto parameter : parameters) { + addParameter(parameter); + } } } } @@ -215,6 +218,9 @@ void OscirenderAudioProcessor::addLuaSlider() { std::make_shared(sliderName, *this), new EffectParameter("Lua " + sliderName, "lua" + sliderName, 0.0, 0.0, 1.0, 0.001, false) )); + + auto& effect = luaEffects.back(); + effect->parameters[0]->disableLfo(); } // effectsLock should be held when calling this diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a995c907..e96312b0 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -122,7 +122,7 @@ public: } } return input; - }, new EffectParameter("Rotate x", "rotateX", 1.0, -1.0, 1.0) + }, new EffectParameter("Rotate X", "rotateX", 1.0, -1.0, 1.0) ); std::shared_ptr rotateY = std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { @@ -137,7 +137,7 @@ public: } } return input; - }, new EffectParameter("Rotate y", "rotateY", 1.0, -1.0, 1.0) + }, new EffectParameter("Rotate Y", "rotateY", 1.0, -1.0, 1.0) ); std::shared_ptr rotateZ = std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { @@ -152,7 +152,7 @@ public: } } return input; - }, new EffectParameter("Rotate z", "rotateZ", 0.0, -1.0, 1.0) + }, new EffectParameter("Rotate Z", "rotateZ", 0.0, -1.0, 1.0) ); std::shared_ptr rotateSpeed = std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { @@ -162,7 +162,7 @@ public: obj->setRotationSpeed(values[0]); } return input; - }, new EffectParameter("Rotate speed", "rotateSpeed", 0.0, -1.0, 1.0) + }, new EffectParameter("Rotate Speed", "rotateSpeed3D", 0.0, -1.0, 1.0) ); std::shared_ptr delayEffect = std::make_shared(); diff --git a/Source/audio/BooleanParameter.h b/Source/audio/BooleanParameter.h index ea9b9399..108d0b76 100644 --- a/Source/audio/BooleanParameter.h +++ b/Source/audio/BooleanParameter.h @@ -42,7 +42,7 @@ public: } bool isDiscrete() const override { - return false; + return true; } bool isBoolean() const override { diff --git a/Source/audio/Effect.cpp b/Source/audio/Effect.cpp index 49163e49..7475c78d 100644 --- a/Source/audio/Effect.cpp +++ b/Source/audio/Effect.cpp @@ -1,30 +1,83 @@ #include "Effect.h" +#include Effect::Effect(std::shared_ptr effectApplication, std::vector parameters) : effectApplication(effectApplication), parameters(parameters), enabled(nullptr) { - smoothValues = std::vector(parameters.size(), 0.0); + actualValues = std::vector(parameters.size(), 0.0); } Effect::Effect(std::shared_ptr effectApplication, EffectParameter* parameter) : Effect(effectApplication, std::vector{parameter}) {} Effect::Effect(std::function&, double)> application, std::vector parameters) : application(application), parameters(parameters), enabled(nullptr) { - smoothValues = std::vector(parameters.size(), 0.0); + actualValues = std::vector(parameters.size(), 0.0); } Effect::Effect(std::function&, double)> application, EffectParameter* parameter) : Effect(application, std::vector{parameter}) {} Vector2 Effect::apply(int index, Vector2 input) { - for (int i = 0; i < parameters.size(); i++) { - double weight = parameters[i]->smoothValueChange ? 0.0005 : 1.0; - smoothValues[i] = (1.0 - weight) * smoothValues[i] + weight * parameters[i]->getValueUnnormalised(); - } + animateValues(); if (application) { - return application(index, input, smoothValues, sampleRate); + return application(index, input, actualValues, sampleRate); } else if (effectApplication != nullptr) { - return effectApplication->apply(index, input, smoothValues, sampleRate); + return effectApplication->apply(index, input, actualValues, sampleRate); } return input; } +void Effect::animateValues() { + for (int i = 0; i < parameters.size(); i++) { + auto parameter = parameters[i]; + float minValue = parameter->min; + float maxValue = parameter->max; + float phase = parameter->lfo != nullptr ? nextPhase(parameter) : 0.0; + float percentage = phase / (2 * std::numbers::pi); + LfoType type = parameter->lfo != nullptr ? (LfoType)(int)parameter->lfo->getValueUnnormalised() : LfoType::Static; + + switch (type) { + case LfoType::Sine: + actualValues[i] = std::sin(phase) * 0.5 + 0.5; + actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue; + break; + case LfoType::Square: + actualValues[i] = (percentage < 0.5) ? maxValue : minValue; + break; + case LfoType::Seesaw: + // modified sigmoid function + actualValues[i] = (percentage < 0.5) ? percentage * 2 : (1 - percentage) * 2; + actualValues[i] = 1 / (1 + std::exp(-16 * (actualValues[i] - 0.5))); + actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue; + break; + case LfoType::Triangle: + actualValues[i] = (percentage < 0.5) ? percentage * 2 : (1 - percentage) * 2; + actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue; + break; + case LfoType::Sawtooth: + actualValues[i] = percentage * (maxValue - minValue) + minValue; + break; + case LfoType::ReverseSawtooth: + actualValues[i] = (1 - percentage) * (maxValue - minValue) + minValue; + break; + case LfoType::Noise: + actualValues[i] = ((float)rand() / RAND_MAX) * (maxValue - minValue) + minValue; + break; + default: + double weight = parameter->smoothValueChange ? 0.0005 : 1.0; + actualValues[i] = (1.0 - weight) * actualValues[i] + weight * parameter->getValueUnnormalised(); + break; + } + } +} + +// should only be the audio thread calling this, but either way it's not a big deal +float Effect::nextPhase(EffectParameter* parameter) { + parameter->phase += parameter->lfoRate->getValueUnnormalised() / sampleRate; + + if (parameter->phase > 1) { + parameter->phase -= 1; + } + + return parameter->phase * 2 * std::numbers::pi; +} + void Effect::apply() { apply(0, Vector2()); } @@ -55,16 +108,28 @@ void Effect::setPrecedence(int precedence) { void Effect::addListener(int index, juce::AudioProcessorParameter::Listener* listener) { parameters[index]->addListener(listener); + if (parameters[index]->lfo != nullptr) { + parameters[index]->lfo->addListener(listener); + } + if (parameters[index]->lfoRate != nullptr) { + parameters[index]->lfoRate->addListener(listener); + } if (enabled != nullptr) { enabled->addListener(listener); } } void Effect::removeListener(int index, juce::AudioProcessorParameter::Listener* listener) { - parameters[index]->removeListener(listener); if (enabled != nullptr) { enabled->removeListener(listener); } + if (parameters[index]->lfoRate != nullptr) { + parameters[index]->lfoRate->removeListener(listener); + } + if (parameters[index]->lfo != nullptr) { + parameters[index]->lfo->removeListener(listener); + } + parameters[index]->removeListener(listener); } void Effect::markEnableable(bool enable) { @@ -76,7 +141,7 @@ void Effect::markEnableable(bool enable) { } juce::String Effect::getId() { - return parameters[0]->id; + return parameters[0]->paramID; } juce::String Effect::getName() { diff --git a/Source/audio/Effect.h b/Source/audio/Effect.h index d0e908a5..cad711db 100644 --- a/Source/audio/Effect.h +++ b/Source/audio/Effect.h @@ -14,6 +14,7 @@ public: Effect(std::function&, double)> application, EffectParameter* parameter); Vector2 apply(int index, Vector2 input); + void apply(); double getValue(int index); double getValue(); @@ -33,11 +34,13 @@ public: private: juce::SpinLock listenerLock; - std::vector smoothValues; - double frequency = 1.0; + std::vector actualValues; int precedence = -1; int sampleRate = 192000; std::function&, double)> application; std::shared_ptr effectApplication; + + void animateValues(); + float nextPhase(EffectParameter* parameter); }; \ No newline at end of file diff --git a/Source/audio/EffectParameter.h b/Source/audio/EffectParameter.h index 81b1a537..440e69c5 100644 --- a/Source/audio/EffectParameter.h +++ b/Source/audio/EffectParameter.h @@ -2,82 +2,78 @@ #include "../shape/Vector2.h" #include -class EffectParameter : public juce::AudioProcessorParameter { +class FloatParameter : public juce::AudioProcessorParameterWithID { public: - juce::String name; - juce::String id; - std::atomic min = 0.0; std::atomic max = 1.0; std::atomic step = 0.001; - std::atomic smoothValueChange = true; - EffectParameter(juce::String name, juce::String id, float value, float min, float max, float step = 0.001, bool smoothValueChange = true) : name(name), id(id), value(value), min(min), max(max), step(step), smoothValueChange(smoothValueChange) {} + FloatParameter(juce::String name, juce::String id, float value, float min, float max, float step = 0.001, juce::String label = "") : juce::AudioProcessorParameterWithID(id, name), value(value), min(min), max(max), step(step), label(label) {} juce::String getName(int maximumStringLength) const override { return name.substring(0, maximumStringLength); } juce::String getLabel() const override { - return juce::String(); - } - + return label; + } + // returns value in range [0, 1] float getNormalisedValue(float value) const { // clip value to valid range - auto min = this->min.load(); - auto max = this->max.load(); + auto min = this->min.load(); + auto max = this->max.load(); value = juce::jlimit(min, max, value); // normalize value to range [0, 1] - return (value - min) / (max - min); - } + return (value - min) / (max - min); + } float getUnnormalisedValue(float value) const { - value = juce::jlimit(0.0f, 1.0f, value); + value = juce::jlimit(0.0f, 1.0f, value); auto min = this->min.load(); auto max = this->max.load(); return min + value * (max - min); - } + } float getValue() const override { return getNormalisedValue(value.load()); } float getValueUnnormalised() const { - return value.load(); - } + return value.load(); + } void setValue(float newValue) override { value = getUnnormalisedValue(newValue); - } + } void setValueUnnormalised(float newValue) { - value = newValue; - } + value = newValue; + } void setUnnormalisedValueNotifyingHost(float newValue) { - setValueNotifyingHost(getNormalisedValue(newValue)); - } + setValueNotifyingHost(getNormalisedValue(newValue)); + } float getDefaultValue() const override { - return 0.0f; - } + return 0.0f; + } int getNumSteps() const override { - return (max.load() - min.load()) / step.load(); - } + return (max.load() - min.load()) / step.load(); + } bool isDiscrete() const override { - return false; - } + return false; + } bool isBoolean() const override { - return false; - } + return false; + } bool isOrientationInverted() const override { - return false; - } + return false; + } juce::String getText(float value, int maximumStringLength) const override { auto string = juce::String(getUnnormalisedValue(value), 3); @@ -85,22 +81,211 @@ public: } float getValueForText(const juce::String& text) const override { - return getNormalisedValue(text.getFloatValue()); - } + return getNormalisedValue(text.getFloatValue()); + } bool isAutomatable() const override { - return true; - } + return true; + } bool isMetaParameter() const override { - return false; - } + return false; + } juce::AudioProcessorParameter::Category getCategory() const override { - return juce::AudioProcessorParameter::genericParameter; - } + return juce::AudioProcessorParameter::genericParameter; + } private: // value is not necessarily in the range [min, max] so effect applications may need to clip to a valid range std::atomic value = 0.0; + juce::String label; +}; + +class IntParameter : public juce::AudioProcessorParameterWithID { +public: + std::atomic min = 0; + std::atomic max = 10; + + IntParameter(juce::String name, juce::String id, int value, int min, int max) : AudioProcessorParameterWithID(id, name), value(value), min(min), max(max) {} + + juce::String getName(int maximumStringLength) const override { + return name.substring(0, maximumStringLength); + } + + juce::String getLabel() const override { + return juce::String(); + } + + // returns value in range [0, 1] + float getNormalisedValue(float value) const { + // clip value to valid range + auto min = this->min.load(); + auto max = this->max.load(); + value = juce::jlimit(min, max, (int) value); + // normalize value to range [0, 1] + return (value - min) / (max - min); + } + + float getUnnormalisedValue(float value) const { + value = juce::jlimit(0.0f, 1.0f, value); + auto min = this->min.load(); + auto max = this->max.load(); + return min + value * (max - min); + } + + float getValue() const override { + return getNormalisedValue(value.load()); + } + + float getValueUnnormalised() const { + return value.load(); + } + + void setValue(float newValue) override { + value = getUnnormalisedValue(newValue); + } + + void setValueUnnormalised(float newValue) { + value = newValue; + } + + void setUnnormalisedValueNotifyingHost(float newValue) { + setValueNotifyingHost(getNormalisedValue(newValue)); + } + + float getDefaultValue() const override { + return 0; + } + + int getNumSteps() const override { + return max.load() - min.load() + 1; + } + + bool isDiscrete() const override { + return true; + } + + bool isBoolean() const override { + return false; + } + + bool isOrientationInverted() const override { + return false; + } + + juce::String getText(float value, int maximumStringLength) const override { + auto string = juce::String((int) getUnnormalisedValue(value)); + return string.substring(0, maximumStringLength); + } + + float getValueForText(const juce::String& text) const override { + return getNormalisedValue(text.getIntValue()); + } + + bool isAutomatable() const override { + return true; + } + + bool isMetaParameter() const override { + return false; + } + + juce::AudioProcessorParameter::Category getCategory() const override { + return juce::AudioProcessorParameter::genericParameter; + } + +private: + // value is not necessarily in the range [min, max] so effect applications may need to clip to a valid range + std::atomic value = 0; +}; + +enum class LfoType : int { + Static = 1, + Sine = 2, + Square = 3, + Seesaw = 4, + Triangle = 5, + Sawtooth = 6, + ReverseSawtooth = 7, + Noise = 8 +}; + +class LfoTypeParameter : public IntParameter { +public: + LfoTypeParameter(juce::String name, juce::String id, int value) : IntParameter(name, id, value, 1, 8) {} + + juce::String getText(float value, int maximumStringLength) const override { + switch ((LfoType)(int)getUnnormalisedValue(value)) { + case LfoType::Static: + return "Static"; + case LfoType::Sine: + return "Sine"; + case LfoType::Square: + return "Square"; + case LfoType::Seesaw: + return "Seesaw"; + case LfoType::Triangle: + return "Triangle"; + case LfoType::Sawtooth: + return "Sawtooth"; + case LfoType::ReverseSawtooth: + return "Reverse Sawtooth"; + case LfoType::Noise: + return "Noise"; + default: + return "Unknown"; + } + } + + float getValueForText(const juce::String& text) const override { + if (text == "Static") { + return (int)LfoType::Static; + } else if (text == "Sine") { + return (int)LfoType::Sine; + } else if (text == "Square") { + return (int)LfoType::Square; + } else if (text == "Seesaw") { + return (int)LfoType::Seesaw; + } else if (text == "Triangle") { + return (int)LfoType::Triangle; + } else if (text == "Sawtooth") { + return (int)LfoType::Sawtooth; + } else if (text == "Reverse Sawtooth") { + return (int)LfoType::ReverseSawtooth; + } else if (text == "Noise") { + return (int)LfoType::Noise; + } else { + return (int)LfoType::Static; + } + } +}; + +class EffectParameter : public FloatParameter { +public: + std::atomic smoothValueChange = true; + LfoTypeParameter* lfo = new LfoTypeParameter(name + " LFO", paramID + "Lfo", 1); + FloatParameter* lfoRate = new FloatParameter(name + " LFO Rate", paramID + "LfoRate", 1.0f, 0.0f, 100.0f, 0.1f, "Hz"); + std::atomic phase = 0.0f; + + std::vector getParameters() { + std::vector parameters; + parameters.push_back(this); + if (lfo != nullptr) { + parameters.push_back(lfo); + } + if (lfoRate != nullptr) { + parameters.push_back(lfoRate); + } + return parameters; + } + + void disableLfo() { + delete lfo; + delete lfoRate; + lfo = nullptr; + lfoRate = nullptr; + } + + EffectParameter(juce::String name, juce::String id, float value, float min, float max, float step = 0.001, bool smoothValueChange = true) : FloatParameter(name, id, value, min, max, step), smoothValueChange(smoothValueChange) {} }; \ No newline at end of file diff --git a/Source/components/EffectComponent.cpp b/Source/components/EffectComponent.cpp index 1fddec7d..586fd901 100644 --- a/Source/components/EffectComponent.cpp +++ b/Source/components/EffectComponent.cpp @@ -1,19 +1,33 @@ #include "EffectComponent.h" -EffectComponent::EffectComponent(Effect& effect, int index) : effect(effect), index(index) { +EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect, int index) : effect(effect), index(index), audioProcessor(p) { addAndMakeVisible(slider); + addAndMakeVisible(lfoSlider); addAndMakeVisible(selected); + addAndMakeVisible(lfo); + + lfo.addItem("Static", static_cast(LfoType::Static)); + lfo.addItem("Sine", static_cast(LfoType::Sine)); + lfo.addItem("Square", static_cast(LfoType::Square)); + lfo.addItem("Seesaw", static_cast(LfoType::Seesaw)); + lfo.addItem("Triangle", static_cast(LfoType::Triangle)); + lfo.addItem("Sawtooth", static_cast(LfoType::Sawtooth)); + lfo.addItem("Reverse Sawtooth", static_cast(LfoType::ReverseSawtooth)); + lfo.addItem("Noise", static_cast(LfoType::Noise)); + + lfo.setLookAndFeel(&lfoLookAndFeel); + effect.addListener(index, this); setupComponent(); } -EffectComponent::EffectComponent(Effect& effect, int index, bool checkboxVisible) : EffectComponent(effect, index) { +EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect, int index, bool checkboxVisible) : EffectComponent(p, effect, index) { setCheckboxVisible(checkboxVisible); } -EffectComponent::EffectComponent(Effect& effect) : EffectComponent(effect, 0) {} +EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect) : EffectComponent(p, effect, 0) {} -EffectComponent::EffectComponent(Effect& effect, bool checkboxVisible) : EffectComponent(effect) { +EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect, bool checkboxVisible) : EffectComponent(p, effect) { setCheckboxVisible(checkboxVisible); } @@ -24,11 +38,50 @@ void EffectComponent::setupComponent() { slider.setValue(parameter->getValueUnnormalised(), juce::dontSendNotification); slider.setSliderStyle(juce::Slider::LinearHorizontal); - slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 90, slider.getTextBoxHeight()); + slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 70, slider.getTextBoxHeight()); bool enabled = effect.enabled == nullptr || effect.enabled->getValue(); selected.setToggleState(enabled, juce::dontSendNotification); + lfoEnabled = parameter->lfo != nullptr && parameter->lfoRate != nullptr; + if (lfoEnabled) { + lfo.setSelectedId(parameter->lfo->getValueUnnormalised(), juce::dontSendNotification); + + lfo.onChange = [this]() { + if (lfo.getSelectedId() != 0) { + effect.parameters[index]->lfo->setUnnormalisedValueNotifyingHost(lfo.getSelectedId()); + + if (lfo.getSelectedId() == static_cast(LfoType::Static)) { + lfoSlider.setVisible(false); + slider.setVisible(true); + } else { + lfoSlider.setVisible(true); + slider.setVisible(false); + } + } + }; + + lfoSlider.setRange(parameter->lfoRate->min, parameter->lfoRate->max, parameter->lfoRate->step); + lfoSlider.setValue(parameter->lfoRate->getValueUnnormalised(), juce::dontSendNotification); + + if (lfo.getSelectedId() == static_cast(LfoType::Static)) { + lfoSlider.setVisible(false); + slider.setVisible(true); + } else { + lfoSlider.setVisible(true); + slider.setVisible(false); + } + + lfoSlider.setSliderStyle(juce::Slider::LinearHorizontal); + lfoSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 70, lfoSlider.getTextBoxHeight()); + lfoSlider.setTextValueSuffix("Hz"); + lfoSlider.setColour(juce::Slider::thumbColourId, juce::Colour(0xff00ff00)); + + lfoSlider.onValueChange = [this]() { + effect.parameters[index]->lfoRate->setUnnormalisedValueNotifyingHost(lfoSlider.getValue()); + }; + } + min.textBox.setValue(parameter->min, juce::dontSendNotification); max.textBox.setValue(parameter->max, juce::dontSendNotification); @@ -64,22 +117,28 @@ EffectComponent::~EffectComponent() { effect.removeListener(index, this); } -void EffectComponent::resized() { - auto sliderRight = getWidth() - 160; +void EffectComponent::resized() { auto bounds = getLocalBounds(); auto componentBounds = bounds.removeFromRight(25); if (component != nullptr) { component->setBounds(componentBounds); } - slider.setBounds(bounds.removeFromRight(sliderRight)); + if (lfoEnabled) { + lfo.setBounds(bounds.removeFromRight(100).reduced(5)); + } + + auto checkboxLabel = bounds.removeFromLeft(110); + if (checkboxVisible) { - bounds.removeFromLeft(2); - selected.setBounds(bounds.removeFromLeft(25)); + checkboxLabel.removeFromLeft(2); + selected.setBounds(checkboxLabel.removeFromLeft(25)); } else { - bounds.removeFromLeft(5); + checkboxLabel.removeFromLeft(5); } - textBounds = bounds; + textBounds = checkboxLabel; + slider.setBounds(bounds); + lfoSlider.setBounds(bounds); } void EffectComponent::paint(juce::Graphics& g) { @@ -108,6 +167,11 @@ void EffectComponent::parameterGestureChanged(int parameterIndex, bool gestureIs void EffectComponent::handleAsyncUpdate() { setupComponent(); + juce::SpinLock::ScopedLockType lock1(audioProcessor.parsersLock); + juce::SpinLock::ScopedLockType lock2(audioProcessor.effectsLock); + if (effect.getId().contains("lua")) { + effect.apply(); + } } void EffectComponent::setComponent(std::shared_ptr component) { diff --git a/Source/components/EffectComponent.h b/Source/components/EffectComponent.h index 24e78046..0b16e6ef 100644 --- a/Source/components/EffectComponent.h +++ b/Source/components/EffectComponent.h @@ -4,13 +4,40 @@ #include "../audio/Effect.h" #include "LabelledTextBox.h" +class SmallComboBoxArrow : public juce::LookAndFeel_V4 { + void drawComboBox(juce::Graphics& g, int width, int height, bool, int, int, int, int, juce::ComboBox& box) override { + auto cornerSize = box.findParentComponentOfClass() != nullptr ? 0.0f : 3.0f; + juce::Rectangle boxBounds{0, 0, width, height}; + + g.setColour(box.findColour(juce::ComboBox::backgroundColourId)); + g.fillRoundedRectangle(boxBounds.toFloat(), cornerSize); + + g.setColour(box.findColour(juce::ComboBox::outlineColourId)); + g.drawRoundedRectangle(boxBounds.toFloat().reduced(0.5f, 0.5f), cornerSize, 1.0f); + + juce::Rectangle arrowZone{width - 15, 0, 10, height}; + juce::Path path; + path.startNewSubPath((float) arrowZone.getX(), (float) arrowZone.getCentreY() - 3.0f); + path.lineTo((float) arrowZone.getCentreX(), (float) arrowZone.getCentreY() + 4.0f); + path.lineTo((float) arrowZone.getRight(), (float) arrowZone.getCentreY() - 3.0f); + path.closeSubPath(); + + g.setColour(box.findColour(juce::ComboBox::arrowColourId).withAlpha((box.isEnabled() ? 0.9f : 0.2f))); + g.fillPath(path); + } + + void positionComboBoxText(juce::ComboBox& box, juce::Label& label) { + label.setBounds(1, 1, box.getWidth() - 15, box.getHeight() - 2); + label.setFont(getComboBoxFont(box)); + } +}; class EffectComponent : public juce::Component, public juce::AudioProcessorParameter::Listener, juce::AsyncUpdater { public: - EffectComponent(Effect& effect, int index); - EffectComponent(Effect& effect, int index, bool checkboxVisible); - EffectComponent(Effect& effect); - EffectComponent(Effect& effect, bool checkboxVisible); + EffectComponent(OscirenderAudioProcessor& p, Effect& effect, int index); + EffectComponent(OscirenderAudioProcessor& p, Effect& effect, int index, bool checkboxVisible); + EffectComponent(OscirenderAudioProcessor& p, Effect& effect); + EffectComponent(OscirenderAudioProcessor& p, Effect& effect, bool checkboxVisible); ~EffectComponent(); void resized() override; @@ -24,15 +51,20 @@ public: void setComponent(std::shared_ptr component); juce::Slider slider; + juce::Slider lfoSlider; Effect& effect; int index; juce::ToggleButton selected; + SmallComboBoxArrow lfoLookAndFeel; + juce::ComboBox lfo; private: void setupComponent(); bool checkboxVisible = true; + bool lfoEnabled = true; juce::Rectangle textBounds; std::shared_ptr component; + OscirenderAudioProcessor& audioProcessor; juce::Label popupLabel; LabelledTextBox min{"Min"}; diff --git a/Source/components/EffectsListComponent.cpp b/Source/components/EffectsListComponent.cpp index 6929b92b..df69b414 100644 --- a/Source/components/EffectsListComponent.cpp +++ b/Source/components/EffectsListComponent.cpp @@ -3,7 +3,7 @@ EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr effect) : DraggableListBoxItem(lb, data, rn), effect(effect) { auto parameters = effect->parameters; for (int i = 0; i < parameters.size(); i++) { - std::shared_ptr effectComponent = std::make_shared(*effect, i, i == 0); + std::shared_ptr effectComponent = std::make_shared(data.audioProcessor, *effect, i, i == 0); // using weak_ptr to avoid circular reference and memory leak std::weak_ptr weakEffectComponent = effectComponent; effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification); diff --git a/Source/components/LuaListComponent.cpp b/Source/components/LuaListComponent.cpp index 1ba4aa38..85995a59 100644 --- a/Source/components/LuaListComponent.cpp +++ b/Source/components/LuaListComponent.cpp @@ -1,7 +1,7 @@ #include "LuaListComponent.h" LuaListComponent::LuaListComponent(OscirenderAudioProcessor& p, Effect& effect) { - effectComponent = std::make_shared(effect); + effectComponent = std::make_shared(p, effect); effectComponent->setCheckboxVisible(false); effectComponent->slider.onValueChange = [this, &effect, &p] { @@ -21,27 +21,16 @@ void LuaListComponent::resized() { void paintListBoxItem(int sliderNum, juce::Graphics& g, int width, int height, bool rowIsSelected) {} int LuaListBoxModel::getNumRows() { - return audioProcessor.luaEffects.size() + 1; + return audioProcessor.luaEffects.size(); } void LuaListBoxModel::paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) {} juce::Component* LuaListBoxModel::refreshComponentForRow(int rowNum, bool isRowSelected, juce::Component *existingComponentToUpdate) { - if (rowNum < getNumRows() - 1) { - juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); - std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); - if (juce::isPositiveAndBelow(rowNum, getNumRows())) { - item = std::make_unique(audioProcessor, *audioProcessor.luaEffects[rowNum]); - } - return item.release(); - } else { - juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); - std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); - item = std::make_unique("+"); - item->onClick = [this]() { - audioProcessor.addLuaSlider(); - listBox.updateContent(); - }; - return item.release(); + juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); + std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); + if (juce::isPositiveAndBelow(rowNum, getNumRows())) { + item = std::make_unique(audioProcessor, *audioProcessor.luaEffects[rowNum]); } + return item.release(); } diff --git a/Source/components/LuaListComponent.h b/Source/components/LuaListComponent.h index 8a1a7e2f..eb2bfbce 100644 --- a/Source/components/LuaListComponent.h +++ b/Source/components/LuaListComponent.h @@ -28,7 +28,7 @@ public: juce::Component* refreshComponentForRow(int sliderNum, bool isRowSelected, juce::Component *existingComponentToUpdate) override; private: - int numSliders = 5; + int numSliders = 26; juce::ListBox& listBox; OscirenderAudioProcessor& audioProcessor; }; diff --git a/Source/components/VisualiserComponent.cpp b/Source/components/VisualiserComponent.cpp index 4b6d02f9..33853065 100644 --- a/Source/components/VisualiserComponent.cpp +++ b/Source/components/VisualiserComponent.cpp @@ -86,6 +86,6 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle area double strength = 10; lengthScale = std::log(strength * lengthScale + 1) / std::log(strength + 1); g.setColour(waveformColour.withAlpha(lengthScale)); - g.drawLine(line, 1.0f); + g.drawLine(line, 2.0f); } }