diff --git a/CMakeLists.txt b/CMakeLists.txt index bcb6603..9d67751 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.22) -project(Mindball VERSION 1.0.0 LANGUAGES C CXX) +project(Mindball VERSION 0.0.5 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -54,8 +54,8 @@ endif() juce_add_plugin(Mindball PRODUCT_NAME "Mindball" VENDOR_NAME "Mindball" - VERSION "1.0.0" - DESCRIPTION "Mindball - a tiny, minimal delay plugin" + VERSION "0.0.5" + DESCRIPTION "Mindball - a minimalist delay plugin" FORMATS ${_formats} PLUGIN_MANUFACTURER_CODE Mind PLUGIN_CODE Mbll @@ -116,7 +116,7 @@ if(Git_FOUND) endif() endif() endif() -string(TIMESTAMP MINDBALL_BUILD_TIME "%Y-%m-%d %H:%M:%S UTC") +string(TIMESTAMP MINDBALL_BUILD_TIME "%Y-%m-%d %H:%M:%S") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Source/BuildInfo.h.in ${CMAKE_CURRENT_BINARY_DIR}/buildinfo/BuildInfo.h @ONLY) diff --git a/README.md b/README.md index f544f11..53359c1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mindball -A tiny, minimal stereo delay plugin with a no-frills UI. Feedback echoes, ping-pong +A minimalist stereo delay plugin with a no-frills UI. Feedback echoes, ping-pong and inverting modes, tempo-synced delay time, and an auto-panner - all wrapped in a clean, resizable interface with 20 factory presets. diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index ea693a5..faff5be 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -68,7 +68,7 @@ public: if (branch.isEmpty() || branch == "n/a") branch = "-"; - buildInfo.setText ("Mindball v1.0.0\n" + buildInfo.setText ("Mindball v0.0.5\n" + hash + " " + branch + dirty + "\n" + juce::String (mindball::BuildInfo::buildTime), juce::dontSendNotification); @@ -88,9 +88,8 @@ public: void timerCallback() override { - const float decay = std::exp (-1.0f / (0.9f * 30.0f)); - dryMeter.update (proc.getDryMeterL(), proc.getDryMeterR(), decay); - wetMeter.update (proc.getWetMeterL(), proc.getWetMeterR(), decay); + dryMeter.update (proc.getDryMeterL(), proc.getDryMeterR(), meterDecay); + wetMeter.update (proc.getWetMeterL(), proc.getWetMeterR(), meterDecay); } void parameterChanged (const juce::String& paramID, float) override @@ -176,6 +175,8 @@ private: using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; using ButtonAttachment = juce::AudioProcessorValueTreeState::ButtonAttachment; + static constexpr float meterDecay = 0.9636077f; // exp (-1.0f / (0.9f * 30.0f)) + MindballAudioProcessor& proc; static const juce::StringArray syncDivisionNames; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 5ec3f0b..9827e65 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -155,18 +155,16 @@ public: void update (float linL, float linR, float decay) { - levels[0] = linL; - levels[1] = linR; - peaks[0] = std::max (peaks[0] * decay, linL); - peaks[1] = std::max (peaks[1] * decay, linR); - repaint(); + const bool changedL = updateChannel (0, linL, decay); + const bool changedR = updateChannel (1, linR, decay); + + if (changedL || changedR) + repaint(); } void paint (juce::Graphics& g) override { constexpr int steps = 15; - constexpr float dbMin = -30.0f; - constexpr float dbPerStep = 2.0f; auto b = getLocalBounds().toFloat(); g.setColour (MindballColors::textDim); @@ -180,33 +178,55 @@ public: for (int ch = 0; ch < 2; ++ch) { auto row = b.removeFromTop (rowH); - const float lin = std::max (0.0f, levels[ch]); - const float db = 20.0f * std::log10 (lin > 0.0f ? lin : 1e-9f); - const int lit = juce::jlimit (0, steps, juce::roundToInt ((db - dbMin) / dbPerStep)); - const int pk = juce::jlimit (0, steps, juce::roundToInt ((20.0f * std::log10 (std::max (1e-9f, peaks[ch])) - dbMin) / dbPerStep)); for (int s = 0; s < steps; ++s) { const auto seg = juce::Rectangle (row.getX() + s * (segW + segGap), row.getY() + 2.0f, segW, rowH - 4.0f); - g.setColour (s < lit ? MindballColors::meterGreen : MindballColors::arcBg); - g.fillRoundedRectangle (seg, 1.0f); + g.setColour (s < lit[ch] ? MindballColors::meterGreen : MindballColors::arcBg); + g.fillRect (seg); } - if (pk > lit) + if (pk[ch] > 0) { - const auto seg = juce::Rectangle (row.getX() + pk * (segW + segGap), + const int peakSeg = juce::jmin (pk[ch], steps - 1); + const auto seg = juce::Rectangle (row.getX() + peakSeg * (segW + segGap), row.getY() + 2.0f, segW, rowH - 4.0f); g.setColour (MindballColors::meterRed); - g.fillRoundedRectangle (seg, 1.0f); + g.fillRect (seg); } } } private: + bool updateChannel (int ch, float lin, float decay) + { + peaks[ch] = std::max (peaks[ch] * decay, lin); + + const int newLit = levelToSteps (lin); + const int newPk = levelToSteps (peaks[ch]); + + if (newLit == lit[ch] && newPk == pk[ch]) + return false; + + lit[ch] = newLit; + pk[ch] = newPk; + return true; + } + + static int levelToSteps (float lin) + { + constexpr int steps = 15; + constexpr float dbMin = -30.0f; + constexpr float dbPerStep = 2.0f; + const float db = 20.0f * std::log10 (lin > 0.0f ? lin : 1e-9f); + return juce::jlimit (0, steps, juce::roundToInt ((db - dbMin) / dbPerStep)); + } + juce::String name; - float levels[2] = { 0.0f, 0.0f }; - float peaks[2] = { 0.0f, 0.0f }; + int lit[2] = { 0, 0 }; + int pk[2] = { 0, 0 }; + float peaks[2] = { 0.0f, 0.0f }; }; // --------------------------------------------------------------------------------