Fix crash with trace, improve and fix wobble effect, remove pitch detector, make changing slider values with small increments more reliable

pull/281/head
James H Ball 2025-02-01 14:28:13 +00:00
rodzic 8f67d26199
commit f36d52c2ae
16 zmienionych plików z 41 dodań i 119 usunięć

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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...");

Wyświetl plik

@ -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"

Wyświetl plik

@ -103,10 +103,15 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
std::make_shared<SmoothEffect>(),
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<Effect>(
std::shared_ptr<Effect> wobble = std::make_shared<Effect>(
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<EffectParameter*>{
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<Effect>(
delayEffect,
std::vector<EffectParameter*>{

Wyświetl plik

@ -21,7 +21,6 @@
#include "audio/SampleRateManager.h"
#include <numbers>
#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<float> frequency = 220.0f;
std::atomic<double> frequency = 220.0;
juce::SpinLock parsersLock;
std::vector<std::shared_ptr<FileParser>> parsers;
@ -177,8 +176,7 @@ public:
double animationTime = 0.f;
PitchDetector pitchDetector{*this};
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(pitchDetector);
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(*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<bool> prevMidiEnabled = !midiEnabled->getBoolValue();
juce::SpinLock audioThreadCallbackLock;
std::function<void(const juce::AudioBuffer<float>&)> audioThreadCallback;

Wyświetl plik

@ -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<OsciPoint>& 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<void(float)> 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;
}

Wyświetl plik

@ -1,33 +0,0 @@
#pragma once
#include <JuceHeader.h>
#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<OsciPoint>& points) override;
void stopTask() override;
void handleAsyncUpdate() override;
int addCallback(std::function<void(float)> callback);
void removeCallback(int index);
std::atomic<float> frequency = 0.0f;
private:
static constexpr int fftOrder = 15;
static constexpr int fftSize = 1 << fftOrder;
juce::dsp::FFT forwardFFT{fftOrder};
std::array<float, fftSize * 2> fftData;
OscirenderAudioProcessor& audioProcessor;
std::vector<std::function<void(float)>> callbacks;
juce::SpinLock lock;
float sampleRate = 192000.0f;
float frequencyFromIndex(int index);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchDetector)
};

Wyświetl plik

@ -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;

Wyświetl plik

@ -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<std::atomic<double>>& 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;
}

Wyświetl plik

@ -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<std::atomic<double>>& values, double sampleRate) override;
private:
PitchDetector& pitchDetector;
OscirenderAudioProcessor& audioProcessor;
double smoothedFrequency = 0;
};

Wyświetl plik

@ -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);

Wyświetl plik

@ -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;

Wyświetl plik

@ -23,7 +23,7 @@ private:
AudioBackgroundThreadManager& manager;
std::unique_ptr<BufferConsumer> consumer = nullptr;
bool shouldBeRunning = false;
std::atomic<bool> shouldBeRunning = false;
std::atomic<bool> isPrepared = false;
std::atomic<bool> deleting = false;

Wyświetl plik

@ -174,6 +174,7 @@ void VisualiserComponent::mouseDoubleClick(const juce::MouseEvent& event) {
}
}
__attribute__((no_sanitize("thread")))
void VisualiserComponent::runTask(const std::vector<OsciPoint>& points) {
{
juce::CriticalSection::ScopedLockType lock(samplesLock);

Wyświetl plik

@ -185,7 +185,7 @@ private:
std::vector<float> smoothedXSamples;
std::vector<float> smoothedYSamples;
std::vector<float> smoothedZSamples;
int sampleBufferCount = 0;
std::atomic<int> sampleBufferCount = 0;
int prevSampleBufferCount = 0;
long lastTriggerPosition = 0;

Wyświetl plik

@ -110,9 +110,6 @@
file="Source/audio/PerspectiveEffect.cpp"/>
<FILE id="h0dMim" name="PerspectiveEffect.h" compile="0" resource="0"
file="Source/audio/PerspectiveEffect.h"/>
<FILE id="t2bsR8" name="PitchDetector.cpp" compile="1" resource="0"
file="Source/audio/PitchDetector.cpp"/>
<FILE id="rQC2gX" name="PitchDetector.h" compile="0" resource="0" file="Source/audio/PitchDetector.h"/>
<FILE id="t5g8pf" name="PublicSynthesiser.h" compile="0" resource="0"
file="Source/audio/PublicSynthesiser.h"/>
<FILE id="Q5kjpU" name="SampleRateManager.h" compile="0" resource="0"