diff --git a/README.md b/README.md index 7c0bb4b..4121b7d 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ and a configurable display. | MODE | 1 / 2 / 3 | BARS / SPECTRUM / LED | | LEDS | 4 – 48 | LEDs per bar in LED mode | | SMOOTH | 0 – 1 | Waveform smoothing amount in SPECTRUM mode | +| GRID | 0 – 1 | Grid (frequency/level) visibility / brightness| ## Building diff --git a/Source/Analyser.h b/Source/Analyser.h index 7ffd933..48c0d8e 100644 --- a/Source/Analyser.h +++ b/Source/Analyser.h @@ -70,5 +70,5 @@ private: double falloffRate = 0.8; // normalized units per second while falling double barReleaseTau = 0.2; // seconds for the live bar to fall (release) double minDb = -48.0; // dBFS-equivalent floor (bands below this -> 0) - double maxDb = -6.0; // dBFS-equivalent ceiling (bands at/above -> 1) + double maxDb = 0.0; // dBFS-equivalent ceiling (bands at/above -> 1) }; diff --git a/Source/AnalyserComponent.h b/Source/AnalyserComponent.h index 3b45f66..9650a43 100644 --- a/Source/AnalyserComponent.h +++ b/Source/AnalyserComponent.h @@ -34,6 +34,7 @@ public: const int mode = juce::roundToInt (params.getRawParameterValue ("mode")->load()); const int leds = juce::roundToInt (params.getRawParameterValue ("leds")->load()); const double smooth = *params.getRawParameterValue ("smooth"); + const double gridVis = *params.getRawParameterValue ("grid"); analyser.setGraceSeconds (graceMs / 1000.0); analyser.setFalloffRate (0.1 + falloff * 5.0); @@ -47,6 +48,7 @@ public: displayMode = mode; ledCount = leds; smoothAmount = static_cast (smooth); + gridAmount = static_cast (gridVis); analyser.update (1.0 / 60.0); repaint(); @@ -66,8 +68,8 @@ public: const float bandWidth = (plot.getWidth() - gap * (n + 1)) / static_cast (n); const float baseY = plot.getBottom(); - const juce::Colour gridColour = lf.transform (juce::Colours::grey.brighter (0.2f)).withAlpha (0.32f); - const juce::Colour labelColour = lf.transform (juce::Colours::grey); + const juce::Colour gridColour = lf.transform (juce::Colours::grey.brighter (0.2f)).withAlpha (gridAmount); + const juce::Colour labelColour = gridColour; // --- Gridlines (drawn first; labels drawn last so they stay readable) --- const double freqTicks[] = { 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000 }; @@ -179,18 +181,33 @@ private: } // Trace the curve. `smooth` controls how much the band levels are - // averaged (above); the path itself is straight segments between band - // centres, so at smooth = 0 it stays raw/edgy and at 1 it is rounded - // by the heavy level smoothing. - juce::Path wave; - wave.startNewSubPath (plot.getX() + gap, baseY); + // averaged (above); the path itself is a quadratic spline through the + // band centres (same technique as the peak wave), so at smooth = 0 it + // stays raw/edgy and at 1 it is rounded by the heavy level smoothing. + // The curve is anchored to the first/last band value right at the plot + // edges so it reaches the full width instead of ramping down to the + // baseline at the sides. + juce::HeapBlock> wpts (static_cast (n)); for (int i = 0; i < n; ++i) { const float x = plot.getX() + gap + i * (bandWidth + gap) + bandWidth * 0.5f; const float y = baseY - levels[i] * plot.getHeight(); - wave.lineTo (x, y); + wpts[i] = { x, y }; } - wave.lineTo (plot.getRight() - gap, baseY); + + juce::Path wave; + wave.startNewSubPath (plot.getX(), wpts[0].y); + wave.lineTo (wpts[0]); + for (int i = 0; i < n - 1; ++i) + { + const juce::Point mid = { (wpts[i].x + wpts[i + 1].x) * 0.5f, + (wpts[i].y + wpts[i + 1].y) * 0.5f }; + wave.quadraticTo (wpts[i], mid); + } + wave.quadraticTo (wpts[n - 1], wpts[n - 1]); + wave.lineTo (plot.getRight(), wpts[n - 1].y); + wave.lineTo (plot.getRight(), baseY); + wave.lineTo (plot.getX(), baseY); wave.closeSubPath(); // Brighter at the top, darker toward the bottom. @@ -233,7 +250,8 @@ private: } juce::Path peakWave; - peakWave.startNewSubPath (ppts[0]); + peakWave.startNewSubPath (plot.getX(), baseY - peaks[0] * plot.getHeight()); + peakWave.lineTo (ppts[0]); for (int i = 0; i < n - 1; ++i) { const juce::Point mid = { (ppts[i].x + ppts[i + 1].x) * 0.5f, @@ -241,6 +259,7 @@ private: peakWave.quadraticTo (ppts[i], mid); } peakWave.quadraticTo (ppts[n - 1], ppts[n - 1]); + peakWave.lineTo (plot.getRight(), baseY - peaks[n - 1] * plot.getHeight()); g.setColour (lf.getPeak()); g.strokePath (peakWave, juce::PathStrokeType (2.0f)); } @@ -292,6 +311,7 @@ private: int displayMode = ModeBars; int ledCount = 16; float smoothAmount = 0.35f; + float gridAmount = 0.32f; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent) }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index d699832..b74983f 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -13,7 +13,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess // reports IPlugView::canResize() == true to the host, which is what FL // Studio (and other hosts) use to enable their native maximize button. setResizable (true, true); - setResizeLimits (420, 260, 4000, 4000); + setResizeLimits (800, 260, 4000, 4000); analyserComponent.setLookAndFeel (&lookAndFeel); addAndMakeVisible (analyserComponent); @@ -36,6 +36,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess setupKnob (modeSlider, ""); setupKnob (ledsSlider, ""); setupKnob (smoothSlider, ""); + setupKnob (gridSlider, ""); barsSlider.setNumDecimalPlacesToDisplay (0); modeSlider.setNumDecimalPlacesToDisplay (0); ledsSlider.setNumDecimalPlacesToDisplay (0); @@ -80,6 +81,11 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess smoothLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey); addAndMakeVisible (smoothLabel); + gridLabel.setText ("GRID", juce::dontSendNotification); + gridLabel.setJustificationType (juce::Justification::centred); + gridLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey); + addAndMakeVisible (gridLabel); + scaleBox.addItemList (juce::StringArray ("75%", "100%", "150%", "200%", "250%", "300%"), 1); scaleBox.setSelectedItemIndex (1); // 100% scaleBox.setColour (juce::ComboBox::backgroundColourId, juce::Colours::black.brighter (0.15f)); @@ -127,6 +133,8 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess processorRef.getParametersState(), "leds", ledsSlider); smoothAttachment = std::make_unique( processorRef.getParametersState(), "smooth", smoothSlider); + gridAttachment = std::make_unique( + processorRef.getParametersState(), "grid", gridSlider); // Set these AFTER the attachments: SliderAttachment installs its own // textFromValueFunction, so assigning afterwards makes ours take effect. @@ -162,7 +170,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess scaleIdx = i; scaleBox.setSelectedItemIndex (scaleIdx); // triggers onChange -> setScaleFactor - const int storedW = uiState.getProperty ("uiWidth", getWidth()); + const int storedW = juce::jmax (800, static_cast (uiState.getProperty ("uiWidth", getWidth()))); const int storedH = uiState.getProperty ("uiHeight", getHeight()); setSize (storedW, storedH); } @@ -207,7 +215,7 @@ void BeamgridAudioProcessorEditor::resized() const int margin = 24; const int gap = 14; - const int count = 8; + const int count = 9; const int titleW = 220; // Available width for the knob row (leave room on the right for the title). @@ -227,6 +235,7 @@ void BeamgridAudioProcessorEditor::resized() { modeSlider, modeLabel }, { ledsSlider, ledsLabel }, { smoothSlider, smoothLabel }, + { gridSlider, gridLabel }, }; const juce::Font labelFont (juce::FontOptions (11.0f)); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index d1d3674..fa5a8d7 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -30,6 +30,7 @@ private: juce::Slider modeSlider; juce::Slider ledsSlider; juce::Slider smoothSlider; + juce::Slider gridSlider; juce::Label graceLabel; juce::Label falloffLabel; @@ -39,6 +40,7 @@ private: juce::Label modeLabel; juce::Label ledsLabel; juce::Label smoothLabel; + juce::Label gridLabel; juce::Label titleLabel; juce::Label statusLabel; @@ -52,6 +54,7 @@ private: std::unique_ptr modeAttachment; std::unique_ptr ledsAttachment; std::unique_ptr smoothAttachment; + std::unique_ptr gridAttachment; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamgridAudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 3142105..d37ea56 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -46,6 +46,11 @@ BeamgridAudioProcessor::createParameterLayout() juce::NormalisableRange (0.0f, 1.0f, 0.001f), 0.35f, juce::AudioParameterFloatAttributes().withLabel (""))); + params.push_back (std::make_unique( + "grid", "Grid Visibility", + juce::NormalisableRange (0.0f, 1.0f, 0.001f), 0.32f, + juce::AudioParameterFloatAttributes().withLabel (""))); + juce::NormalisableRange barsRange (8.0f, 128.0f, [] (float start, float end, float v) { return start + v * (end - start); }, // convertFrom0To1 [] (float start, float end, float v) { return (v - start) / (end - start); }, // convertTo0To1