kopia lustrzana https://github.com/jameshball/osci-render
commit
ee181b0276
|
@ -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)
|
||||
};
|
|
@ -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)"};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -46,26 +46,26 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
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>(
|
||||
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>(
|
||||
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>(
|
||||
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>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
input.x += values[0];
|
||||
input.y += values[1];
|
||||
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>(
|
||||
std::make_shared<SmoothEffect>(),
|
||||
|
@ -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<LuaEffect>(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
|
||||
|
|
|
@ -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<Effect> rotateY = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& 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<Effect> rotateZ = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& 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<Effect> rotateSpeed = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& 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> delayEffect = std::make_shared<DelayEffect>();
|
||||
|
|
|
@ -42,7 +42,7 @@ public:
|
|||
}
|
||||
|
||||
bool isDiscrete() const override {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isBoolean() const override {
|
||||
|
|
|
@ -1,30 +1,83 @@
|
|||
#include "Effect.h"
|
||||
#include <numbers>
|
||||
|
||||
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::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}) {}
|
||||
|
||||
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() {
|
||||
|
|
|
@ -14,6 +14,7 @@ public:
|
|||
Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, 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<double> smoothValues;
|
||||
double frequency = 1.0;
|
||||
std::vector<double> actualValues;
|
||||
int precedence = -1;
|
||||
int sampleRate = 192000;
|
||||
std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application;
|
||||
|
||||
std::shared_ptr<EffectApplication> effectApplication;
|
||||
|
||||
void animateValues();
|
||||
float nextPhase(EffectParameter* parameter);
|
||||
};
|
|
@ -2,82 +2,78 @@
|
|||
#include "../shape/Vector2.h"
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class EffectParameter : public juce::AudioProcessorParameter {
|
||||
class FloatParameter : public juce::AudioProcessorParameterWithID {
|
||||
public:
|
||||
juce::String name;
|
||||
juce::String id;
|
||||
|
||||
std::atomic<float> min = 0.0;
|
||||
std::atomic<float> max = 1.0;
|
||||
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 {
|
||||
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<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) {}
|
||||
};
|
|
@ -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<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);
|
||||
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<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);
|
||||
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<juce::Component> component) {
|
||||
|
|
|
@ -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<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 {
|
||||
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<juce::Component> 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<int> textBounds;
|
||||
std::shared_ptr<juce::Component> component;
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
|
||||
juce::Label popupLabel;
|
||||
LabelledTextBox min{"Min"};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr<Effect> effect) : DraggableListBoxItem(lb, data, rn), effect(effect) {
|
||||
auto parameters = effect->parameters;
|
||||
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
|
||||
std::weak_ptr<EffectComponent> weakEffectComponent = effectComponent;
|
||||
effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "LuaListComponent.h"
|
||||
|
||||
LuaListComponent::LuaListComponent(OscirenderAudioProcessor& p, Effect& effect) {
|
||||
effectComponent = std::make_shared<EffectComponent>(effect);
|
||||
effectComponent = std::make_shared<EffectComponent>(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<LuaListComponent> item(dynamic_cast<LuaListComponent*>(existingComponentToUpdate));
|
||||
if (juce::isPositiveAndBelow(rowNum, getNumRows())) {
|
||||
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();
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
std::unique_ptr<LuaListComponent> item(dynamic_cast<LuaListComponent*>(existingComponentToUpdate));
|
||||
if (juce::isPositiveAndBelow(rowNum, getNumRows())) {
|
||||
item = std::make_unique<LuaListComponent>(audioProcessor, *audioProcessor.luaEffects[rowNum]);
|
||||
}
|
||||
return item.release();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -86,6 +86,6 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle<float> 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);
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue