kopia lustrzana https://github.com/jameshball/osci-render
Resize effect component when too small, and add sidechain ability
rodzic
10fecca39e
commit
72cc253159
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"};
|
||||
|
|
Ładowanie…
Reference in New Issue