Resize effect component when too small, and add sidechain ability

pull/207/head
James Ball 2024-01-01 15:09:46 +00:00 zatwierdzone przez James H Ball
rodzic 10fecca39e
commit 72cc253159
7 zmienionych plików z 117 dodań i 30 usunięć

Wyświetl plik

@ -235,6 +235,7 @@ void OscirenderAudioProcessor::changeProgramName(int index, const juce::String&
void OscirenderAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) {
currentSampleRate = sampleRate;
volumeBuffer = std::vector<double>(VOLUME_BUFFER_SECONDS * sampleRate, 0);
pitchDetector.setSampleRate(sampleRate);
synth.setCurrentPlaybackSampleRate(sampleRate);
retriggerMidi = true;
@ -571,6 +572,26 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
const double EPSILON = 0.00001;
// update currentVolume by iterating over input channel samples
for (auto sample = 0; sample < buffer.getNumSamples(); ++sample) {
auto left = 0.0;
auto right = 0.0;
if (totalNumInputChannels >= 2) {
left = buffer.getSample(0, sample);
right = buffer.getSample(1, sample);
} else if (totalNumInputChannels == 1) {
left = buffer.getSample(0, sample);
right = buffer.getSample(0, sample);
}
// update volume using a moving average
int oldestBufferIndex = (volumeBufferIndex + 1) % volumeBuffer.size();
currentVolume -= volumeBuffer[oldestBufferIndex] / volumeBuffer.size();
volumeBufferIndex = oldestBufferIndex;
volumeBuffer[volumeBufferIndex] = std::sqrt(left * left + right * right);
currentVolume += volumeBuffer[volumeBufferIndex] / volumeBuffer.size();
}
if (usingInput && totalNumInputChannels >= 2) {
// handle all midi messages
auto midiIterator = midiMessages.cbegin();
@ -586,7 +607,6 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
juce::SpinLock::ScopedLockType lock2(effectsLock);
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
}
midiMessages.clear();
@ -606,12 +626,12 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
if (volume > EPSILON) {
for (auto& effect : toggleableEffects) {
if (effect->enabled->getValue()) {
channels = effect->apply(sample, channels);
channels = effect->apply(sample, channels, currentVolume);
}
}
}
for (auto& effect : permanentEffects) {
channels = effect->apply(sample, channels);
channels = effect->apply(sample, channels, currentVolume);
}
}

Wyświetl plik

@ -332,6 +332,12 @@ private:
AudioWebSocketServer softwareOscilloscopeServer{*this};
ObjectServer objectServer{*this};
const double VOLUME_BUFFER_SECONDS = 0.1;
std::vector<double> volumeBuffer;
int volumeBufferIndex = 0;
double currentVolume = 0;
void updateObjValues();
std::shared_ptr<Effect> getEffect(juce::String id);
BooleanParameter* getBooleanParameter(juce::String id);

Wyświetl plik

