From 49ed695c2cb25883fb5815ea6c6ce03dc345852b Mon Sep 17 00:00:00 2001 From: user1303836 Date: Fri, 25 Sep 2026 09:25:35 -0400 Subject: [PATCH 1/5] Simplify suppression controls and clarify signal metering --- README.md | 43 +- Source/CMakeLists.txt | 21 +- Source/Metering.h | 86 ++++ Source/PluginEditor.cpp | 906 +++++++++++----------------------- Source/PluginEditor.h | 116 ++--- Source/PluginLookAndFeel.cpp | 502 +++---------------- Source/PluginLookAndFeel.h | 77 +-- Source/PluginProcessor.cpp | 43 +- Source/PluginProcessor.h | 23 +- Tests/CMakeLists.txt | 6 +- Tests/test_clap_gui.cpp | 100 ++++ Tests/test_metering.cpp | 89 ++++ Tests/test_ui.cpp | 733 ++++++++++++++------------- docs/images/suppressor-ui.png | Bin 81611 -> 81840 bytes 14 files changed, 1140 insertions(+), 1605 deletions(-) create mode 100644 Source/Metering.h create mode 100644 Tests/test_clap_gui.cpp create mode 100644 Tests/test_metering.cpp diff --git a/README.md b/README.md index 5dd4f69..f966ed8 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ high-gain amp-sim and reamping chains. It sits on the raw DI *before* the amp sim, strips pickup-borne hiss/buzz/EMI between notes, and leaves the guitar body alone. Formats: **VST3** and **CLAP** (Windows, macOS, Linux). -[![CI](https://github.com/user1303836/suppressor/actions/workflows/ci.yml/badge.svg)](https://github.com/user1303836/suppressor/actions/workflows/ci.yml) +[![CI](https://github.com/affinedsp/suppressor/actions/workflows/ci.yml/badge.svg)](https://github.com/affinedsp/suppressor/actions/workflows/ci.yml)

- Suppressor plugin interface + Suppressor: three essential knobs, input/output peak meters, and a clearly labelled high-band reduction display

## What it does @@ -30,14 +30,28 @@ biquads plus an envelope per channel). ## Controls -| Section | Controls | +| Control | What it does | |---|---| -| **Core** | Strength (crossover 8 kHz → 1.4 kHz), Threshold, Release (2–30 ms, exponential) | -| **Gate** | Reduction / Tight Gate mode, Depth floor, Hysteresis, Hold, Adaptive Release | -| **Detection** | Transient Cue, internal/external detector source, 0/1/2 ms lookahead | -| **Hum Removal** | Learn-and-lock resonant dips on the 50/60 Hz family, strength, harmonic count | -| **Multiband** | Optional 4/6-band learned-threshold mode (advanced; one-split is the default) | -| **Output** | Delta (removed-signal) audition, output gain, detector/GR metering | +| **Threshold** | Sets when highs are reduced. Raise it until noise between notes is suppressed. | +| **Strength** | Sets how much of the high-frequency range is treated (8 kHz → 1.4 kHz). Zero is not bypass. | +| **Release** | Sets how quickly suppression returns after a note (2–30 ms). Increase it to preserve tails. | +| **Listen removed** | Auditions input minus processed audio; switch it off to hear the processed signal. This difference includes filter phase effects, not just noise. | + +Input and output meters show **peak dBFS**. The large readout shows +**high-band gain reduction**, not whole-signal loudness loss; in multiband +sessions it shows the deepest band reduction. Clip warnings, bypass, silence, +learning, and stopped audio are labelled explicitly. Stopped meters clear +instead of holding stale readings. + +Drag a dial to adjust; **Shift-drag** for fine control, **double-click / Return** +for exact entry, and **Alt/Option-click / Home** to reset. Tab and arrow keys +work too. Use your host's bypass to compare with the dry input. + +Existing sessions and automation remain compatible. The additional engine +settings remain available in the host's parameter view, not in the simplified +editor. **Host settings active** indicates non-default legacy settings; hover +for details. In multiband mode, Threshold and Strength are inactive because +that mode uses its own splits and learned thresholds. ## Building @@ -58,6 +72,17 @@ cmake -S . -B build-tests -G Ninja -DSUPPRESSOR_BUILD_PLUGIN=OFF -DSUPPRESSOR_BU cmake --build build-tests && ctest --test-dir build-tests --output-on-failure ``` +### UI and wrapper tests + +```bash +cmake --build build --target SuppressorUITests +ctest --test-dir build --output-on-failure +``` + +These also exercise native VST3/CLAP editor embedding. Linux needs a display +(or `xvfb-run -a`). Set `SUPPRESSOR_UI_CAPTURE_DIR` to an absolute directory to +save real editor captures at 100–200% scaling. + ### CI `.github/workflows/ci.yml` gates every push/PR on: diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 2602190..95c315c 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -8,7 +8,7 @@ juce_add_binary_data(SuppressorAssets juce_add_plugin(Suppressor VERSION 0.1.0 COMPANY_NAME "Suppressor Audio" - COMPANY_WEBSITE "https://github.com/user1303836/suppressor" + COMPANY_WEBSITE "https://github.com/affinedsp/suppressor" COMPANY_EMAIL "dev@suppressor.audio" PLUGIN_MANUFACTURER_CODE SpAu PLUGIN_CODE Supr @@ -70,7 +70,24 @@ if (SUPPRESSOR_BUILD_TESTS) target_compile_definitions(SuppressorUITests PRIVATE JUCE_WEB_BROWSER=0 - JUCE_USE_CURL=0) + JUCE_USE_CURL=0 + JUCE_MODAL_LOOPS_PERMITTED=1 + JUCE_PLUGINHOST_VST3=1) add_test(NAME suppressor-ui-unit COMMAND SuppressorUITests) + set_tests_properties(suppressor-ui-unit PROPERTIES ENVIRONMENT + "SUPPRESSOR_VST3_PATH=$>") + add_dependencies(SuppressorUITests Suppressor_VST3) + if (SUPPRESSOR_ENABLE_CLAP) + target_sources(SuppressorUITests PRIVATE ../Tests/test_clap_gui.cpp) + target_link_libraries(SuppressorUITests PRIVATE clap-core) + add_dependencies(SuppressorUITests Suppressor_CLAP) + if (APPLE) + set(clap_test_path "$") + else() + set(clap_test_path "$") + endif() + set_property(TEST suppressor-ui-unit APPEND PROPERTY ENVIRONMENT + "SUPPRESSOR_CLAP_PATH=${clap_test_path}") + endif() endif() diff --git a/Source/Metering.h b/Source/Metering.h new file mode 100644 index 0000000..319dc54 --- /dev/null +++ b/Source/Metering.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include + +namespace suppressor +{ +struct MeterSnapshot +{ + enum Flag : uint32_t { active = 1, bypassed = 2, removed = 4, multiband = 8, + learning = 16, inputClip = 32, outputClip = 64 }; + float input = 0.0f, output = 0.0f, reduction = 0.0f; + uint32_t flags = 0, sequence = 0; +}; + +// Single audio writer, message-thread readers. Atomic fields make even a +// rejected read data-race-free. The writer never waits for the UI. +class Metering +{ +public: + static_assert (std::atomic::is_always_lock_free); + static_assert (std::atomic::is_always_lock_free); + + void reset() noexcept + { + in = out = reduction = inClipTime = outClipTime = 0.0f; + previousRoute = 0; + publish (0); + } + + void push (float inputPeak, float outputPeak, float reductionDb, + int samples, double sampleRate, uint32_t flags) noexcept + { + if (samples <= 0 || sampleRate <= 0.0) return; + const auto route = flags & (MeterSnapshot::bypassed | MeterSnapshot::removed + | MeterSnapshot::multiband | MeterSnapshot::learning); + if (route != previousRoute) + in = out = reduction = inClipTime = outClipTime = 0.0f; + previousRoute = route; + const auto seconds = static_cast (samples / sampleRate); + const auto decay = std::exp (-seconds / 0.3f); + in = std::max (clean (inputPeak), in * decay); + out = std::max (clean (outputPeak), out * decay); + // Cap the display envelope, not just the drawing. A zero-gain startup + // sample must not leave a >100 dB peak hanging above the real depth. + reduction = (flags & (MeterSnapshot::bypassed | MeterSnapshot::learning)) != 0 + ? 0.0f : std::max (std::clamp (clean (reductionDb), 0.0f, 60.0f), + reduction * std::exp (-seconds / 0.12f)); + inClipTime = inputPeak >= 1.0f ? 1.0f : std::max (0.0f, inClipTime - seconds); + outClipTime = outputPeak >= 1.0f ? 1.0f : std::max (0.0f, outClipTime - seconds); + if (inClipTime > 0.0f) flags |= MeterSnapshot::inputClip; + if (outClipTime > 0.0f) flags |= MeterSnapshot::outputClip; + publish (flags | MeterSnapshot::active); + } + + bool read (MeterSnapshot& result) const noexcept + { + // seq_cst across the atomic fields gives a simple portable coherent + // snapshot, not a seqlock around non-atomic (racy) storage. + for (int attempt = 0; attempt < 3; ++attempt) + { + const auto before = sequence.load(); + if ((before & 1u) != 0) continue; + MeterSnapshot s { input.load(), output.load(), gr.load(), status.load(), before }; + if (before == sequence.load()) { result = s; return true; } + } + return false; + } + +private: + static float clean (float v) noexcept { return std::isfinite (v) ? std::max (0.0f, v) : 0.0f; } + void publish (uint32_t flags) noexcept + { + sequence.fetch_add (1); + input.store (in); output.store (out); gr.store (reduction); status.store (flags); + sequence.fetch_add (1); + } + + float in = 0.0f, out = 0.0f, reduction = 0.0f, inClipTime = 0.0f, outClipTime = 0.0f; + uint32_t previousRoute = 0; + std::atomic input { 0 }, output { 0 }, gr { 0 }; + std::atomic status { 0 }, sequence { 0 }; +}; +} // namespace suppressor diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index ee04536..728530d 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1,668 +1,354 @@ #include "PluginEditor.h" +#include -#include - -class ActivityDisplay::ValueInterface final : public juce::AccessibilityValueInterface +namespace { -public: - explicit ValueInterface (ActivityDisplay& displayToUse) : display (displayToUse) {} - - bool isReadOnly() const override { return true; } - double getCurrentValue() const override { return display.reduction; } - juce::String getCurrentValueAsString() const override - { - return display.accessibleValueText(); - } - void setValue (double) override {} - void setValueAsString (const juce::String&) override {} - AccessibleValueRange getRange() const override { return {}; } +using namespace SuppressorTheme; +const auto bg = canvas, panel = surface, line = divider, text = primaryText, + secondary = secondaryText, neutral = controlOutline; -private: - ActivityDisplay& display; -}; - -void ActivityDisplay::setState (float detectorDb, float gainReductionDb, - float thresholdDb, int bandMode, bool deltaAudition, - bool multibandLearning) +void label (juce::Graphics& g, const juce::String& s, juce::Rectangle r, + float size, juce::Colour colour, juce::Justification align = juce::Justification::left) { - const auto previousStatus = semanticStatus(); - const auto newReduction = juce::jmax (0.0f, -gainReductionDb); - const auto changed = juce::roundToInt (detector * 10.0f) - != juce::roundToInt (detectorDb * 10.0f) - || juce::roundToInt (reduction * 10.0f) - != juce::roundToInt (newReduction * 10.0f) - || juce::roundToInt (threshold * 10.0f) - != juce::roundToInt (thresholdDb * 10.0f) - || mode != bandMode || delta != deltaAudition - || learning != multibandLearning; - - if (! changed) - return; - - detector = detectorDb; - reduction = newReduction; - threshold = thresholdDb; - mode = bandMode; - delta = deltaAudition; - learning = multibandLearning; - setDescription (accessibleValueText()); - repaint(); - - if (semanticStatus() != previousStatus) - if (auto* handler = getAccessibilityHandler()) - handler->notifyAccessibilityEvent (juce::AccessibilityEvent::valueChanged); + g.setColour (colour); + g.setFont (SuppressorTheme::makeFont (size, size >= 20.0f)); + g.drawText (s, r, align); } +float db (float peak) { return juce::Decibels::gainToDecibels (peak, -100.0f); } +juce::String levelText (float peak) { return peak < 0.00001f ? "-inf" : juce::String (db (peak), 1); } +} // namespace -juce::String ActivityDisplay::semanticStatus() const +SuppressorDial::SuppressorDial (juce::AudioProcessorValueTreeState& state, const juce::String& id, + const juce::String& help) + : parameter (*state.getParameter (id)), attachment (state, id, *this) { - if (delta) - return "OUTPUT: REMOVED SIGNAL"; - if (learning) - return "LEARN ARMED"; - if (mode > 0) - return "MULTIBAND MODE"; - if (detector <= -120.0f) - return "NO SIGNAL"; - return reduction > 0.5f ? "REDUCTION OBSERVED" : "NO REDUCTION"; + setComponentID (id); + setName (parameter.getName (64)); + setTitle (getName()); + setDescription (help); + setTooltip (help + " Double-click or Return to type. Shift-drag for fine adjustment. " + "Alt/Option-click to reset; right-click for the menu."); + setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); + setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0); + setTextValueSuffix (" " + parameter.getLabel()); + setRotaryParameters (juce::MathConstants::pi * 1.25f, + juce::MathConstants::pi * 2.75f, true); + setScrollWheelEnabled (false); + setWantsKeyboardFocus (true); + setMouseClickGrabsKeyboardFocus (true); + setDoubleClickReturnValue (false, 0.0); + addChildComponent (entry); + entry.setName (getName() + " exact value"); + entry.setJustification (juce::Justification::centred); + entry.setFont (SuppressorTheme::makeFont (18.0f)); + entry.onReturnKey = [this] { finishEntry (true); }; + entry.onEscapeKey = [this] { finishEntry (false); }; + entry.onFocusLost = [this] { finishEntry (false); }; } -juce::String ActivityDisplay::accessibleValueText() const +SuppressorDial::~SuppressorDial() { cancelInteraction(); } +void SuppressorDial::resized() { - juce::String detectorText; - if (mode > 0) - detectorText = "per-band detector; global detector unavailable"; - else if (detector <= -120.0f) - detectorText = "no input detected"; - else - detectorText = "detector " + juce::String (detector, 1) + " decibels"; - - const auto reductionText = learning - ? juce::String ("gain reduction unavailable while multiband learning is armed; suppression passes while processing") - : reduction >= 60.0f - ? juce::String ("at least 60 decibels gain reduction") - : juce::String (reduction, 1) + " decibels gain reduction"; - return "Last processing snapshot: " + semanticStatus().toLowerCase() - + ", " + detectorText + ", " + reductionText; + juce::Slider::resized(); + entry.setBounds (getLocalBounds().withSizeKeepingCentre (100, 32)); } - -std::unique_ptr ActivityDisplay::createAccessibilityHandler() +void SuppressorDial::setWithGesture (double v) { - return std::make_unique ( - *this, juce::AccessibilityRole::staticText, juce::AccessibilityActions {}, - juce::AccessibilityHandler::Interfaces { std::make_unique (*this) }); + juce::Slider::ScopedDragNotification gesture (*this); + setValue (v, juce::sendNotificationSync); } - -void ActivityDisplay::paint (juce::Graphics& graphics) +void SuppressorDial::mouseDown (const juce::MouseEvent& e) { - auto bounds = getLocalBounds(); - auto statusBounds = bounds.removeFromTop (54); - const auto status = semanticStatus(); - const auto statusColour = status == "NO SIGNAL" || status == "LEARN ARMED" - || status == "OUTPUT: REMOVED SIGNAL" - ? SuppressorTheme::warning - : SuppressorTheme::accent; - - graphics.setColour (statusColour); - graphics.fillRoundedRectangle (static_cast (statusBounds.getX()), - static_cast (statusBounds.getY() + 8), - 4.0f, 31.0f, 2.0f); - graphics.setColour (SuppressorTheme::primaryText); - graphics.setFont (SuppressorTheme::makeFont (27.0f, true, 0.02f)); - graphics.drawFittedText (status, statusBounds.withTrimmedLeft (14), - juce::Justification::centredLeft, 1); - - auto drawCaption = [&graphics] (juce::Rectangle row, const juce::String& title, - const juce::String& value) + if (! isEnabled()) return; + grabKeyboardFocus(); + if (e.mods.isPopupMenu()) { showMenu(); return; } + if (e.mods.isAltDown()) { - graphics.setColour (SuppressorTheme::secondaryText); - graphics.setFont (SuppressorTheme::makeFont (11.5f, true, 0.09f)); - graphics.drawText (title, row.removeFromLeft (row.getWidth() * 2 / 3), - juce::Justification::centredLeft); - graphics.setColour (SuppressorTheme::primaryText); - graphics.setFont (SuppressorTheme::makeFont (14.0f, true)); - graphics.drawText (value, row, juce::Justification::centredRight); - }; - - auto drawMeter = [&graphics] (juce::Rectangle meterBounds, float proportion) - { - auto track = meterBounds.toFloat(); - graphics.setColour (SuppressorTheme::divider); - graphics.fillRoundedRectangle (track, 2.5f); - track.setWidth (track.getWidth() * juce::jlimit (0.0f, 1.0f, proportion)); - graphics.setColour (SuppressorTheme::accent); - graphics.fillRoundedRectangle (track, 2.5f); - }; - - bounds.removeFromTop (4); - auto detectorLabel = bounds.removeFromTop (22); - if (mode > 0) - { - drawCaption (detectorLabel, "DETECTOR", "PER-BAND"); - auto message = bounds.removeFromTop (25); - graphics.setColour (SuppressorTheme::secondaryText); - graphics.setFont (SuppressorTheme::makeFont (13.0f)); - graphics.drawFittedText ("GLOBAL LEVEL NOT EXPOSED", message, - juce::Justification::centredLeft, 1); - bounds.removeFromTop (13); - } - else - { - const auto detectorText = detector <= -120.0f - ? juce::String ("NO SIGNAL") - : juce::String (detector, 1) + " dB"; - drawCaption (detectorLabel, "DETECTOR", detectorText); - auto meter = bounds.removeFromTop (7).reduced (0, 1); - drawMeter (meter, juce::jmap (juce::jlimit (-80.0f, 0.0f, detector), - -80.0f, 0.0f, 0.0f, 1.0f)); - - const auto thresholdPosition = juce::jmap (juce::jlimit (-80.0f, 0.0f, threshold), - -80.0f, 0.0f, 0.0f, 1.0f); - const auto thresholdX = static_cast (meter.getX()) - + static_cast (meter.getWidth()) * thresholdPosition; - graphics.setColour (SuppressorTheme::primaryText); - graphics.drawVerticalLine (juce::roundToInt (thresholdX), - static_cast (meter.getY() - 2), - static_cast (meter.getBottom() + 2)); - bounds.removeFromTop (25); - } - - auto reductionLabel = bounds.removeFromTop (22); - if (learning) - { - drawCaption (reductionLabel, "GAIN REDUCTION", "UNAVAILABLE"); - graphics.setColour (SuppressorTheme::secondaryText); - graphics.setFont (SuppressorTheme::makeFont (13.0f)); - graphics.drawFittedText ("SUPPRESSION PASSES WHILE PROCESSING", - bounds.removeFromTop (25), - juce::Justification::centredLeft, 1); - } - else - { - const auto reductionText = reduction >= 60.0f - ? juce::String ("60+ dB") - : juce::String (reduction, 1) + " dB"; - drawCaption (reductionLabel, "GAIN REDUCTION", reductionText); - auto reductionMeter = bounds.removeFromTop (7).reduced (0, 1); - drawMeter (reductionMeter, juce::jlimit (0.0f, 1.0f, reduction / 60.0f)); + setWithGesture (parameter.convertFrom0to1 (parameter.getDefaultValue())); + return; } - - bounds.removeFromTop (14); - graphics.setColour (SuppressorTheme::secondaryText); - graphics.setFont (SuppressorTheme::makeFont (12.5f)); - graphics.drawFittedText ("UPDATES WHILE PROCESSING", bounds.removeFromTop (18), - juce::Justification::centredLeft, 1); + lastDrag = e.position; + dragProportion = valueToProportionOfLength (getValue()); + drag = std::make_unique (*this); } - -SuppressorEditor::SuppressorEditor (SuppressorProcessor& processorToUse) - : juce::AudioProcessorEditor (&processorToUse), proc (processorToUse) +void SuppressorDial::mouseDrag (const juce::MouseEvent& e) { - setLookAndFeel (&lookAndFeel); - setOpaque (true); - - activityDisplay.setName ("Suppression activity"); - activityDisplay.setTitle ("Suppression activity"); - activityDisplay.setExplicitFocusOrder (nextFocusOrder++); - addAndMakeVisible (activityDisplay); - - addCombo ("gateMode", "Gate Mode"); - addToggle ("adaptiveRelease", "Adaptive Release"); - auto& suppressionStrength = addKnob ("strength", "Strength"); - suppressionStrength.setName ("Suppression Strength"); - suppressionStrength.setTitle ("Suppression Strength"); - addKnob ("threshold", "Threshold"); - addKnob ("release", "Release"); - addKnob ("depth", "Depth"); - addKnob ("hysteresis", "Hysteresis"); - addKnob ("hold", "Hold"); - - addKnob ("cue", "Transient Cue"); - addCombo ("sidechain", "Detector Source"); - addCombo ("lookahead", "Lookahead"); - - addToggle ("humEnable", "Hum Removal"); - addCombo ("humBase", "Mains Family"); - addKnob ("humHarmonics", "Harmonics"); - addKnob ("humStrength", "Hum Strength"); - addAction ("humLearn", humLearnButton, "Learn hum profile"); - - addCombo ("bandMode", "Band Mode"); - addAction ("bandsLearn", bandsLearnButton, "Learn multiband thresholds"); - addKnob ("outputGain", "Output Gain"); - addToggle ("deltaAudition", "Delta Audition"); - - auto setupStatus = [this] (juce::Label& label, const juce::String& name) - { - label.setName (name); - label.setTitle (name); - label.setFont (SuppressorTheme::makeFont (13.0f, true, 0.03f)); - label.setColour (juce::Label::textColourId, SuppressorTheme::secondaryText); - label.setJustificationType (juce::Justification::centredLeft); - label.setMinimumHorizontalScale (0.72f); - addAndMakeVisible (label); - }; - setupStatus (humStatus, "Hum learning status"); - setupStatus (bandsStatus, "Multiband learning status"); - - setSize (1000, 640); - updateModePresentation(); - timerCallback(); - updateTimerState(); + if (drag == nullptr) return; + const auto delta = e.position - lastDrag; + const double sensitivity = e.mods.isShiftDown() ? 2400.0 : 240.0; + // Accumulate sub-step movement so fine drags work on the 0.1 dB grid. + dragProportion = juce::jlimit (0.0, 1.0, dragProportion + (delta.x - delta.y) / sensitivity); + setValue (proportionOfLengthToValue (dragProportion), juce::sendNotificationSync); + lastDrag = e.position; } - -SuppressorEditor::~SuppressorEditor() +void SuppressorDial::mouseUp (const juce::MouseEvent&) { drag.reset(); } +void SuppressorDial::mouseDoubleClick (const juce::MouseEvent& e) { - stopTimer(); - for (const auto& slider : sliders) - slider->endActiveGesture(); - entries.clear(); - setLookAndFeel (nullptr); + if (! e.mods.isAltDown() && ! e.mods.isPopupMenu()) beginEntry(); } - -ParameterSlider& SuppressorEditor::addKnob (const juce::String& parameterId, - const juce::String& labelText) +void SuppressorDial::cancelInteraction() { - sliders.push_back (std::make_unique()); - auto* slider = sliders.back().get(); - addAndMakeVisible (*slider); - slider->setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); - slider->setRotaryParameters (juce::MathConstants::pi * 1.25f, - juce::MathConstants::pi * 2.75f, true); - slider->setTextBoxStyle (juce::Slider::TextBoxBelow, false, 88, 24); - slider->setMouseDragSensitivity (220); - slider->setVelocityBasedMode (false); - slider->setVelocityModeParameters (0.24, 1, 0.0, true, - juce::ModifierKeys::shiftModifier); - slider->setScrollWheelEnabled (false); - slider->setWantsKeyboardFocus (true); - slider->setExplicitFocusOrder (nextFocusOrder++); - slider->setComponentID (parameterId); - slider->setName (labelText); - slider->setTitle (labelText); - slider->setDescription ("Drag to adjust. Shift-drag for fine control. Double-click or press Return for exact entry. Alt/Option-click resets to default."); - slider->setTooltip ("Return/double-click: exact. Shift-drag: fine. Alt/Option-click: reset."); - - auto entry = std::make_unique(); - entry->id = parameterId; - entry->slider = slider; - entry->label = std::make_unique(); - entry->label->setText (labelText.toUpperCase(), juce::dontSendNotification); - entry->label->setFont (SuppressorTheme::makeFont (11.5f, true, 0.08f)); - entry->label->setColour (juce::Label::textColourId, SuppressorTheme::secondaryText); - entry->label->setJustificationType (juce::Justification::centred); - entry->label->setInterceptsMouseClicks (false, false); - entry->label->setAccessible (false); - addAndMakeVisible (*entry->label); - entry->sliderAttachment = std::make_unique< - juce::AudioProcessorValueTreeState::SliderAttachment> (proc.apvts, parameterId, *slider); - if (auto* parameter = proc.apvts.getParameter (parameterId)) - { - slider->setDoubleClickReturnValue ( - true, parameter->convertFrom0to1 (parameter->getDefaultValue()), - juce::ModifierKeys::altModifier); - slider->setKeyboardGestureCallbacks ( - [parameter] { parameter->beginChangeGesture(); }, - [parameter] { parameter->endChangeGesture(); }); - if (parameter->getLabel().isNotEmpty()) - slider->setTextValueSuffix (" " + parameter->getLabel()); - } - entries.push_back (std::move (entry)); - return *slider; + drag.reset(); + finishEntry (false); } - -juce::ComboBox& SuppressorEditor::addCombo (const juce::String& parameterId, - const juce::String& labelText) +void SuppressorDial::enablementChanged() { - comboBoxes.push_back (std::make_unique()); - auto* combo = comboBoxes.back().get(); - if (auto* parameter = dynamic_cast ( - proc.apvts.getParameter (parameterId))) - combo->addItemList (parameter->choices, 1); - combo->setJustificationType (juce::Justification::centredLeft); - combo->setWantsKeyboardFocus (true); - combo->setExplicitFocusOrder (nextFocusOrder++); - combo->setComponentID (parameterId); - combo->setName (labelText); - combo->setTitle (labelText); - combo->setDescription ("Choose " + labelText.toLowerCase()); - combo->setTooltip (combo->getDescription()); - addAndMakeVisible (*combo); - - auto entry = std::make_unique(); - entry->id = parameterId; - entry->combo = combo; - entry->label = std::make_unique(); - entry->label->setText (labelText.toUpperCase(), juce::dontSendNotification); - entry->label->setFont (SuppressorTheme::makeFont (11.5f, true, 0.08f)); - entry->label->setColour (juce::Label::textColourId, SuppressorTheme::secondaryText); - entry->label->setJustificationType (juce::Justification::centredLeft); - entry->label->setInterceptsMouseClicks (false, false); - entry->label->setAccessible (false); - addAndMakeVisible (*entry->label); - entry->comboAttachment = std::make_unique< - juce::AudioProcessorValueTreeState::ComboBoxAttachment> (proc.apvts, parameterId, *combo); - entries.push_back (std::move (entry)); - - if (parameterId == "gateMode" || parameterId == "bandMode") - combo->onChange = [this] - { - updateModePresentation(); - timerCallback(); - }; - - return *combo; + if (! isEnabled()) cancelInteraction(); + repaint(); } - -juce::ToggleButton& SuppressorEditor::addToggle (const juce::String& parameterId, - const juce::String& labelText) +bool SuppressorDial::keyPressed (const juce::KeyPress& key) { - toggleButtons.push_back (std::make_unique (labelText.toUpperCase())); - auto* button = toggleButtons.back().get(); - button->setWantsKeyboardFocus (true); - button->setExplicitFocusOrder (nextFocusOrder++); - button->setComponentID (parameterId); - button->setName (labelText); - button->setTitle (labelText); - button->setTooltip ("Toggle " + labelText.toLowerCase()); - addAndMakeVisible (*button); - - auto entry = std::make_unique(); - entry->id = parameterId; - entry->button = button; - entry->buttonAttachment = std::make_unique< - juce::AudioProcessorValueTreeState::ButtonAttachment> (proc.apvts, parameterId, *button); - entries.push_back (std::move (entry)); - - if (parameterId == "humEnable") - button->onClick = [this] { updateModePresentation(); }; - - return *button; + if (! isEnabled()) return false; + if (key == juce::KeyPress::returnKey) { beginEntry(); return true; } + const int code = key.getKeyCode(); + if (code == juce::KeyPress::upKey || code == juce::KeyPress::rightKey + || code == juce::KeyPress::downKey || code == juce::KeyPress::leftKey) + { + const double step = getComponentID() == "strength" ? 0.01 : 0.1; + const int direction = code == juce::KeyPress::upKey || code == juce::KeyPress::rightKey ? 1 : -1; + const auto fine = key.getModifiers().isShiftDown() && getInterval() <= 0.0 ? 0.1 : 1.0; + setWithGesture (getValue() + direction * step * fine); + return true; + } + if (code == juce::KeyPress::homeKey) + { + setWithGesture (parameter.convertFrom0to1 (parameter.getDefaultValue())); + return true; + } + return juce::Slider::keyPressed (key); } - -void SuppressorEditor::addAction (const juce::String& parameterId, - juce::TextButton& button, - const juce::String& accessibleName) +void SuppressorDial::beginEntry() { - button.setClickingTogglesState (true); - button.setWantsKeyboardFocus (true); - button.setExplicitFocusOrder (nextFocusOrder++); - button.setComponentID (parameterId); - button.setName (accessibleName); - button.setTitle (accessibleName); - button.setTooltip (accessibleName); - addAndMakeVisible (button); - - auto entry = std::make_unique(); - entry->id = parameterId; - entry->button = &button; - entry->buttonAttachment = std::make_unique< - juce::AudioProcessorValueTreeState::ButtonAttachment> (proc.apvts, parameterId, button); - entries.push_back (std::move (entry)); + if (! isEnabled()) return; + drag.reset(); + entry.setText (getTextFromValue (getValue()), false); + entry.setVisible (true); + entry.grabKeyboardFocus(); + entry.selectAll(); } - -SuppressorEditor::Entry* SuppressorEditor::findEntry (const juce::String& parameterId) const +void SuppressorDial::finishEntry (bool commit) { - for (const auto& entry : entries) - if (entry->id == parameterId) - return entry.get(); - return nullptr; + if (! entry.isVisible()) return; + const auto typed = entry.getText().trim(); + entry.setVisible (false); + if (commit) + { + // Accept one finite number plus an optional matching unit. Do not let + // the parameter's permissive parser turn malformed input into zero. + auto number = typed; + const auto unit = parameter.getLabel(); + if (number.endsWithIgnoreCase (unit)) number = number.dropLastCharacters (unit.length()).trimEnd(); + const auto utf8 = number.toRawUTF8(); + char* end = nullptr; + const auto value = std::strtod (utf8, &end); + if (number.containsOnly ("0123456789.+-") && end != utf8 && *end == '\0' && std::isfinite (value)) + { + const double plain = getComponentID() == "strength" ? value / 100.0 : value; + setWithGesture (plain); + } + grabKeyboardFocus(); + } + repaint(); } - -void SuppressorEditor::placeKnob (const juce::String& parameterId, - juce::Rectangle bounds) +void SuppressorDial::showMenu() { - if (auto* entry = findEntry (parameterId)) + juce::PopupMenu menu; + const juce::Component::SafePointer safe (this); + menu.addItem ("Enter value...", [safe] { if (safe != nullptr) safe->beginEntry(); }); + menu.addItem ("Reset to default", [safe] { - entry->label->setBounds (bounds.removeFromTop (18)); - entry->slider->setBounds (bounds); - } + if (safe != nullptr) + safe->setWithGesture (safe->parameter.convertFrom0to1 (safe->parameter.getDefaultValue())); + }); + if (auto* editor = findParentComponentOfClass()) + if (auto* host = editor->getHostContext()) + if (auto hostMenu = host->getContextMenuForParameter (¶meter)) + menu.addSubMenu ("Host", hostMenu->getEquivalentPopupMenu()); + menu.showMenuAsync (juce::PopupMenu::Options().withTargetComponent (this)); } -void SuppressorEditor::placeCombo (const juce::String& parameterId, - juce::Rectangle bounds) +SuppressorEditor::SuppressorEditor (SuppressorProcessor& p) + : juce::AudioProcessorEditor (&p), proc (p), + threshold (p.apvts, "threshold", "Highs below this level are reduced. Raise it until noise between notes is suppressed."), + strength (p.apvts, "strength", "Higher strength suppresses a wider high-frequency range. Zero is not bypass."), + release (p.apvts, "release", "How quickly suppression returns after a note. Increase to preserve longer tails."), + listenAttachment (p.apvts, "deltaAudition", listen) { - if (auto* entry = findEntry (parameterId)) + setLookAndFeel (&theme); + int order = 1; + for (auto* dial : { &threshold, &strength, &release }) + { + addAndMakeVisible (*dial); + dial->setExplicitFocusOrder (order++); + } + listen.setClickingTogglesState (true); + listen.setComponentID ("deltaAudition"); + listen.setTitle ("Listen removed"); + listen.setTooltip ("Audition input minus processed audio, including filter phase differences; not isolated noise. Turn off to hear the processed signal."); + listen.setExplicitFocusOrder (4); + addAndMakeVisible (listen); + for (auto* l : { &status, &meterSummary, &hostSettings }) { - entry->label->setBounds (bounds.getX(), bounds.getY() - 17, - bounds.getWidth(), 15); - entry->combo->setBounds (bounds); + addAndMakeVisible (*l); + l->setFont (SuppressorTheme::makeFont (16.0f)); + l->setBorderSize (juce::BorderSize (0)); } + status.setJustificationType (juce::Justification::centredRight); + status.setName ("Processing status"); + meterSummary.setName ("Signal meters"); + meterSummary.setTooltip ("Input and output: peak across channels, dBFS. Reduction: deepest gate attenuation, not overall loudness loss. 300 ms peak decay; clips held for one second."); + hostSettings.setJustificationType (juce::Justification::centredRight); + hostSettings.setName ("Host settings"); + setSize (640, 460); + // A reopened editor must see a NEW audio block before calling data live. + proc.readMeters (meter); + lastSequence = meter.sequence; + lastUpdateMs = juce::Time::getMillisecondCounterHiRes() - 1000.0; + status.setText ("No audio", juce::dontSendNotification); + timerCallback(); + startTimerHz (30); } - -void SuppressorEditor::placeButton (const juce::String& parameterId, - juce::Rectangle bounds) +SuppressorEditor::~SuppressorEditor() { - if (auto* entry = findEntry (parameterId)) - entry->button->setBounds (bounds); + stopTimer(); + setLookAndFeel (nullptr); } - void SuppressorEditor::resized() { - coreBounds = { 18, 82, 650, 250 }; - activityBounds = { 680, 82, 302, 250 }; - detectionBounds = { 18, 344, 262, 278 }; - humBounds = { 292, 344, 392, 278 }; - outputBounds = { 696, 344, 286, 278 }; - - placeCombo ("gateMode", { coreBounds.getX() + 14, coreBounds.getY() + 51, 168, 34 }); - placeButton ("adaptiveRelease", - { coreBounds.getX() + 196, coreBounds.getY() + 51, 178, 34 }); - - const auto knobWidth = (coreBounds.getWidth() - 28) / 6; - const juce::StringArray coreKnobs { "strength", "threshold", "release", - "depth", "hysteresis", "hold" }; - for (int index = 0; index < coreKnobs.size(); ++index) - placeKnob (coreKnobs[index], - { coreBounds.getX() + 14 + index * knobWidth, - coreBounds.getY() + 94, knobWidth, 142 }); - - auto activityContent = activityBounds.reduced (14); - activityContent.removeFromTop (28); - activityDisplay.setBounds (activityContent); - - placeKnob ("cue", { detectionBounds.getX() + 14, detectionBounds.getY() + 41, - 110, 218 }); - placeCombo ("sidechain", - { detectionBounds.getX() + 138, detectionBounds.getY() + 69, 110, 34 }); - placeCombo ("lookahead", - { detectionBounds.getX() + 138, detectionBounds.getY() + 145, 110, 34 }); - - placeButton ("humEnable", - { humBounds.getX() + 14, humBounds.getY() + 48, 132, 34 }); - placeCombo ("humBase", - { humBounds.getX() + 250, humBounds.getY() + 48, 128, 34 }); - placeKnob ("humHarmonics", - { humBounds.getX() + 14, humBounds.getY() + 96, 108, 164 }); - placeKnob ("humStrength", - { humBounds.getX() + 126, humBounds.getY() + 96, 108, 164 }); - placeButton ("humLearn", - { humBounds.getX() + 250, humBounds.getY() + 111, 128, 36 }); - humStatus.setBounds (humBounds.getX() + 250, humBounds.getY() + 154, 128, 62); - - placeCombo ("bandMode", - { outputBounds.getX() + 14, outputBounds.getY() + 50, 122, 34 }); - placeButton ("bandsLearn", - { outputBounds.getX() + 150, outputBounds.getY() + 50, 122, 34 }); - bandsStatus.setBounds (outputBounds.getX() + 14, outputBounds.getY() + 91, 258, 25); - placeKnob ("outputGain", - { outputBounds.getX() + 14, outputBounds.getY() + 139, 110, 124 }); - placeButton ("deltaAudition", - { outputBounds.getX() + 140, outputBounds.getY() + 175, 132, 38 }); -} - -void SuppressorEditor::updateModePresentation() -{ - const auto bandMode = findEntry ("bandMode")->combo->getSelectedItemIndex(); - const auto gateMode = findEntry ("gateMode")->combo->getSelectedItemIndex(); - const auto humEnabled = findEntry ("humEnable")->button->getToggleState(); - - auto setEmphasis = [this] (const juce::String& parameterId, bool active) - { - if (auto* entry = findEntry (parameterId)) - if (entry->slider != nullptr) - { - entry->slider->setColour (juce::Slider::rotarySliderFillColourId, - active ? SuppressorTheme::accent - : SuppressorTheme::inactiveControl); - entry->slider->repaint(); - } - }; - - setEmphasis ("strength", bandMode <= 0); - setEmphasis ("threshold", bandMode <= 0); - setEmphasis ("depth", gateMode <= 0); - setEmphasis ("humHarmonics", humEnabled); - setEmphasis ("humStrength", humEnabled); - - auto setApplicability = [this] (const juce::String& parameterId, bool active, - const juce::String& inactiveReason) - { - if (auto* entry = findEntry (parameterId)) - if (entry->slider != nullptr) - { - const auto interaction = " Drag to adjust; Shift-drag is fine control; double-click or Return opens exact entry; Alt/Option-click resets."; - const auto description = active - ? entry->slider->getName() + "." + interaction - : "Inactive but editable. " + inactiveReason + interaction; - entry->slider->setDescription (description); - entry->slider->setTooltip (active - ? "Return/double-click: exact. Shift-drag: fine. Alt/Option-click: reset." - : inactiveReason + " Still editable."); - } - }; - - setApplicability ("strength", bandMode <= 0, "Applies only in One-Split mode."); - setApplicability ("threshold", bandMode <= 0, "Global Threshold applies only in One-Split mode."); - setApplicability ("depth", gateMode <= 0, "Tight Gate uses a hard-zero floor; this Reduction depth is retained for automation and preconfiguration."); - setApplicability ("humHarmonics", humEnabled, "Hum Removal is off; this setting is retained."); - setApplicability ("humStrength", humEnabled, "Hum Removal is off; this setting is retained."); -} - -void SuppressorEditor::timerCallback() -{ - const auto desiredIntervalMs = isShowing() ? 50 : 500; - if (getTimerInterval() != desiredIntervalMs) - startTimer (desiredIntervalMs); - - const auto bandMode = juce::jmax (0, findEntry ("bandMode")->combo->getSelectedItemIndex()); - const auto threshold = static_cast (findEntry ("threshold")->slider->getValue()); - const auto deltaAudition = findEntry ("deltaAudition")->button->getToggleState(); - const auto bandsLearning = bandsLearnButton.getToggleState(); - activityDisplay.setState (proc.detectorDb(), proc.gainReductionDb(), threshold, - bandMode, deltaAudition, bandsLearning && bandMode > 0); - - const auto humLearning = humLearnButton.getToggleState(); - humLearnButton.setButtonText (humLearning ? "STOP LEARNING" : "LEARN"); - humLearnButton.setName (humLearning ? "Stop hum learning" : "Learn hum profile"); - humLearnButton.setTitle (humLearnButton.getName()); - humLearnButton.setTooltip (humLearning - ? "Stop the armed request. Capture and hum pass-through occur while processing." - : "Arm hum-profile learning; capture begins while processing."); - humStatus.setText (humLearning ? "ARMED / PASSES IN PROCESS" - : "READY TO LEARN", - juce::dontSendNotification); - - const auto multibandMode = bandMode > 0; - bandsLearnButton.setButtonText (bandsLearning ? "STOP LEARNING" - : multibandMode ? "LEARN" - : "ARM LEARNING"); - bandsLearnButton.setName (bandsLearning ? "Stop multiband learning" - : multibandMode ? "Learn multiband thresholds" - : "Arm multiband learning"); - bandsLearnButton.setTitle (bandsLearnButton.getName()); - bandsLearnButton.setTooltip ( - bandsLearning - ? multibandMode - ? "Stop the armed request. Suppression passes while learning during processing." - : "Stop the armed request; it is waiting for 4/6-Band." - : multibandMode - ? "Arm multiband learning; suppression passes while processing." - : "Arm multiband learning now; capture waits for 4/6-Band processing."); - if (bandsLearning) - bandsStatus.setText (multibandMode ? "ARMED / PASSES WHILE PROCESSING" - : "ARMED / WAITING FOR 4/6-BAND", - juce::dontSendNotification); - else - bandsStatus.setText (multibandMode ? "READY TO LEARN" - : "CAN ARM FOR 4/6-BAND", - juce::dontSendNotification); - - updateModePresentation(); + status.setBounds (365, 24, 251, 28); + meterSummary.setBounds (36, 224, 355, 18); + threshold.setBounds (59, 277, 122, 116); + strength.setBounds (259, 277, 122, 116); + release.setBounds (459, 277, 122, 116); + listen.setBounds (24, 412, 160, 32); + hostSettings.setBounds (464, 419, 152, 20); } - void SuppressorEditor::visibilityChanged() { - updateTimerState(); + if (! isShowing()) + for (auto* dial : { &threshold, &strength, &release }) dial->cancelInteraction(); } - -void SuppressorEditor::updateTimerState() +int SuppressorEditor::getControlParameterIndex (juce::Component& c) { - startTimer (isShowing() ? 50 : 500); + for (auto* dial : { &threshold, &strength, &release }) + if (&c == dial || dial->isParentOf (&c)) return dial->parameter.getParameterIndex(); + if (&c == &listen || listen.isParentOf (&c)) + return proc.apvts.getParameter ("deltaAudition")->getParameterIndex(); + return -1; } - -int SuppressorEditor::getControlParameterIndex (juce::Component& component) +void SuppressorEditor::timerCallback() { - for (const auto& entry : entries) + const auto now = juce::Time::getMillisecondCounterHiRes(); + suppressor::MeterSnapshot next; + if (proc.readMeters (next) && next.sequence != lastSequence) { - auto* control = entry->slider != nullptr - ? static_cast (entry->slider) - : (entry->combo != nullptr - ? static_cast (entry->combo) - : static_cast (entry->button)); - if (control == &component || control->isParentOf (&component)) - return proc.getParameters().indexOf (proc.apvts.getParameter (entry->id)); + meter = next; + lastSequence = next.sequence; + lastUpdateMs = now; } - - return -1; + fresh = (meter.flags & suppressor::MeterSnapshot::active) != 0 && now - lastUpdateMs < 500.0; + // A parameter change may precede the next audio block (or happen while + // stopped). Do not relabel old output/reduction as the new routing mode. + if ((meter.flags & suppressor::MeterSnapshot::bypassed) == 0) + fresh = fresh + && (((meter.flags & suppressor::MeterSnapshot::removed) != 0) == listen.getToggleState()) + && (((meter.flags & suppressor::MeterSnapshot::multiband) != 0) + == (proc.apvts.getRawParameterValue ("bandMode")->load() > 0.5f)); + if (! isShowing()) return; + + using M = suppressor::MeterSnapshot; + const bool bypass = fresh && (meter.flags & M::bypassed) != 0; + const bool learning = fresh && (meter.flags & M::learning) != 0; + const bool audition = listen.getToggleState() && ! bypass; + listen.setButtonText (listen.getToggleState() ? "Stop listening" : "Listen removed"); + juce::String state = ! fresh ? "No audio" : bypass ? "Bypassed" : learning ? "Learning bands" + : meter.input < 0.00001f ? "No input" : audition ? "Listening to difference" + : meter.reduction > 0.5f ? "Suppressing" : "Passing signal"; + status.setText (state, juce::dontSendNotification); + status.setColour (juce::Label::textColourId, audition ? warning : text); + const bool multi = proc.apvts.getRawParameterValue ("bandMode")->load() > 0.5f; + threshold.setEnabled (! multi); + strength.setEnabled (! multi); + + juce::StringArray changed; + for (auto* param : proc.getParameters()) + if (auto* ranged = dynamic_cast (param)) + if (ranged->paramID != "threshold" && ranged->paramID != "strength" + && ranged->paramID != "release" && ranged->paramID != "deltaAudition" + && std::abs (ranged->getValue() - ranged->getDefaultValue()) > 0.00001f) + changed.add (ranged->getName (64) + ": " + ranged->getCurrentValueAsText()); + hostSettings.setText (changed.isEmpty() ? "" : "Host settings active", juce::dontSendNotification); + hostSettings.setTooltip ("Legacy settings are preserved. Edit them in your host's parameter view.\n" + changed.joinIntoString ("\n")); + hostSettings.setDescription (hostSettings.getTooltip()); + meterSummary.setText ("PEAK LEVEL / dBFS", juce::dontSendNotification); + meterSummary.setDescription (! fresh ? "No current audio readings" : + "Input " + levelText (meter.input) + " dBFS, " + (audition ? "removed " : "output ") + + levelText (meter.output) + " dBFS. " + (bypass ? "Bypassed." : learning ? "Learning; reduction unavailable." + : meter.input < 0.00001f ? "No input; reduction unavailable." + : (multi ? "Maximum band reduction " : "High-band reduction ") + + (meter.reduction >= 60.0f ? "at least 60" : juce::String (meter.reduction, 1)) + " dB.")); + if (fresh) + { + auto description = meterSummary.getDescription(); + if ((meter.flags & M::inputClip) != 0) description += " Input clipped."; + if ((meter.flags & M::outputClip) != 0) description += " Output clipped."; + meterSummary.setDescription (description); + } + repaint(); } - -void SuppressorEditor::paintPanel (juce::Graphics& graphics, - juce::Rectangle bounds, - const juce::String& title) const +void SuppressorEditor::drawLevel (juce::Graphics& g, int y, const juce::String& name, float peak, bool clip) { - const auto panel = bounds.toFloat(); - graphics.setColour (SuppressorTheme::surface); - graphics.fillRoundedRectangle (panel, SuppressorTheme::cornerRadius); - graphics.setColour (SuppressorTheme::divider); - graphics.drawRoundedRectangle (panel.reduced (0.5f), SuppressorTheme::cornerRadius, 1.0f); - - graphics.setColour (SuppressorTheme::secondaryText); - graphics.setFont (SuppressorTheme::makeFont (11.5f, true, 0.10f)); - graphics.drawText (title, bounds.getX() + 14, bounds.getY() + 8, - bounds.getWidth() - 28, 15, juce::Justification::centredLeft); - graphics.setColour (SuppressorTheme::divider); - graphics.drawHorizontalLine (bounds.getY() + 29, - static_cast (bounds.getX() + 14), - static_cast (bounds.getRight() - 14)); + label (g, name, { 36, y, 66, 20 }, 16.0f, secondary); + const auto track = juce::Rectangle (106.0f, (float) y + 6.0f, 205.0f, 8.0f); + g.setColour (line); g.fillRoundedRectangle (track, 2.0f); + if (fresh) + { + g.setColour (name == "Input" ? secondary : accent); + if (name == "Removed") g.setColour (warning); + g.fillRoundedRectangle (track.withWidth (205.0f * juce::jlimit (0.0f, 1.0f, (db (peak) + 60.0f) / 60.0f)), 2.0f); + } + label (g, fresh ? levelText (peak) : "--", { 322, y - 1, 66, 22 }, 19.0f, text, juce::Justification::right); + if (fresh && clip) label (g, "CLIP", { 319, y + 19, 69, 15 }, 14.0f, error, juce::Justification::right); } - -void SuppressorEditor::paint (juce::Graphics& graphics) +void SuppressorEditor::paint (juce::Graphics& g) { - graphics.fillAll (SuppressorTheme::canvas); - graphics.setColour (SuppressorTheme::header); - graphics.fillRect (0, 0, getWidth(), 64); - graphics.setColour (SuppressorTheme::divider); - graphics.drawHorizontalLine (63, 0.0f, static_cast (getWidth())); - - graphics.setColour (SuppressorTheme::accent); - graphics.fillRoundedRectangle (18.0f, 16.0f, 5.0f, 32.0f, 2.5f); - graphics.setColour (SuppressorTheme::primaryText); - graphics.setFont (SuppressorTheme::makeFont (24.0f, true, 0.03f)); - graphics.drawText ("SUPPRESSOR", 36, 8, 220, 29, juce::Justification::centredLeft); - graphics.setColour (SuppressorTheme::secondaryText); - graphics.setFont (SuppressorTheme::makeFont (11.0f, true, 0.11f)); - graphics.drawText ("FREQUENCY-SPLIT NOISE CONTROL", 36, 35, 300, 15, - juce::Justification::centredLeft); - graphics.drawText ("DYNAMIC HIGH-BAND PROCESSOR", getWidth() - 300, 24, 282, 16, - juce::Justification::centredRight); - - paintPanel (graphics, coreBounds, "SUPPRESSION CORE"); - paintPanel (graphics, activityBounds, "ACTIVITY / LAST PROCESSING SNAPSHOT"); - paintPanel (graphics, detectionBounds, "DETECTION"); - paintPanel (graphics, humBounds, "HUM FILTER"); - paintPanel (graphics, outputBounds, "MULTIBAND / OUTPUT"); - - graphics.setColour (SuppressorTheme::divider); - graphics.drawHorizontalLine (outputBounds.getY() + 128, - static_cast (outputBounds.getX() + 14), - static_cast (outputBounds.getRight() - 14)); + using M = suppressor::MeterSnapshot; + const bool bypass = fresh && (meter.flags & M::bypassed) != 0; + const bool multi = proc.apvts.getRawParameterValue ("bandMode")->load() > 0.5f; + const bool learning = fresh && (meter.flags & M::learning) != 0; + const bool audition = listen.getToggleState() && ! bypass; + const bool hasReduction = fresh && ! learning && (bypass || meter.input >= 0.00001f); + g.fillAll (bg); + label (g, "suppressor", { 24, 19, 270, 32 }, 30.0f, text); + label (g, "GUITAR DI / NOISE SUPPRESSOR", { 25, 54, 340, 16 }, 14.0f, secondary); + g.setColour (panel); g.fillRoundedRectangle (24.0f, 86.0f, 592.0f, 162.0f, 8.0f); + drawLevel (g, 112, "Input", meter.input, (meter.flags & M::inputClip) != 0); + drawLevel (g, 164, audition ? "Removed" : "Output", meter.output, (meter.flags & M::outputClip) != 0); + for (int value : { -60, -36, -12, 0 }) + { + const int x = 106 + (value + 60) * 205 / 60; + g.setColour (neutral); g.drawVerticalLine (x, 195.0f, 199.0f); + label (g, juce::String (value), { x - 14, 202, 28, 16 }, 14.0f, secondary, juce::Justification::centred); + } + g.setColour (line); g.drawVerticalLine (406, 106.0f, 230.0f); + label (g, multi ? "MAX BAND REDUCTION" : "HIGH-BAND REDUCTION", { 423, 108, 178, 18 }, 14.0f, secondary); + label (g, hasReduction ? (meter.reduction >= 60.0f ? "60+" : juce::String (meter.reduction, 1)) : "--", { 422, 134, 131, 56 }, 52.0f, + hasReduction && meter.reduction > 0.5f ? accent : text); + label (g, "dB", { 548, 158, 40, 24 }, 20.0f, secondary); + const float amount = hasReduction ? juce::jlimit (0.0f, 1.0f, meter.reduction / 60.0f) : 0.0f; + g.setColour (line); g.fillRect (424.0f, 202.0f, 172.0f, 6.0f); + g.setColour (accent); g.fillRect (424.0f, 202.0f, 172.0f * amount, 6.0f); + for (int value : { 0, 20, 40, 60 }) + label (g, juce::String (value), { 416 + value * 172 / 60, 215, 22, 17 }, 14.0f, secondary, juce::Justification::centred); + + label (g, "Threshold", { 24, 259, 192, 22 }, 18.0f, text, juce::Justification::centred); + label (g, "Strength", { 224, 259, 192, 22 }, 18.0f, text, juce::Justification::centred); + label (g, "Release", { 424, 259, 192, 22 }, 18.0f, text, juce::Justification::centred); + label (g, multi ? "One-split only" : "Reduce highs below this level", { 24, 388, 192, 17 }, 14.0f, secondary, juce::Justification::centred); + const auto cutoff = suppressor::DenoiserEngine::strengthToCutoff (strength.getValue(), + proc.meterSampleRate()); + label (g, multi ? "One-split only" : "Above " + juce::String (cutoff / 1000.0, 1) + " kHz", { 224, 388, 192, 17 }, 14.0f, secondary, juce::Justification::centred); + label (g, "How quickly highs close", { 424, 388, 192, 17 }, 14.0f, secondary, juce::Justification::centred); + label (g, bypass ? "Monitor: dry input" : audition ? "Monitor: input minus processed" : "Monitor: processed signal", + { 196, 419, 265, 20 }, 15.0f, audition ? warning : secondary); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index aa6e659..9fed4bb 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -1,91 +1,77 @@ #pragma once -#include "PluginLookAndFeel.h" #include "PluginProcessor.h" -#include +#include "PluginLookAndFeel.h" -class ActivityDisplay final : public juce::Component +class SuppressorDial final : public juce::Slider { public: - void setState (float detectorDb, float gainReductionDb, float thresholdDb, - int bandMode, bool deltaAudition, bool multibandLearning); - void paint (juce::Graphics&) override; - std::unique_ptr createAccessibilityHandler() override; + SuppressorDial (juce::AudioProcessorValueTreeState&, const juce::String& id, + const juce::String& help); + ~SuppressorDial() override; + void resized() override; + void mouseDown (const juce::MouseEvent&) override; + void mouseDrag (const juce::MouseEvent&) override; + void mouseUp (const juce::MouseEvent&) override; + void mouseDoubleClick (const juce::MouseEvent&) override; + bool keyPressed (const juce::KeyPress&) override; + void focusGained (FocusChangeType) override { repaint(); } + void focusLost (FocusChangeType) override { repaint(); } + void enablementChanged() override; + void beginEntry(); + void cancelInteraction(); + juce::RangedAudioParameter& parameter; private: - class ValueInterface; - juce::String accessibleValueText() const; - juce::String semanticStatus() const; + void finishEntry (bool commit); + void setWithGesture (double); + void showMenu(); + juce::TextEditor entry; + juce::Point lastDrag; + double dragProportion = 0.0; + std::unique_ptr drag; + juce::AudioProcessorValueTreeState::SliderAttachment attachment; +}; - float detector = -180.0f; - float reduction = 0.0f; - float threshold = -40.0f; - int mode = 0; - bool delta = false; - bool learning = false; +class ListenButton final : public juce::TextButton +{ +public: + ListenButton() : juce::TextButton ("Listen removed") {} + bool keyPressed (const juce::KeyPress& key) override + { + if (isEnabled() && key.isKeyCode (' ')) + { + setToggleState (! getToggleState(), juce::sendNotificationSync); + return true; + } + return juce::TextButton::keyPressed (key); + } }; -class SuppressorEditor final : public juce::AudioProcessorEditor, - private juce::Timer +class SuppressorEditor final : public juce::AudioProcessorEditor, private juce::Timer { public: explicit SuppressorEditor (SuppressorProcessor&); ~SuppressorEditor() override; - void paint (juce::Graphics&) override; void resized() override; void visibilityChanged() override; int getControlParameterIndex (juce::Component&) override; private: - struct Entry - { - juce::String id; - ParameterSlider* slider = nullptr; - juce::ComboBox* combo = nullptr; - juce::Button* button = nullptr; - std::unique_ptr label; - std::unique_ptr sliderAttachment; - std::unique_ptr comboAttachment; - std::unique_ptr buttonAttachment; - }; - void timerCallback() override; - ParameterSlider& addKnob (const juce::String& parameterId, const juce::String& label); - juce::ComboBox& addCombo (const juce::String& parameterId, const juce::String& label); - juce::ToggleButton& addToggle (const juce::String& parameterId, const juce::String& label); - void addAction (const juce::String& parameterId, juce::TextButton& button, - const juce::String& accessibleName); - Entry* findEntry (const juce::String& parameterId) const; - void placeKnob (const juce::String& parameterId, juce::Rectangle bounds); - void placeCombo (const juce::String& parameterId, juce::Rectangle bounds); - void placeButton (const juce::String& parameterId, juce::Rectangle bounds); - void updateModePresentation(); - void updateTimerState(); - void paintPanel (juce::Graphics&, juce::Rectangle, const juce::String&) const; - + void drawLevel (juce::Graphics&, int y, const juce::String&, float peak, bool clip); SuppressorProcessor& proc; - SuppressorLookAndFeel lookAndFeel; - ActivityDisplay activityDisplay; - juce::TooltipWindow tooltipWindow { this, 1200 }; - - std::vector> sliders; - std::vector> comboBoxes; - std::vector> toggleButtons; - std::vector> entries; - - ParameterTextButton humLearnButton { "LEARN" }; - ParameterTextButton bandsLearnButton { "LEARN" }; - juce::Label humStatus; - juce::Label bandsStatus; - - juce::Rectangle coreBounds; - juce::Rectangle activityBounds; - juce::Rectangle detectionBounds; - juce::Rectangle humBounds; - juce::Rectangle outputBounds; - - int nextFocusOrder = 1; + SuppressorLookAndFeel theme; + SuppressorDial threshold, strength, release; + ListenButton listen; + juce::AudioProcessorValueTreeState::ButtonAttachment listenAttachment; + juce::Label status, meterSummary, hostSettings; + juce::TooltipWindow tooltips { this, 700 }; + suppressor::MeterSnapshot meter; + uint32_t lastSequence = 0; + double lastUpdateMs = 0.0; + bool fresh = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SuppressorEditor) }; diff --git a/Source/PluginLookAndFeel.cpp b/Source/PluginLookAndFeel.cpp index 62409af..c529105 100644 --- a/Source/PluginLookAndFeel.cpp +++ b/Source/PluginLookAndFeel.cpp @@ -1,455 +1,85 @@ #include "PluginLookAndFeel.h" #include "SuppressorAssets.h" -#include - -namespace -{ -const juce::Typeface::Ptr& regularTypeface() -{ - static auto typeface = juce::Typeface::createSystemTypefaceFor ( - SuppressorAssets::BarlowCondensedRegular_ttf, - SuppressorAssets::BarlowCondensedRegular_ttfSize); - return typeface; -} - -const juce::Typeface::Ptr& semiboldTypeface() -{ - static auto typeface = juce::Typeface::createSystemTypefaceFor ( - SuppressorAssets::BarlowCondensedSemiBold_ttf, - SuppressorAssets::BarlowCondensedSemiBold_ttfSize); - return typeface; -} - -juce::TextLayout makeTooltipLayout (const juce::String& text, juce::Colour colour, - float maximumWidth) -{ - juce::AttributedString attributed; - attributed.setJustification (juce::Justification::centred); - attributed.setWordWrap (juce::AttributedString::byWord); - attributed.append (text, SuppressorTheme::makeFont (14.0f), colour); - juce::TextLayout layout; - layout.createLayout (attributed, maximumWidth); - return layout; -} -} - -juce::Font SuppressorTheme::makeFont (float size, bool semibold, float tracking) -{ - auto typeface = semibold ? semiboldTypeface() : regularTypeface(); - return juce::Font (juce::FontOptions (typeface) - .withHeight (size) - .withKerningFactor (tracking)); -} - -void ParameterSlider::mouseDoubleClick (const juce::MouseEvent& event) -{ - if (isEnabled() && isTextBoxEditable() && ! event.mods.isPopupMenu()) - showTextBox(); -} - -bool ParameterSlider::keyPressed (const juce::KeyPress& key) -{ - if (key == juce::KeyPress::returnKey && isEnabled() && isTextBoxEditable()) - { - showTextBox(); - return true; - } - - const auto isAdjustmentKey = ! key.getModifiers().isAnyModifierKeyDown() - && (key.isKeyCode (juce::KeyPress::leftKey) - || key.isKeyCode (juce::KeyPress::rightKey) - || key.isKeyCode (juce::KeyPress::upKey) - || key.isKeyCode (juce::KeyPress::downKey)); - if (! isEnabled() || ! isAdjustmentKey || getThumbBeingDragged() >= 0) - return juce::Slider::keyPressed (key); - - if (beginKeyboardGesture != nullptr) - beginKeyboardGesture(); - const auto handled = juce::Slider::keyPressed (key); - if (endKeyboardGesture != nullptr) - endKeyboardGesture(); - return handled; -} - -void ParameterSlider::setKeyboardGestureCallbacks (GestureCallback begin, - GestureCallback end) -{ - beginKeyboardGesture = std::move (begin); - endKeyboardGesture = std::move (end); -} - -void ParameterSlider::endActiveGesture() -{ - if (getThumbBeingDragged() < 0) - return; - - const auto now = juce::Time::getCurrentTime(); - const auto position = getLocalBounds().getCentre().toFloat(); - juce::Slider::mouseUp ( - { juce::Desktop::getInstance().getMainMouseSource(), position, {}, - 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, this, this, now, position, now, 1, true }); -} - -double ParameterSlider::getValueFromText (const juce::String& text) -{ - auto numericText = text.trim(); - const auto suffix = getTextValueSuffix().trim(); - if (suffix.isNotEmpty() && numericText.endsWithIgnoreCase (suffix)) - numericText = numericText.dropLastCharacters (suffix.length()).trimEnd(); - - bool hasDigit = false; - bool hasDecimalPoint = false; - for (int index = 0; index < numericText.length(); ++index) - { - const auto character = numericText[index]; - if (character >= '0' && character <= '9') - { - hasDigit = true; - continue; - } - if (character == '.' && ! hasDecimalPoint) - { - hasDecimalPoint = true; - continue; - } - if (index == 0 && (character == '+' || character == '-')) - continue; - return getValue(); - } - - if (! hasDigit) - return getValue(); - - const auto parsed = numericText.getDoubleValue(); - return std::isfinite (parsed) ? juce::Slider::getValueFromText (numericText) - : getValue(); -} - -bool ParameterToggleButton::keyPressed (const juce::KeyPress& key) +juce::Font SuppressorTheme::makeFont (float size, bool semibold) { - if (isEnabled() && key.isKeyCode (' ')) - { - setToggleState (! getToggleState(), juce::sendNotificationSync); - return true; - } - return juce::ToggleButton::keyPressed (key); -} - -bool ParameterTextButton::keyPressed (const juce::KeyPress& key) -{ - if (isEnabled() && key.isKeyCode (' ')) - { - setToggleState (! getToggleState(), juce::sendNotificationSync); - return true; - } - return juce::TextButton::keyPressed (key); + static auto regular = juce::Typeface::createSystemTypefaceFor ( + SuppressorAssets::BarlowCondensedRegular_ttf, SuppressorAssets::BarlowCondensedRegular_ttfSize); + static auto bold = juce::Typeface::createSystemTypefaceFor ( + SuppressorAssets::BarlowCondensedSemiBold_ttf, SuppressorAssets::BarlowCondensedSemiBold_ttfSize); + return juce::Font (juce::FontOptions (semibold ? bold : regular).withHeight (size)); } SuppressorLookAndFeel::SuppressorLookAndFeel() { - setColour (juce::Slider::rotarySliderFillColourId, SuppressorTheme::accent); - setColour (juce::Slider::rotarySliderOutlineColourId, SuppressorTheme::controlOutline); - setColour (juce::Slider::textBoxTextColourId, SuppressorTheme::primaryText); - setColour (juce::Slider::textBoxBackgroundColourId, juce::Colours::transparentBlack); - setColour (juce::Slider::textBoxOutlineColourId, juce::Colours::transparentBlack); - setColour (juce::Slider::textBoxHighlightColourId, SuppressorTheme::accent.withAlpha (0.28f)); - setColour (juce::ComboBox::textColourId, SuppressorTheme::primaryText); - setColour (juce::ComboBox::backgroundColourId, SuppressorTheme::surfaceRaised); - setColour (juce::ComboBox::outlineColourId, SuppressorTheme::controlOutline); - setColour (juce::ComboBox::arrowColourId, SuppressorTheme::secondaryText); - setColour (juce::ComboBox::focusedOutlineColourId, SuppressorTheme::accent); - setColour (juce::PopupMenu::backgroundColourId, SuppressorTheme::surfaceRaised); - setColour (juce::PopupMenu::textColourId, SuppressorTheme::primaryText); - setColour (juce::PopupMenu::highlightedBackgroundColourId, SuppressorTheme::accent); - setColour (juce::PopupMenu::highlightedTextColourId, SuppressorTheme::canvas); - setColour (juce::Label::textColourId, SuppressorTheme::primaryText); - setColour (juce::Label::textWhenEditingColourId, SuppressorTheme::primaryText); - setColour (juce::Label::backgroundWhenEditingColourId, SuppressorTheme::surfaceRaised); - setColour (juce::Label::outlineWhenEditingColourId, SuppressorTheme::accent); - setColour (juce::TextEditor::textColourId, SuppressorTheme::primaryText); - setColour (juce::TextEditor::backgroundColourId, SuppressorTheme::surfaceRaised); - setColour (juce::TextEditor::highlightColourId, SuppressorTheme::accent.withAlpha (0.35f)); - setColour (juce::TextEditor::highlightedTextColourId, SuppressorTheme::primaryText); - setColour (juce::CaretComponent::caretColourId, SuppressorTheme::accent); - setColour (juce::TooltipWindow::backgroundColourId, SuppressorTheme::surfaceRaised); - setColour (juce::TooltipWindow::textColourId, SuppressorTheme::primaryText); - setColour (juce::TooltipWindow::outlineColourId, SuppressorTheme::controlOutline); -} - -void SuppressorLookAndFeel::drawRotarySlider (juce::Graphics& graphics, int x, int y, - int width, int height, float sliderPosition, - float startAngle, float endAngle, - juce::Slider& slider) -{ - const auto size = static_cast (juce::jmin (width, height)); - const auto radius = size * 0.5f - 8.0f; - const auto centre = juce::Point ( - static_cast (x) + static_cast (width) * 0.5f, - static_cast (y) + static_cast (height) * 0.5f); - const auto angle = juce::jmap (sliderPosition, 0.0f, 1.0f, startAngle, endAngle); - const auto alpha = slider.isEnabled() ? 1.0f : 0.38f; - - juce::Path track; - track.addCentredArc (centre.x, centre.y, radius, radius, 0.0f, - startAngle, endAngle, true); - graphics.setColour (slider.findColour (juce::Slider::rotarySliderOutlineColourId) - .withMultipliedAlpha (alpha)); - graphics.strokePath (track, juce::PathStrokeType (4.0f, - juce::PathStrokeType::curved, - juce::PathStrokeType::rounded)); - - if (sliderPosition > 0.0f) + using namespace SuppressorTheme; + setColour (juce::Label::textColourId, secondaryText); + setColour (juce::TextEditor::backgroundColourId, surfaceRaised); + setColour (juce::TextEditor::textColourId, primaryText); + setColour (juce::TextEditor::outlineColourId, controlOutline); + setColour (juce::TextEditor::focusedOutlineColourId, accent); + setColour (juce::TextEditor::highlightColourId, accent.withAlpha (0.3f)); + setColour (juce::CaretComponent::caretColourId, primaryText); + setColour (juce::TextButton::textColourOffId, primaryText); + setColour (juce::TextButton::textColourOnId, warning); + setColour (juce::PopupMenu::backgroundColourId, surface); + setColour (juce::PopupMenu::textColourId, primaryText); + setColour (juce::PopupMenu::highlightedBackgroundColourId, surfaceRaised); + setColour (juce::PopupMenu::highlightedTextColourId, accent); + setColour (juce::TooltipWindow::backgroundColourId, surfaceRaised); + setColour (juce::TooltipWindow::textColourId, primaryText); + setColour (juce::TooltipWindow::outlineColourId, controlOutline); +} + +void SuppressorLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, int w, int h, + float position, float start, float end, juce::Slider& s) +{ + using namespace SuppressorTheme; + const auto bounds = juce::Rectangle (x, y, w, h).toFloat().reduced (8.0f); + const auto centre = bounds.getCentre(); + const auto radius = juce::jmin (bounds.getWidth(), bounds.getHeight()) * 0.5f - 4.0f; + g.setColour (surfaceRaised); + g.fillEllipse (bounds.reduced (7.0f)); + juce::Path track, value; + track.addCentredArc (centre.x, centre.y, radius, radius, 0.0f, start, end, true); + value.addCentredArc (centre.x, centre.y, radius, radius, 0.0f, start, + start + position * (end - start), true); + g.setColour (controlOutline); + g.strokePath (track, juce::PathStrokeType (3.0f)); + g.setColour (s.isEnabled() ? accent : controlOutline); + g.strokePath (value, juce::PathStrokeType (3.0f)); + const auto angle = start + position * (end - start); + const auto point = centre + juce::Point (std::sin (angle), -std::cos (angle)) * radius; + g.fillEllipse (point.x - 3.0f, point.y - 3.0f, 6.0f, 6.0f); + g.setColour (s.isEnabled() ? primaryText : secondaryText); + g.setFont (makeFont (21.0f, true)); + g.drawText (s.getTextFromValue (s.getValue()), bounds, juce::Justification::centred); + if (s.hasKeyboardFocus (true)) { - juce::Path value; - value.addCentredArc (centre.x, centre.y, radius, radius, 0.0f, - startAngle, angle, true); - graphics.setColour (slider.findColour (juce::Slider::rotarySliderFillColourId) - .withMultipliedAlpha (alpha)); - graphics.strokePath (value, juce::PathStrokeType (4.0f, - juce::PathStrokeType::curved, - juce::PathStrokeType::rounded)); + g.setColour (accent); + g.drawRoundedRectangle (s.getLocalBounds().toFloat().reduced (1.0f), 6.0f, 1.5f); } - - const auto innerRadius = radius - 10.0f; - graphics.setColour (SuppressorTheme::surfaceRaised.withMultipliedAlpha (alpha)); - graphics.fillEllipse (centre.x - innerRadius, centre.y - innerRadius, - innerRadius * 2.0f, innerRadius * 2.0f); - - auto innerOutline = slider.isMouseOverOrDragging() - ? SuppressorTheme::secondaryText - : SuppressorTheme::divider; - graphics.setColour (innerOutline.withMultipliedAlpha (alpha)); - graphics.drawEllipse (centre.x - innerRadius, centre.y - innerRadius, - innerRadius * 2.0f, innerRadius * 2.0f, 1.0f); - - const auto markerStart = centre.getPointOnCircumference (innerRadius - 9.0f, angle); - const auto markerEnd = centre.getPointOnCircumference (innerRadius - 3.0f, angle); - graphics.setColour (slider.findColour (juce::Slider::rotarySliderFillColourId) - .withMultipliedAlpha (alpha)); - graphics.drawLine ({ markerStart, markerEnd }, 2.5f); - - if (slider.hasKeyboardFocus (true)) - { - graphics.setColour (SuppressorTheme::accent); - graphics.drawEllipse (centre.x - radius - 4.0f, centre.y - radius - 4.0f, - (radius + 4.0f) * 2.0f, (radius + 4.0f) * 2.0f, 2.0f); - } -} - -juce::Label* SuppressorLookAndFeel::createSliderTextBox (juce::Slider& slider) -{ - auto* label = juce::LookAndFeel_V4::createSliderTextBox (slider); - label->setFont (SuppressorTheme::makeFont (15.0f, true)); - label->setJustificationType (juce::Justification::centred); - label->onEditorShow = [label] - { - if (auto* editor = label->getCurrentTextEditor()) - { - editor->setFont (label->getFont()); - editor->setJustification (juce::Justification::centred); - } - }; - label->setColour (juce::Label::backgroundColourId, juce::Colours::transparentBlack); - label->setColour (juce::Label::backgroundWhenEditingColourId, juce::Colours::transparentBlack); - label->setColour (juce::Label::outlineColourId, juce::Colours::transparentBlack); - label->setColour (juce::Label::outlineWhenEditingColourId, juce::Colours::transparentBlack); - label->setColour (juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack); - label->setColour (juce::TextEditor::outlineColourId, juce::Colours::transparentBlack); - label->setColour (juce::TextEditor::focusedOutlineColourId, juce::Colours::transparentBlack); - label->setColour (juce::TextEditor::shadowColourId, juce::Colours::transparentBlack); - label->setInterceptsMouseClicks (false, true); - return label; -} - -juce::Slider::SliderLayout SuppressorLookAndFeel::getSliderLayout (juce::Slider& slider) -{ - juce::Slider::SliderLayout layout; - const auto bounds = slider.getLocalBounds(); - layout.sliderBounds = bounds; - - if (slider.getTextBoxPosition() != juce::Slider::NoTextBox) - { - const auto width = juce::jmin (slider.getTextBoxWidth(), bounds.getWidth() - 4); - const auto height = juce::jmin (slider.getTextBoxHeight(), bounds.getHeight() - 4); - layout.textBoxBounds = juce::Rectangle (width, height).withCentre (bounds.getCentre()); - } - - return layout; -} - -void SuppressorLookAndFeel::drawComboBox (juce::Graphics& graphics, int width, int height, - bool isButtonDown, int, int, int, int, - juce::ComboBox& box) -{ - auto bounds = juce::Rectangle (0.5f, 0.5f, - static_cast (width) - 1.0f, - static_cast (height) - 1.0f); - graphics.setColour (SuppressorTheme::surfaceRaised); - graphics.fillRoundedRectangle (bounds, 6.0f); - - const auto focused = box.hasKeyboardFocus (false); - const auto outline = focused || isButtonDown - ? SuppressorTheme::accent - : (box.isMouseOver() ? SuppressorTheme::secondaryText - : SuppressorTheme::controlOutline); - graphics.setColour (outline); - graphics.drawRoundedRectangle (bounds, 6.0f, focused ? 2.0f : 1.0f); - - const auto arrowCentreX = static_cast (width - 17); - const auto arrowCentreY = static_cast (height) * 0.5f; - juce::Path arrow; - arrow.startNewSubPath (arrowCentreX - 4.0f, arrowCentreY - 2.0f); - arrow.lineTo (arrowCentreX, arrowCentreY + 2.0f); - arrow.lineTo (arrowCentreX + 4.0f, arrowCentreY - 2.0f); - graphics.setColour (focused ? SuppressorTheme::accent : SuppressorTheme::secondaryText); - graphics.strokePath (arrow, juce::PathStrokeType (1.7f, - juce::PathStrokeType::curved, - juce::PathStrokeType::rounded)); -} - -juce::Font SuppressorLookAndFeel::getComboBoxFont (juce::ComboBox&) -{ - return SuppressorTheme::makeFont (15.0f); -} - -juce::Font SuppressorLookAndFeel::getPopupMenuFont() -{ - return SuppressorTheme::makeFont (15.0f); -} - -juce::Rectangle SuppressorLookAndFeel::getTooltipBounds ( - const juce::String& text, juce::Point screenPosition, - juce::Rectangle parentArea) -{ - const auto layout = makeTooltipLayout (text, SuppressorTheme::primaryText, 300.0f); - const auto width = juce::roundToInt (std::ceil (layout.getWidth())) + 16; - const auto height = juce::roundToInt (std::ceil (layout.getHeight())) + 10; - return juce::Rectangle ( - screenPosition.x > parentArea.getCentreX() ? screenPosition.x - width - 12 - : screenPosition.x + 24, - screenPosition.y > parentArea.getCentreY() ? screenPosition.y - height - 6 - : screenPosition.y + 6, - width, height) - .constrainedWithin (parentArea); -} - -void SuppressorLookAndFeel::drawTooltip (juce::Graphics& graphics, - const juce::String& text, - int width, int height) -{ - const auto bounds = juce::Rectangle (width, height).toFloat(); - graphics.setColour (SuppressorTheme::surfaceRaised); - graphics.fillRoundedRectangle (bounds, 5.0f); - graphics.setColour (SuppressorTheme::controlOutline); - graphics.drawRoundedRectangle (bounds.reduced (0.5f), 5.0f, 1.0f); - - const auto textBounds = bounds.reduced (8.0f, 5.0f); - auto layout = makeTooltipLayout (text, SuppressorTheme::primaryText, - textBounds.getWidth()); - layout.draw (graphics, textBounds); -} - -void SuppressorLookAndFeel::positionComboBoxText (juce::ComboBox& box, juce::Label& label) -{ - label.setBounds (12, 1, box.getWidth() - 38, box.getHeight() - 2); - label.setFont (getComboBoxFont (box)); -} - -void SuppressorLookAndFeel::drawToggleButton (juce::Graphics& graphics, - juce::ToggleButton& button, - bool isMouseOver, bool isButtonDown) -{ - const auto alpha = button.isEnabled() ? 1.0f : 0.42f; - auto bounds = button.getLocalBounds().toFloat().reduced (0.5f); - auto fill = SuppressorTheme::surfaceRaised; - if (isButtonDown) - fill = fill.brighter (0.08f); - graphics.setColour (fill.withMultipliedAlpha (alpha)); - graphics.fillRoundedRectangle (bounds, 6.0f); - - const auto focused = button.hasKeyboardFocus (false); - const auto outline = focused ? SuppressorTheme::accent - : (isMouseOver ? SuppressorTheme::secondaryText - : SuppressorTheme::controlOutline); - graphics.setColour (outline.withMultipliedAlpha (alpha)); - graphics.drawRoundedRectangle (bounds, 6.0f, focused ? 2.0f : 1.0f); - - auto indicator = juce::Rectangle (12.0f, - static_cast (button.getHeight()) * 0.5f - 8.0f, - 16.0f, 16.0f); - graphics.setColour ((button.getToggleState() ? SuppressorTheme::accent - : SuppressorTheme::surface) - .withMultipliedAlpha (alpha)); - graphics.fillRoundedRectangle (indicator, 3.0f); - graphics.setColour ((button.getToggleState() ? SuppressorTheme::accent - : SuppressorTheme::controlOutline) - .withMultipliedAlpha (alpha)); - graphics.drawRoundedRectangle (indicator, 3.0f, 1.0f); - - if (button.getToggleState()) - { - juce::Path check; - check.startNewSubPath (indicator.getX() + 3.5f, indicator.getCentreY()); - check.lineTo (indicator.getX() + 7.0f, indicator.getBottom() - 4.0f); - check.lineTo (indicator.getRight() - 3.0f, indicator.getY() + 4.0f); - graphics.setColour (SuppressorTheme::canvas.withMultipliedAlpha (alpha)); - graphics.strokePath (check, juce::PathStrokeType (2.0f, - juce::PathStrokeType::curved, - juce::PathStrokeType::rounded)); - } - - graphics.setColour (SuppressorTheme::primaryText.withMultipliedAlpha (alpha)); - graphics.setFont (SuppressorTheme::makeFont (14.5f, true, 0.02f)); - graphics.drawFittedText (button.getButtonText(), 38, 0, button.getWidth() - 48, - button.getHeight(), juce::Justification::centredLeft, 1); -} - -void SuppressorLookAndFeel::drawButtonBackground (juce::Graphics& graphics, - juce::Button& button, - const juce::Colour&, - bool isMouseOver, bool isButtonDown) -{ - const auto alpha = button.isEnabled() ? 1.0f : 0.42f; - const auto active = button.getToggleState(); - auto bounds = button.getLocalBounds().toFloat().reduced (0.5f); - auto fill = active ? SuppressorTheme::accent : SuppressorTheme::surfaceRaised; - if (isButtonDown) - fill = fill.brighter (active ? 0.04f : 0.08f); - graphics.setColour (fill.withMultipliedAlpha (alpha)); - graphics.fillRoundedRectangle (bounds, 6.0f); - - const auto focused = button.hasKeyboardFocus (false); - const auto outline = active ? SuppressorTheme::accent - : (isMouseOver ? SuppressorTheme::secondaryText - : SuppressorTheme::controlOutline); - graphics.setColour ((focused ? (active ? SuppressorTheme::primaryText - : SuppressorTheme::accent) - : outline) - .withMultipliedAlpha (alpha)); - graphics.drawRoundedRectangle (bounds, 6.0f, focused ? 2.0f : 1.0f); - if (focused && active) + else if (s.isMouseOver() && s.isEnabled()) { - graphics.setColour (SuppressorTheme::canvas.withMultipliedAlpha (alpha)); - graphics.drawRoundedRectangle (bounds.reduced (3.0f), 4.0f, 1.5f); + g.setColour (controlOutline); + g.drawRoundedRectangle (s.getLocalBounds().toFloat().reduced (1.0f), 6.0f, 1.0f); } } -void SuppressorLookAndFeel::drawButtonText (juce::Graphics& graphics, - juce::TextButton& button, - bool, bool) +void SuppressorLookAndFeel::drawButtonBackground (juce::Graphics& g, juce::Button& b, + const juce::Colour&, bool hover, bool down) { - const auto alpha = button.isEnabled() ? 1.0f : 0.42f; - graphics.setColour ((button.getToggleState() ? SuppressorTheme::canvas - : SuppressorTheme::primaryText) - .withMultipliedAlpha (alpha)); - graphics.setFont (getTextButtonFont (button, button.getHeight())); - graphics.drawFittedText (button.getButtonText(), button.getLocalBounds().reduced (8, 2), - juce::Justification::centred, 1); + using namespace SuppressorTheme; + const auto r = b.getLocalBounds().toFloat().reduced (1.0f); + g.setColour (down ? divider : surfaceRaised); + g.fillRoundedRectangle (r, 5.0f); + g.setColour (b.getToggleState() ? warning : (hover ? primaryText : controlOutline)); + g.drawRoundedRectangle (r, 5.0f, b.hasKeyboardFocus (true) ? 2.0f : 1.0f); + if (b.hasKeyboardFocus (true)) g.drawRoundedRectangle (r.reduced (4.0f), 3.0f, 1.0f); } -juce::Font SuppressorLookAndFeel::getTextButtonFont (juce::TextButton&, int buttonHeight) +juce::Font SuppressorLookAndFeel::getTextButtonFont (juce::TextButton&, int) { - return SuppressorTheme::makeFont (juce::jmin (15.0f, static_cast (buttonHeight) * 0.5f), - true, 0.04f); + return SuppressorTheme::makeFont (17.0f, true); } +juce::Font SuppressorLookAndFeel::getPopupMenuFont() { return SuppressorTheme::makeFont (17.0f); } diff --git a/Source/PluginLookAndFeel.h b/Source/PluginLookAndFeel.h index a9088b3..a5448be 100644 --- a/Source/PluginLookAndFeel.h +++ b/Source/PluginLookAndFeel.h @@ -2,82 +2,23 @@ #include -#include -#include - namespace SuppressorTheme { -inline const juce::Colour canvas { 0xff0a0e14 }; -inline const juce::Colour header { 0xff0d141e }; -inline const juce::Colour surface { 0xff111a24 }; -inline const juce::Colour surfaceRaised { 0xff182432 }; -inline const juce::Colour divider { 0xff2a3a4b }; -inline const juce::Colour controlOutline { 0xff66798c }; -inline const juce::Colour primaryText { 0xfff1f5f8 }; -inline const juce::Colour secondaryText { 0xffa6b2be }; -inline const juce::Colour accent { 0xff8ea7ff }; -inline const juce::Colour warning { 0xfff2b84b }; -inline const juce::Colour error { 0xffff6f7d }; -inline const juce::Colour inactiveControl { 0xff6c7c8f }; -inline constexpr float cornerRadius = 9.0f; - -juce::Font makeFont (float size, bool semibold = false, float tracking = 0.0f); +inline const juce::Colour canvas { 0xff0b0e11 }, surface { 0xff12171c }, + surfaceRaised { 0xff171d23 }, divider { 0xff2a333c }, primaryText { 0xffe8edf1 }, + secondaryText { 0xff89939d }, controlOutline { 0xff66717b }, accent { 0xffb8f24a }, + warning { 0xffe7a84b }, error { 0xffff8080 }; +juce::Font makeFont (float size, bool semibold = false); } -class ParameterSlider final : public juce::Slider -{ -public: - using GestureCallback = std::function; - - void mouseDoubleClick (const juce::MouseEvent&) override; - bool keyPressed (const juce::KeyPress&) override; - double getValueFromText (const juce::String&) override; - void setKeyboardGestureCallbacks (GestureCallback begin, GestureCallback end); - void endActiveGesture(); - -private: - GestureCallback beginKeyboardGesture; - GestureCallback endKeyboardGesture; -}; - -class ParameterToggleButton final : public juce::ToggleButton -{ -public: - using juce::ToggleButton::ToggleButton; - bool keyPressed (const juce::KeyPress&) override; -}; - -class ParameterTextButton final : public juce::TextButton -{ -public: - using juce::TextButton::TextButton; - bool keyPressed (const juce::KeyPress&) override; -}; - class SuppressorLookAndFeel final : public juce::LookAndFeel_V4 { public: SuppressorLookAndFeel(); - - void drawRotarySlider (juce::Graphics&, int x, int y, int width, int height, - float sliderPosition, float startAngle, float endAngle, + void drawRotarySlider (juce::Graphics&, int, int, int, int, float, float, float, juce::Slider&) override; - juce::Label* createSliderTextBox (juce::Slider&) override; - juce::Slider::SliderLayout getSliderLayout (juce::Slider&) override; - void drawComboBox (juce::Graphics&, int width, int height, bool isButtonDown, - int buttonX, int buttonY, int buttonWidth, int buttonHeight, - juce::ComboBox&) override; - juce::Font getComboBoxFont (juce::ComboBox&) override; - juce::Font getPopupMenuFont() override; - juce::Rectangle getTooltipBounds (const juce::String&, juce::Point, - juce::Rectangle) override; - void drawTooltip (juce::Graphics&, const juce::String&, int width, int height) override; - void positionComboBoxText (juce::ComboBox&, juce::Label&) override; - void drawToggleButton (juce::Graphics&, juce::ToggleButton&, bool isMouseOver, - bool isButtonDown) override; void drawButtonBackground (juce::Graphics&, juce::Button&, const juce::Colour&, - bool isMouseOver, bool isButtonDown) override; - void drawButtonText (juce::Graphics&, juce::TextButton&, bool isMouseOver, - bool isButtonDown) override; - juce::Font getTextButtonFont (juce::TextButton&, int buttonHeight) override; + bool, bool) override; + juce::Font getTextButtonFont (juce::TextButton&, int) override; + juce::Font getPopupMenuFont() override; }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index b3698ae..20405f0 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -120,15 +120,17 @@ void SuppressorProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) juce::ignoreUnused (samplesPerBlock); if (sampleRate <= 0.0) return; // stress hosts may probe 0/0: keep the previous state + displaySampleRate.store (static_cast (sampleRate)); engine.prepare (sampleRate, 2, (int) std::ceil (0.0025 * sampleRate)); currentLatency = (int) std::round (apvts.getRawParameterValue ("lookahead")->load() * 0.001 * sampleRate); setLatencySamples (currentLatency); engine.setTargets (readTargets (sampleRate)); engine.reset(); + meters.reset(); } -void SuppressorProcessor::releaseResources() {} +void SuppressorProcessor::releaseResources() { meters.reset(); } bool SuppressorProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { @@ -244,15 +246,24 @@ void SuppressorProcessor::processBlock (juce::AudioBuffer& buffer, juce:: if (numSc > 1) sc[1] = scBus.getReadPointer (1); } + const float inputPeak = mainBus.getMagnitude (0, mainBus.getNumSamples()); engine.processBlock (io, sc, numSc, buffer.getNumSamples(), numMain); - detectorDbAtomic.store (engine.detectorLevelDb()); - grDbAtomic.store (engine.gainReductionDb()); + using M = suppressor::MeterSnapshot; + uint32_t flags = targets.deltaAudition ? M::removed : 0u; + if (targets.bandMode != 0) flags |= M::multiband; + if (targets.bandMode != 0 && engine.multi (0).learning()) flags |= M::learning; + meters.push (inputPeak, mainBus.getMagnitude (0, mainBus.getNumSamples()), + -engine.gainReductionDb(), buffer.getNumSamples(), fs, flags); } -void SuppressorProcessor::processBlockBypassed (juce::AudioBuffer&, juce::MidiBuffer& midi) +void SuppressorProcessor::processBlockBypassed (juce::AudioBuffer& buffer, juce::MidiBuffer& midi) { midi.clear(); + auto mainBus = getBusBuffer (buffer, true, 0); + const float peak = mainBus.getMagnitude (0, mainBus.getNumSamples()); + meters.push (peak, peak, 0.0f, buffer.getNumSamples(), getSampleRate(), + suppressor::MeterSnapshot::bypassed); } void SuppressorProcessor::handleAsyncUpdate() @@ -264,30 +275,6 @@ void SuppressorProcessor::handleAsyncUpdate() prm->setValueNotifyingHost (0.0f); } -// ------------------------------------------------------------------------ -bool SuppressorProcessor::humLearning() const noexcept { return humLearningNow; } -bool SuppressorProcessor::humLocked() const noexcept -{ - if (engine.numChannels() == 0) return false; - return const_cast (engine).hum (0).locked(); -} -bool SuppressorProcessor::bandsLearning() const noexcept { return bandsLearningNow; } -bool SuppressorProcessor::bandsLocked() const noexcept -{ - if (engine.numChannels() == 0) return false; - return const_cast (engine).multi (0).locked(); -} -int SuppressorProcessor::humActiveDips() const noexcept -{ - if (engine.numChannels() == 0) return 0; - return const_cast (engine).hum (0).activeHarmonicCount(); -} -double SuppressorProcessor::humDetectedBaseHz() const noexcept -{ - if (engine.numChannels() == 0) return 60.0; - return const_cast (engine).hum (0).baseHz(); -} - // ------------------------------------------------------------------------ juce::AudioProcessorEditor* SuppressorProcessor::createEditor() { diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 9571752..aae6478 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -2,6 +2,7 @@ #include #include "DSP/DenoiserEngine.h" +#include "Metering.h" class SuppressorProcessor : public juce::AudioProcessor, private juce::AsyncUpdater @@ -38,17 +39,9 @@ class SuppressorProcessor : public juce::AudioProcessor, // ------------------------------------------------------------------ juce::AudioProcessorValueTreeState apvts; - // Metering / status for the editor (read on the message thread) - float detectorDb() const noexcept { return detectorDbAtomic.load(); } - float gainReductionDb() const noexcept { return grDbAtomic.load(); } - bool humLearning() const noexcept; - bool humLocked() const noexcept; - bool bandsLearning() const noexcept; - bool bandsLocked() const noexcept; - int humActiveDips() const noexcept; - double humDetectedBaseHz() const noexcept; - void requestHumLearnStop() { humLearnRequest = false; } - void requestBandsLearnStop() { bandsLearnRequest = false; } + // Bounded, race-free telemetry; never expose mutable DSP state to the editor. + bool readMeters (suppressor::MeterSnapshot& result) const noexcept { return meters.read (result); } + float meterSampleRate() const noexcept { return displaySampleRate.load(); } static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); @@ -58,13 +51,13 @@ class SuppressorProcessor : public juce::AudioProcessor, suppressor::DenoiserEngine engine; - std::atomic detectorDbAtomic { -120.0f }; - std::atomic grDbAtomic { 0.0f }; + suppressor::Metering meters; + std::atomic displaySampleRate { 44100.0f }; std::atomic latencyDirty { false }; std::atomic humAutoFinished { false }; - bool humLearnRequest = false, humLearningNow = false; - bool bandsLearnRequest = false, bandsLearningNow = false; + bool humLearningNow = false; + bool bandsLearningNow = false; int currentLatency = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SuppressorProcessor) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index bf6ebe9..c6af2d2 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -12,9 +12,11 @@ add_executable(suppressor-tests test_engine.cpp test_hum.cpp test_multiband.cpp - test_cue.cpp) + test_cue.cpp + test_metering.cpp) -target_link_libraries(suppressor-tests PRIVATE suppressor-dsp doctest::doctest) +find_package(Threads REQUIRED) +target_link_libraries(suppressor-tests PRIVATE suppressor-dsp doctest::doctest Threads::Threads) target_include_directories(suppressor-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) add_test(NAME suppressor-dsp-unit COMMAND suppressor-tests) diff --git a/Tests/test_clap_gui.cpp b/Tests/test_clap_gui.cpp new file mode 100644 index 0000000..5dddd1a --- /dev/null +++ b/Tests/test_clap_gui.cpp @@ -0,0 +1,100 @@ +#include +#include +#include + +namespace +{ +struct ClapHost +{ + juce::DynamicLibrary library; + const clap_plugin_entry_t* entry = nullptr; + const clap_plugin_t* plugin = nullptr; + const clap_plugin_gui_t* gui = nullptr; + bool initialised = false, created = false; + std::atomic callbackRequested { false }; + clap_host_t host { CLAP_VERSION, this, "Suppressor UI tests", "Suppressor Audio", + "https://github.com/affinedsp/suppressor", "1.0", + [] (const clap_host_t*, const char*) -> const void* { return nullptr; }, + [] (const clap_host_t*) {}, [] (const clap_host_t*) {}, + [] (const clap_host_t* h) { static_cast (h->host_data)->callbackRequested = true; } }; + void pump() + { + if (callbackRequested.exchange (false) && plugin != nullptr) + { + plugin->on_main_thread (plugin); + } + juce::MessageManager::getInstance()->runDispatchLoopUntil (40); + } + ~ClapHost() + { + if (created) gui->destroy (plugin); + if (plugin != nullptr) plugin->destroy (plugin); + if (initialised) entry->deinit(); + } +}; +} + +TEST_CASE ("CLAP embeds, hides, reopens and destroys the actual native editor") +{ + const auto path = juce::SystemStats::getEnvironmentVariable ("SUPPRESSOR_CLAP_PATH", {}); + if (path.isEmpty()) return; + juce::ScopedJuceInitialiser_GUI initialiser; + juce::Component parent; + parent.setBounds (80, 80, 640, 460); + parent.addToDesktop (0); + parent.setVisible (true); + ClapHost h; + auto binary = juce::File (path); + #if JUCE_MAC + binary = binary.getChildFile ("Contents/MacOS/suppressor"); + #endif + REQUIRE (h.library.open (binary.getFullPathName())); + h.entry = static_cast (h.library.getFunction ("clap_entry")); + REQUIRE (h.entry != nullptr); + h.initialised = h.entry->init (path.toRawUTF8()); + REQUIRE (h.initialised); + auto* factory = static_cast (h.entry->get_factory (CLAP_PLUGIN_FACTORY_ID)); + REQUIRE (factory != nullptr); + REQUIRE (factory->get_plugin_count (factory) == 1); + h.plugin = factory->create_plugin (factory, &h.host, factory->get_plugin_descriptor (factory, 0)->id); + REQUIRE (h.plugin != nullptr); + REQUIRE (h.plugin->init (h.plugin)); + h.gui = static_cast (h.plugin->get_extension (h.plugin, CLAP_EXT_GUI)); + REQUIRE (h.gui != nullptr); + clap_window_t window {}; + #if JUCE_MAC + window.api = CLAP_WINDOW_API_COCOA; + window.cocoa = parent.getPeer()->getNativeHandle(); + #elif JUCE_WINDOWS + window.api = CLAP_WINDOW_API_WIN32; + window.win32 = parent.getPeer()->getNativeHandle(); + #else + window.api = CLAP_WINDOW_API_X11; + window.x11 = reinterpret_cast (parent.getPeer()->getNativeHandle()); + #endif + REQUIRE (h.gui->is_api_supported (h.plugin, window.api, false)); + for (int reopen = 0; reopen < 3; ++reopen) + { + h.created = h.gui->create (h.plugin, window.api, false); + REQUIRE (h.created); + uint32_t width = 0, height = 0; + REQUIRE (h.gui->get_size (h.plugin, &width, &height)); + CHECK (width == 640); + CHECK (height == 460); + CHECK_FALSE (h.gui->can_resize (h.plugin)); + REQUIRE (h.gui->set_parent (h.plugin, &window)); + REQUIRE (h.gui->show (h.plugin)); + h.pump(); + // The pinned wrapper returns false from hide(). Embedded hosts can + // hide their containing window instead; exercise that fallback too. + h.gui->hide (h.plugin); + parent.setVisible (false); + h.pump(); + parent.setVisible (true); + REQUIRE (h.gui->show (h.plugin)); + h.pump(); + h.gui->destroy (h.plugin); + h.created = false; + h.pump(); + } +} diff --git a/Tests/test_metering.cpp b/Tests/test_metering.cpp new file mode 100644 index 0000000..5a25dbc --- /dev/null +++ b/Tests/test_metering.cpp @@ -0,0 +1,89 @@ +#include +#include "Metering.h" +#include +#include + +using suppressor::Metering; +using M = suppressor::MeterSnapshot; + +TEST_CASE ("meter snapshots start empty and reset on release") +{ + Metering meter; + M s; + REQUIRE (meter.read (s)); + CHECK (s.flags == 0); + meter.push (0.5f, 0.25f, 20.0f, 480, 48000, 0); + REQUIRE (meter.read (s)); + CHECK (s.input == 0.5f); + CHECK (s.output == 0.25f); + CHECK (s.reduction == 20.0f); + CHECK ((s.flags & M::active) != 0); + const auto sequence = s.sequence; + meter.push (1, 1, 1, 0, 48000, 0); + meter.push (1, 1, 1, 480, 0, 0); + REQUIRE (meter.read (s)); + CHECK (s.sequence == sequence); + meter.reset(); + REQUIRE (meter.read (s)); + CHECK (s.flags == 0); + CHECK (s.input == 0); + CHECK (s.reduction == 0); +} + +TEST_CASE ("peak decay is time-based and clips are held for one second") +{ + for (int block : { 48, 480, 4800 }) + { + Metering meter; + M s; + meter.push (1.1f, 0.4f, 40.0f, block, 48000, 0); + for (int n = 0; n < 14400 / block; ++n) meter.push (0, 0, 0, block, 48000, 0); + REQUIRE (meter.read (s)); + CHECK (s.input == doctest::Approx (1.1f * std::exp (-1.0f)).epsilon (0.0001)); + CHECK (s.reduction == doctest::Approx (40.0f * std::exp (-2.5f)).epsilon (0.0001)); + CHECK ((s.flags & M::inputClip) != 0); + CHECK ((s.flags & M::outputClip) == 0); + for (int n = 0; n < 48000 / block; ++n) meter.push (0, 0, 0, block, 48000, 0); + REQUIRE (meter.read (s)); + CHECK ((s.flags & M::inputClip) == 0); + } +} + +TEST_CASE ("bypass and learning never claim suppression, nonfinite input cannot poison meters") +{ + Metering meter; + M s; + meter.push (0.5f, 0.1f, 40, 480, 48000, 0); + for (auto flags : { M::bypassed, M::learning }) + { + meter.push (0.5f, 0.5f, 40, 480, 48000, flags); + REQUIRE (meter.read (s)); + CHECK (s.reduction == 0); + } + meter.push (std::numeric_limits::quiet_NaN(), + std::numeric_limits::infinity(), -2, 480, 48000, 0); + REQUIRE (meter.read (s)); + CHECK (std::isfinite (s.input)); + CHECK (std::isfinite (s.output)); + CHECK (s.reduction >= 0); +} + +TEST_CASE ("concurrent meter reads are coherent and never block the writer") +{ + Metering meter; + std::atomic done { false }; + std::thread writer ([&] + { + for (int n = 1; n <= 100000; ++n) + meter.push (static_cast (n), static_cast (n * 2), 0, 48, 48000, 0); + done.store (true); + }); + bool coherent = true; + do + { + M s; + if (meter.read (s)) coherent = coherent && s.output == s.input * 2 && (s.sequence & 1u) == 0; + } while (! done.load()); + writer.join(); + CHECK (coherent); +} diff --git a/Tests/test_ui.cpp b/Tests/test_ui.cpp index 666a473..f685b0e 100644 --- a/Tests/test_ui.cpp +++ b/Tests/test_ui.cpp @@ -1,429 +1,422 @@ #include - #include "PluginEditor.h" -#include "PluginLookAndFeel.h" - -#include #include -#include #include namespace { -float relativeLuminance (juce::Colour colour) +void pump (int ms = 50) { - auto linearise = [] (float value) - { - return value <= 0.04045f ? value / 12.92f - : std::pow ((value + 0.055f) / 1.055f, 2.4f); - }; - - return 0.2126f * linearise (colour.getFloatRed()) - + 0.7152f * linearise (colour.getFloatGreen()) - + 0.0722f * linearise (colour.getFloatBlue()); + juce::MessageManager::getInstance()->runDispatchLoopUntil (ms); + juce::Timer::callPendingTimersSynchronously(); } - -float contrastRatio (juce::Colour first, juce::Colour second) +void setParameter (SuppressorProcessor& p, const char* id, float plain) { - const auto lighter = std::max (relativeLuminance (first), relativeLuminance (second)); - const auto darker = std::min (relativeLuminance (first), relativeLuminance (second)); - return (lighter + 0.05f) / (darker + 0.05f); + auto* parameter = p.apvts.getParameter (id); + parameter->setValueNotifyingHost (parameter->convertTo0to1 (plain)); } - -int countPixelsNear (juce::Image& image, juce::Colour target) +void prepare (juce::AudioProcessor& p) +{ + p.setRateAndBufferSizeDetails (48000, 256); + p.prepareToPlay (48000, 256); +} +void feed (juce::AudioProcessor& p, int blocks = 120, bool silence = false, bool bypass = false) { - int count = 0; - for (int y = 0; y < image.getHeight(); ++y) - for (int x = 0; x < image.getWidth(); ++x) + juce::AudioBuffer buffer (2, 256); + juce::MidiBuffer midi; + for (int b = 0; b < blocks; ++b) + { + for (int n = 0; n < 256; ++n) { - const auto pixel = image.getPixelAt (x, y); - const auto distance = std::abs (pixel.getFloatRed() - target.getFloatRed()) - + std::abs (pixel.getFloatGreen() - target.getFloatGreen()) - + std::abs (pixel.getFloatBlue() - target.getFloatBlue()); - if (distance < 0.08f) - ++count; + const auto phase = juce::MathConstants::twoPi * (b * 256 + n) / 48000.0; + const auto sample = silence ? 0.0f : static_cast (0.08 * std::sin (220.0 * phase) + + 0.003 * std::sin (11000.0 * phase)); + buffer.setSample (0, n, sample); + buffer.setSample (1, n, sample * 0.7f); } - return count; + if (bypass) p.processBlockBypassed (buffer, midi); else p.processBlock (buffer, midi); + } } - -juce::MouseEvent makeMouseEvent (juce::Component& target, juce::ModifierKeys modifiers, - int clicks) +juce::MouseEvent event (juce::Component& c, juce::Point pos = { 60, 60 }, int modifiers = juce::ModifierKeys::leftButtonModifier) { const auto now = juce::Time::getCurrentTime(); - return { juce::Desktop::getInstance().getMainMouseSource(), { 60.0f, 70.0f }, - modifiers, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, &target, &target, now, - { 60.0f, 70.0f }, now, clicks, false }; + return { juce::Desktop::getInstance().getMainMouseSource(), pos, juce::ModifierKeys (modifiers), + 1, 0, 0, 0, 0, &c, &c, now, { 60, 60 }, now, 1, true }; } - -struct ParameterGestureCounter final : juce::AudioProcessorParameter::Listener +struct Gestures final : juce::AudioProcessorParameter::Listener { - void parameterValueChanged (int, float) override {} - void parameterGestureChanged (int, bool starting) override - { - starting ? ++starts : ++ends; - } - - void reset() - { - starts = 0; - ends = 0; - } - - int starts = 0; - int ends = 0; + void parameterValueChanged (int, float) override { ++changes; } + void parameterGestureChanged (int, bool start) override { start ? ++starts : ++ends; } + int starts = 0, ends = 0, changes = 0; }; - +juce::Label* namedLabel (juce::Component& editor, const juce::String& name) +{ + for (auto* c : editor.getChildren()) + if (c->getName() == name) return dynamic_cast (c); + return nullptr; } - -TEST_CASE ("theme colours meet text and essential-control contrast targets") +void capture (juce::Component& editor, const juce::String& name, float scale = 1.0f) { - CHECK (contrastRatio (SuppressorTheme::primaryText, SuppressorTheme::canvas) >= 4.5f); - CHECK (contrastRatio (SuppressorTheme::secondaryText, SuppressorTheme::surface) >= 4.5f); - CHECK (contrastRatio (SuppressorTheme::accent, SuppressorTheme::canvas) >= 4.5f); - CHECK (contrastRatio (SuppressorTheme::warning, SuppressorTheme::surface) >= 4.5f); - CHECK (contrastRatio (SuppressorTheme::error, SuppressorTheme::surface) >= 4.5f); - CHECK (contrastRatio (SuppressorTheme::controlOutline, - SuppressorTheme::surfaceRaised) >= 3.0f); + const auto path = juce::SystemStats::getEnvironmentVariable ("SUPPRESSOR_UI_CAPTURE_DIR", {}); + if (path.isEmpty()) return; + auto dir = juce::File (path); + REQUIRE (dir.createDirectory().wasOk()); + const auto image = editor.createComponentSnapshot (editor.getLocalBounds(), true, scale); + auto file = dir.getChildFile (name + ".png"); + file.deleteFile(); + auto stream = file.createOutputStream(); + REQUIRE (stream != nullptr); + REQUIRE (juce::PNGImageFormat().writeImageToStream (image, *stream)); + stream->flush(); } - -TEST_CASE ("rotary value is represented by a proportional accent arc") +float contrast (juce::Colour a, juce::Colour b) { - SuppressorLookAndFeel lookAndFeel; - juce::Slider slider; - slider.setLookAndFeel (&lookAndFeel); - - auto render = [&] (float position) + auto lum = [] (juce::Colour c) { - juce::Image image (juce::Image::RGB, 120, 120, true); - juce::Graphics graphics (image); - lookAndFeel.drawRotarySlider (graphics, 10, 10, 100, 100, position, - juce::MathConstants::pi * 1.25f, - juce::MathConstants::pi * 2.75f, - slider); - return image; + auto linear = [] (float v) { return v <= 0.04045f ? v / 12.92f : std::pow ((v + 0.055f) / 1.055f, 2.4f); }; + return .2126f * linear (c.getFloatRed()) + .7152f * linear (c.getFloatGreen()) + .0722f * linear (c.getFloatBlue()); }; - - auto minimum = render (0.0f); - auto maximum = render (1.0f); - CHECK (countPixelsNear (maximum, SuppressorTheme::accent) - > countPixelsNear (minimum, SuppressorTheme::accent) + 100); -} - -TEST_CASE ("rotary value field is centred and leaves the complete dial draggable") -{ - juce::ScopedJuceInitialiser_GUI gui; - SuppressorLookAndFeel lookAndFeel; - juce::Component parent; - ParameterSlider slider; - parent.setLookAndFeel (&lookAndFeel); - parent.setBounds (0, 0, 120, 140); - parent.addAndMakeVisible (slider); - slider.setBounds (parent.getLocalBounds()); - slider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); - slider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 80, 24); - - const auto layout = lookAndFeel.getSliderLayout (slider); - CHECK (layout.sliderBounds == slider.getLocalBounds()); - CHECK (layout.textBoxBounds.getCentre() == slider.getLocalBounds().getCentre()); - - auto* valueLabel = dynamic_cast (slider.getChildComponent (0)); - REQUIRE (valueLabel != nullptr); - bool labelInterceptsClicks = true; - bool editorInterceptsClicks = false; - valueLabel->getInterceptsMouseClicks (labelInterceptsClicks, editorInterceptsClicks); - CHECK_FALSE (labelInterceptsClicks); - CHECK (editorInterceptsClicks); - CHECK (slider.getComponentAt (layout.textBoxBounds.getCentre()) == &slider); + return (std::max (lum (a), lum (b)) + .05f) / (std::min (lum (a), lum (b)) + .05f); } +} // namespace -TEST_CASE ("bundled display font is available in both weights") +TEST_CASE ("theme retains bundled fonts and accessible contrast") { - const auto regular = SuppressorTheme::makeFont (18.0f); - const auto semibold = SuppressorTheme::makeFont (18.0f, true); - CHECK (regular.getTypefaceName() == "Barlow Condensed"); - CHECK (semibold.getTypefaceName() == "Barlow Condensed"); - CHECK (regular.getTypefaceStyle() != semibold.getTypefaceStyle()); + using namespace SuppressorTheme; + CHECK (makeFont (18).getTypefaceName() == "Barlow Condensed"); + CHECK (makeFont (18).getTypefaceStyle() != makeFont (18, true).getTypefaceStyle()); + for (auto colour : { primaryText, secondaryText, accent, warning, error }) + CHECK (contrast (colour, surface) >= 4.5f); + CHECK (contrast (controlOutline, surfaceRaised) >= 3.0f); } -TEST_CASE ("double-click opens exact value entry instead of resetting") +TEST_CASE ("four controls preserve the complete host parameter and saved-state contract") { juce::ScopedJuceInitialiser_GUI gui; - SuppressorLookAndFeel lookAndFeel; - ParameterSlider slider; - slider.setLookAndFeel (&lookAndFeel); - slider.setBounds (0, 0, 120, 140); - slider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); - slider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 80, 24); - slider.setDoubleClickReturnValue (true, 0.5); - slider.setValue (0.8); - - auto event = makeMouseEvent (slider, - juce::ModifierKeys (juce::ModifierKeys::leftButtonModifier), - 2); - slider.mouseDoubleClick (event); - - auto* valueLabel = dynamic_cast (slider.getChildComponent (0)); - REQUIRE (valueLabel != nullptr); - CHECK (valueLabel->isBeingEdited()); - CHECK (slider.getValue() == doctest::Approx (0.8)); - slider.hideTextBox (true); -} - -TEST_CASE ("invalid exact-entry text preserves the current value") -{ - ParameterSlider slider; - slider.setRange (-80.0, 0.0, 0.1); - slider.setTextValueSuffix (" dB"); - slider.setValue (-40.0); - - CHECK (slider.getValueFromText ("not a number") == doctest::Approx (-40.0)); - CHECK (slider.getValueFromText ("--12") == doctest::Approx (-40.0)); - CHECK (slider.getValueFromText ("1e2") == doctest::Approx (-40.0)); - CHECK (slider.getValueFromText ("0x10") == doctest::Approx (-40.0)); - CHECK (slider.getValueFromText ("-12.5 garbage") == doctest::Approx (-40.0)); - CHECK (slider.getValueFromText ("-12.5 dB") == doctest::Approx (-12.5)); -} - -TEST_CASE ("Alt/Option-click remains the non-conflicting default reset gesture") -{ - struct DragCounter final : juce::Slider::Listener + SuppressorProcessor p; + SuppressorEditor editor (p); + editor.addToDesktop (0); + editor.setVisible (true); + const juce::StringArray ids { "strength", "threshold", "release", "gateMode", "depth", "hysteresis", "hold", + "adaptiveRelease", "cue", "sidechain", "lookahead", "humEnable", "humBase", "humHarmonics", "humStrength", + "humLearn", "bandMode", "bandsLearn", "deltaAudition", "outputGain" }; + const std::array defaults { .9f, -40, 6, 0, 40, 6, 2, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0 }; + REQUIRE (p.getParameters().size() == ids.size()); + CHECK (editor.getWidth() == 640); + CHECK (editor.getHeight() == 460); + CHECK_FALSE (editor.isResizable()); + std::set focus; + int controls = 0; + for (int i = 0; i < ids.size(); ++i) { - void sliderValueChanged (juce::Slider*) override {} - void sliderDragStarted (juce::Slider*) override { ++starts; } - void sliderDragEnded (juce::Slider*) override { ++ends; } - - int starts = 0; - int ends = 0; - } counter; - - juce::ScopedJuceInitialiser_GUI gui; - ParameterSlider slider; - slider.addListener (&counter); - slider.setBounds (0, 0, 120, 140); - slider.setRange (0.0, 1.0, 0.01); - slider.setDoubleClickReturnValue (true, 0.25, juce::ModifierKeys::altModifier); - slider.setValue (0.8); - - auto event = makeMouseEvent ( - slider, - juce::ModifierKeys (juce::ModifierKeys::leftButtonModifier - | juce::ModifierKeys::altModifier), - 1); - slider.mouseDown (event); - CHECK (slider.getValue() == doctest::Approx (0.25)); - CHECK (counter.starts == 1); - CHECK (counter.ends == 1); - - auto dragEvent = makeMouseEvent ( - slider, juce::ModifierKeys (juce::ModifierKeys::leftButtonModifier), 1); - slider.mouseDown (dragEvent); - CHECK (counter.starts == 2); - CHECK (counter.ends == 1); - slider.endActiveGesture(); - CHECK (counter.ends == 2); - CHECK (slider.getThumbBeingDragged() == -1); + auto* param = p.apvts.getParameter (ids[i]); + REQUIRE (param != nullptr); + CHECK (p.getParameters()[i] == param); + CHECK (param->getVersionHint() == 1); + CHECK (param->convertFrom0to1 (param->getDefaultValue()) == doctest::Approx (defaults[static_cast (i)])); + auto* component = editor.findChildWithID (ids[i]); + const bool visible = ids[i] == "strength" || ids[i] == "threshold" || ids[i] == "release" || ids[i] == "deltaAudition"; + CHECK ((component != nullptr) == visible); + if (component != nullptr) + { + ++controls; + CHECK (editor.getControlParameterIndex (*component) == i); + CHECK (component->getWidth() >= 32); + CHECK (component->getHeight() >= 32); + CHECK (component->getWantsKeyboardFocus()); + CHECK (component->getName().isNotEmpty()); + CHECK (component->getAccessibilityHandler() != nullptr); + CHECK (focus.insert (component->getExplicitFocusOrder()).second); + } + param->setValueNotifyingHost (param->convertTo0to1 (param->convertFrom0to1 (0.27f))); + } + CHECK (controls == 4); + juce::MemoryBlock saved; + p.getStateInformation (saved); + SuppressorProcessor recalled; + recalled.setStateInformation (saved.getData(), static_cast (saved.getSize())); + for (const auto& id : ids) + CHECK (recalled.apvts.getParameter (id)->getValue() == doctest::Approx (p.apvts.getParameter (id)->getValue())); } - -TEST_CASE ("editor integrates every parameter with truthful state and balanced gestures") +TEST_CASE ("dials support balanced drag, fine, exact, cancel, invalid, reset and close gestures") { juce::ScopedJuceInitialiser_GUI gui; - SuppressorProcessor processor; - auto editor = std::make_unique (processor); - - const juce::StringArray parameterIds { - "strength", "threshold", "release", "gateMode", "depth", "hysteresis", "hold", - "adaptiveRelease", "cue", "sidechain", "lookahead", "humEnable", "humBase", - "humHarmonics", "humStrength", "humLearn", "bandMode", "bandsLearn", - "deltaAudition", "outputGain" + SuppressorProcessor p; + auto editor = std::make_unique (p); + editor->addToDesktop (0); + editor->setVisible (true); + auto* dial = dynamic_cast (editor->findChildWithID ("strength")); + REQUIRE (dial != nullptr); + auto& param = dial->parameter; + Gestures gestures; + param.addListener (&gestures); + CHECK (dial->getComponentAt (dial->getLocalBounds().getCentre()) == dial); + setParameter (p, "strength", .5f); + dial->mouseDown (event (*dial)); + dial->mouseDrag (event (*dial, { 60, 36 })); + dial->mouseUp (event (*dial)); + CHECK (dial->getValue() == doctest::Approx (.6)); + CHECK (gestures.starts == 1); + CHECK (gestures.ends == 1); + dial->mouseDown (event (*dial)); + dial->mouseDrag (event (*dial, { 60, 36 }, juce::ModifierKeys::leftButtonModifier | juce::ModifierKeys::shiftModifier)); + dial->mouseUp (event (*dial)); + CHECK (dial->getValue() == doctest::Approx (.61)); + CHECK (dial->keyPressed (juce::KeyPress (juce::KeyPress::rightKey))); + CHECK (dial->getValue() == doctest::Approx (.62)); + auto* threshold = dynamic_cast (editor->findChildWithID ("threshold")); + REQUIRE (threshold != nullptr); + threshold->mouseDown (event (*threshold)); + for (int pixel = 1; pixel <= 6; ++pixel) + threshold->mouseDrag (event (*threshold, { 60, 60.0f - static_cast (pixel) }, + juce::ModifierKeys::leftButtonModifier | juce::ModifierKeys::shiftModifier)); + threshold->mouseUp (event (*threshold)); + CHECK (threshold->getValue() == doctest::Approx (-39.8)); + + auto enter = [&] (const juce::String& value, bool commit) + { + dial->keyPressed (juce::KeyPress (juce::KeyPress::returnKey)); + auto* entry = dynamic_cast (dial->getChildComponent (0)); + REQUIRE (entry != nullptr); + REQUIRE (entry->isVisible()); + CHECK (editor->getControlParameterIndex (*entry) == param.getParameterIndex()); + entry->setText (value); + if (commit) entry->onReturnKey(); else entry->onEscapeKey(); + CHECK_FALSE (entry->isVisible()); }; - - CHECK (editor->getWidth() == 1000); - CHECK (editor->getHeight() == 640); - CHECK_FALSE (editor->isResizable()); - REQUIRE (editor->getNumChildComponents() > 0); - CHECK (editor->getChildComponent (0)->getName() == "Suppression activity"); - CHECK (editor->getChildComponent (0)->getExplicitFocusOrder() == 1); - CHECK (processor.getParameters().size() == parameterIds.size()); - - std::set focusOrders; - std::set accessibleNames; - for (const auto& id : parameterIds) + enter ("35 %", true); + CHECK (param.convertFrom0to1 (param.getValue()) == doctest::Approx (.35)); + enter ("80 %", false); + CHECK (dial->getValue() == doctest::Approx (.35)); + for (const auto& invalid : { "junk", "nan", "inf", "--12", "12 garbage", "0x10", "1e2", "" }) { - CAPTURE (id); - auto* component = editor->findChildWithID (id); - auto* parameter = processor.apvts.getParameter (id); - REQUIRE (component != nullptr); - REQUIRE (parameter != nullptr); - - const auto parameterIndex = processor.getParameters().indexOf (parameter); - CHECK (editor->getControlParameterIndex (*component) == parameterIndex); - CHECK (component->isVisible()); - CHECK (component->getWidth() >= 32); - CHECK (component->getHeight() >= 32); - CHECK (component->getWantsKeyboardFocus()); - CHECK (component->getExplicitFocusOrder() > 0); - CHECK (focusOrders.insert (component->getExplicitFocusOrder()).second); - CHECK (component->getName().isNotEmpty()); - CHECK (accessibleNames.insert (component->getName().toStdString()).second); - - if (auto* slider = dynamic_cast (component)) - { - CHECK (slider->getTextFromValue (slider->getValue()).isNotEmpty()); - REQUIRE (slider->getNumChildComponents() > 0); - CHECK (editor->getControlParameterIndex (*slider->getChildComponent (0)) - == parameterIndex); - } + enter (invalid, true); + CHECK (dial->getValue() == doctest::Approx (.35)); } - CHECK (focusOrders.size() == 20); - CHECK (accessibleNames.size() == 20); - - auto* strength = dynamic_cast (editor->findChildWithID ("strength")); - auto* strengthParameter = processor.apvts.getParameter ("strength"); - REQUIRE (strength != nullptr); - REQUIRE (strengthParameter != nullptr); - CHECK (strength->getValueFromText ("50 %") == doctest::Approx (0.5)); - CHECK (strength->getValueFromText ("50 garbage") - == doctest::Approx (strength->getValue())); - CHECK (strength->getValueFromText ("1e2") - == doctest::Approx (strength->getValue())); - - ParameterGestureCounter gestures; - strengthParameter->addListener (&gestures); - strength->setValue (0.2, juce::sendNotificationSync); - gestures.reset(); - CHECK (strength->keyPressed (juce::KeyPress (juce::KeyPress::rightKey))); - CHECK (gestures.starts == 1); - CHECK (gestures.ends == 1); - - strength->setValue (0.2, juce::sendNotificationSync); - gestures.reset(); - auto resetEvent = makeMouseEvent ( - *strength, - juce::ModifierKeys (juce::ModifierKeys::leftButtonModifier - | juce::ModifierKeys::altModifier), - 1); - strength->mouseDown (resetEvent); - CHECK (strength->getValue() == doctest::Approx (0.9)); - CHECK (gestures.starts == 1); - CHECK (gestures.ends == 1); - - auto* adaptive = dynamic_cast ( - editor->findChildWithID ("adaptiveRelease")); - auto* adaptiveParameter = processor.apvts.getParameter ("adaptiveRelease"); - REQUIRE (adaptive != nullptr); - REQUIRE (adaptiveParameter != nullptr); - ParameterGestureCounter buttonGestures; - adaptiveParameter->addListener (&buttonGestures); - CHECK (adaptive->keyPressed (juce::KeyPress (' '))); - CHECK (adaptive->getToggleState()); + enter ("999 %", true); + CHECK (dial->getValue() == 1.0); + dial->mouseDown (event (*dial, { 60, 60 }, juce::ModifierKeys::leftButtonModifier | juce::ModifierKeys::altModifier)); + CHECK (dial->getValue() == doctest::Approx (.9)); + setParameter (p, "strength", .42f); + CHECK (dial->getValue() == doctest::Approx (.42)); + capture (*editor, "suppressor-exact-entry-before"); + dial->beginEntry(); + capture (*editor, "suppressor-exact-entry"); + CHECK (gestures.starts == gestures.ends); + auto* listen = dynamic_cast (editor->findChildWithID ("deltaAudition")); + REQUIRE (listen != nullptr); + Gestures buttonGestures; + auto* listenParam = p.apvts.getParameter ("deltaAudition"); + listenParam->addListener (&buttonGestures); + CHECK (listen->keyPressed (juce::KeyPress (' '))); + CHECK (listenParam->getValue() == doctest::Approx (1.0)); CHECK (buttonGestures.starts == 1); CHECK (buttonGestures.ends == 1); - adaptiveParameter->removeListener (&buttonGestures); - - auto* bandMode = dynamic_cast (editor->findChildWithID ("bandMode")); - auto* bandsLearn = dynamic_cast ( - editor->findChildWithID ("bandsLearn")); - auto* bandsLearnParameter = processor.apvts.getParameter ("bandsLearn"); - REQUIRE (bandMode != nullptr); - REQUIRE (bandsLearn != nullptr); - REQUIRE (bandsLearnParameter != nullptr); - ParameterGestureCounter learnGestures; - bandsLearnParameter->addListener (&learnGestures); - bandMode->setSelectedItemIndex (1, juce::sendNotificationSync); - CHECK (bandsLearn->keyPressed (juce::KeyPress (' '))); - CHECK (bandsLearn->getToggleState()); - CHECK (bandsLearnParameter->getValue() == doctest::Approx (1.0f)); - CHECK (learnGestures.starts == 1); - CHECK (learnGestures.ends == 1); - bandsLearnParameter->removeListener (&learnGestures); - bandMode->setSelectedItemIndex (2, juce::sendNotificationSync); + listenParam->removeListener (&buttonGestures); + dial->mouseDown (event (*dial)); + editor->setVisible (false); + CHECK (gestures.starts == gestures.ends); + editor->setVisible (true); + dial->mouseDown (event (*dial)); + CHECK (gestures.starts == gestures.ends + 1); + editor.reset(); + CHECK (gestures.starts == gestures.ends); + param.removeListener (&gestures); +} - juce::Component* activity = nullptr; - juce::Label* bandsStatus = nullptr; - for (auto* child : editor->getChildren()) +TEST_CASE ("processor metering follows actual output without altering DSP") +{ + juce::ScopedJuceInitialiser_GUI gui; + SuppressorProcessor p; + prepare (p); + suppressor::DenoiserEngine reference; + reference.prepare (48000, 2, 120); + suppressor::EngineParams targets; + targets.strength01 = p.apvts.getRawParameterValue ("strength")->load(); + reference.setTargets (targets); + reference.reset(); + juce::AudioBuffer audio (2, 256), expected (2, 256); + juce::MidiBuffer midi; + float* io[] { expected.getWritePointer (0), expected.getWritePointer (1) }; + for (int block = 0; block < 50; ++block) { - if (child->getName() == "Suppression activity") - activity = child; - if (child->getName() == "Multiband learning status") - bandsStatus = dynamic_cast (child); + for (int ch = 0; ch < 2; ++ch) + for (int n = 0; n < 256; ++n) + audio.setSample (ch, n, static_cast (.03 * std::sin ((block * 256 + n) * .12))); + expected.makeCopyOf (audio); + reference.setTargets (targets); + reference.processBlock (io, nullptr, 0, 256, 2); + p.processBlock (audio, midi); + for (int ch = 0; ch < 2; ++ch) + CHECK (std::equal (audio.getReadPointer (ch), audio.getReadPointer (ch) + 256, expected.getReadPointer (ch))); } - REQUIRE (activity != nullptr); - REQUIRE (bandsStatus != nullptr); - CHECK (activity->getDescription().containsIgnoreCase ("learn armed")); - CHECK (activity->getDescription().containsIgnoreCase ("gain reduction unavailable")); - CHECK (activity->getDescription().containsIgnoreCase ("passes while processing")); - CHECK (bandsStatus->getText().containsIgnoreCase ("armed")); - CHECK_FALSE (bandsStatus->getText().containsIgnoreCase ("capturing")); - - auto* delta = dynamic_cast (editor->findChildWithID ("deltaAudition")); - REQUIRE (delta != nullptr); - delta->setToggleState (true, juce::sendNotificationSync); - bandMode->setSelectedItemIndex (1, juce::sendNotificationSync); - CHECK (activity->getDescription().containsIgnoreCase ("output: removed signal")); + for (bool delta : { false, true }) + { + p.releaseResources(); + prepare (p); + setParameter (p, "deltaAudition", delta ? 1.0f : 0.0f); + setParameter (p, "outputGain", 6); + audio.clear(); + audio.setSample (1, 0, .8f); + p.processBlock (audio, midi); + suppressor::MeterSnapshot s; + REQUIRE (p.readMeters (s)); + CHECK (s.input == doctest::Approx (.8f)); + CHECK (s.output == doctest::Approx (audio.getMagnitude (0, 256))); + CHECK (((s.flags & suppressor::MeterSnapshot::removed) != 0) == delta); + } + p.releaseResources(); + suppressor::MeterSnapshot s; + REQUIRE (p.readMeters (s)); + CHECK (s.flags == 0); +} - gestures.reset(); +TEST_CASE ("live meter states, stale data, legacy modes and monitor labels are explicit") +{ + juce::ScopedJuceInitialiser_GUI gui; + SuppressorProcessor p; + prepare (p); + auto editor = std::make_unique (p); + editor->addToDesktop (0); + editor->setVisible (true); + pump(); + juce::Component::unfocusAllComponents(); + auto* status = namedLabel (*editor, "Processing status"); + auto* summary = namedLabel (*editor, "Signal meters"); + REQUIRE (status != nullptr); + REQUIRE (summary != nullptr); + CHECK (status->getText() == "No audio"); + capture (*editor, "suppressor-default"); + feed (p); pump(); + CHECK (status->getText() == "Suppressing"); + CHECK (summary->getDescription().contains ("High-band reduction")); + capture (*editor, "suppressor-ui"); + for (float scale : { 1.0f, 1.25f, 1.5f, 1.75f, 2.0f }) { - auto dragEvent = makeMouseEvent ( - *strength, juce::ModifierKeys (juce::ModifierKeys::leftButtonModifier), 1); - strength->mouseDown (dragEvent); + editor->setScaleFactor (scale); + CHECK (editor->getWidth() == 640); + CHECK (editor->getLocalBounds().toFloat().transformedBy (editor->getTransform()).getWidth() + == doctest::Approx (640 * scale)); + capture (*editor, "suppressor-scale-" + juce::String (juce::roundToInt (scale * 100)), scale); } - CHECK (gestures.starts == 1); - CHECK (gestures.ends == 0); + editor->setScaleFactor (1); + setParameter (p, "deltaAudition", 1); pump(); + CHECK (status->getText() == "No audio"); // old output is not relabelled as removed audio + feed (p); pump(); + CHECK (status->getText() == "Listening to difference"); + CHECK (summary->getDescription().contains ("removed")); + capture (*editor, "suppressor-delta-audition"); + feed (p, 120, false, true); pump(); + CHECK (status->getText() == "Bypassed"); + capture (*editor, "suppressor-bypassed"); + setParameter (p, "deltaAudition", 0); + feed (p, 700, true); pump(); + CHECK (status->getText() == "No input"); + capture (*editor, "suppressor-silence"); + setParameter (p, "bandMode", 1); setParameter (p, "bandsLearn", 1); + feed (p); pump(); + CHECK (status->getText() == "Learning bands"); + CHECK (summary->getDescription().contains ("unavailable")); + CHECK_FALSE (editor->findChildWithID ("threshold")->isEnabled()); + CHECK_FALSE (editor->findChildWithID ("strength")->isEnabled()); + CHECK (namedLabel (*editor, "Host settings")->getText() == "Host settings active"); + capture (*editor, "suppressor-learning"); + setParameter (p, "bandsLearn", 0); feed (p); pump(); + CHECK (summary->getDescription().contains ("Maximum band reduction")); + pump (600); + CHECK (status->getText() == "No audio"); + CHECK (summary->getDescription() == "No current audio readings"); editor.reset(); - CHECK (gestures.ends == 1); - strengthParameter->removeListener (&gestures); + editor = std::make_unique (p); + editor->addToDesktop (0); + editor->setVisible (true); pump(); + CHECK (namedLabel (*editor, "Processing status")->getText() == "No audio"); } -TEST_CASE ("declared host scale factors preserve logical layout and render cleanly") +TEST_CASE ("sidechain levels are excluded and clipping is visible and accessible") { juce::ScopedJuceInitialiser_GUI gui; - const std::array scales { 1.0f, 1.25f, 1.5f, 1.75f, 2.0f }; + SuppressorProcessor p; + auto layout = p.getBusesLayout(); + layout.inputBuses.set (1, juce::AudioChannelSet::stereo()); + REQUIRE (p.setBusesLayout (layout)); + prepare (p); + SuppressorEditor editor (p); + editor.addToDesktop (0); + editor.setVisible (true); + juce::AudioBuffer buffer (4, 256); + juce::MidiBuffer midi; + buffer.clear(); + buffer.setSample (0, 0, .25f); + buffer.setSample (2, 0, 4.0f); + p.processBlock (buffer, midi); + suppressor::MeterSnapshot meters; + REQUIRE (p.readMeters (meters)); + CHECK (meters.input == doctest::Approx (.25f)); + CHECK ((meters.flags & suppressor::MeterSnapshot::inputClip) == 0); + buffer.clear(); + buffer.setSample (1, 0, 1.25f); + p.processBlockBypassed (buffer, midi); + pump(); + auto* summary = namedLabel (editor, "Signal meters"); + REQUIRE (summary != nullptr); + CHECK (summary->getDescription().contains ("Input clipped")); + CHECK (summary->getDescription().contains ("Output clipped")); + capture (editor, "suppressor-clipping"); + for (float value : { 0.0f, 1.0f }) + { + for (const char* id : { "threshold", "strength", "release" }) + p.apvts.getParameter (id)->setValueNotifyingHost (value); + pump(); + capture (editor, value > .5f ? "suppressor-maximum" : "suppressor-minimum"); + } +} - for (const auto scale : scales) +TEST_CASE ("VST3 native editor lifecycle and host parameter state recall") +{ + const auto path = juce::SystemStats::getEnvironmentVariable ("SUPPRESSOR_VST3_PATH", {}); + if (path.isEmpty()) return; + juce::ScopedJuceInitialiser_GUI gui; + juce::VST3PluginFormat format; + juce::OwnedArray types; + format.findAllTypesForFile (types, path); + REQUIRE (types.size() == 1); + juce::String errorMessage; + auto p = format.createInstanceFromDescription (*types[0], 48000, 256, errorMessage); + INFO (errorMessage); + REQUIRE (p != nullptr); + prepare (*p); + for (int reopen = 0; reopen < 3; ++reopen) { - CAPTURE (scale); - SuppressorProcessor processor; - auto editor = std::make_unique (processor); - const auto width = juce::roundToInt (1000.0f * scale); - const auto height = juce::roundToInt (640.0f * scale); + std::unique_ptr editor (p->createEditorIfNeeded()); + REQUIRE (editor != nullptr); + editor->addToDesktop (0); editor->setVisible (true); - editor->setScaleFactor (scale); - - CHECK (editor->getWidth() == 1000); - CHECK (editor->getHeight() == 640); - const auto transformedBounds = editor->getLocalBounds().toFloat().transformedBy ( - editor->getTransform()); - CHECK (transformedBounds.getWidth() == doctest::Approx (width)); - CHECK (transformedBounds.getHeight() == doctest::Approx (height)); - - juce::Image image (juce::Image::ARGB, width, height, true); - { - juce::Graphics graphics (image); - graphics.addTransform (juce::AffineTransform::scale (scale)); - editor->paintEntireComponent (graphics, true); - } - CHECK (image.getPixelAt (width - 2, height - 2).getAlpha() == 0xff); - CHECK (countPixelsNear (image, SuppressorTheme::accent) - > juce::roundToInt (100.0f * scale * scale)); - - const auto captureDirectory = juce::SystemStats::getEnvironmentVariable ( - "SUPPRESSOR_UI_CAPTURE_DIR", {}); - auto captureSucceeded = true; - if (captureDirectory.isNotEmpty()) - { - const auto directory = juce::File (captureDirectory); - captureSucceeded = directory.createDirectory().wasOk(); - auto output = directory.getChildFile ( - "suppressor-ui-" + juce::String (juce::roundToInt (scale * 100.0f)) - + ".png"); - juce::PNGImageFormat format; - auto stream = output.createOutputStream(); - captureSucceeded = captureSucceeded && stream != nullptr; - if (stream != nullptr) - captureSucceeded = captureSucceeded - && format.writeImageToStream (image, *stream); - } - CHECK (captureSucceeded); + pump(); + feed (*p); pump(); + CHECK (editor->getWidth() == 640); + CHECK (editor->getHeight() == 460); + // This is the host's native embedding view, not the plugin's JUCE + // component tree. Exercise the real wrapper rather than casting it. + REQUIRE (p->getParameters().size() == 21); // includes VST3 wrapper bypass + auto* parameter = p->getParameters()[1]; + CHECK (parameter->getName (64) == "Threshold"); + parameter->setValueNotifyingHost (.6f); + feed (*p); pump(); + juce::MemoryBlock state; + p->getStateInformation (state); + const auto saved = parameter->getValue(); + parameter->setValueNotifyingHost (.8f); + feed (*p); pump(); + CHECK (parameter->getValue() == doctest::Approx (.8f)); + p->setStateInformation (state.getData(), static_cast (state.getSize())); + feed (*p); pump(); + CHECK (parameter->getValue() == doctest::Approx (saved)); + editor->setVisible (false); + pump(); + editor->setVisible (true); + pump(); } + p->releaseResources(); } diff --git a/docs/images/suppressor-ui.png b/docs/images/suppressor-ui.png index 17e261490761f84cfa1c599ac6efa0b6dac074b3..96e01a23f1a56855d0b0db3a6f4dd7458036527a 100644 GIT binary patch literal 81840 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%0AFFhbJxh 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 z

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 Z=}p7_VX>Xh=QH4td#)s1BKi8={{nj*jrafn From 87e1df30759cfdfc24394f34b3ca30115c9464ed Mon Sep 17 00:00:00 2001 From: user1303836 Date: Fri, 25 Sep 2026 09:50:46 -0400 Subject: [PATCH 2/5] Stabilize native UI tests across CI desktops --- .github/workflows/ci.yml | 32 ++++++++++++++++++++---- README.md | 2 +- Tests/test_ui.cpp | 53 ++++++++++++++++++++++++++-------------- 3 files changed, 63 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 356777d..7453bd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build pkg-config xvfb \ + sudo apt-get install -y ninja-build pkg-config xvfb openbox x11-utils \ libasound2-dev libx11-dev libxext-dev libxinerama-dev libxrandr-dev \ libxcursor-dev libgl1-mesa-dev libfreetype6-dev libfontconfig1-dev @@ -92,12 +92,34 @@ jobs: run: | mkdir -p ui-captures if [ "${{ runner.os }}" = "Windows" ]; then - export SUPPRESSOR_UI_CAPTURE_DIR="$(pwd -W)/ui-captures" + SUPPRESSOR_UI_CAPTURE_DIR="$(pwd -W)/ui-captures" else - export SUPPRESSOR_UI_CAPTURE_DIR="$PWD/ui-captures" + SUPPRESSOR_UI_CAPTURE_DIR="$PWD/ui-captures" fi + export SUPPRESSOR_UI_CAPTURE_DIR if [ "${{ runner.os }}" = "Linux" ]; then - xvfb-run -a ctest --test-dir build --output-on-failure -C Release -R suppressor-ui-unit + # Native JUCE peers need WM_PROTOCOLS and other window-manager atoms, + # which a bare Xvfb server does not supply. Variables below belong to + # the inner shell running on that display. + # shellcheck disable=SC2016 + xvfb-run -a sh -c ' + openbox --sm-disable >/tmp/suppressor-openbox.log 2>&1 & + wm_pid=$! + trap "kill $wm_pid 2>/dev/null || true" EXIT + ready=false + for attempt in $(seq 1 50); do + if xprop -root _NET_SUPPORTING_WM_CHECK | grep -q "window id"; then + ready=true + break + fi + sleep 0.1 + done + if [ "$ready" != true ]; then + echo "Window manager did not start" + exit 1 + fi + ctest --test-dir build --output-on-failure -C Release -R suppressor-ui-unit + ' else ctest --test-dir build --output-on-failure -C Release -R suppressor-ui-unit fi @@ -233,7 +255,7 @@ jobs: for d in *.vst3; do [ -e "$d" ] && zip -qr "../suppressor-${short}-vst3.zip" "$d"; done && \ for f in *.clap; do [ -e "$f" ] && zip -qr "../suppressor-${short}-clap.zip" "$f"; done) done - ls -la *.zip + ls -la -- *.zip - uses: actions/upload-artifact@v4 with: diff --git a/README.md b/README.md index f966ed8..1eee6e5 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ ctest --test-dir build --output-on-failure ``` These also exercise native VST3/CLAP editor embedding. Linux needs a display -(or `xvfb-run -a`). Set `SUPPRESSOR_UI_CAPTURE_DIR` to an absolute directory to +with a window manager (CI uses Xvfb + Openbox). Set `SUPPRESSOR_UI_CAPTURE_DIR` to an absolute directory to save real editor captures at 100–200% scaling. ### CI diff --git a/Tests/test_ui.cpp b/Tests/test_ui.cpp index f685b0e..c796d6d 100644 --- a/Tests/test_ui.cpp +++ b/Tests/test_ui.cpp @@ -10,6 +10,24 @@ void pump (int ms = 50) juce::MessageManager::getInstance()->runDispatchLoopUntil (ms); juce::Timer::callPendingTimersSynchronously(); } +template +bool waitFor (Predicate predicate) +{ + // Message/timer delivery is asynchronous, especially on loaded CI hosts. + // Wait for the actual state with a deadline, not an assumed 50 ms tick. + const auto deadline = juce::Time::getMillisecondCounterHiRes() + 2000.0; + do + { + pump (20); + if (predicate()) return true; + } while (juce::Time::getMillisecondCounterHiRes() < deadline); + return false; +} +void expectStatus (juce::Label& status, const juce::String& expected) +{ + const bool matched = waitFor ([&] { return status.getText() == expected; }); + CHECK_MESSAGE (matched, "Expected ", expected, "; got ", status.getText()); +} void setParameter (SuppressorProcessor& p, const char* id, float plain) { auto* parameter = p.apvts.getParameter (id); @@ -288,8 +306,8 @@ TEST_CASE ("live meter states, stale data, legacy modes and monitor labels are e REQUIRE (summary != nullptr); CHECK (status->getText() == "No audio"); capture (*editor, "suppressor-default"); - feed (p); pump(); - CHECK (status->getText() == "Suppressing"); + feed (p); + expectStatus (*status, "Suppressing"); CHECK (summary->getDescription().contains ("High-band reduction")); capture (*editor, "suppressor-ui"); for (float scale : { 1.0f, 1.25f, 1.5f, 1.75f, 2.0f }) @@ -301,31 +319,30 @@ TEST_CASE ("live meter states, stale data, legacy modes and monitor labels are e capture (*editor, "suppressor-scale-" + juce::String (juce::roundToInt (scale * 100)), scale); } editor->setScaleFactor (1); - setParameter (p, "deltaAudition", 1); pump(); - CHECK (status->getText() == "No audio"); // old output is not relabelled as removed audio - feed (p); pump(); - CHECK (status->getText() == "Listening to difference"); + setParameter (p, "deltaAudition", 1); + expectStatus (*status, "No audio"); // old output is not relabelled as removed audio + feed (p); + expectStatus (*status, "Listening to difference"); CHECK (summary->getDescription().contains ("removed")); capture (*editor, "suppressor-delta-audition"); - feed (p, 120, false, true); pump(); - CHECK (status->getText() == "Bypassed"); + feed (p, 120, false, true); + expectStatus (*status, "Bypassed"); capture (*editor, "suppressor-bypassed"); setParameter (p, "deltaAudition", 0); - feed (p, 700, true); pump(); - CHECK (status->getText() == "No input"); + feed (p, 700, true); + expectStatus (*status, "No input"); capture (*editor, "suppressor-silence"); setParameter (p, "bandMode", 1); setParameter (p, "bandsLearn", 1); - feed (p); pump(); - CHECK (status->getText() == "Learning bands"); + feed (p); + expectStatus (*status, "Learning bands"); CHECK (summary->getDescription().contains ("unavailable")); CHECK_FALSE (editor->findChildWithID ("threshold")->isEnabled()); CHECK_FALSE (editor->findChildWithID ("strength")->isEnabled()); CHECK (namedLabel (*editor, "Host settings")->getText() == "Host settings active"); capture (*editor, "suppressor-learning"); - setParameter (p, "bandsLearn", 0); feed (p); pump(); - CHECK (summary->getDescription().contains ("Maximum band reduction")); - pump (600); - CHECK (status->getText() == "No audio"); + setParameter (p, "bandsLearn", 0); feed (p); + CHECK (waitFor ([&] { return summary->getDescription().contains ("Maximum band reduction"); })); + expectStatus (*status, "No audio"); CHECK (summary->getDescription() == "No current audio readings"); editor.reset(); editor = std::make_unique (p); @@ -361,8 +378,8 @@ TEST_CASE ("sidechain levels are excluded and clipping is visible and accessible pump(); auto* summary = namedLabel (editor, "Signal meters"); REQUIRE (summary != nullptr); - CHECK (summary->getDescription().contains ("Input clipped")); - CHECK (summary->getDescription().contains ("Output clipped")); + CHECK (waitFor ([&] { return summary->getDescription().contains ("Input clipped") + && summary->getDescription().contains ("Output clipped"); })); capture (editor, "suppressor-clipping"); for (float value : { 0.0f, 1.0f }) { From 97c976489179862cd550fd228255df9c56af47d8 Mon Sep 17 00:00:00 2001 From: user1303836 Date: Fri, 25 Sep 2026 10:02:55 -0400 Subject: [PATCH 3/5] Capture native-window failure diagnostics in CI --- .github/workflows/ci.yml | 12 ++++++++++-- Source/CMakeLists.txt | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7453bd6..90bad33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build pkg-config xvfb openbox x11-utils \ + sudo apt-get install -y ninja-build pkg-config xvfb openbox x11-utils gdb \ libasound2-dev libx11-dev libxext-dev libxinerama-dev libxrandr-dev \ libxcursor-dev libgl1-mesa-dev libfreetype6-dev libfontconfig1-dev @@ -118,7 +118,15 @@ jobs: echo "Window manager did not start" exit 1 fi - ctest --test-dir build --output-on-failure -C Release -R suppressor-ui-unit + if ! ctest --test-dir build --output-on-failure -C Release -R suppressor-ui-unit; then + # Native X11 errors terminate the process rather than fail an + # assertion. Collect a stack while keeping the test gate failed. + export SUPPRESSOR_VST3_PATH="$PWD/build/Source/Suppressor_artefacts/Release/VST3/suppressor.vst3" + export SUPPRESSOR_CLAP_PATH="$PWD/build/Source/Suppressor_artefacts/Release/CLAP/suppressor.clap" + gdb --batch -ex "set breakpoint pending on" -ex "break _XDefaultError" \ + -ex run -ex bt --args build/Source/SuppressorUITests --success || true + exit 1 + fi ' else ctest --test-dir build --output-on-failure -C Release -R suppressor-ui-unit diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 95c315c..15efdec 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -74,7 +74,8 @@ if (SUPPRESSOR_BUILD_TESTS) JUCE_MODAL_LOOPS_PERMITTED=1 JUCE_PLUGINHOST_VST3=1) - add_test(NAME suppressor-ui-unit COMMAND SuppressorUITests) + # Keep assertion context in CTest's failure log, including native-window exits. + add_test(NAME suppressor-ui-unit COMMAND SuppressorUITests --success) set_tests_properties(suppressor-ui-unit PROPERTIES ENVIRONMENT "SUPPRESSOR_VST3_PATH=$>") add_dependencies(SuppressorUITests Suppressor_VST3) From 2445c4007c4a1762a775ff801d8104b315c0d4c4 Mon Sep 17 00:00:00 2001 From: user1303836 Date: Fri, 25 Sep 2026 10:11:30 -0400 Subject: [PATCH 4/5] Remove dial helper captions and refresh the UI image --- Source/PluginEditor.cpp | 5 ----- Source/PluginProcessor.cpp | 1 - Source/PluginProcessor.h | 2 -- docs/images/suppressor-ui.png | Bin 81840 -> 74458 bytes 4 files changed, 8 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 728530d..d4b96b4 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -344,11 +344,6 @@ void SuppressorEditor::paint (juce::Graphics& g) label (g, "Threshold", { 24, 259, 192, 22 }, 18.0f, text, juce::Justification::centred); label (g, "Strength", { 224, 259, 192, 22 }, 18.0f, text, juce::Justification::centred); label (g, "Release", { 424, 259, 192, 22 }, 18.0f, text, juce::Justification::centred); - label (g, multi ? "One-split only" : "Reduce highs below this level", { 24, 388, 192, 17 }, 14.0f, secondary, juce::Justification::centred); - const auto cutoff = suppressor::DenoiserEngine::strengthToCutoff (strength.getValue(), - proc.meterSampleRate()); - label (g, multi ? "One-split only" : "Above " + juce::String (cutoff / 1000.0, 1) + " kHz", { 224, 388, 192, 17 }, 14.0f, secondary, juce::Justification::centred); - label (g, "How quickly highs close", { 424, 388, 192, 17 }, 14.0f, secondary, juce::Justification::centred); label (g, bypass ? "Monitor: dry input" : audition ? "Monitor: input minus processed" : "Monitor: processed signal", { 196, 419, 265, 20 }, 15.0f, audition ? warning : secondary); } diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 20405f0..3519036 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -120,7 +120,6 @@ void SuppressorProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) juce::ignoreUnused (samplesPerBlock); if (sampleRate <= 0.0) return; // stress hosts may probe 0/0: keep the previous state - displaySampleRate.store (static_cast (sampleRate)); engine.prepare (sampleRate, 2, (int) std::ceil (0.0025 * sampleRate)); currentLatency = (int) std::round (apvts.getRawParameterValue ("lookahead")->load() * 0.001 * sampleRate); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index aae6478..242e1d4 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -41,7 +41,6 @@ class SuppressorProcessor : public juce::AudioProcessor, // Bounded, race-free telemetry; never expose mutable DSP state to the editor. bool readMeters (suppressor::MeterSnapshot& result) const noexcept { return meters.read (result); } - float meterSampleRate() const noexcept { return displaySampleRate.load(); } static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); @@ -52,7 +51,6 @@ class SuppressorProcessor : public juce::AudioProcessor, suppressor::DenoiserEngine engine; suppressor::Metering meters; - std::atomic displaySampleRate { 44100.0f }; std::atomic latencyDirty { false }; std::atomic humAutoFinished { false }; diff --git a/docs/images/suppressor-ui.png b/docs/images/suppressor-ui.png index 96e01a23f1a56855d0b0db3a6f4dd7458036527a..eac0e8740c3fce479e9fac72f3b4cd12aa5888d8 100644 GIT binary patch delta 25542 zcmc$`cTkkgvp>3sf{Ey>NRR~q5fD(xaZwNi1QaAkL2}M{A5aNO1`&yiB3W{h3@#bT zNpe_nl*|IV+*#lAty`z+{&7y-->pj(RU4k^>FMt2F#VY=JUc(ue7>$qT_prq}*y3{cXRfz~^2XWcDsyTf$%;@!*VF6f+J zc`T%(Qo zOMPcS9K(m?On8lJWI1NWDn9y}iAoqx*r6vaCjVe?MPo+V{cvRajHPxZ&qC$C5URr#s{O z_0U$|k>v$C;om>dar!d@O=*Fv^#z|2^%(rL(8t82g?PW%UYN|YlNGqxN>+i3ujqaT zoARVH);xOJ&R9Jdh%jTan)Bo5kKfjlhyr`syJ_!Mqw_l;{p)u$NtrZG_b5tS23O`u zMYqTI`smf^#5^ONLc$XLDw~nluTu=S?z$xq=4u3Unc#?WQ;C8v!85 zd`k7Mt5@DBnU;l2N=8e~d+gHF(>Yl9I{%1xzP&s&_&c>~j4mdZn9_Ehw90JYYekNL zr-r}NWhgY!ulbtIPKCKOZih3*`rikB9usRw0l8TY#|5qKc$0JtH~W_*(^KztDvq@G zkewWJCw`LvtBI{W`{>OQARUO_ICr8-VfYXNALcCN>8dw9w_xfcyekGb{i z`q$}%?`v{oXEQsyRqMYE3CjImDzh0Le)lD5!!xjs&ZT|pcHOcBN2fZ~5@`g5U^PGP z+r2!G0{MM`ycb!Ne(`M-d*|0Enz#gcq@Kw?b6g#!xx>NHb=0dPU8YuFRVvd3h3QX6 zUBQL)tPS)~k5JmwVVHQ(3tCI`F8ae=lm)0?Ihdu=|VFJo%Y}3mEBz}oyUcop9 z`upua1p3`T^;j&XM8G)5@yM-obU%^H&03QqQ;p>_pgfhM0CX6OrmA|bD6gb$tQzgM zaW~|#E9{pPgiIA0OOO~V{+X+7{;hNN>f|0mpfi&S6S$lPZ3X{*20Wq#F){YRWPTkT zyR$Esc3vY}I+a|B*G<@4c`P^|SQH-aHnfpDG{Y=yS`uLQOPonag>2t7XIhg!U==1q zjrFg~%Cl1M>XuOwYd8IIsM+|mUX+fnY&*sZ+6^J5d@)aY{AQnYj*LvI=^fQIlI1G7 zpqq@oao^lg+iP7*3QGBxw=>c!wHYgPv2QP5UAjU-lWU{)T}VHbD|wKTkmQ4+P9=9CUe^`- z`bo&fNj+F|TtP6`*t~L9@A2a|;UcrFF;Ip#@!t~8Sh<-QK+bl_p`%Upve-hF#b z9M;4PD1x26{f30;>*k3&V|vXiJ9K5>>UrF3RoETPcODg9%?||x1aEdPEYeu~oX@_a zd7g>7jpE(-+Yy_W(yni2CsL#A^c_cD+|h*o{e~VWkr;TSob7>Kfy3Jv!_r#DM7#@z zQ9$5k5MS^0h}N_PIB)cbbJu@VjQIk0!vu>Maia@^OVP@A7dY<+U3+i9v@ znx0`gYhX>JJH%0hb^8G>TV_jF#)4YhDz{q1RJ_aVum&-?Z`An*N30>-myXtGwk`FXsN>1k*ROWoCw%<~*>LYWf-=_vcAGQb z%jg3&br^4f=ZSm;XSqOv9@Y1pva%;A->MD=p2mg#JxJRba4uyS3BTW&-!F?>Qn?lA zeCfTU{%$n~U_X_&jpDQE*)irCT4Wd=e232O{-mRMXz!0vMs(!|B_156h&qPr%}}iU zbVkA|C%Wuxxy>RDxS;&l}S>_Tl*RnU?Vuw0IF%RxkZL=`W4C+K*u2 zV|`lWd^2zPhPDsEmN|Ba`*!whukX-K z55Az8vN?@EPbt<$A#(*74jtfXs1lL%E$J}^NBOCKrFXcj4A>$5YtffSxHve%7P%OJPD#a_dUw4HcgWUtJ=BfHIQc46mG?z`Wj^N|ge?`27pf#epBxw~ogs zimxu|tR{OM*lkt?w2hU%O4iZUy}A@GZk4f4j=8|-w|pBxY1=bZtIfTyjIJy}CgnB# zgAsxCJwY;!0hjTq7xvDQr{>j%ey7mGDUaRv8fE;n@Oa{%&1?Hsn5wcs=WNJwg^n+! zMA%PD28_{3l<-=L7erSM+C=bF`P09F6$JyPittkR08?tLnN==3%`FvT(-mBg>?JF@ z=0Efr#@h3$OZz6zPP~&>6S+*-s#cu$Z#HR&_P^;?MYfos8A&xEa)*9afItfFy;s)DR%!^ z)g5{E5ZD*t^G|@%JrDtdo$mg2Vr6z5+(r0mXH!;I7 zse3@=){7Iib6JhX|qR zG$0cN^Mx2xloUt)yf9J9C-I{K3tJ9jKcEfIH1l@Zq!{hI6ntTx)R=0GLWUk}pb5)3 z+G)>RuWCib>2Xs6g`rsQ(t_b5ng+*f?1_40Fc$E1-&Y~`94~Jgb0ULCOD6#c78(f; zegZa(EkBIv1$&Z+s`lH^h`KKCVGsDNhI@&E-}xANI%rc*-`tMLCF-RGJRhJkLxr4s zcwE+g@X3DGBlGKSqiA~g@Ae4J8sO#PXDG;h)_bbi0ocB%QRy8=M;{7NF^KthP54^? zlj)T7unk(}4BNWs1GOyNoALYmAuyN(uxX7i2bo*Vzr>{3!|NujoCN%eY}s_LwuXEN z;$Rc#nzw>IohO|`|Aflv!y}Rw`yyYA-q9qM0#xCEfDVB`H+EKQl*}%0$*yE=gQnvb z@5TFtX*SgpcJ6IIChbPr9$?a*Q~5bV!r`Y<&@b=Z(G91H06eE{iK4mwgFBb66B*(B zJ%H=%drVA8#fRCGL2s9xmE@MSAGI+U2^Nf`?QD!+T?&jaCL)zV_hr&g8~L9~-LtfG zvIgNCicLX&*fzf@^%_{MIG|+l1sIC(;@&HTEq*jT2 z`z)Vic^b$tMhGQPmuPV;6=9YvItU)ahn-4^zXCJ}#`f8}ZkyzH?6}@oarrQThP9Q= zVBHuUx^gzHC0>uLw>}CU%&vFMZ(kNPY;-oVBuwjoFP35WeR>q< zyU7V>InD>oG!Hj-6|NXeQ3ZVPLyuRc=}8(cFAXm!yhQqMQ<=hM!R}|QJ?SUM)_lR_ zv!WK09!#9R@k4^0EN+Y=Dgk-K@2+Eo{Mn=MeI95?!|^R>wkB1BlG|MTI0D1?c0mDa@}-XGK2pxhsOsO;cttMm~~4fW4nmg!hXFR`xv{=k;awD8-g% zXvpDM!dO>MF39R&Qy__pLHw`k0>BD#^Q;C^@>=4EdL$>0O30YRfiU@iFs}h!487Bq z55z!gH(WpKUFsF{c2mC@PQm$_#egXqJXKu=r-d|H8IvuX*rd+Dyi;IaZ7{EmYQKKR ztyRIKlcNnQup_-PbDSb(kNhTlL>QF8`(qIIH45i^F?_;1LEayetbE*<{OXtyMi;&# zjBe2RT}(_{B9EG+5}g`b?)_8-l`Ybo-+OE#T{|`|%}e7b%+PV`C1t<|*cuBMlBhA1 zmB+F7*2euaOFQf+YCD?j%R=cND)hRP()2&*tApia2CsJDig8XfmOSpiCjSH$? zW*Yx+o`b%JochJuXsdCl1aZF>XKv5zQ`2f9nw0^RwV;Npz$xCi7SS!hP%zr4A3_;% znp40h8Di2Ph3OIXn*w5GL!u15-C1vTD{Wh$#uIt*g7M1vMEJ1BD^hU|vC|enTLpd;?oU>z^>esR^$WFTt57 zAfYq&InNWL_Q${QHo{0?e1vxTA}jQHJKq1lkD34H-DW+rJexJ$v;6ZHBU)rJJ$9g& z1Y2Vmh{&o#P=Gd^;x0e_JMr=@U>+dmM9C6U`Yt|mfbMdSACc>s(ysw0bTJ~MMHan1 zSn2}Yf$O~Z@Jxz7ZL!PuE9n{%0Plo=Lx;M(H(KZ@QcUX;J(j>=W#y1) zNJ&Y;7@=!x`z6kCbi*y(^tJ4l+YMe|zzY-oY8`rMdS6f=8P7Ek%!mS*5y&No9#v|RTJla~l550VZDfCA`5OSMKvM{{y=R6Vie8&kE_x&;xfHEms8Lw#~^ zHjsInc7Dpu-~3}f`z%oH;s;8z+Uj{cE?}|*)Ka})%87PQUr3G8YdPQX|B5B(v)q0h%R-R-PZ$XMO>#O4e%arfXdQC4T$HT8kTo zKF01h^D-C|Xbxn+eSeRT(>|1`uUS)8O7?o)mejK}V2IzTVf5U4;OgSC_apylox5h; zUi!7YnJpwezguGfjpbz3e%1c)*}zD*F#3bEs2foIY!fdB7Vk?)1T~ zcHu@CoxsEUi-#xsqvmUSP{qX8;$kQC$Spqaaz^A4vk1cp%{rnQyObcbmCT5nEXv8( z;k$Z4b=Jc-wr5`9wsm)s$=d`d`lU`4uCO3c#5vHw6U&BJ+#5g_cy)2w!f#QVzoJOO?q+e0q-59gl}?iPvI;qyS!kcQp}xO zU)WI|kKd9~kb5R~nGLv(=+j%^%Sy|wMvE6Dj@}#V;--2_&R99jBe%^NrFyg;AVkGZ z)=Tz&?m`a;6Z~*A*&m_7e^pZvNtfHA&nqLYir6Q;4S}F^e9t-65uI7rLKnBq;Ls6Q z&+zbI>J%H~qhxIS)vkI&gN;2XrNy`KMVjpDYA$kO!|OJ7%1mT6Yo%(pdpu2FHf~HO zj956pFxaoKPzRah)Dh>pS-Ir0R!Oj+g$3U}*3i}(2Vn-U`PrazAx#MhbQ0KkHLrup z`YdQ+cdO_0R2+98MDecXfXh%s-=sDCk~k8Nr8%c7uk}bjcH3N74AD5R0JARELAnUO zZlj!Z83+riU3ojOGRTW`EGaGt;|-Ghe0`W4M+960HJQ|&wpaLeYgT@TDE*&1Y-}q( zFcDX1Q3$A!+0cEvna0xL#f!G1z%Y8UL&*PlQK)Amm(XCLc`o=Kevwu z{an{tGjqoI5)CD}MrVX`&x*mWN_2mJzsruiGjRm4&O1t?kK^M{gxCu5^M{4HPcj&h z^DiWhX0}|npTuR=*6R1;`P50S-lX(M2CPOvdua_A-;;xtQFGMVa1S-Fm0JZwA8t-Q zuW8T3@ck1!S^qd$@ne73on%&1Q`427S9q3(o%4#_3PnM3%Zj}62$gO+ezny)Hx$B# z$vH1F&{H>!cXqr~Rds3`QIdGLw|8tfedbrUpLb=itN@iBfiR%sH?J5MFx{EA8~UgH zki23=uB$gRzQh$6?@)$PqBOK<-ji}YAcuUbVQ$}uM>Cq>vtOs5I*2AGJY?GwJIF}1 z8~(_Wx#B;4D)@Tyvq|`+QxV2pzi0yebs}cJ~lLJ96`0DYpX+3Y+lWeen!qCY$c6oFVDO_M}dHLeK;0e15 z{B{o%SYy`IMm3qk5xsTGjxj=RV`D?7%&RQF>e#BRtn5NpM5wBM5N7)({E70@8EPIN z?T>IQ*sXDpdhDl9tI6)uv8=VHJ6z1}VQ>krmoq)vpK0Qg51MU-m(63oRVkZjqfi{5 zqocoW86rK6YQ7rB86dH-XuSS*MrETgLKu1eT~O=}#wblT?6Ma)y4~-E!!=Az(+M6E z>(h9`5YF(bwoDot-akg0I0A}M1(gqtF`RijJVhDey(f|)w>SA=SFT)n z!&snIq?(s};Hve*A<9S6u$K`Y5D>5${Q(yb(`Q$4%|4?$*ZmRhE-n_h zV>0N`v~MzRNLXvlk;{H&)t@)^iqXpWopvt_!k%OioXaSm_Q#!z(1faFqg{Z}yiitl zGdhiugU7aBgU-C+$|Gb_YZG7o3BJR%(Aub%KO?SPX5~6_Z{_&d%l3ESqigKSSAS)? z>@HG9{ff8u!RVQqL)(*e$VsoE!Qs_hLr6m3L^k4uqGE_7_I0e}>2we$?=-VK9RgXS zEQ}WpyZPX;Taf;^V#ONJ(ZDBe-K%Heb92n1n?Er8r9ZShJw0>s^3*|?(fF>H?YcJX zp4&|*ouXtwdNFJY6KXzVWQcZ|aCCm$y_%KvJDrSPkl~G{X9oA{x=sU_usng;P(9=ICN7?DaHEVkcN#gq&ytuiG43yd$9TahmhmY4D zP1`DUDLsm~Mosfzqg)DT&b#5JYnw6|5vL~e`+Eu-XkU03PmoGDUT9SB9 z2y0C{buFuVQL*xQEg{fi9GnNEw_EybA!fsotK6PSVt22&ZIc|o>y>KfuH#(L7w;ki#Qi8 zX4)*AP=nV>bG;ij0pu-{u1mL$k1*+0HZ~tw^&y;!OJjv}NqWhE3POoCp7yveB7ar4 z?|~nPbA&MWYnC5xTAs&-S7}5{EY!;}im)w1|C|mFYv6WK1@Rh&-+UJK2Ka4$7Z%pr zgOgZ0{HPKgzs2+3Bj722;@tzJHLnbK7!(p0H7Q^n|KO3)KoL}X!U97x54m-HptK3! z!&-%>PkTqrlTC%sexn#ioO^IjK+GN%SxUNf(-34eH@J0dG~zRML0ZZb4V(}}D&}Ce zPgG*w(+^Ff3V<~>XjxijZ`N*&=v29A^DBgAfs8a6!h3ohuOM=>vX%?G>WRZ87H=lI zjb#(^or{_`wg1iQs2*qtXW6@8?M+Q%hrf^3$MtzmAIY|{6&2LuWhb`G`H?=$lda$g zZNHHeZZ+RVMaP!`dArk#&Ge`sI5G#aXVG%+^cZ`GhclM&J3gpY0sSQ2-~L)O5!k-o zUZ#riTP~ieOC`y`2xK#<5jn6cJs}8Qu1g)s#?*SmJa6~nrl|vYtK=S{+646 z{Gu01oIb--^l^?b==%n~r(pUz_W(i8&cU&_`3N0EF#=tSIFqM1U99k$4ao1w3du+q z)8W>wdIo}9?%v*BB(|kRmRqm%DWBP|UcaQI)Jl0d24?w$2bkr^kv#1ZS>C!m_pn=H z8ga;_=+sI}wagY@;zC6r4-!J2i>z0YtzQvkaPyQf*u8R@C9V35$76ad?o6RSl3Dt3 zi(Ng0>u#95H|?`aM#jUrdT^qIlALsy*l=50M4dfWS68p)`bSEyL>1{F&PZ@)G3+Yj zpHW7 z92Kw52)k0Oiey-y;e$~uj9KRo&ox6KWwyGO?{ZfwLwNDwoSdBe*2B*v@R_ZT7YiyE zMSY8{Vgo7Q`B3daL~7-u3nv9P=)^r7+SVA4F=(ASpMWW^HQTNP5j9Lp7}+mEmi!BG z!HAo%c05mciKDQsYIS89g91q84Dcr*fpUGKxj8xg&2f4;^WRvz;+-LzHXrv*JpERG z{~KO4YkT{VGyGva;CZWSBs~t9*#oH%eDjHoK5Cj>^}EMEw83opIOyK+PFnS+0xjA> z*vD_)Ta~)?#|!#_-pumD7BI$c*AatjM2@Ov(SdcjG07p`I8=UJ+OGzm3Z@%p*R&0k^2>vCN_3lgV+93;ppG;4=H=n)!d=)v4(gIk{(QZcZBJ zK6Dy0cWAzmAJ1oQaj>rK-+FdDQm?LYBT)!>nO*bi_SvH39Pz~v4{c~K@CEUohV(k1 zG-yDP7ca29lq6}J5WnE6Xt`3}fEphkFS8!8BfF);aFdI7nyS>azuY$c5m^HF-%DG~*cFFFR@7@cDxuBePkDwLXO;rNPae2VIA-8 z@9%pp#A>q1(+k-R{tO6tvl#N>a%^()^|1ab7YB-g>4lao(iD5~dZz{UoV-bzhsX{N z!{KSe@RotvBXv7l4Wx@tf`-*$)t8Kz?t!Ix&Ap|N z9Q0g*6tB?msG69V7}Zo&=?Dl2F!xk6v~%iNjl}+|57{L;O_2|R3Z0jfeb>fmIy{3Fi&(R_cWdudiQ?#dF}v8kDQ{?Z ze{##<#!Y$wtB=t!F+*cxbj=-)ML<}Wd08_rrxF_*yV9zu&n@54*%>>srlzD6wpoL5 zOLiTOCzTjaAY%q$gW%_99DRLjQ#==GO<%uW&Sr;GR7QR5S&Esoi$l-$N*talaOZI! zFQ)~V#oZEY*XM_cSr9VPp;PF7|HK5t-j6R|nCC^}Y2(deO{yp)kDi;Oob&Cfwq=t& z=HjlR_)J>*ypFRoT^1;islF$xzqs%J0;lcK7O<~9V~Js{10{rtC<7#SGS%mw<{*}7 zXuz?|Hl}4IM!l%4-gv3&gP5~7IQn9k<3Lnl%6088iF*5>1H-DI!#YFwj)2DRf{r#3 zb5#1=9Pfem-%wjrByT8&$@Sj`nJjL75oO&T9TA(Dn0vBSuQHIK91AMCin{%-A9k<| zbGIQFefe2E!;_!5aUST?%K^X6Lh4-*$}nmA_I zGHvcc>)y)18#}?+xHy_Y*sqSu64DqcVyKtXlUcr9UcPFlgX>%b7Z=z5oFWv*+La{0 zcOTzCT;CVX23H(|-tX;?OlJuhoW+3xnBR7cUnIFZRcQ_*?^d^M8q7^LF-3KeG2_dcXg;Zl#mZt*88{Om!-a&}w!j#Ys{%w6B=#*rNHi#jbLpyy1=MQg`wrbE#*E zg6bnxK5(Ki)8Oyl8X$c5*K(Bb+9nTYdVBaV>sC!H2 z2V9W4nOVl_+8UdDYnO=NynwB(;X2h#E^SKkifLqGx9IMge`~HrUcY<&p)Vl^qA_&J zo-+KC^6=rqbGvd3j_;ylq6g_(yZ&bNf+EtWq0I-9qA0$*_=tx6p~l2L(PU-crRWeg zc}q*kQuOR}_pw2V>o`qV;g+6L(Ub>nx@vRGdaoDGIEXmgR$%&kay%7Ooav;}_B&Qn z?~6Y%>FT>{%DoRt%-z>~$#06fWCfDn$|2MtC8#Z;q0u_u6Jw|P@NYcf4Q1t}pu1qC zq|r%{w)GpO=blVB*g{R8@};wqo~asYKn9Gze~TOJ&KYTIYgg@w#rbSY>Q=0T3E{7K z)L0*GHlRXCD9F9?y3EuJrto}CzLF@nE%L6f9#OH@KB2yb*Fj=2YgBf+Y=dqgRAbk! zxq>iqSA^s5=B48^d)8YszM(OCun}#{e&uJgT}iuL1t~rrsG}w97FRoTq-t3`$rz(~ zeIEd2WT?%7N6FzDV@-b-YQDsHY(|f3>`(6mC!NX{=}z#6yd#Hohx*`1frE|hs6KIg zW0)P~Q$#U^ofbz}AdmjcCJ0z(e7cAI(X-`ySFFSRU1+k`Du?L);M8RN_C8ea)`k_D z_Q}=UqPKkC-Xb?M2I+`^3hM39jtTHapsJrKx~6if8khJ9DnBS`Kw$)U$q#Uy^_iv0 zahVc(tq78CNs&L!jNZQgnpFg+YV<1_vT3(*VOTuj=&0j!IMp28|npA!aXKb}LaM94xO0{>>@yR#9WlJ8-pi`Ib>HN4`sMInq zAG5d0#k=NSj|;=^{Z6Y|Ovokf5j%3-z9Tk$boJw^{&wyC3&sIMJ830PLT==KHP);^ zo&CH5#rz|tO{om$*1Ngm?e67Or7UU*<;#XMQ0<&T_4q*T^e0#U>-=yue>k4oPJb4T~NHUle%=|BgyS?hLfy6oc$QL)f>WFJGilQ zIGl+>=2++B50 z?CB7i60WyVTeo#5YGNU{UywLg;RET3y_*<2K;cz9<$0(tUlem;wiKiRla8(WkIbXp zO&i}F?yr~h#6MSu2yJ3k(Dy;b+I^3jo)Hsa8%Qhtbu=A>d7eNaN0rTaQ5_| zf?^fCrxYG@nVAN;#PQ5qiZ>~1hbP3D;}!hwzAjM5hMVK-^T{|g)M#&x#`>dR|3_^!~S0KIhb zvLNo=iYd>_1uZ(ieZs-{c;TzJ-6DtLwY%Nbf$npY<+h3WwVUi!OH(IrTGC=KaFb4^ zeytQaa_nr3pg^>z_X4H7%zu)4|eOsI{LeOR+YptOE1)>&Dg7ziC z5E|hL+yX3Ob!UceKL7c(3SmAzxbMO@MOrYCIdmoWszDzDG>a@jao+K(?ITxTI%tVl zf^Zi^#+BIR_IqjXpq8Q)U`#=f^O39vCygL4!$|5?vv7&Yu_yggwe$+KQVpPmf)`mA z3-53(1)*o3J31Ea?C!qQ)=s}A=60S3l1TFS6*AS<(V_b474$`PY+SwAHFpJqSf8aN zXRe_-$Z74f+*XUT;ZH#SEa7_+JXs`J864?mry51BuF=0VFktLA64Rr0XFzDoaXo8Tw_Y?JBU!Di$`lE83^TXA;Tu+7}^azU_^y4HdaKT!3_V3P72KcgKx<=#FQK ze3~dx#l_OR)6w62y>pfF&ld`U?xh<4*inh@&dL{ia^h5TK#2?}m2NuxVn~@{s7gr> zw_B&#QZ!8y>eoFCEPvEGSy91H3$!v7Yy>T5C^03`!#vsipp`jB7zz=_61v41>anX9 zS8&22`pB?EQ6_)_sf|mCV$S4q33=%*7eq(D3P2-9`iUCq9ZJZC+p4E7)(jv#3OR7!(GETneJzQvh>NU@QXFBLni~%)sZ${)gwb`A2*EmR zLwVWXani^J&j0F93!=CM>uz~(%M|>b?f!_cAD6p20aSnhP4zdgu2Bov6mR39T*;ZM zw|7TS;zoO|Na*2#OHVOm$6tzZNo znxxL8kW2=q3!*OLAt4TRmZ}FQfeA>?$3eF{S^W-qQx{X5OXcUF`fX7 zxDfCp5EEPNo&es|1775&Ypo-lcES=VNQIO7DUcHV-#+-^gHEMt04|vTOVHx>t>|~C zac~K8+6fhEAu$f?W3GGyO(YTpWh+g z4&5jH1rD!YmF?6oSlPO~3msqY-!`X%kHURMMrIUk(Yp8Lqkh+s=+z3IX75 zQ{PMjAwdRmHi>cCFAD#HeZU&{2ml#wAM_FbDf6|7j$iV?SW~_Ac;a?QXzd%SO8`3j zzW_4XKMG}5ZtgNaZl|6BKvo10 zgbfe`)fRr}DEP@{FL_u>Gy&T7B|SZp%e(=GuIz`RmHq=#YPY5+%d$KCVoy@MQDP?H zOCRS%6eSA~Hvk#I5A-T?59wV^cR*+F=DQlua>4-XduY>Sf!@w&AyL=dBuJPbeH9Wi zx^#*&ah>r`6p=mj;!FES``x?VE^8vF?7VnB5x=K|hd}X4_v04rZ8NC3MzE{)?jDPv zX}(IkrE;0zzRj);6Ey>Q=7%6!6tbsJh)ERSao!SRXEz)y_Ms1DzzRsntyPV4SuXuxvbRfCf5F zr?55PO<=&>pqZJ|WeZoUk-&Sl`k&H-QuO6^DT2>|Z@{Vf*8mcx+h%g@lD4|GKG7?V zOLr3V$c$``?qUYE+&{(Tl%UG>b(*Ot@p&DuDI?>-?5YB0LCpMX{!NCq^ z4!rii$i-kV85|5Iz&ycRa|Yf%$vUgIpg?ECvq$9s;(8Kx3(@CLQM3v*a06X=N_9*0Q<}ql?WxprWNlr1NxJoBv8S>X5yVEK;Q2rR zR|fX21z?{y00@=C;22F;CU_GbaMDtyT|D}X=OS*bNnY)0E2OKdTk5(?0+3Slb%7*}^C9nV>!5L-EX(MofIu=c*K+U*Qn^FQOm{x4|B3tISo87)uPT4+^ z2UCQ5DE<~K2)`qOQ~?Ln1SD{7ME=m!&UtjDw&9`m-ZzFAuE$VRO3Ih0v{t(Aq2cwR zgS-jPE5WAoz`jl)*c?5e!3+?p7ObiH|JI5IT12y0W#rWENBi~0oD4<*wM&;;PU?J) z+aLa|e*YiGTBTGUUQ>^wg`fvF!q$2NM(FK7Uv?P@evuZN7-ui6SzV!%I8q=0@V)>9 z#nj(MK>$9<&;hOiZt4Z*P%&ca`EAsQ2mYI)4V{>)U*(fVQ2m$izv_<)bOs=w0jF6b%zqzs`v0$7||j@&_DtFEMLl!wnc8mFJCxW`If}m zO%VoiG*$q0Pi?YJCnonCrSd%KZU0l)cLFpY$e$E!I#;O5w9+l^W{?foEuxumpCn z1ioS3f_~^TOJI)C>*YSGMEwMx$Hjs?+u7wg0+)xFhrvoL4jc`m2Uhr#rfX*T@(fEl z0IAQw>C4RBM2yb;DDi85WxvoTb`|09ImD)j^yNx9&O57iZJ3%d(u9B zG!~D*w{ALEEEcu8SFYIU>gM7rj8c3gQSGrr_rdjx6rkFt^Q2w?N?ZfbOHE0EV}k1T z?p7@;mc_KCS!yLdqE5qVqO`S zN$IB@6{98b%k??GUzi!ZfPD=I_nRW&1Yox?15jEx36t^g@q#+tORaCO441L~`5>7* zcK`(Cy&RaXQ=hel6HHjEwX(B{>{${h;*^Q$SyESYew&a;SLM@P&P`wD)!ceV;{ww# zVB;p1C>7jRUd+SRVtCq)Wv`yg_V7I3%FiIoW3>Y*=O<2P`D1`#kP&@~mxb`?DUi9|*F9#ERg^ zj(I^9W2)HIw#X<81=E8&Nd{Z9h+@HNeR&Q`Ct+banw}bih%udnDp#2CCRSWC)ZnUstjvW?c6dd;T{b?|m z?>o@`y`I@;&yAff)9ZGAa)Zm-H3t{t_MK?7mxdZUU_M(nzK$G-qK8s8p%BpDn#K>^S3f)i!VW*ss2mU5D!c##Q92kR12XZD zMmB+U%7?d;h-g7t! zC-?C*N7pb$h>k(Jzs-sL79!zMPmr)e&K;Iw01UWECN;xEo~d@}eLF+cm1_~L-0t37 zjaM3W+9Ljd4Z24&FXsUQ*#kO1DVlY1KvLdG_}cHZv>?E54PynU=;wE-YhXZOX@|g& zaJ1My?|D)O4}O?Gx^Q$|!GVplG4^iaB;$q?WSlU)6jPn1zZvc(IC7zPD+)d-hlDlI zv_))!z`hCeyywT)$Y%cdD}yq-+=67qnMI-;qkFaZX1GNhG3>$n(~E%fhS@0imo|!2 zchJ_UH7XgMfA;j_z(O>-4XX;NMq;_v!rpedQPMKOy;d{{O=?3G@F?NkXvy8K3_fn*SS;(EnL+ z{;%1D{(sT>|IOxK{r|tUBxl)bzo$->)B-N^7T3vnG~BvjY;2irvko%V_h9$u_q~RO z$G-ZO35=DM=$YzPWTmA^jG6wm>^Ue2oQ96WH*Wvz0$I*!6Gy^*(;U70CW3FN?eFW$ z-$&*52fCs+tih6#bjjX6B|>%I_MSkt#_M21-SU=)G@um#GeRC=_E5T`4W# z;N#L27*f|ctv}1j&VB&aCshFQe+zT0+rrhGS--y9ERb;ymPae?oNzl&kPwI%o$H){ zE_i@N(?up`f~mRnPouysu~Nlzev;1BlR8yLEZ`cim6Tmdi%Jr;&r2%4|9}ZLq6q|G zjxE3`#c{p3mhmi>6)+yU2!|;;f|0dO@vM%{EQDvIBDB8}3ERwDRO-FY$)7-{K(Kq+ zX7*)3YHbS&&|q&=TADsN;Yp@&t*=B{_kUh zy(dXU&qlHW$NS&EB-kI}Tc*Fy!C)r;pSV1#8T#2Ay$&Z2e{o)aKmg|p+(j5h&+lXR zI=BitmUf?jWS;PM%noigJ$U_xa?%4s24Vw>X|?vSj)&eaGubVNfYy`mPkr>(rE5sD z5B4s+P ze50P1nfa0X*=n4V@U7@?>bV+g0W|nA+#4b-jW?Zaah>Ul85tIFZ*GELsmKdF7RBQ5 zge(LGYvKpM&X~ig#DY%VGlQA!Hy5@J3AYNsuRegjB&2qZ0AU4@-VjQ z<)hX3C98=h`QzpK-7=f@p9b|k+DpE=F%e1mVCQ?yA0Bp&pw};DZk;WQn!Yw&!DhlW z)fYCQtRccqbO9$#$FDINN}`7oy8vGBQh;C33UsN`^r`*sD<~>zT3IES{Ypto4?$eE z+DUfk=TK1Wi7c&mgxjIzLO*rJ-j$~lFw;vV63$9}?p7^b%^6y8N6H5L+JG7u8Lg&z zyeIiK=xQvh>C)Bb26(28QFW^D9WPTO$S#FikB9Bf9oam>o}773fRyCq7NDN?wqWw=Nq;goLsz`*4EZW-77AfVq#*;So!zwJ32a|L3`QR+1Uv6^#DBcsWb_N zMo_i1sp&#aem=REEkND@Tn(E%xDZ1|Mh3upg@NSU8lY8HZZrC|yc{Y=*rH-wT}vx#j#+yC;D@?~2II~|P0d6DN>@i`XmI6WM^X}ld~_=&sya?L}G(oyiM0F#^TkxdvuKOa8a8Y}VEEbSyH)fhB3 z_S%>G()L%mAPVu6dL#dQfJBu)v8QyJEE~z1N%cJk(eN64J%JyE0VTU)7p4a&AP3Xl z(_is?`?I85xpj30ttf}DAmQtlI0OjNp$O3PxxP2S&Bj)>RcC13{2?~_FaD4{nP2FB zz4Zvs^&2-x7%NK4SHs7*20{OCd;?{;~-hNe0U$??;pe+hw zITx|Ey6UnoFv7vbr*=5$9-*qHM(lj$Moq!lNxkkHIu~=4@2C9AW2=Sv^Fk^Waj`3S zLqzJ65E^R$@;e?di2%PfU}Y`fzS$oA4S-X>&=?#k8s!%iy>JdcIXl=Cm(1bpqCYAa zetChqc#Gaz>)pHa)HjHWe}C}v`}vy*`j91|>^|Q8JFV;2ctN@7WF8;q<=+S6ETvaE z`pGy$-hAK=VPd{SdF~SPJGSh5uPNlM45tvBuGzZk@;OY*&+&Dr2)No|O4U+jkZ!uT z$ddSgZxwq(%8~a*y zf;yD0$h5D=Q%@sHNN4bn4B<~=C%>Qg1-#t_~+mumh%(AuZSbAGevz}ea}+?9td5f!a0*IF@2r^J3zWpo8~ z$D~cw3|pHi#U;h>=HalLR|CQQmrfBZDU6w!`S(#DsaAzu*j@0p#H_1nK;=x;12-R| zGLt@CyZoU2Q*=_O`?N2qTBh=&=7r0Bm46EFH#Lu*i37wtd0!=H|Dm=n!KUCqB>kiInT3!uhP1&;jbYf{g4Fljp%F zC720v>i73!hNpr$kjl5I!sS`1-v!7qiI`q|LrS)~?L`&!BGPqNV*b4z56v;47W_U$ z&O_$gU2<6D)AWnavzcPXp^+QHMya~bB%0SIc*Jj0%X3ImQl_pJCoAO1K2{yUkpDJ6 z3IsG+`G0Yn8IB1<#;Y7|m5=>@9LY4bupNmHPc}10DNs>TGKChD6#lu$%0_Xcg;Og1 z&ExEUy1_j@k*t?iGOaDlqJrHf|0=3N6mNKM|9vKe4YBJ9diQQ->1^kp4~hz@eHM|C zkxWSgYx`Mwc~3^MyTPHMO=>DQ=ZcYNg>;YEfNA4CVko5cgVLRtSKD)>MP?hK1!L8s z?wfk3rlDj~Mjx2U7V8E}Xe!Jyz>g>l_PQZG4HL#NUpiQiDG*=`efkSE(F+#{&3(+XuDugYvHH5uNJMm@IsKDHu;YRJgThA{!4%Gv%@gn zVG~m>IKN3$Bbe26Wn9XW?uC-0?%NlrMd6kspS*$9PfQY0lr%+jw-qN}yt@3v`V0Bb z1I2gMUSiLQN5@A?OpG7GD-b>EcKYg0O2B$rGs%3X{?+&s{}V6Q=Aa2a8d7Kcf`X%d z_p=6x=sfJ|=A<+#(SNrbivVl`ehYrHs9{oW#nONyZ$Yi6O$c(9-(@6S-^Kau`%c5# ztmD|(an|&!oBVORvC%2buB3gK+cCP=N{MaD$Qm3+$vVCWkT~2AtYYHPn1)q$hbcDg zVt@t^pDWJ1o?Z_y(+ZFxbogoaag#S#sJNk#ZNcsW7?s7u$EX?`zuq)29+`tGc-kwQ z@&2G;y_iF%?RXW=tj@i5IbglQ!K{1}CU-F5neawR@37~Z?R2~+R4DZGqf_y+ylMo6 zvVlQ@g^<%y|9oDuwYuTp!@;eE1xL(AaZwR7Bx+>*-Y-YY*ZCfaPX#zg{@sW1zkRS$ zfSGMP_(6^$AlOfhsi>&X)YqonD;`Tm5Z zl6EVGT2obaU#hi?W3Jrz0_9l3ABz_S@&DW$HY_*)Aj*seLUI>cQ}$k+ggu5Q1%aIy zWB48Id$fHFHYY;K$w}FVyVr+DesFV(lTqEUxz0NzD@!Yb+#)qQUY>7T6VYh)?@x`- zv_-*Q?e7;S^!!Tk>$em#|45N&C^i`v{%MCr47nB3ON3am&V$5-S;HYrjgWY@RYcszdRH1<1vFJ~lqinW&YR@H(~&6KL)#?J}jpb>uRzYA2P3 zn-4x{{F)*|3V1E;K1h*85G{D$`ozvYu3sm-%M#Nlg1En8x7AiyT$ z1wd?KR= zpnA2L1MrYXh=JVO+gsGz@x7NY)PM*y#22P>>& z6!m4K#QiSOkzJ0UU>2&=YuzIFhLP6yEh#{G>unRX#HEAvn}eP4)J#lK4f*FNd9$OG{#>D}vvHNMKj`&tOerm;pslQhba zw`AYyTuM#1oOQlMSZ0NEyLKEamfZ=mx?!Y&23u8oOt}LLNQkJIBeWU+4qw1HOc1_? z1-Kq*@$wgNKH4^B>%aZt_V=}(1O0TNmN=XRSCUr6Zeo|1<4=-UR!sCuiGl{VS7KtU z(9u}Bp%mmwz-HDj67u+#O==!P?|c%6A#u(mEjzKaEo^f`=vz1RRg`{1bbFGls6k3% zASfroi1G)C^27mcQ#mf)4_1B$r`?X?vrmK-tfFuhippM_oI*jnNVPj%_S@-0$aQyQ z{DI1ah2s0y429BfzmbuV?9oSCTS&9>sdM|MfwCMHN0-C}9Jzc{7#1g_%hHMipyCpu z&svv1YlUkkcXSgM4F^eeCz+p!*Uh~IX$FzlAbefU(8}fy3s3Uy=BcRS@&SA@R>Gw( zDh?x$GAR_bG&bgyElB8@teTU>?FM5X#L-049X&b9cHq&7>nOKt&-5B&EonFX+J=qVYL5sgQlu;I}?ozDVaU2m9KHxz4rdM@Uy~5dTVJjhxFL~smX}#86 ztke(B4{E>LXIVY$W;Ql`2!McC#TH3?Y*_9NVJd3js*tn9r?D$^3!`1~a%~wURO|1a z_?IqFxSRYXtCoqY6sV4qm}EkBxGUG)A=qG9^{Rl94eAB~2MA5GYA#7QG- z$S517)-|ZEh1L47=`k$$jq3aNV=S{;^2mS5fa^j{XTP<7mY961x;YQ-7QF%n=5-AJ z(Nm-sdP;(cLMHGgBUEce6Q`S!5DnD>{6$7iUSZdMBIC6=<7ah}>Jl98l5v#Ozfl=b zv1Dutola|Y6!#nN#MZENt6_7qcUISF;;!^Xo1{f*rQ9EZx`=$BcZSqKhwb-Vcf+*P z=LFRUu%_vI`d|f=RvIk_r>%2coM-Kyq*{pg8_p<~4P_NoD9c#qRWRlzYVsy@JC@7H zkAJw)tA~S!W9Z4k1+QaiHKya5*xpOhM@7Xxw-gpuFtj#-2L{ca-f}-X47x-`raag^ zZ@o7<=6wU{A(dumY8n&P;rP{Y4mtSnb73iiKw|?M3l8>4KCC***@MKnD*~bH?k)$k zINl^9RRap&mt3{V$u|#u&DEYDd5f5_FIl+^oAqgdBKUj`4((srxJ(t97rMy&Ui3h@ zSC%5z;{rv~lNw1LVFj3!@0t9pE?X(v*=X17m_nH3BrjV%3O`nA{pAfB^h5Rkiunbs z-Ejq@E`|rA4q$`{I7&Ki`z>wLwLCZ{e^KaO{unoYI2R+b)fUrSpVcwZn>`dL&AM!o zl#CHEpQ`toB^W$<{Ba;h^K-Sl^dylHC*v660|lBAh@m?!FAn9G~>zo`OMG3 z4dm{ovSIc}n$#5@BMm#Hna>LLb93_3Lw<5pO&x<4b%A(DgejYMszGMDQl62r@PVaO z>IFi}CY_f!_b!eE3ZV|E7Aew+xSoE(6zI*Z$Osv; z&fl7d;h$NCoF~w0-M0>%H#{I=`P+{*a-mfO?qDprWw=;I#=33#@EkE7ZHXD#cTR;RT{0`1Sj;&d0 zNcP~XGF`@3)&19Oj=FWUzgv_pj8F9(SOK|yz$6g>>S1JOkv|;$wLOAn5o|$xoJ!%{ zxBOv19|NY7Dsn&P;~dT09PQSE2>x^b$)7j>tt&*J(kLt;L0GF1PjW6)11d-%rk>~Z za4>wAU3f<3N2ANOs>l4nUn`W^x)z+8}TeALs7}(Y_i~=zY2)W{u6^%CcjUb4)4Zj2LllPrWBFiN9qbBrKSVUGbV3FWc(- zjSZ708Is^Xp2)AN``ZERJ5d~SXvQlNf}V3;BNqKFHCw3$#e{a1<`phjm%@@7UsWv6 z1(|;9zk_MYL(!IPGfAwg`0OX`Uk?CL7TxUL!E4rd(YRADI%?|mBr${(Yl(1lK}rf! z!&c|H?NXdoVaZib4#gV+#Qh2=2@LTWZPiuCox8``+C8aN2=xJ;O@ z+td2~em{?+0mT4zKUV}3I~ z{#SQ33HUm?9J-b;b)V~fS7@LL8W?~;AV|o_X!<7QQBuQjN$b-|ZBZ7q0Xzu+@{FRp zOhCEck3Y-{=uHdasD4{i(j{UueMy$EkJfV@QCR<+nn%OoFe*_~N9$Ss zwsy zPMz-5XN@*%A;#RTkf#KM{P@98HWuZ;65f$ZXKZ4r66nmp@2C&6wW%WqbSd|dOtS8m zh^W}(Vi@~)5KsB-?QzW|%Wlq@)yNt*H}|k!w{SK1m)!@7ty7a22WWjqop%crLHca; z&xt9RqJ|9}49vZWAc})bid?I8r5YCo-7qEv$h9uOu2vj>sIK@e-S0ozXm={5+REzgEF ze%JTA&-ap;>JFq;a-DQKY|f`;7ue-vW20YX8VHupbz@@2q@^Y2+@nw2om>pX#M-_a zYCL~0;O57ff!_n8;;f23JjG7wBMI~pF!er#%C;Z>T;#Q%!A zYbTCd;*oeKnCD_|8OUE^@>g!UMDO9aj z29DpBtEx||iA_f`qxlC9Z&mwrtY=F9bfNk4Ci3U3#wIV4*T&qX=;+fsUv!ypOCO{x zzf;d?{Sq_hgeIn9fBu)vIw^oZt6i!%Z-EWULmg053x-I`>fiJ0R1%Za7!E zKHQj^yzR9XxnV~iG_`wp+++2%zi}T-J@B=P86hYhAxj$}i}^CDar#bt2E`I=CCizQ2xV zyA$381I`bRwb_n6u0h{H{(4kP3vAidvmI-*PWOfjHI!eRBK$hpZbySPjOi^4YdK;^ zW8ksn$rZ#y4;9rQtEC#>M&q$G89DSu#w2Feu~m#owpk>Nh1F|!69$Crl;%yzfj4?} ze^xp12){{gb^F`~U7+L5IW@dk^}*wVTU-|cwvJwt=u+fZpvU7!sKaO*0C?=~Qx>dw zre9>N^1+0MQz<$kLcLo4{3$gZUD!+}r>2uQRF@n@qkC4=JV!>x5)~Gv2JwvR&9~N8 z9&0`Fmx+kD`S>p5aEQW^;V7Et!}Sb8`0K#TE6R<1AKppu)!&XJyOtM{}eD6E3cHF-%WDMKNy-gn;(mqrF; z8xysFt~YJn&50P!hQy)fWVXGl%?Gt$i^BLl_$%e{!M+XYKYMZmNh-Q`t4pVD3TloK z0rh&Zt~K$51kTvln4yu;cMa@5KI4=5jTbR7C%jkcF7`dv@gl>0@dKf`q83uf!OSv)pFYOpj$h*#>+hqn$X*ossG23oASLTMEDn zJ=&bzdxrZ!_P*+Oaz;FJ^ph~eSl{(U=cL#E50EqE7V# z)Ofjb9Bgm0c-4XpCC%-B7@s?PJj#X_K&unJAlC$h|WXcBD<1nItJybOJ&tn6A+rrMp_ zhQIP>!yr#fLfk3jI1hg%Tl%NFl<|GgT75ggj){)u#m_@EO9bMXUXNRxmj zgBKvL-EJ--(D`Rc=yUxauKM%rwu27vM`2yyek8^Z{JW1)QKw9_A^$ZcIQHN5E+#2L zzTdwjqE5W_hl?+`72@T1$mjacISXAEU@qWajQ?*q|3LZgV8H)leEugm{}ULw|J}Zw aftPD|OK_!slS6r3;O-qgwNe$^=l=n@$K^x- delta 32982 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%0ACMNzzOKm2P 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 From b25e74506632074cc69925b2ee87dbe11dbbc231 Mon Sep 17 00:00:00 2001 From: user1303836 Date: Fri, 25 Sep 2026 10:13:21 -0400 Subject: [PATCH 5/5] Run native editor tests inside a real JUCE host application --- Source/CMakeLists.txt | 6 ++++-- Tests/test_ui.cpp | 10 ++++++++-- Tests/test_ui_main.cpp | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 Tests/test_ui_main.cpp diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 15efdec..a0c2352 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -56,6 +56,7 @@ if (SUPPRESSOR_BUILD_TESTS) add_executable(SuppressorUITests) target_sources(SuppressorUITests PRIVATE + ../Tests/test_ui_main.cpp ../Tests/test_ui.cpp) target_include_directories(SuppressorUITests PRIVATE @@ -63,7 +64,7 @@ if (SUPPRESSOR_BUILD_TESTS) target_link_libraries(SuppressorUITests PRIVATE Suppressor - doctest::doctest_with_main + doctest::doctest juce::juce_audio_utils juce::juce_recommended_config_flags juce::juce_recommended_warning_flags) @@ -72,7 +73,8 @@ if (SUPPRESSOR_BUILD_TESTS) JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_MODAL_LOOPS_PERMITTED=1 - JUCE_PLUGINHOST_VST3=1) + JUCE_PLUGINHOST_VST3=1 + $<$:_CONSOLE=1>) # Keep assertion context in CTest's failure log, including native-window exits. add_test(NAME suppressor-ui-unit COMMAND SuppressorUITests --success) diff --git a/Tests/test_ui.cpp b/Tests/test_ui.cpp index c796d6d..dbb5458 100644 --- a/Tests/test_ui.cpp +++ b/Tests/test_ui.cpp @@ -404,12 +404,18 @@ TEST_CASE ("VST3 native editor lifecycle and host parameter state recall") INFO (errorMessage); REQUIRE (p != nullptr); prepare (*p); + juce::Component hostWindow; + hostWindow.setBounds (80, 80, 640, 460); + hostWindow.addToDesktop (0); + hostWindow.setVisible (true); + pump(); for (int reopen = 0; reopen < 3; ++reopen) { std::unique_ptr editor (p->createEditorIfNeeded()); REQUIRE (editor != nullptr); - editor->addToDesktop (0); - editor->setVisible (true); + // Exercise embedding in the host's native window, rather than making + // the VST3 adapter itself the top-level window. + hostWindow.addAndMakeVisible (*editor); pump(); feed (*p); pump(); CHECK (editor->getWidth() == 640); diff --git a/Tests/test_ui_main.cpp b/Tests/test_ui_main.cpp new file mode 100644 index 0000000..10ba639 --- /dev/null +++ b/Tests/test_ui_main.cpp @@ -0,0 +1,33 @@ +#define DOCTEST_CONFIG_IMPLEMENT +#include +#include + +// Native embedding needs an actual host application, not only a message-thread +// initializer. In particular JUCE's XEmbed host makes an initial property query +// before the client window exists; JUCEApplication owns the platform event/error +// handling for that lifecycle, just as AudioPluginHost and pluginval do. +class UITestApplication final : public juce::JUCEApplication +{ +public: + const juce::String getApplicationName() override { return "Suppressor UI tests"; } + const juce::String getApplicationVersion() override { return "1.0"; } + bool moreThanOneInstanceAllowed() override { return true; } + + void initialise (const juce::String&) override + { + juce::MessageManager::callAsync ([this] + { + auto arguments = getCommandLineParameterArray(); + arguments.insert (0, getApplicationName()); + std::vector argv; + for (const auto& argument : arguments) argv.push_back (argument.toRawUTF8()); + doctest::Context context (static_cast (argv.size()), argv.data()); + setApplicationReturnValue (context.run()); + quit(); + }); + } + + void shutdown() override {} +}; + +START_JUCE_APPLICATION (UITestApplication)