From f36d52c2aed61849e5ec410bf103c09aeb836b5e Mon Sep 17 00:00:00 2001 From: James H Ball Date: Sat, 1 Feb 2025 14:28:13 +0000 Subject: [PATCH] Fix crash with trace, improve and fix wobble effect, remove pitch detector, make changing slider values with small increments more reliable --- Source/CommonPluginEditor.cpp | 2 +- Source/EffectsComponent.cpp | 2 +- Source/MainComponent.h | 1 - Source/PluginProcessor.cpp | 11 ++-- Source/PluginProcessor.h | 8 ++- Source/audio/PitchDetector.cpp | 59 ---------------------- Source/audio/PitchDetector.h | 33 ------------ Source/audio/ShapeVoice.cpp | 5 +- Source/audio/WobbleEffect.cpp | 10 ++-- Source/audio/WobbleEffect.h | 6 +-- Source/components/EffectComponent.cpp | 14 ++++- Source/components/EffectComponent.h | 1 + Source/concurrency/AudioBackgroundThread.h | 2 +- Source/visualiser/VisualiserComponent.cpp | 1 + Source/visualiser/VisualiserComponent.h | 2 +- osci-render.jucer | 3 -- 16 files changed, 41 insertions(+), 119 deletions(-) delete mode 100644 Source/audio/PitchDetector.cpp delete mode 100644 Source/audio/PitchDetector.h diff --git a/Source/CommonPluginEditor.cpp b/Source/CommonPluginEditor.cpp index b1c717a..b8a1cd2 100644 --- a/Source/CommonPluginEditor.cpp +++ b/Source/CommonPluginEditor.cpp @@ -143,7 +143,7 @@ void CommonPluginEditor::saveProjectAs() { void CommonPluginEditor::updateTitle() { juce::String title = appName; if (!audioProcessor.currentProjectFile.isEmpty()) { - appName += " - " + audioProcessor.currentProjectFile; + title += " - " + audioProcessor.currentProjectFile; } getTopLevelComponent()->setName(title); } diff --git a/Source/EffectsComponent.cpp b/Source/EffectsComponent.cpp index d682781..d39a452 100644 --- a/Source/EffectsComponent.cpp +++ b/Source/EffectsComponent.cpp @@ -12,7 +12,7 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP frequency.slider.setValue(audioProcessor.frequencyEffect->getValue(), juce::dontSendNotification); frequency.slider.onValueChange = [this] { - audioProcessor.frequencyEffect->setValue(frequency.slider.getValue()); + audioProcessor.frequencyEffect->parameters[0]->setUnnormalisedValueNotifyingHost(frequency.slider.getValue()); }; /*addBtn.setButtonText("Add Item..."); diff --git a/Source/MainComponent.h b/Source/MainComponent.h index 790471c..fb0e758 100644 --- a/Source/MainComponent.h +++ b/Source/MainComponent.h @@ -5,7 +5,6 @@ #include "parser/FileParser.h" #include "parser/FrameProducer.h" #include "visualiser/VisualiserComponent.h" -#include "audio/PitchDetector.h" #include "UGen/ugen_JuceEnvelopeComponent.h" #include "components/SvgButton.h" diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index e2aab96..7bcf6c2 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -103,10 +103,15 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse std::make_shared(), new EffectParameter("Smoothing", "This works as a low-pass frequency filter that removes high frequencies, making the image look smoother, and audio sound less harsh.", "smoothing", VERSION_HINT, 0.75, 0.0, 1.0) )); - toggleableEffects.push_back(std::make_shared( + std::shared_ptr wobble = std::make_shared( wobbleEffect, - new EffectParameter("Wobble", "Adds a sine wave of the prominent frequency in the audio currently playing. The sine wave's frequency is slightly offset to create a subtle 'wobble' in the image. Increasing the slider increases the strength of the wobble.", "wobble", VERSION_HINT, 0.3, 0.0, 1.0) - )); + std::vector{ + new EffectParameter("Wobble Amount", "Adds a sine wave of the prominent frequency in the audio currently playing. The sine wave's frequency is slightly offset to create a subtle 'wobble' in the image. Increasing the slider increases the strength of the wobble.", "wobble", VERSION_HINT, 0.3, 0.0, 1.0), + new EffectParameter("Wobble Phase", "Controls the phase of the wobble.", "wobblePhase", VERSION_HINT, 0.0, -1.0, 1.0), + } + ); + wobble->getParameter("wobblePhase")->lfo->setUnnormalisedValueNotifyingHost((int) LfoType::Sawtooth); + toggleableEffects.push_back(wobble); toggleableEffects.push_back(std::make_shared( delayEffect, std::vector{ diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 5450926..f62b73e 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -21,7 +21,6 @@ #include "audio/SampleRateManager.h" #include #include "audio/DelayEffect.h" -#include "audio/PitchDetector.h" #include "audio/WobbleEffect.h" #include "audio/PerspectiveEffect.h" #include "obj/ObjectServer.h" @@ -111,7 +110,7 @@ public: BooleanParameter* midiEnabled = new BooleanParameter("MIDI Enabled", "midiEnabled", VERSION_HINT, false, "Enable MIDI input for the synth. If disabled, the synth will play a constant tone, as controlled by the frequency slider."); BooleanParameter* inputEnabled = new BooleanParameter("Audio Input Enabled", "inputEnabled", VERSION_HINT, false, "Enable to use input audio, instead of the generated audio."); - std::atomic frequency = 220.0f; + std::atomic frequency = 220.0; juce::SpinLock parsersLock; std::vector> parsers; @@ -177,8 +176,7 @@ public: double animationTime = 0.f; - PitchDetector pitchDetector{*this}; - std::shared_ptr wobbleEffect = std::make_shared(pitchDetector); + std::shared_ptr wobbleEffect = std::make_shared(*this); juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain); @@ -208,7 +206,7 @@ public: void notifyErrorListeners(int lineNumber, juce::String id, juce::String error); private: - bool prevMidiEnabled = !midiEnabled->getBoolValue(); + std::atomic prevMidiEnabled = !midiEnabled->getBoolValue(); juce::SpinLock audioThreadCallbackLock; std::function&)> audioThreadCallback; diff --git a/Source/audio/PitchDetector.cpp b/Source/audio/PitchDetector.cpp deleted file mode 100644 index 02cd2ea..0000000 --- a/Source/audio/PitchDetector.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "PitchDetector.h" -#include "../PluginProcessor.h" - -PitchDetector::PitchDetector(OscirenderAudioProcessor& audioProcessor) : AudioBackgroundThread("PitchDetector", audioProcessor.threadManager), audioProcessor(audioProcessor) {} - -void PitchDetector::runTask(const std::vector& points) { - // buffer is for 2 channels, so we need to only use one - for (int i = 0; i < fftSize; i++) { - fftData[i] = points[i].x; - } - - forwardFFT.performFrequencyOnlyForwardTransform(fftData.data()); - - // get frequency of the peak - int maxIndex = 0; - for (int i = 0; i < fftSize / 2; ++i) { - if (frequencyFromIndex(i) < 20 || frequencyFromIndex(i) > 20000) { - continue; - } - - auto current = fftData[i]; - if (current > fftData[maxIndex]) { - maxIndex = i; - } - } - - frequency = frequencyFromIndex(maxIndex); - triggerAsyncUpdate(); -} - -int PitchDetector::prepareTask(double sampleRate, int samplesPerBlock) { - this->sampleRate = sampleRate; - return fftSize; -} - -void PitchDetector::stopTask() {} - -void PitchDetector::handleAsyncUpdate() { - juce::SpinLock::ScopedLockType scope(lock); - for (auto& callback : callbacks) { - callback(frequency); - } -} - -int PitchDetector::addCallback(std::function callback) { - juce::SpinLock::ScopedLockType scope(lock); - callbacks.push_back(callback); - return callbacks.size() - 1; -} - -void PitchDetector::removeCallback(int index) { - juce::SpinLock::ScopedLockType scope(lock); - callbacks.erase(callbacks.begin() + index); -} - -float PitchDetector::frequencyFromIndex(int index) { - auto binWidth = sampleRate / fftSize; - return index * binWidth; -} diff --git a/Source/audio/PitchDetector.h b/Source/audio/PitchDetector.h deleted file mode 100644 index 1870744..0000000 --- a/Source/audio/PitchDetector.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include -#include "../concurrency/AudioBackgroundThread.h" - -class OscirenderAudioProcessor; -class PitchDetector : public AudioBackgroundThread, public juce::AsyncUpdater { -public: - PitchDetector(OscirenderAudioProcessor& audioProcessor); - - int prepareTask(double sampleRate, int samplesPerBlock) override; - void runTask(const std::vector& points) override; - void stopTask() override; - void handleAsyncUpdate() override; - int addCallback(std::function callback); - void removeCallback(int index); - - std::atomic frequency = 0.0f; - -private: - static constexpr int fftOrder = 15; - static constexpr int fftSize = 1 << fftOrder; - - juce::dsp::FFT forwardFFT{fftOrder}; - std::array fftData; - OscirenderAudioProcessor& audioProcessor; - std::vector> callbacks; - juce::SpinLock lock; - float sampleRate = 192000.0f; - - float frequencyFromIndex(int index); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchDetector) -}; diff --git a/Source/audio/ShapeVoice.cpp b/Source/audio/ShapeVoice.cpp index 27ebf6e..1fb2703 100644 --- a/Source/audio/ShapeVoice.cpp +++ b/Source/audio/ShapeVoice.cpp @@ -83,7 +83,7 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star if (audioProcessor.midiEnabled->getBoolValue()) { actualFrequency = frequency * pitchWheelAdjustment; } else { - actualFrequency = audioProcessor.frequency; + actualFrequency = audioProcessor.frequency.load(); } for (auto sample = startSample; sample < startSample + numSamples; ++sample) { @@ -168,12 +168,13 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star } if (!renderingSample && frameDrawn >= drawnFrameLength) { + double currentShapeLength = frame[currentShape]->len; if (sound.load() != nullptr && currentlyPlaying) { frameLength = sound.load()->updateFrame(frame); } frameDrawn -= drawnFrameLength; if (traceEnabled) { - shapeDrawn = juce::jlimit(0.0, frame[currentShape]->len, frameDrawn); + shapeDrawn = juce::jlimit(0.0, currentShapeLength, frameDrawn); } currentShape = 0; diff --git a/Source/audio/WobbleEffect.cpp b/Source/audio/WobbleEffect.cpp index 3688f55..b5f74b9 100644 --- a/Source/audio/WobbleEffect.cpp +++ b/Source/audio/WobbleEffect.cpp @@ -1,14 +1,14 @@ #include "WobbleEffect.h" +#include "../PluginProcessor.h" -WobbleEffect::WobbleEffect(PitchDetector& pitchDetector) : pitchDetector(pitchDetector) {} +WobbleEffect::WobbleEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {} WobbleEffect::~WobbleEffect() {} OsciPoint WobbleEffect::apply(int index, OsciPoint input, const std::vector>& values, double sampleRate) { - // TODO: this doesn't consider sample rate - smoothedFrequency = smoothedFrequency * 0.99995 + pitchDetector.frequency * 0.00005; - double theta = nextPhase(smoothedFrequency, sampleRate); - double delta = 0.5 * values[0] * std::sin(theta); + double wobblePhase = values[1] * std::numbers::pi; + double theta = nextPhase(audioProcessor.frequency, sampleRate) + wobblePhase; + double delta = 0.5 * values[0] * std::sin(theta); return input + delta; } diff --git a/Source/audio/WobbleEffect.h b/Source/audio/WobbleEffect.h index 35d72b4..d070675 100644 --- a/Source/audio/WobbleEffect.h +++ b/Source/audio/WobbleEffect.h @@ -1,16 +1,16 @@ #pragma once #include "EffectApplication.h" #include "../shape/OsciPoint.h" -#include "PitchDetector.h" +class OscirenderAudioProcessor; class WobbleEffect : public EffectApplication { public: - WobbleEffect(PitchDetector& pitchDetector); + WobbleEffect(OscirenderAudioProcessor& p); ~WobbleEffect(); OsciPoint apply(int index, OsciPoint input, const std::vector>& values, double sampleRate) override; private: - PitchDetector& pitchDetector; + OscirenderAudioProcessor& audioProcessor; double smoothedFrequency = 0; }; diff --git a/Source/components/EffectComponent.cpp b/Source/components/EffectComponent.cpp index 0d900dd..2e3e18f 100644 --- a/Source/components/EffectComponent.cpp +++ b/Source/components/EffectComponent.cpp @@ -54,6 +54,17 @@ EffectComponent::EffectComponent(Effect& effect, int index) : effect(effect), in EffectComponent::EffectComponent(Effect& effect) : EffectComponent(effect, 0) {} +void EffectComponent::setSliderValueIfChanged(FloatParameter* parameter, juce::Slider& slider) { + juce::String newSliderValue = juce::String(parameter->getValueUnnormalised(), 3); + juce::String oldSliderValue = juce::String((float) slider.getValue(), 3); + + // only set the slider value if the parameter value is different so that we prefer the more + // precise slider value. + if (newSliderValue != oldSliderValue) { + slider.setValue(parameter->getValueUnnormalised(), juce::dontSendNotification); + } +} + void EffectComponent::setupComponent() { EffectParameter* parameter = effect.parameters[index]; @@ -64,7 +75,7 @@ void EffectComponent::setupComponent() { label.setInterceptsMouseClicks(false, false); slider.setRange(parameter->min, parameter->max, parameter->step); - slider.setValue(parameter->getValueUnnormalised(), juce::dontSendNotification); + setSliderValueIfChanged(parameter, slider); slider.setDoubleClickReturnValue(true, parameter->defaultValue); lfoEnabled = parameter->lfo != nullptr && parameter->lfoRate != nullptr; @@ -86,6 +97,7 @@ void EffectComponent::setupComponent() { }; lfoSlider.setRange(parameter->lfoRate->min, parameter->lfoRate->max, parameter->lfoRate->step); + setSliderValueIfChanged(parameter->lfoRate, lfoSlider); lfoSlider.setValue(parameter->lfoRate->getValueUnnormalised(), juce::dontSendNotification); lfoSlider.setSkewFactorFromMidPoint(parameter->lfoRate->min + 0.1 * (parameter->lfoRate->max - parameter->lfoRate->min)); lfoSlider.setDoubleClickReturnValue(true, 1.0); diff --git a/Source/components/EffectComponent.h b/Source/components/EffectComponent.h index 706ab84..cf38d37 100644 --- a/Source/components/EffectComponent.h +++ b/Source/components/EffectComponent.h @@ -86,6 +86,7 @@ private: const int TEXT_WIDTH = 120; const int SMALL_TEXT_WIDTH = 60; + void setSliderValueIfChanged(FloatParameter* parameter, juce::Slider& slider); void setupComponent(); bool lfoEnabled = true; bool sidechainEnabled = true; diff --git a/Source/concurrency/AudioBackgroundThread.h b/Source/concurrency/AudioBackgroundThread.h index 07b6b43..2df0208 100644 --- a/Source/concurrency/AudioBackgroundThread.h +++ b/Source/concurrency/AudioBackgroundThread.h @@ -23,7 +23,7 @@ private: AudioBackgroundThreadManager& manager; std::unique_ptr consumer = nullptr; - bool shouldBeRunning = false; + std::atomic shouldBeRunning = false; std::atomic isPrepared = false; std::atomic deleting = false; diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp index 59e363e..5da3a50 100644 --- a/Source/visualiser/VisualiserComponent.cpp +++ b/Source/visualiser/VisualiserComponent.cpp @@ -174,6 +174,7 @@ void VisualiserComponent::mouseDoubleClick(const juce::MouseEvent& event) { } } +__attribute__((no_sanitize("thread"))) void VisualiserComponent::runTask(const std::vector& points) { { juce::CriticalSection::ScopedLockType lock(samplesLock); diff --git a/Source/visualiser/VisualiserComponent.h b/Source/visualiser/VisualiserComponent.h index a8aec5b..5370b62 100644 --- a/Source/visualiser/VisualiserComponent.h +++ b/Source/visualiser/VisualiserComponent.h @@ -185,7 +185,7 @@ private: std::vector smoothedXSamples; std::vector smoothedYSamples; std::vector smoothedZSamples; - int sampleBufferCount = 0; + std::atomic sampleBufferCount = 0; int prevSampleBufferCount = 0; long lastTriggerPosition = 0; diff --git a/osci-render.jucer b/osci-render.jucer index dc5bb42..d7e78e5 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -110,9 +110,6 @@ file="Source/audio/PerspectiveEffect.cpp"/> - -