diff --git a/Source/DelayEngine.h b/Source/DelayEngine.h index e77443d..c7d5bdb 100644 --- a/Source/DelayEngine.h +++ b/Source/DelayEngine.h @@ -231,6 +231,13 @@ public: } } + static float syncTimeMs (int index, double bpm) + { + const int idx = std::clamp (index, 0, numDivisions - 1); + const double secondsPerBeat = 60.0 / std::max (1.0, bpm); + return static_cast (divisionBeats[static_cast (idx)] * secondsPerBeat * 1000.0); + } + private: static float softClip (float x) { diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index faff5be..9a3bab6 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -57,6 +57,22 @@ public: panKnob.setValueTextFunction ([] (double v) { return juce::String (juce::roundToInt (v * 100.0)) + "%"; }); onToggle.setInverted (true); + syncToggle.onClick = [this] + { + if (syncToggle.getToggleState()) + return; + + const int index = juce::roundToInt (proc.apvts.getRawParameterValue (ParameterIDs::timeSync)->load()); + const float ms = mindball::DelayEngine::syncTimeMs (index, proc.getCurrentBpm()); + + if (auto* timeParam = proc.apvts.getParameter (ParameterIDs::time)) + { + timeParam->beginChangeGesture(); + timeParam->setValueNotifyingHost (timeParam->convertTo0to1 (juce::roundToInt (ms))); + timeParam->endChangeGesture(); + } + }; + buildInfo.setJustificationType (juce::Justification::centredRight); buildInfo.setFont (juce::Font (juce::Font::getDefaultMonospacedFontName(), 9.0f, 0)); diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 3995975..f22564b 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -84,6 +84,11 @@ bool MindballAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) && (mainIn == juce::AudioChannelSet::mono() || mainIn == juce::AudioChannelSet::stereo()); } +double MindballAudioProcessor::getCurrentBpm() const +{ + return lastBpm.load (std::memory_order_relaxed); +} + void MindballAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) { juce::ScopedNoDenormals noDenormals; @@ -109,13 +114,14 @@ void MindballAudioProcessor::processBlock (juce::AudioBuffer& buffer, juc } wasBypassed = false; - double bpm = 120.0; + double bpm = lastBpm.load (std::memory_order_relaxed); if (auto* playHead = getPlayHead()) { juce::AudioPlayHead::CurrentPositionInfo pos; - if (playHead->getCurrentPosition (pos)) - bpm = pos.bpm > 0 ? pos.bpm : 120.0; + if (playHead->getCurrentPosition (pos) && pos.bpm > 0) + bpm = pos.bpm; } + lastBpm.store (bpm, std::memory_order_relaxed); mindball::DelayEngine::Params p; p.feedback = *apvts.getRawParameterValue (ParameterIDs::feedback); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 0981a8b..b465b8f 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -53,6 +53,8 @@ public: void getStateInformation (juce::MemoryBlock& destData) override; void setStateInformation (const void* data, int sizeInBytes) override; + double getCurrentBpm() const; + float getDryMeterL() const { return dryMeterL.load (std::memory_order_relaxed); } float getDryMeterR() const { return dryMeterR.load (std::memory_order_relaxed); } float getWetMeterL() const { return wetMeterL.load (std::memory_order_relaxed); } @@ -70,6 +72,7 @@ private: float meterDryL = 0.0f, meterDryR = 0.0f; float meterWetL = 0.0f, meterWetR = 0.0f; float meterDecay = 0.0f; + std::atomic lastBpm { 120.0 }; std::atomic dryMeterL { 0.0f }, dryMeterR { 0.0f }; std::atomic wetMeterL { 0.0f }, wetMeterR { 0.0f }; };