Merge pull request #48 from jameshball/lfo

Introduce effect LFOs/animation
pull/170/head
James H Ball 2023-07-21 11:47:26 +01:00 zatwierdzone przez GitHub
commit ee181b0276
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
15 zmienionych plików z 461 dodań i 112 usunięć

Wyświetl plik

@ -23,7 +23,7 @@ private:
EffectsListBoxModel listBoxModel; EffectsListBoxModel listBoxModel;
DraggableListBox listBox; DraggableListBox listBox;
EffectComponent frequency = EffectComponent(*audioProcessor.frequencyEffect, false); EffectComponent frequency = EffectComponent(audioProcessor, *audioProcessor.frequencyEffect, false);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsComponent) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsComponent)
}; };

Wyświetl plik

@ -17,11 +17,11 @@ private:
OscirenderAudioProcessor& audioProcessor; OscirenderAudioProcessor& audioProcessor;
OscirenderAudioProcessorEditor& pluginEditor; OscirenderAudioProcessorEditor& pluginEditor;
EffectComponent focalLength{*audioProcessor.focalLength, false}; EffectComponent focalLength{audioProcessor, *audioProcessor.focalLength, false};
EffectComponent rotateX{*audioProcessor.rotateX, false}; EffectComponent rotateX{audioProcessor, *audioProcessor.rotateX, false};
EffectComponent rotateY{*audioProcessor.rotateY, false}; EffectComponent rotateY{audioProcessor, *audioProcessor.rotateY, false};
EffectComponent rotateZ{*audioProcessor.rotateZ, false}; EffectComponent rotateZ{audioProcessor, *audioProcessor.rotateZ, false};
EffectComponent rotateSpeed{*audioProcessor.rotateSpeed, false}; EffectComponent rotateSpeed{audioProcessor, *audioProcessor.rotateSpeed, false};
juce::TextButton resetRotation{"Reset Rotation"}; juce::TextButton resetRotation{"Reset Rotation"};
juce::ToggleButton mouseRotate{"Rotate with Mouse (Esc to disable)"}; juce::ToggleButton mouseRotate{"Rotate with Mouse (Esc to disable)"};

Wyświetl plik

