From fd8935a5898cdfadd0ccecfd1b693eb78d109ea6 Mon Sep 17 00:00:00 2001 From: James Ball Date: Thu, 21 Dec 2023 14:14:33 +0000 Subject: [PATCH] Slightly improve Lua variable performance, and add control for MIDI voices --- Source/MidiComponent.cpp | 30 +++++++++++++++++++++++++- Source/MidiComponent.h | 2 ++ Source/PluginProcessor.cpp | 22 ++++++++++++++++++- Source/PluginProcessor.h | 2 ++ Source/components/EffectComponent.cpp | 2 +- Source/components/MainMenuBarModel.cpp | 6 +++++- Source/lua/LuaParser.cpp | 25 ++++++++++++++++----- Source/lua/LuaParser.h | 1 + Source/shape/CircleArc.cpp | 8 ++++--- Source/shape/Line.cpp | 8 +++++++ Source/shape/Line.h | 1 + 11 files changed, 95 insertions(+), 12 deletions(-) diff --git a/Source/MidiComponent.cpp b/Source/MidiComponent.cpp index c8450de..ef8fe04 100644 --- a/Source/MidiComponent.cpp +++ b/Source/MidiComponent.cpp @@ -5,14 +5,33 @@ MidiComponent::MidiComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess setText("MIDI Settings"); addAndMakeVisible(midiToggle); + addAndMakeVisible(voicesSlider); + addAndMakeVisible(voicesLabel); addAndMakeVisible(keyboard); midiToggle.setToggleState(audioProcessor.midiEnabled->getBoolValue(), juce::dontSendNotification); + midiToggle.setTooltip("Enable MIDI input for the synth. If disabled, the synth will play a constant tone, as controlled by the frequency slider."); midiToggle.onClick = [this]() { audioProcessor.midiEnabled->setBoolValueNotifyingHost(midiToggle.getToggleState()); }; + audioProcessor.midiEnabled->addListener(this); + + voicesSlider.setRange(1, 16, 1); + voicesSlider.setValue(audioProcessor.voices->getValueUnnormalised(), juce::dontSendNotification); + voicesSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 50, 20); + + voicesLabel.setText("Voices", juce::dontSendNotification); + voicesLabel.attachToComponent(&voicesSlider, true); + voicesLabel.setTooltip("Number of voices for the synth to use. Larger numbers will use more CPU, and may cause audio glitches."); + + voicesSlider.onValueChange = [this]() { + audioProcessor.voices->setUnnormalisedValueNotifyingHost(voicesSlider.getValue()); + }; + + audioProcessor.voices->addListener(this); + addAndMakeVisible(envelope); envelope.setAdsrMode(true); envelope.setEnv(audioProcessor.adsrEnv); @@ -39,6 +58,9 @@ MidiComponent::~MidiComponent() { audioProcessor.sustainLevel->removeListener(this); audioProcessor.releaseTime->removeListener(this); audioProcessor.releaseShape->removeListener(this); + + audioProcessor.midiEnabled->removeListener(this); + audioProcessor.voices->removeListener(this); } void MidiComponent::parameterValueChanged(int parameterIndex, float newValue) { @@ -48,6 +70,9 @@ void MidiComponent::parameterValueChanged(int parameterIndex, float newValue) { void MidiComponent::parameterGestureChanged(int parameterIndex, bool gestureIsStarting) {} void MidiComponent::handleAsyncUpdate() { + midiToggle.setToggleState(audioProcessor.midiEnabled->getBoolValue(), juce::dontSendNotification); + voicesSlider.setValue(audioProcessor.voices->getValueUnnormalised(), juce::dontSendNotification); + Env newEnv = Env( { 0.0, @@ -74,7 +99,10 @@ void MidiComponent::handleAsyncUpdate() { void MidiComponent::resized() { auto area = getLocalBounds().withTrimmedTop(20).reduced(20); - midiToggle.setBounds(area.removeFromTop(30)); + auto topRow = area.removeFromTop(30); + midiToggle.setBounds(topRow.removeFromLeft(120)); + topRow.removeFromLeft(80); + voicesSlider.setBounds(topRow.removeFromLeft(250)); area.removeFromTop(5); keyboard.setBounds(area.removeFromBottom(50)); envelope.setBounds(area); diff --git a/Source/MidiComponent.h b/Source/MidiComponent.h index 6e221e3..3469967 100644 --- a/Source/MidiComponent.h +++ b/Source/MidiComponent.h @@ -20,6 +20,8 @@ private: OscirenderAudioProcessorEditor& pluginEditor; juce::ToggleButton midiToggle{"Enable MIDI"}; + juce::Slider voicesSlider; + juce::Label voicesLabel; juce::MidiKeyboardComponent keyboard{audioProcessor.keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard}; EnvelopeContainerComponent envelope; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 26c3008..753e417 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -157,9 +157,12 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() addParameter(releaseTime); addParameter(releaseShape); - for (int i = 0; i < 4; i++) { + for (int i = 0; i < voices->getValueUnnormalised(); i++) { synth.addVoice(new ShapeVoice(*this)); } + + addParameter(voices); + voices->addListener(this); synth.addSound(defaultSound); } @@ -168,6 +171,7 @@ OscirenderAudioProcessor::~OscirenderAudioProcessor() { for (auto& effect : luaEffects) { effect->removeListener(0, this); } + voices->removeListener(this); } const juce::String OscirenderAudioProcessor::getName() const { @@ -766,6 +770,22 @@ void OscirenderAudioProcessor::parameterValueChanged(int parameterIndex, float n return; } } + + if (parameterIndex == voices->getParameterIndex()) { + int numVoices = voices->getValueUnnormalised(); + // if the number of voices has changed, update the synth without clearing all the voices + if (numVoices != synth.getNumVoices()) { + if (numVoices > synth.getNumVoices()) { + for (int i = synth.getNumVoices(); i < numVoices; i++) { + synth.addVoice(new ShapeVoice(*this)); + } + } else { + for (int i = synth.getNumVoices() - 1; i >= numVoices; i--) { + synth.removeVoice(i); + } + } + } + } } void OscirenderAudioProcessor::parameterGestureChanged(int parameterIndex, bool gestureIsStarting) {} diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 7561c78..9e6eb8c 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -270,6 +270,8 @@ public: juce::MidiKeyboardState keyboardState; + IntParameter* voices = new IntParameter("Voices", "voices", VERSION_HINT, 4, 1, 16); + private: juce::SpinLock consumerLock; std::vector> consumers; diff --git a/Source/components/EffectComponent.cpp b/Source/components/EffectComponent.cpp index 6a69964..6b1ac85 100644 --- a/Source/components/EffectComponent.cpp +++ b/Source/components/EffectComponent.cpp @@ -27,7 +27,7 @@ EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect) : void EffectComponent::setupComponent() { EffectParameter* parameter = effect.parameters[index]; - label.setTooltip(parameter->description); + setTooltip(parameter->description); label.setText(parameter->name, juce::dontSendNotification); label.setInterceptsMouseClicks(false, false); diff --git a/Source/components/MainMenuBarModel.cpp b/Source/components/MainMenuBarModel.cpp index e34e0cd..c8389a0 100644 --- a/Source/components/MainMenuBarModel.cpp +++ b/Source/components/MainMenuBarModel.cpp @@ -2,7 +2,11 @@ #include "../PluginEditor.h" juce::StringArray MainMenuBarModel::getMenuBarNames() { - return juce::StringArray("File", "Options"); + if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) { + return juce::StringArray("File", "Options"); + } else { + return juce::StringArray("File"); + } } juce::PopupMenu MainMenuBarModel::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) { diff --git a/Source/lua/LuaParser.cpp b/Source/lua/LuaParser.cpp index 45312c7..b6c2db8 100644 --- a/Source/lua/LuaParser.cpp +++ b/Source/lua/LuaParser.cpp @@ -59,10 +59,14 @@ void LuaParser::parse(lua_State*& L) { // only the audio thread runs this fuction std::vector LuaParser::run(lua_State*& L, const LuaVariables vars, long& step, double& phase) { + juce::SpinLock::ScopedLockType lock(variableLock); + // if we haven't seen this state before, reset it - if (std::find(seenStates.begin(), seenStates.end(), L) == seenStates.end()) { + int stateIndex = std::find(seenStates.begin(), seenStates.end(), L) - seenStates.begin(); + if (stateIndex == seenStates.size()) { reset(L, script); seenStates.push_back(L); + staleStates.push_back(true); } std::vector values; @@ -79,14 +83,14 @@ std::vector LuaParser::run(lua_State*& L, const LuaVariables vars, long& lua_pushnumber(L, phase); lua_setglobal(L, "phase"); - // this CANNOT run at the same time as setVariable - juce::SpinLock::ScopedTryLockType lock(variableLock); - if (lock.isLocked()) { + if (staleStates[stateIndex]) { + // update variables for (int i = 0; i < variableNames.size(); i++) { lua_pushnumber(L, variables[i]); lua_setglobal(L, variableNames[i].toUTF8()); } - } + staleStates[stateIndex] = false; + } lua_geti(L, LUA_REGISTRYINDEX, functionRef); @@ -147,15 +151,26 @@ void LuaParser::setVariable(juce::String variableName, double value) { break; } } + + bool changed = false; if (index == -1) { // add new variable variableNames.push_back(variableName); variables.push_back(value); + changed = true; } else { // update existing variable + changed = variables[index] != value; variables[index] = value; } + + if (changed) { + // mark all states as stale + for (int i = 0; i < staleStates.size(); i++) { + staleStates[i] = true; + } + } } bool LuaParser::isFunctionValid() { diff --git a/Source/lua/LuaParser.h b/Source/lua/LuaParser.h index 86af0d6..f8cac42 100644 --- a/Source/lua/LuaParser.h +++ b/Source/lua/LuaParser.h @@ -43,4 +43,5 @@ private: std::function errorCallback; juce::String fileName; std::vector seenStates; + std::vector staleStates; }; \ No newline at end of file diff --git a/Source/shape/CircleArc.cpp b/Source/shape/CircleArc.cpp index 420c25a..cb855fc 100644 --- a/Source/shape/CircleArc.cpp +++ b/Source/shape/CircleArc.cpp @@ -58,10 +58,12 @@ double CircleArc::length() { len = 0; // TODO: Replace this, it's stupid. Do a real approximation. int segments = 5; + Vector2 start; + Vector2 end = nextVector(0); for (int i = 0; i < segments; i++) { - Vector2 v1 = nextVector(i / (double) segments); - Vector2 v2 = nextVector((i + 1) / (double) segments); - len += Line(v1.x, v1.y, v2.x, v2.y).length(); + start = end; + end = nextVector((i + 1) / (double) segments); + len += Line::length(start.x, start.y, end.x, end.y); } } return len; diff --git a/Source/shape/Line.cpp b/Source/shape/Line.cpp index 31d7832..bb05e93 100644 --- a/Source/shape/Line.cpp +++ b/Source/shape/Line.cpp @@ -38,6 +38,14 @@ void Line::translate(double x, double y) { y2 += y; } +double Line::length(double x1, double y1, double x2, double y2) { + // Euclidean distance approximation based on octagonal boundary + double dx = std::abs(x2 - x1); + double dy = std::abs(y2 - y1); + + return 0.41 * std::min(dx, dy) + 0.941246 * std::max(dx, dy); +} + double inline Line::length() { if (len < 0) { // Euclidean distance approximation based on octagonal boundary diff --git a/Source/shape/Line.h b/Source/shape/Line.h index 7ff4464..edc36c5 100644 --- a/Source/shape/Line.h +++ b/Source/shape/Line.h @@ -11,6 +11,7 @@ public: void rotate(double theta) override; void scale(double x, double y) override; void translate(double x, double y) override; + static double length(double x1, double y1, double x2, double y2); double length() override; std::unique_ptr clone() override; std::string type() override;