#include "PluginEditor.h" #include "BuildInfo.h" namespace pascal { void MinimalLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) { const auto centre = juce::Point (x + width * 0.5f, y + height * 0.5f); const auto radius = juce::jmin (width, height) * 0.5f - 7.0f; const auto angle = rotaryStartAngle + (rotaryEndAngle - rotaryStartAngle) * sliderPos; juce::Path trackPath; trackPath.addArc (centre.x - radius, centre.y - radius, radius * 2.0f, radius * 2.0f, rotaryStartAngle, rotaryEndAngle, true); juce::Path fillPath; fillPath.addArc (centre.x - radius, centre.y - radius, radius * 2.0f, radius * 2.0f, rotaryStartAngle, angle, true); const auto thickness = juce::jmax (1.0f, radius * 0.10f); juce::PathStrokeType stroke (thickness, juce::PathStrokeType::curved, juce::PathStrokeType::rounded); g.setColour (track); g.strokePath (trackPath, stroke); g.setColour (slider.isMouseOverOrDragging() ? juce::Colours::white : accent); g.strokePath (fillPath, stroke); const auto indicator = centre + juce::Point (std::cos (angle - juce::MathConstants::halfPi), std::sin (angle - juce::MathConstants::halfPi)) * radius; g.fillEllipse (indicator.x - 2.0f, indicator.y - 2.0f, 4.0f, 4.0f); } KnobComponent::KnobComponent (juce::AudioProcessorValueTreeState& apvts, const juce::String& paramId, const juce::String& displayName, float defaultValue, juce::LookAndFeel& lnf, std::function fmt) : format (std::move (fmt)) { slider.setSliderStyle (juce::Slider::RotaryVerticalDrag); slider.setRotaryParameters (juce::MathConstants::pi * 1.25f, juce::MathConstants::pi * 2.75f, true); slider.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0); slider.setLookAndFeel (&lnf); slider.setDoubleClickReturnValue (true, defaultValue); slider.setWantsKeyboardFocus (true); slider.addListener (this); addAndMakeVisible (slider); nameLabel.setText (displayName, juce::dontSendNotification); nameLabel.setJustificationType (juce::Justification::centred); nameLabel.setColour (juce::Label::textColourId, labelColour); nameLabel.setColour (juce::Label::backgroundColourId, juce::Colours::transparentBlack); nameLabel.setColour (juce::Label::outlineColourId, juce::Colours::transparentBlack); addAndMakeVisible (nameLabel); valueLabel.setJustificationType (juce::Justification::centred); valueLabel.setColour (juce::Label::textColourId, valueColour); valueLabel.setColour (juce::Label::backgroundColourId, juce::Colours::transparentBlack); valueLabel.setColour (juce::Label::outlineColourId, juce::Colours::transparentBlack); addAndMakeVisible (valueLabel); attachment = std::make_unique ( apvts, paramId, slider); valueLabel.setText (format (slider.getValue()), juce::dontSendNotification); } void KnobComponent::resized() { auto bounds = getLocalBounds(); auto valueArea = bounds.removeFromBottom (18); auto nameArea = bounds.removeFromTop (16); const auto square = juce::jmin (bounds.getWidth(), bounds.getHeight()); nameLabel.setBounds (nameArea); slider.setBounds (bounds.withSizeKeepingCentre (square, square)); valueLabel.setBounds (valueArea); } void KnobComponent::sliderValueChanged (juce::Slider* slider) { valueLabel.setText (format (slider->getValue()), juce::dontSendNotification); } namespace { juce::String buildArch() { #if defined (__arm64__) || defined (_M_ARM64) || defined (__aarch64__) return "arm64"; #elif defined (__x86_64__) || defined (_M_X64) return "x64"; #else return "x86"; #endif } } FooterBar::FooterBar (PascalcompressorAudioProcessor& p) : processor (p) { leftText = juce::String ("Pascalcompressor 1.0.0") + " \u00B7 " + buildArch() + " \u00B7 VST3 + AU" + " \u00B7 built " + juce::String (PASCAL_BUILD_TIME) + " (local)"; startTimerHz (2); } void FooterBar::timerCallback() { const auto newRight = juce::String (juce::roundToInt (processor.getSampleRate())) + " Hz" + " \u00B7 " + juce::String (processor.getBlockSize()) + " smp"; if (newRight != rightText) { rightText = newRight; repaint(); } } void FooterBar::paint (juce::Graphics& g) { g.fillAll (background); g.setColour (ruleColour); g.fillRect (0, 0, getWidth(), 1); g.setColour (footerColour); g.setFont (juce::FontOptions (11.0f)); auto bounds = getLocalBounds().reduced (14, 0); g.drawText (leftText, bounds, juce::Justification::centredLeft); g.drawText (rightText, bounds, juce::Justification::centredRight); } PascalcompressorAudioProcessorEditor::PascalcompressorAudioProcessorEditor (PascalcompressorAudioProcessor& p) : AudioProcessorEditor (&p), processorRef (p), footer (p) { setOpaque (true); setResizeLimits (500, 340, 1100, 860); setResizable (true, true); struct Spec { const char* id; const char* name; float defaultValue; std::function format; }; const Spec specs[] = { { "input", "Input", 0.0f, [] (double v) { return juce::String::formatted ("%+.1f dB", v); } }, { "threshold", "Threshold", -18.0f, [] (double v) { return juce::String::formatted ("%.1f dB", v); } }, { "ratio", "Ratio", 2.0f, [] (double v) { return juce::String::formatted ("%.2f : 1", v); } }, { "attack", "Attack", 10.0f, [] (double v) { return juce::String::formatted ("%.1f ms", v); } }, { "release", "Release", 250.0f, [] (double v) { return juce::String::formatted ("%.0f ms", v); } }, { "makeup", "Makeup", 0.0f, [] (double v) { return juce::String::formatted ("%+.1f dB", v); } }, { "drywet", "Dry/Wet", 100.0f, [] (double v) { return juce::String (juce::roundToInt (v)) + " %"; } }, }; for (const auto& spec : specs) { auto* knob = new KnobComponent (processorRef.apvts, spec.id, spec.name, spec.defaultValue, lookAndFeel, spec.format); knobs.add (knob); addAndMakeVisible (knob); } addAndMakeVisible (footer); ready = true; setSize (620, 420); } void PascalcompressorAudioProcessorEditor::paint (juce::Graphics& g) { jassert (ready); g.fillAll (background); } void PascalcompressorAudioProcessorEditor::resized() { if (! ready) return; auto bounds = getLocalBounds(); footer.setBounds (bounds.removeFromBottom (24)); bounds.reduce (24, 16); const auto knobSize = juce::jmin (bounds.getWidth() / 4 - 42, (bounds.getHeight() - 20) / 2 - 40, 120); const auto knobHeight = knobSize + 40; const auto rowGap = 20; const auto rowsTotal = knobHeight * 2 + rowGap; const auto rowsTop = bounds.getY() + (bounds.getHeight() - rowsTotal) / 2; const auto xStepTop = bounds.getWidth() / 4; for (int i = 0; i < 4; ++i) { const auto x = bounds.getX() + xStepTop * i + (xStepTop - knobSize) / 2; knobs.getUnchecked (i)->setBounds (x, rowsTop, knobSize, knobHeight); } const auto xStepBottom = bounds.getWidth() / 3; const auto rowY = rowsTop + knobHeight + rowGap; for (int i = 4; i < 7; ++i) { const auto x = bounds.getX() + xStepBottom * (i - 4) + (xStepBottom - knobSize) / 2; knobs.getUnchecked (i)->setBounds (x, rowY, knobSize, knobHeight); } } }