@ -74,10 +74,15 @@ void OscirenderAudioProcessorEditor::resized() {
} else { } else {
collapseButton.setBounds(0, 0, 0, 0); collapseButton.setBounds(0, 0, 0, 0);
} }
effects.setBounds(area.removeFromRight(getWidth() / sections)); auto effectsSection = area.removeFromRight(1.2 * getWidth() / sections);
main.setBounds(area.removeFromTop(getHeight() / 2)); main.setBounds(area);
lua.setBounds(area); if (lua.isVisible() || obj.isVisible()) {
obj.setBounds(area); auto altEffectsSection = effectsSection.removeFromBottom(juce::jmin(effectsSection.getHeight() / 2, 300));
lua.setBounds(altEffectsSection);
obj.setBounds(altEffectsSection);
}
effects.setBounds(effectsSection);
} }
void OscirenderAudioProcessorEditor::addCodeEditor(int index) { void OscirenderAudioProcessorEditor::addCodeEditor(int index) {

Wyświetl plik

@ -46,26 +46,26 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
)); ));
toggleableEffects.push_back(std::make_shared<Effect>( toggleableEffects.push_back(std::make_shared<Effect>(
std::make_shared<RotateEffect>(), std::make_shared<RotateEffect>(),
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<Effect>( toggleableEffects.push_back(std::make_shared<Effect>(
std::make_shared<VectorCancellingEffect>(), std::make_shared<VectorCancellingEffect>(),
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<Effect>( toggleableEffects.push_back(std::make_shared<Effect>(
std::make_shared<DistortEffect>(false), std::make_shared<DistortEffect>(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<Effect>( toggleableEffects.push_back(std::make_shared<Effect>(
std::make_shared<DistortEffect>(true), std::make_shared<DistortEffect>(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<Effect>( toggleableEffects.push_back(std::make_shared<Effect>(
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) { [this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
input.x += values[0]; input.x += values[0];
input.y += values[1]; input.y += values[1];
return input; return input;
}, std::vector<EffectParameter*>{new EffectParameter("Translate x", "translateX", 0.0, -1.0, 1.0), new EffectParameter("Translate y", "translateY", 0.0, -1.0, 1.0)} }, std::vector<EffectParameter*>{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<Effect>( toggleableEffects.push_back(std::make_shared<Effect>(
std::make_shared<SmoothEffect>(), std::make_shared<SmoothEffect>(),
@ -97,7 +97,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
permanentEffects.push_back(rotateZ); permanentEffects.push_back(rotateZ);
permanentEffects.push_back(focalLength); permanentEffects.push_back(focalLength);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 26; i++) {
addLuaSlider(); addLuaSlider();
} }
@ -106,8 +106,11 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
effects.insert(effects.end(), luaEffects.begin(), luaEffects.end()); effects.insert(effects.end(), luaEffects.begin(), luaEffects.end());
for (auto effect : effects) { for (auto effect : effects) {
for (auto parameter : effect->parameters) { for (auto effectParameter : effect->parameters) {
addParameter(parameter); auto parameters = effectParameter->getParameters();
for (auto parameter : parameters) {
addParameter(parameter);
}
} }
} }
} }
@ -215,6 +218,9 @@ void OscirenderAudioProcessor::addLuaSlider() {
std::make_shared<LuaEffect>(sliderName, *this), std::make_shared<LuaEffect>(sliderName, *this),
new EffectParameter("Lua " + sliderName, "lua" + sliderName, 0.0, 0.0, 1.0, 0.001, false) 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 // effectsLock should be held when calling this

Wyświetl plik

@ -122,7 +122,7 @@ public:
} }
} }
return input; 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<Effect> rotateY = std::make_shared<Effect>( std::shared_ptr<Effect> rotateY = std::make_shared<Effect>(
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) { [this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
@ -137,7 +137,7 @@ public:
} }
} }
return input; 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<Effect> rotateZ = std::make_shared<Effect>( std::shared_ptr<Effect> rotateZ = std::make_shared<Effect>(
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) { [this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
@ -152,7 +152,7 @@ public:
} }
} }
return input; 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<Effect> rotateSpeed = std::make_shared<Effect>( std::shared_ptr<Effect> rotateSpeed = std::make_shared<Effect>(
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) { [this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
@ -162,7 +162,7 @@ public:
obj->setRotationSpeed(values[0]); obj->setRotationSpeed(values[0]);
} }
return input; 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> delayEffect = std::make_shared<DelayEffect>(); std::shared_ptr<DelayEffect> delayEffect = std::make_shared<DelayEffect>();

Wyświetl plik

@ -42,7 +42,7 @@ public:
} }
bool isDiscrete() const override { bool isDiscrete() const override {
return false; return true;
} }
bool isBoolean() const override { bool isBoolean() const override {

Wyświetl plik

@ -1,30 +1,83 @@
#include "Effect.h" #include "Effect.h"
#include <numbers>
Effect::Effect(std::shared_ptr<EffectApplication> effectApplication, std::vector<EffectParameter*> parameters) : effectApplication(effectApplication), parameters(parameters), enabled(nullptr) { Effect::Effect(std::shared_ptr<EffectApplication> effectApplication, std::vector<EffectParameter*> parameters) : effectApplication(effectApplication), parameters(parameters), enabled(nullptr) {
smoothValues = std::vector<double>(parameters.size(), 0.0); actualValues = std::vector<double>(parameters.size(), 0.0);
} }
Effect::Effect(std::shared_ptr<EffectApplication> effectApplication, EffectParameter* parameter) : Effect(effectApplication, std::vector<EffectParameter*>{parameter}) {} Effect::Effect(std::shared_ptr<EffectApplication> effectApplication, EffectParameter* parameter) : Effect(effectApplication, std::vector<EffectParameter*>{parameter}) {}
Effect::Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, std::vector<EffectParameter*> parameters) : application(application), parameters(parameters), enabled(nullptr) { Effect::Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, std::vector<EffectParameter*> parameters) : application(application), parameters(parameters), enabled(nullptr) {
smoothValues = std::vector<double>(parameters.size(), 0.0); actualValues = std::vector<double>(parameters.size(), 0.0);
} }
Effect::Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, EffectParameter* parameter) : Effect(application, std::vector<EffectParameter*>{parameter}) {} Effect::Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, EffectParameter* parameter) : Effect(application, std::vector<EffectParameter*>{parameter}) {}
Vector2 Effect::apply(int index, Vector2 input) { Vector2 Effect::apply(int index, Vector2 input) {
for (int i = 0; i < parameters.size(); i++) { animateValues();
double weight = parameters[i]->smoothValueChange ? 0.0005 : 1.0;
smoothValues[i] = (1.0 - weight) * smoothValues[i] + weight * parameters[i]->getValueUnnormalised();
}
if (application) { if (application) {
return application(index, input, smoothValues, sampleRate); return application(index, input, actualValues, sampleRate);
} else if (effectApplication != nullptr) { } else if (effectApplication != nullptr) {
return effectApplication->apply(index, input, smoothValues, sampleRate); return effectApplication->apply(index, input, actualValues, sampleRate);
} }
return input; 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() { void Effect::apply() {
apply(0, Vector2()); apply(0, Vector2());
} }
@ -55,16 +108,28 @@ void Effect::setPrecedence(int precedence) {
void Effect::addListener(int index, juce::AudioProcessorParameter::Listener* listener) { void Effect::addListener(int index, juce::AudioProcessorParameter::Listener* listener) {
parameters[index]->addListener(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) { if (enabled != nullptr) {
enabled->addListener(listener); enabled->addListener(listener);
} }
} }
void Effect::removeListener(int index, juce::AudioProcessorParameter::Listener* listener) { void Effect::removeListener(int index, juce::AudioProcessorParameter::Listener* listener) {
parameters[index]->removeListener(listener);
if (enabled != nullptr) { if (enabled != nullptr) {
enabled->removeListener(listener); 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) { void Effect::markEnableable(bool enable) {
@ -76,7 +141,7 @@ void Effect::markEnableable(bool enable) {
} }
juce::String Effect::getId() { juce::String Effect::getId() {
return parameters[0]->id; return parameters[0]->paramID;
} }
juce::String Effect::getName() { juce::String Effect::getName() {

Wyświetl plik

@ -14,6 +14,7 @@ public:
Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, EffectParameter* parameter); Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, EffectParameter* parameter);
Vector2 apply(int index, Vector2 input); Vector2 apply(int index, Vector2 input);
void apply(); void apply();
double getValue(int index); double getValue(int index);
double getValue(); double getValue();
@ -33,11 +34,13 @@ public:
private: private:
juce::SpinLock listenerLock; juce::SpinLock listenerLock;
std::vector<double> smoothValues; std::vector<double> actualValues;
double frequency = 1.0;
int precedence = -1; int precedence = -1;
int sampleRate = 192000; int sampleRate = 192000;
std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application; std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application;
std::shared_ptr<EffectApplication> effectApplication; std::shared_ptr<EffectApplication> effectApplication;
void animateValues();
float nextPhase(EffectParameter* parameter);
}; };

Wyświetl plik

@ -2,82 +2,78 @@
#include "../shape/Vector2.h" #include "../shape/Vector2.h"
#include <JuceHeader.h> #include <JuceHeader.h>
class EffectParameter : public juce::AudioProcessorParameter { class FloatParameter : public juce::AudioProcessorParameterWithID {
public: public:
juce::String name;
juce::String id;
std::atomic<float> min = 0.0; std::atomic<float> min = 0.0;
std::atomic<float> max = 1.0; std::atomic<float> max = 1.0;
std::atomic<float> step = 0.001; std::atomic<float> step = 0.001;
std::atomic<bool> 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 { juce::String getName(int maximumStringLength) const override {
return name.substring(0, maximumStringLength); return name.substring(0, maximumStringLength);
} }
juce::String getLabel() const override { juce::String getLabel() const override {
return juce::String(); return label;
} }
// returns value in range [0, 1] // returns value in range [0, 1]
float getNormalisedValue(float value) const { float getNormalisedValue(float value) const {
// clip value to valid range // clip value to valid range
auto min = this->min.load(); auto min = this->min.load();
auto max = this->max.load(); auto max = this->max.load();
value = juce::jlimit(min, max, value); value = juce::jlimit(min, max, value);
// normalize value to range [0, 1] // normalize value to range [0, 1]
return (value - min) / (max - min); return (value - min) / (max - min);
} }
float getUnnormalisedValue(float value) const { 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 min = this->min.load();
auto max = this->max.load(); auto max = this->max.load();
return min + value * (max - min); return min + value * (max - min);
} }
float getValue() const override { float getValue() const override {
return getNormalisedValue(value.load()); return getNormalisedValue(value.load());
} }
float getValueUnnormalised() const { float getValueUnnormalised() const {
return value.load(); return value.load();
} }
void setValue(float newValue) override { void setValue(float newValue) override {
value = getUnnormalisedValue(newValue); value = getUnnormalisedValue(newValue);
} }
void setValueUnnormalised(float newValue) { void setValueUnnormalised(float newValue) {
value = newValue; value = newValue;
} }
void setUnnormalisedValueNotifyingHost(float newValue) { void setUnnormalisedValueNotifyingHost(float newValue) {
setValueNotifyingHost(getNormalisedValue(newValue)); setValueNotifyingHost(getNormalisedValue(newValue));
} }
float getDefaultValue() const override { float getDefaultValue() const override {
return 0.0f; return 0.0f;
} }
int getNumSteps() const override { int getNumSteps() const override {
return (max.load() - min.load()) / step.load(); return (max.load() - min.load()) / step.load();
} }
bool isDiscrete() const override { bool isDiscrete() const override {
return false; return false;
} }
bool isBoolean() const override { bool isBoolean() const override {
return false; return false;
} }
bool isOrientationInverted() const override { bool isOrientationInverted() const override {
return false; return false;
} }
juce::String getText(float value, int maximumStringLength) const override { juce::String getText(float value, int maximumStringLength) const override {
auto string = juce::String(getUnnormalisedValue(value), 3); auto string = juce::String(getUnnormalisedValue(value), 3);
@ -85,22 +81,211 @@ public:
} }
float getValueForText(const juce::String& text) const override { float getValueForText(const juce::String& text) const override {
return getNormalisedValue(text.getFloatValue()); return getNormalisedValue(text.getFloatValue());
} }
bool isAutomatable() const override { bool isAutomatable() const override {
return true; return true;
} }
bool isMetaParameter() const override { bool isMetaParameter() const override {
return false; return false;
} }
juce::AudioProcessorParameter::Category getCategory() const override { juce::AudioProcessorParameter::Category getCategory() const override {
return juce::AudioProcessorParameter::genericParameter; return juce::AudioProcessorParameter::genericParameter;
} }
private: private:
// value is not necessarily in the range [min, max] so effect applications may need to clip to a valid range // value is not necessarily in the range [min, max] so effect applications may need to clip to a valid range
std::atomic<float> value = 0.0; std::atomic<float> value = 0.0;
juce::String label;
};
class IntParameter : public juce::AudioProcessorParameterWithID {
public:
std::atomic<int> min = 0;
std::atomic<int> 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<int> 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<bool> 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<float> phase = 0.0f;
std::vector<juce::AudioProcessorParameter*> getParameters() {
std::vector<juce::AudioProcessorParameter*> 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) {}
}; };

Wyświetl plik

@ -1,19 +1,33 @@
#include "EffectComponent.h" #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(slider);
addAndMakeVisible(lfoSlider);
addAndMakeVisible(selected); addAndMakeVisible(selected);
addAndMakeVisible(lfo);
lfo.addItem("Static", static_cast<int>(LfoType::Static));
lfo.addItem("Sine", static_cast<int>(LfoType::Sine));
lfo.addItem("Square", static_cast<int>(LfoType::Square));
lfo.addItem("Seesaw", static_cast<int>(LfoType::Seesaw));
lfo.addItem("Triangle", static_cast<int>(LfoType::Triangle));
lfo.addItem("Sawtooth", static_cast<int>(LfoType::Sawtooth));
lfo.addItem("Reverse Sawtooth", static_cast<int>(LfoType::ReverseSawtooth));
lfo.addItem("Noise", static_cast<int>(LfoType::Noise));
lfo.setLookAndFeel(&lfoLookAndFeel);
effect.addListener(index, this); effect.addListener(index, this);
setupComponent(); 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); 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); setCheckboxVisible(checkboxVisible);
} }
@ -24,11 +38,50 @@ void EffectComponent::setupComponent() {
slider.setValue(parameter->getValueUnnormalised(), juce::dontSendNotification); slider.setValue(parameter->getValueUnnormalised(), juce::dontSendNotification);
slider.setSliderStyle(juce::Slider::LinearHorizontal); 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(); bool enabled = effect.enabled == nullptr || effect.enabled->getValue();
selected.setToggleState(enabled, juce::dontSendNotification); 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<int>(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<int>(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); min.textBox.setValue(parameter->min, juce::dontSendNotification);
max.textBox.setValue(parameter->max, juce::dontSendNotification); max.textBox.setValue(parameter->max, juce::dontSendNotification);
@ -64,22 +117,28 @@ EffectComponent::~EffectComponent() {
effect.removeListener(index, this); effect.removeListener(index, this);
} }
void EffectComponent::resized() { void EffectComponent::resized() {
auto sliderRight = getWidth() - 160;
auto bounds = getLocalBounds(); auto bounds = getLocalBounds();
auto componentBounds = bounds.removeFromRight(25); auto componentBounds = bounds.removeFromRight(25);
if (component != nullptr) { if (component != nullptr) {
component->setBounds(componentBounds); component->setBounds(componentBounds);
} }
slider.setBounds(bounds.removeFromRight(sliderRight)); if (lfoEnabled) {
lfo.setBounds(bounds.removeFromRight(100).reduced(5));
}
auto checkboxLabel = bounds.removeFromLeft(110);
if (checkboxVisible) { if (checkboxVisible) {
bounds.removeFromLeft(2); checkboxLabel.removeFromLeft(2);
selected.setBounds(bounds.removeFromLeft(25)); selected.setBounds(checkboxLabel.removeFromLeft(25));
} else { } else {
bounds.removeFromLeft(5); checkboxLabel.removeFromLeft(5);
} }
textBounds = bounds; textBounds = checkboxLabel;
slider.setBounds(bounds);
lfoSlider.setBounds(bounds);
} }
void EffectComponent::paint(juce::Graphics& g) { void EffectComponent::paint(juce::Graphics& g) {
@ -108,6 +167,11 @@ void EffectComponent::parameterGestureChanged(int parameterIndex, bool gestureIs
void EffectComponent::handleAsyncUpdate() { void EffectComponent::handleAsyncUpdate() {
setupComponent(); 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<juce::Component> component) { void EffectComponent::setComponent(std::shared_ptr<juce::Component> component) {

Wyświetl plik

@ -4,13 +4,40 @@
#include "../audio/Effect.h" #include "../audio/Effect.h"
#include "LabelledTextBox.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<juce::ChoicePropertyComponent>() != nullptr ? 0.0f : 3.0f;
juce::Rectangle<int> 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<int> 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 { class EffectComponent : public juce::Component, public juce::AudioProcessorParameter::Listener, juce::AsyncUpdater {
public: public:
EffectComponent(Effect& effect, int index); EffectComponent(OscirenderAudioProcessor& p, Effect& effect, int index);
EffectComponent(Effect& effect, int index, bool checkboxVisible); EffectComponent(OscirenderAudioProcessor& p, Effect& effect, int index, bool checkboxVisible);
EffectComponent(Effect& effect); EffectComponent(OscirenderAudioProcessor& p, Effect& effect);
EffectComponent(Effect& effect, bool checkboxVisible); EffectComponent(OscirenderAudioProcessor& p, Effect& effect, bool checkboxVisible);
~EffectComponent(); ~EffectComponent();
void resized() override; void resized() override;
@ -24,15 +51,20 @@ public:
void setComponent(std::shared_ptr<juce::Component> component); void setComponent(std::shared_ptr<juce::Component> component);
juce::Slider slider; juce::Slider slider;
juce::Slider lfoSlider;
Effect& effect; Effect& effect;
int index; int index;
juce::ToggleButton selected; juce::ToggleButton selected;
SmallComboBoxArrow lfoLookAndFeel;
juce::ComboBox lfo;
private: private:
void setupComponent(); void setupComponent();
bool checkboxVisible = true; bool checkboxVisible = true;
bool lfoEnabled = true;
juce::Rectangle<int> textBounds; juce::Rectangle<int> textBounds;
std::shared_ptr<juce::Component> component; std::shared_ptr<juce::Component> component;
OscirenderAudioProcessor& audioProcessor;
juce::Label popupLabel; juce::Label popupLabel;
LabelledTextBox min{"Min"}; LabelledTextBox min{"Min"};

Wyświetl plik

@ -3,7 +3,7 @@
EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr<Effect> effect) : DraggableListBoxItem(lb, data, rn), effect(effect) { EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr<Effect> effect) : DraggableListBoxItem(lb, data, rn), effect(effect) {
auto parameters = effect->parameters; auto parameters = effect->parameters;
for (int i = 0; i < parameters.size(); i++) { for (int i = 0; i < parameters.size(); i++) {
std::shared_ptr<EffectComponent> effectComponent = std::make_shared<EffectComponent>(*effect, i, i == 0); std::shared_ptr<EffectComponent> effectComponent = std::make_shared<EffectComponent>(data.audioProcessor, *effect, i, i == 0);
// using weak_ptr to avoid circular reference and memory leak // using weak_ptr to avoid circular reference and memory leak
std::weak_ptr<EffectComponent> weakEffectComponent = effectComponent; std::weak_ptr<EffectComponent> weakEffectComponent = effectComponent;
effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification); effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification);

Wyświetl plik

@ -1,7 +1,7 @@
#include "LuaListComponent.h" #include "LuaListComponent.h"
LuaListComponent::LuaListComponent(OscirenderAudioProcessor& p, Effect& effect) { LuaListComponent::LuaListComponent(OscirenderAudioProcessor& p, Effect& effect) {
effectComponent = std::make_shared<EffectComponent>(effect); effectComponent = std::make_shared<EffectComponent>(p, effect);
effectComponent->setCheckboxVisible(false); effectComponent->setCheckboxVisible(false);
effectComponent->slider.onValueChange = [this, &effect, &p] { 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) {} void paintListBoxItem(int sliderNum, juce::Graphics& g, int width, int height, bool rowIsSelected) {}
int LuaListBoxModel::getNumRows() { 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) {} 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) { juce::Component* LuaListBoxModel::refreshComponentForRow(int rowNum, bool isRowSelected, juce::Component *existingComponentToUpdate) {
if (rowNum < getNumRows() - 1) { juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); std::unique_ptr<LuaListComponent> item(dynamic_cast<LuaListComponent*>(existingComponentToUpdate));
std::unique_ptr<LuaListComponent> item(dynamic_cast<LuaListComponent*>(existingComponentToUpdate)); if (juce::isPositiveAndBelow(rowNum, getNumRows())) {
if (juce::isPositiveAndBelow(rowNum, getNumRows())) { item = std::make_unique<LuaListComponent>(audioProcessor, *audioProcessor.luaEffects[rowNum]);
item = std::make_unique<LuaListComponent>(audioProcessor, *audioProcessor.luaEffects[rowNum]);
}
return item.release();
} else {
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
std::unique_ptr<juce::TextButton> item(dynamic_cast<juce::TextButton*>(existingComponentToUpdate));
item = std::make_unique<juce::TextButton>("+");
item->onClick = [this]() {
audioProcessor.addLuaSlider();
listBox.updateContent();
};
return item.release();
} }
return item.release();
} }

Wyświetl plik

@ -28,7 +28,7 @@ public:
juce::Component* refreshComponentForRow(int sliderNum, bool isRowSelected, juce::Component *existingComponentToUpdate) override; juce::Component* refreshComponentForRow(int sliderNum, bool isRowSelected, juce::Component *existingComponentToUpdate) override;
private: private:
int numSliders = 5; int numSliders = 26;
juce::ListBox& listBox; juce::ListBox& listBox;
OscirenderAudioProcessor& audioProcessor; OscirenderAudioProcessor& audioProcessor;
}; };

Wyświetl plik

@ -86,6 +86,6 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle<float> area
double strength = 10; double strength = 10;
lengthScale = std::log(strength * lengthScale + 1) / std::log(strength + 1); lengthScale = std::log(strength * lengthScale + 1) / std::log(strength + 1);
g.setColour(waveformColour.withAlpha(lengthScale)); g.setColour(waveformColour.withAlpha(lengthScale));
g.drawLine(line, 1.0f); g.drawLine(line, 2.0f);
} }
} }