@ -9,16 +9,16 @@ Effect::Effect(std::shared_ptr<EffectApplication> effectApplication, const std::
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, const std::vector<EffectParameter*>& parameters) :
Effect::Effect(EffectApplicationType application, const std::vector<EffectParameter*>& parameters) :
application(application),
parameters(parameters),
enabled(nullptr),
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(EffectApplicationType application, EffectParameter* parameter) : Effect(application, std::vector<EffectParameter*>{parameter}) {}
Vector2 Effect::apply(int index, Vector2 input) {
animateValues();
Vector2 Effect::apply(int index, Vector2 input, double volume) {
animateValues(volume);
if (application) {
return application(index, input, actualValues, sampleRate);
} else if (effectApplication != nullptr) {
@ -27,7 +27,7 @@ Vector2 Effect::apply(int index, Vector2 input) {
return input;
}
void Effect::animateValues() {
void Effect::animateValues(double volume) {
for (int i = 0; i < parameters.size(); i++) {
auto parameter = parameters[i];
float minValue = parameter->min;
@ -66,7 +66,13 @@ void Effect::animateValues() {
break;
default:
double weight = parameter->smoothValueChange ? 0.0005 : 1.0;
actualValues[i] = (1.0 - weight) * actualValues[i] + weight * parameter->getValueUnnormalised();
double newValue;
if (parameter->sidechain->getBoolValue()) {
newValue = volume * (maxValue - minValue) + minValue;
} else {
newValue = parameter->getValueUnnormalised();
}
actualValues[i] = (1.0 - weight) * actualValues[i] + weight * newValue;
break;
}
}
@ -132,9 +138,11 @@ void Effect::addListener(int index, juce::AudioProcessorParameter::Listener* lis
if (enabled != nullptr) {
enabled->addListener(listener);
}
parameters[index]->sidechain->addListener(listener);
}
void Effect::removeListener(int index, juce::AudioProcessorParameter::Listener* listener) {
parameters[index]->sidechain->removeListener(listener);
if (enabled != nullptr) {
enabled->removeListener(listener);
}

Wyświetl plik

@ -5,15 +5,16 @@
#include "EffectParameter.h"
#include "BooleanParameter.h"
typedef std::function<Vector2(int index, Vector2 input, const std::vector<double>& values, double sampleRate)> EffectApplicationType;
class Effect {
public:
Effect(std::shared_ptr<EffectApplication> effectApplication, const std::vector<EffectParameter*>& parameters);
Effect(std::shared_ptr<EffectApplication> effectApplication, EffectParameter* parameter);
Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, const std::vector<EffectParameter*>& parameters);
Effect(std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application, EffectParameter* parameter);
Effect(EffectApplicationType application, const std::vector<EffectParameter*>& parameters);
Effect(EffectApplicationType application, EffectParameter* parameter);
Vector2 apply(int index, Vector2 input);
Vector2 apply(int index, Vector2 input, double volume = 0.0);
void apply();
double getValue(int index);
@ -43,10 +44,10 @@ private:
std::vector<double> actualValues;
int precedence = -1;
int sampleRate = 192000;
std::function<Vector2(int, Vector2, const std::vector<double>&, double)> application;
EffectApplicationType application;
std::shared_ptr<EffectApplication> effectApplication;
void animateValues();
void animateValues(double volume);
float nextPhase(EffectParameter* parameter);
};

Wyświetl plik

@ -1,6 +1,7 @@
#pragma once
#include "../shape/Vector2.h"
#include <JuceHeader.h>
#include "BooleanParameter.h"
class FloatParameter : public juce::AudioProcessorParameterWithID {
public:
@ -328,6 +329,7 @@ public:
std::atomic<bool> smoothValueChange = true;
LfoTypeParameter* lfo = new LfoTypeParameter(name + " LFO", paramID + "Lfo", getVersionHint(), 1);
FloatParameter* lfoRate = new FloatParameter(name + " LFO Rate", paramID + "LfoRate", getVersionHint(), 1.0f, 0.0f, 100.0f, 0.1f, "Hz");
BooleanParameter* sidechain = new BooleanParameter(name + " Sidechain Enabled", paramID + "Sidechain", getVersionHint(), false);
std::atomic<float> phase = 0.0f;
juce::String description;
@ -340,6 +342,7 @@ public:
if (lfoRate != nullptr) {
parameters.push_back(lfoRate);
}
parameters.push_back(sidechain);
return parameters;
}
@ -358,16 +361,31 @@ public:
lfo->save(lfoXml);
lfoRate->save(lfoXml);
}
auto sidechainXml = xml->createNewChildElement("sidechain");
sidechain->save(sidechainXml);
}
void load(juce::XmlElement* xml) {
FloatParameter::load(xml);
auto lfoXml = xml->getChildByName("lfo");
if (lfoXml != nullptr) {
lfo->load(lfoXml);
lfoRate->load(lfoXml);
}
if (lfo != nullptr && lfoRate != nullptr) {
auto lfoXml = xml->getChildByName("lfo");
if (lfoXml != nullptr) {
lfo->load(lfoXml);
lfoRate->load(lfoXml);
} else {
lfo->setValueNotifyingHost(lfo->getValueForText("Static"));
lfoRate->setUnnormalisedValueNotifyingHost(1.0f);
}
}
auto sidechainXml = xml->getChildByName("sidechain");
if (sidechainXml != nullptr) {
sidechain->load(sidechainXml);
} else {
sidechain->setBoolValueNotifyingHost(false);
}
}
EffectParameter(juce::String name, juce::String description, juce::String id, int versionHint, float value, float min, float max, float step = 0.01, bool smoothValueChange = true) : FloatParameter(name, id, versionHint, value, min, max, step), smoothValueChange(smoothValueChange), description(description) {}

Wyświetl plik

@ -6,6 +6,17 @@ EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect, in
addChildComponent(lfoSlider);
addAndMakeVisible(lfo);
addAndMakeVisible(label);
addAndMakeVisible(sidechainButton);
slider.setSliderStyle(juce::Slider::LinearHorizontal);
slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, TEXT_BOX_WIDTH, slider.getTextBoxHeight());
lfoSlider.setSliderStyle(juce::Slider::LinearHorizontal);
lfoSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, TEXT_BOX_WIDTH, lfoSlider.getTextBoxHeight());
lfoSlider.setTextValueSuffix("Hz");
lfoSlider.setColour(sliderThumbOutlineColourId, juce::Colour(0xff00ff00));
sidechainButton.setTooltip("When enabled, the volume of the input audio controls the value of the slider, acting like a sidechain effect.");
label.setFont(juce::Font(13.0f));
@ -34,9 +45,6 @@ void EffectComponent::setupComponent() {
slider.setRange(parameter->min, parameter->max, parameter->step);
slider.setValue(parameter->getValueUnnormalised(), juce::dontSendNotification);
slider.setSliderStyle(juce::Slider::LinearHorizontal);
slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 70, slider.getTextBoxHeight());
lfoEnabled = parameter->lfo != nullptr && parameter->lfoRate != nullptr;
if (lfoEnabled) {
lfo.setSelectedId(parameter->lfo->getValueUnnormalised(), juce::dontSendNotification);
@ -66,11 +74,6 @@ void EffectComponent::setupComponent() {
slider.setVisible(false);
}
lfoSlider.setSliderStyle(juce::Slider::LinearHorizontal);
lfoSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 70, lfoSlider.getTextBoxHeight());
lfoSlider.setTextValueSuffix("Hz");
lfoSlider.setColour(sliderThumbOutlineColourId, juce::Colour(0xff00ff00));
lfoSlider.onValueChange = [this]() {
effect.parameters[index]->lfoRate->setUnnormalisedValueNotifyingHost(lfoSlider.getValue());
};
@ -104,6 +107,20 @@ void EffectComponent::setupComponent() {
popupLabel.setText(parameter->name + " Settings", juce::dontSendNotification);
popupLabel.setJustificationType(juce::Justification::centred);
popupLabel.setFont(juce::Font(14.0f, juce::Font::bold));
sidechainButton.onClick = [this] {
effect.parameters[index]->sidechain->setBoolValueNotifyingHost(!effect.parameters[index]->sidechain->getBoolValue());
};
if (effect.parameters[index]->sidechain->getBoolValue()) {
slider.setEnabled(false);
slider.setColour(sliderThumbOutlineColourId, juce::Colour(0xffff0000));
slider.setTooltip("Sidechain effect applied - click the microphone icon to disable this.");
} else {
slider.setEnabled(true);
slider.setColour(sliderThumbOutlineColourId, findColour(sliderThumbOutlineColourId));
slider.setTooltip("");
}
}
@ -118,12 +135,20 @@ void EffectComponent::resized() {
component->setBounds(componentBounds);
}
sidechainButton.setBounds(bounds.removeFromRight(20));
bool drawingSmall = bounds.getWidth() < 3 * TEXT_WIDTH;
if (lfoEnabled) {
lfo.setBounds(bounds.removeFromRight(100).reduced(5));
lfo.setBounds(bounds.removeFromRight(drawingSmall ? 70 : 100).reduced(5));
}
bounds.removeFromLeft(5);
label.setBounds(bounds.removeFromLeft(120));
label.setBounds(bounds.removeFromLeft(drawingSmall ? SMALL_TEXT_WIDTH : TEXT_WIDTH));
slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, drawingSmall ? SMALL_TEXT_BOX_WIDTH : TEXT_BOX_WIDTH, slider.getTextBoxHeight());
lfoSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, drawingSmall ? SMALL_TEXT_BOX_WIDTH : TEXT_BOX_WIDTH, lfoSlider.getTextBoxHeight());
slider.setBounds(bounds);
lfoSlider.setBounds(bounds);
}

Wyświetl plik

@ -3,6 +3,7 @@
#include "../PluginProcessor.h"
#include "../audio/Effect.h"
#include "LabelledTextBox.h"
#include "SvgButton.h"
class EffectComponent : public juce::Component, public juce::AudioProcessorParameter::Listener, juce::AsyncUpdater, public juce::SettableTooltipClient {
public:
@ -22,15 +23,23 @@ public:
juce::Slider slider;
juce::Slider lfoSlider;
Effect& effect;
int index;
int index = 0;
juce::ComboBox lfo;
private:
const int TEXT_BOX_WIDTH = 70;
const int SMALL_TEXT_BOX_WIDTH = 50;
const int TEXT_WIDTH = 120;
const int SMALL_TEXT_WIDTH = 60;
void setupComponent();
bool lfoEnabled = true;
std::shared_ptr<juce::Component> component;
OscirenderAudioProcessor& audioProcessor;
SvgButton sidechainButton{ effect.parameters[index]->name, BinaryData::microphone_svg, "white", "red", effect.parameters[index]->sidechain };
juce::Label popupLabel;
juce::Label label;
LabelledTextBox min{"Min"};