From 447bc7025c59f85bf33745d860ee14f24954a222 Mon Sep 17 00:00:00 2001 From: George Reales Date: Thu, 10 Feb 2022 11:00:39 +0100 Subject: [PATCH 1/5] Update PluginProcessor.cpp Fixed issue on Linux not displaying or having working MIDI XML templates and learn functions --- Source/PluginProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index c8380a5..715b6c6 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -794,7 +794,7 @@ File ObxdAudioProcessor::getBanksFolder() const File ObxdAudioProcessor::getMidiFolder() const { - return getDocumentFolder().getChildFile("Midi"); + return getDocumentFolder().getChildFile("MIDI"); } From 7c371aae296827276e8f2151672d637342e1158d Mon Sep 17 00:00:00 2001 From: George Reales Date: Sun, 20 Feb 2022 19:49:51 +0100 Subject: [PATCH 2/5] Update OB-Xd.jucer --- OB-Xd.jucer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OB-Xd.jucer b/OB-Xd.jucer index d1c57d3..2df16f6 100644 --- a/OB-Xd.jucer +++ b/OB-Xd.jucer @@ -1,6 +1,6 @@ - Date: Sun, 20 Feb 2022 19:51:30 +0100 Subject: [PATCH 3/5] Implemented shift + click to reset panning knobs --- Source/Gui/Knob.h | 22 +++++++++++++++++++++- Source/PluginEditor.cpp | 34 ++++++++++++++++++++++++++++++++++ Source/PluginEditor.h | 6 ++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Source/Gui/Knob.h b/Source/Gui/Knob.h index 8d8b36b..99b75a1 100644 --- a/Source/Gui/Knob.h +++ b/Source/Gui/Knob.h @@ -25,7 +25,7 @@ #include "../Source/Engine/SynthEngine.h" #include "../Components/ScaleComponent.h" class ObxdAudioProcessor; -class Knob : public Slider, public ScalableComponent +class Knob : public Slider, public ScalableComponent, public ActionBroadcaster { juce::String img_name; public: @@ -57,6 +57,18 @@ public: */ repaint(); } + + void mouseDown(const MouseEvent& event) override + { + if (event.mods.isShiftDown()) + { + if (shouldResetOnShiftClick) + { + sendActionMessage(resetActionMessage); + } + } + Slider::mouseDown(event); + } // Source: https://git.iem.at/audioplugins/IEMPluginSuite/-/blob/master/resources/customComponents/ReverseSlider.h public: class KnobAttachment : public juce::AudioProcessorValueTreeState::SliderAttachment @@ -106,9 +118,17 @@ public: } ~Knob() override {}; + + void resetOnShiftClick(bool value, const String& identifier) + { + shouldResetOnShiftClick = value; + resetActionMessage = identifier; + } private: Image kni; int fh, numFr; int w2, h2; + bool shouldResetOnShiftClick{ false }; + String resetActionMessage{}; AudioProcessorParameter* parameter {nullptr}; }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index deb5cb1..86b61da 100755 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -420,35 +420,51 @@ void ObxdAudioProcessorEditor::loadSkin (ObxdAudioProcessor& ownerFilter) if (name == "pan1Knob"){ pan1Knob = addKnob (x, y, d, ownerFilter, PAN1, "1", 0.5); + pan1Knob->resetOnShiftClick(true, Action::panReset); + pan1Knob->addActionListener(this); mappingComps["pan1Knob"] = pan1Knob; } if (name == "pan2Knob"){ pan2Knob = addKnob (x, y, d, ownerFilter, PAN2, "2", 0.5); + pan2Knob->resetOnShiftClick(true, Action::panReset); + pan2Knob->addActionListener(this); mappingComps["pan2Knob"] = pan2Knob; } if (name == "pan3Knob"){ pan3Knob = addKnob (x, y, d, ownerFilter, PAN3, "3", 0.5); + pan3Knob->resetOnShiftClick(true, Action::panReset); + pan3Knob->addActionListener(this); mappingComps["pan3Knob"] = pan3Knob; } if (name == "pan4Knob"){ pan4Knob = addKnob (x, y, d, ownerFilter, PAN4, "4", 0.5); + pan4Knob->resetOnShiftClick(true, Action::panReset); + pan4Knob->addActionListener(this); mappingComps["pan4Knob"] = pan4Knob; } if (name == "pan5Knob"){ pan5Knob = addKnob (x, y, d, ownerFilter, PAN5, "5", 0.5); + pan5Knob->resetOnShiftClick(true, Action::panReset); + pan5Knob->addActionListener(this); mappingComps["pan5Knob"] = pan5Knob; } if (name == "pan6Knob"){ pan6Knob = addKnob (x, y, d, ownerFilter, PAN6, "6", 0.5); + pan6Knob->resetOnShiftClick(true, Action::panReset); + pan6Knob->addActionListener(this); mappingComps["pan6Knob"] = pan6Knob; } if (name == "pan7Knob"){ pan7Knob = addKnob (x, y, d, ownerFilter, PAN7, "7", 0.5); + pan7Knob->resetOnShiftClick(true, Action::panReset); + pan7Knob->addActionListener(this); mappingComps["pan7Knob"] = pan7Knob; } if (name == "pan8Knob"){ pan8Knob = addKnob (x, y, d, ownerFilter, PAN8, "8", 0.5); + pan8Knob->resetOnShiftClick(true, Action::panReset); + pan8Knob->addActionListener(this); mappingComps["pan8Knob"] = pan8Knob; } @@ -730,6 +746,22 @@ TooglableButton* ObxdAudioProcessorEditor::addButton (int x, int y, int w, int h return button; } + +void ObxdAudioProcessorEditor::actionListenerCallback(const String& message) +{ + if (message.equalsIgnoreCase(Action::panReset)) + { + const StringArray parameters{ "pan1Knob", "pan2Knob", "pan3Knob", "pan4Knob", "pan5Knob", "pan6Knob", "pan7Knob", "pan8Knob" }; + for (const auto& parameter : parameters) + { + if (auto* knob = dynamic_cast(mappingComps[parameter])) + { + knob->setValue(knob->getDoubleClickReturnValue()); + } + } + } +} + Rectangle ObxdAudioProcessorEditor::transformBounds(int x, int y, int w, int h) { if (getScaleFactor() == 1.0f) @@ -1449,6 +1481,8 @@ void ObxdAudioProcessorEditor::filesDropped(const StringArray& files, int x, int //createMenu(); } } + +const String ObxdAudioProcessorEditor::Action::panReset{ "panReset" }; /* bool ObxdAudioProcessorEditor::keyPressed(const KeyPress & press) { if (press.getKeyCode() == '+' || press.getKeyCode() == KeyPress::numberPadAdd) diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 06e20ae..5efb1dd 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -55,6 +55,7 @@ class ObxdAudioProcessorEditor : public AudioProcessorEditor // , public Slider::Listener , public Button::Listener // , public ComboBox::Listener + , public ActionListener , public ApplicationCommandTarget , public Timer , public FileDragAndDropTarget @@ -177,6 +178,7 @@ public: { return processor.physicalPixelScaleFactor > 1.0; } + void actionListenerCallback(const String& message) override; private: Rectangle transformBounds(int x, int y, int w, int h); Knob* addKnob (int x, int y, int d, ObxdAudioProcessor& filter, int parameter, String name, float defval); @@ -304,6 +306,10 @@ private: int menuScaleNum; int countTimerForLed = 0; + struct Action + { + static const String panReset; + }; }; #endif // PLUGINEDITOR_H_INCLUDED From 67115a1bf65b4759b406131d0b4797cb003a19ae Mon Sep 17 00:00:00 2001 From: George Reales Date: Sun, 20 Feb 2022 19:53:09 +0100 Subject: [PATCH 4/5] Implemented knob value bubble display --- Source/Gui/Knob.h | 32 +++++++++-- Source/PluginProcessor.cpp | 114 +++++++++++++++++++++++++++++++++++-- Source/PluginProcessor.h | 2 + 3 files changed, 140 insertions(+), 8 deletions(-) diff --git a/Source/Gui/Knob.h b/Source/Gui/Knob.h index 99b75a1..08e7df3 100644 --- a/Source/Gui/Knob.h +++ b/Source/Gui/Knob.h @@ -25,6 +25,24 @@ #include "../Source/Engine/SynthEngine.h" #include "../Components/ScaleComponent.h" class ObxdAudioProcessor; + +class KnobLookAndFeel : public LookAndFeel_V4 +{ +public: + KnobLookAndFeel() + { + setColour(BubbleComponent::ColourIds::backgroundColourId, Colours::white.withAlpha(0.8f)); + setColour(BubbleComponent::ColourIds::outlineColourId, Colours::transparentBlack); + setColour(TooltipWindow::textColourId, Colours::black); + } + int getSliderPopupPlacement(Slider&) override + { + return BubbleComponent::BubblePlacement::above; + } +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(KnobLookAndFeel) +}; + class Knob : public Slider, public ScalableComponent, public ActionBroadcaster { juce::String img_name; @@ -43,8 +61,15 @@ public: h2 = fh; w2 = kni.getWidth(); numFr = kni.getHeight() / h2; - - }; + setPopupDisplayEnabled(true, true, getParentComponent()); + setLookAndFeel(&lookAndFeel); + } + + ~Knob() override + { + setLookAndFeel(nullptr); + } + void scaleFactorChanged() override { kni = getScaledImageFromCache(img_name, getScaleFactor(), getIsHighResolutionDisplay()); @@ -116,8 +141,6 @@ public: int ofs = (int) ((getValue() - getMinimum()) / (getMaximum() - getMinimum()) * (numFr - 1)); g.drawImage (kni, 0, 0, getWidth(), getHeight(), 0, h2 * ofs * getScaleInt(), w2 * getScaleInt(), h2 * getScaleInt()); } - - ~Knob() override {}; void resetOnShiftClick(bool value, const String& identifier) { @@ -131,4 +154,5 @@ private: bool shouldResetOnShiftClick{ false }; String resetActionMessage{}; AudioProcessorParameter* parameter {nullptr}; + KnobLookAndFeel lookAndFeel; }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 715b6c6..02a0f61 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -39,10 +39,12 @@ AudioProcessorValueTreeState::ParameterLayout createParameterLayout() auto name = TRANS (id); auto range = NormalisableRange {0.0f, 1.0f}; auto defaultValue = defaultParams.values[i]; - auto parameter = std::make_unique (id, - name, - range, - defaultValue); + auto parameter = std::make_unique ( + id, name, range, defaultValue, String{}, AudioProcessorParameter::genericParameter, + [=](float value, int /*maxStringLength*/) + { + return ObxdAudioProcessor::getTrueParameterValueFromNormalizedRange(i, value); + }); params.push_back (std::move (parameter)); } @@ -914,6 +916,110 @@ String ObxdAudioProcessor::getEngineParameterId (size_t index) return "Undefined"; } +String ObxdAudioProcessor::getTrueParameterValueFromNormalizedRange(size_t index, float value) +{ + switch (index) + { + // case SELF_OSC_PUSH: return "SelfOscPush"; + // case ENV_PITCH_BOTH: return "EnvPitchBoth"; + // case FENV_INVERT: return "FenvInvert"; + // case PW_OSC2_OFS: return "PwOfs"; + // case LEVEL_DIF: return "LevelDif"; + // case PW_ENV_BOTH: return "PwEnvBoth"; + // case PW_ENV: return "PwEnv"; + // case LFO_SYNC: return "LfoSync"; + // case ECONOMY_MODE: return "EconomyMode"; + // case UNLEARN: return "MidiUnlearn"; + // case MIDILEARN: return "MidiLearn"; + // case VAMPENV: return "VAmpFactor"; + // case VFLTENV: return "VFltFactor"; + // case ASPLAYEDALLOCATION: return "AsPlayedAllocation"; + case BENDLFORATE: return String{ logsc(value, 3, 10), 2 } + " Hz"; + // case FOURPOLE: return "FourPole"; + // case LEGATOMODE: return "LegatoMode"; + // case ENVPITCH: return "EnvelopeToPitch"; + // case OSCQuantize: return "PitchQuant"; + // case VOICE_COUNT: return "VoiceCount"; + // case BANDPASS: return "BandpassBlend"; + // case FILTER_WARM: return "Filter_Warm"; + // case BENDRANGE: return "BendRange"; + // case BENDOSC2: return "BendOsc2Only"; + case OCTAVE: return String{ (roundToInt(value * 4) - 2) * 12.f, 0 } + " Semitones"; + case TUNE: return String{ value * 200 - 100, 1 } + " Cents"; + // case BRIGHTNESS: return "Brightness"; + case NOISEMIX: { + const auto decibels = Decibels::gainToDecibels(logsc(value, 0, 1, 35)); + if (decibels < -80) return "-Inf"; + return String{ decibels, 2 } + " dB"; + } + case OSC1MIX: + case OSC2MIX: { + const auto decibels = Decibels::gainToDecibels(value); + if (decibels < -80) return "-Inf"; + return String{ decibels, 2 } + " dB"; + } + // case MULTIMODE: return "Multimode"; + // case LFOSHWAVE: return "LfoSampleHoldWave"; + // case LFOSINWAVE: return "LfoSineWave"; + // case LFOSQUAREWAVE: return "LfoSquareWave"; + // case LFO1AMT: return "LfoAmount1"; + // case LFO2AMT: return "LfoAmount2"; + // case LFOFILTER: return "LfoFilter"; + // case LFOOSC1: return "LfoOsc1"; + // case LFOOSC2: return "LfoOsc2"; + case LFOFREQ: return String{ logsc(value, 0, 50, 120), 2 } + " Hz"; + // case LFOPW1: return "LfoPw1"; + // case LFOPW2: return "LfoPw2"; + // case PORTADER: return "PortamentoDetune"; + // case FILTERDER: return "FilterDetune"; + // case ENVDER: return "EnvelopeDetune"; + case PAN1: + case PAN2: + case PAN3: + case PAN4: + case PAN5: + case PAN6: + case PAN7: + case PAN8: { + const auto pan = value - 0.5f; + if (pan < 0.f) return String{ pan, 2 } + " (Left)"; + if (pan > 0.f) return String{ pan, 2 } + " (Right)"; + return String{ pan, 2 } + " (Center)"; + } + // case XMOD: return "Xmod"; + // case OSC2HS: return "Osc2HardSync"; + // case OSC1P: return String{ getPitch(value * 48), 2 }; + // case OSC2P: return String{ getPitch(value * 48), 2 }; + // case PORTAMENTO: return "Portamento"; + // case UNISON: return "Unison"; + // case FLT_KF: return "FilterKeyFollow"; + // case PW: return "PulseWidth"; + // case OSC2Saw: return "Osc2Saw"; + // case OSC1Saw: return "Osc1Saw"; + // case OSC1Pul: return "Osc1Pulse"; + // case OSC2Pul: return "Osc2Pulse"; + //case VOLUME: return String{ Decibels::gainToDecibels(linsc(value, 0, 0.30)), 2 }; + // case UDET: return "VoiceDetune"; + // case OSC2_DET: return "Oscillator2detune"; + // case CUTOFF: return "Cutoff"; + // case RESONANCE: return "Resonance"; + // case ENVELOPE_AMT: return "FilterEnvAmount"; + // case LATK: return String{ logsc(value, 4, 60000, 900) / 1000.f, 2}; + // case LDEC: return String{ logsc(value, 4, 60000, 900) / 1000.f, 2}; + // case LSUS: return String{ value, 2}; + // case LREL: return String{ logsc(value, 8, 60000, 900) / 1000.f, 2}; + // case FATK: return String{ logsc(value, 1, 60000, 900) / 1000.f, 2}; + // case FDEC: return String{ logsc(value, 1, 60000, 900) / 1000.f, 2}; + // case FSUS: return String{ value, 2}; + // case FREL: return String{ logsc(value, 1, 60000, 900) / 1000.f, 2 }; + + default: + break; + } + + return String{ value, 2 }; +} + int ObxdAudioProcessor::getParameterIndexFromId (String paramId) { for (size_t i = 0; i < PARAM_COUNT; ++i) diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index cbb0d5f..0656600 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -198,6 +198,8 @@ public: void setGuiSize(const int gui_size); //============================================================================== static String getEngineParameterId (size_t); + static String getTrueParameterValueFromNormalizedRange(size_t, float normalizedValue); + int getParameterIndexFromId (String); void setEngineParameterValue (int, float, bool notifyToHost= false); void parameterChanged (const String&, float) override; From b9b885c1481d9c6c4e0d0d84cce9aec98cf6a0d9 Mon Sep 17 00:00:00 2001 From: George Reales Date: Mon, 28 Feb 2022 18:22:16 +0100 Subject: [PATCH 5/5] Oscillator octave lock using shift and mouse --- Source/Gui/Knob.h | 15 +++++++++++++++ Source/PluginEditor.cpp | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Source/Gui/Knob.h b/Source/Gui/Knob.h index 08e7df3..fbced57 100644 --- a/Source/Gui/Knob.h +++ b/Source/Gui/Knob.h @@ -94,6 +94,19 @@ public: } Slider::mouseDown(event); } + + void mouseDrag(const MouseEvent& event) override + { + Slider::mouseDrag(event); + if (event.mods.isShiftDown()) + { + if (shiftDragCallback) + { + setValue(shiftDragCallback(getValue()), sendNotificationAsync); + } + } + } + // Source: https://git.iem.at/audioplugins/IEMPluginSuite/-/blob/master/resources/customComponents/ReverseSlider.h public: class KnobAttachment : public juce::AudioProcessorValueTreeState::SliderAttachment @@ -147,6 +160,8 @@ public: shouldResetOnShiftClick = value; resetActionMessage = identifier; } + + std::function shiftDragCallback; private: Image kni; int fh, numFr; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 86b61da..95c5981 100755 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -210,6 +210,14 @@ void ObxdAudioProcessorEditor::loadSkin (ObxdAudioProcessor& ownerFilter) } if (name == "osc1PitchKnob"){ osc1PitchKnob = addKnob (x, y, d, ownerFilter, OSC1P, "Osc1Pitch", 0); + osc1PitchKnob->shiftDragCallback = [](double value) + { + if (value < 0.125) return 0.0; + if (value < 0.375) return 0.25; + if (value < 0.625) return 0.5; + if (value < 0.875) return 0.75; + return 1.0; + }; mappingComps["osc1PitchKnob"] = osc1PitchKnob; } if (name == "pulseWidthKnob"){ @@ -218,6 +226,14 @@ void ObxdAudioProcessorEditor::loadSkin (ObxdAudioProcessor& ownerFilter) } if (name == "osc2PitchKnob"){ osc2PitchKnob = addKnob (x, y, d, ownerFilter, OSC2P, "Osc2Pitch", 0); + osc2PitchKnob->shiftDragCallback = [](double value) + { + if (value < 0.125) return 0.0; + if (value < 0.375) return 0.25; + if (value < 0.625) return 0.5; + if (value < 0.875) return 0.75; + return 1.0; + }; mappingComps["osc2PitchKnob"] = osc2PitchKnob; }