From ca9613e56d30504987a481177e62780473fdfa8e Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Thu, 14 Aug 2025 00:39:51 -0700 Subject: [PATCH] Make dash frequency invariant and smoother --- Source/PluginProcessor.cpp | 14 +++++++--- Source/PluginProcessor.h | 3 --- Source/audio/DashedLineEffect.h | 48 +++++++++++++++++++-------------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 535f7e9..fb0e1c3 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -14,6 +14,7 @@ #include "audio/DistortEffect.h" #include "audio/MultiplexEffect.h" #include "audio/SmoothEffect.h" +#include "audio/DashedLineEffect.h" #include "audio/VectorCancellingEffect.h" #include "parser/FileParser.h" #include "parser/FrameProducer.h" @@ -137,11 +138,16 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse std::vector{ new osci::EffectParameter("Delay Decay", "Adds repetitions, delays, or echos to the audio. This slider controls the volume of the echo.", "delayDecay", VERSION_HINT, 0.4, 0.0, 1.0), new osci::EffectParameter("Delay Length", "Controls the time in seconds between echos.", "delayLength", VERSION_HINT, 0.5, 0.0, 1.0)})); - toggleableEffects.push_back(std::make_shared( - dashedLineEffect, + auto dashedLineEffect = std::make_shared( + std::make_shared(*this), std::vector{ - new osci::EffectParameter("Dash Length", "Controls the length of the dashed line.", "dashLength", VERSION_HINT, 0.2, 0.0, 1.0), - })); + new osci::EffectParameter("Dash Count", "Controls the number of dashed lines in the drawing.", "dashCount", VERSION_HINT, 16.0, 1.0, 32.0), + new osci::EffectParameter("Dash Coverage", "Controls the fraction of each dash unit that is drawn.", "dashCoverage", VERSION_HINT, 0.5, 0.0, 1.0), + new osci::EffectParameter("Dash Offset", "Offsets the location of the dashed lines.", "dashOffset", VERSION_HINT, 0.0, 0.0, 1.0), + }); + dashedLineEffect->getParameter("dashOffset")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); + dashedLineEffect->getParameter("dashOffset")->lfoRate->setUnnormalisedValueNotifyingHost(1.0); + toggleableEffects.push_back(dashedLineEffect); toggleableEffects.push_back(custom); toggleableEffects.push_back(trace); trace->getParameter("traceLength")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 5609ff9..734161a 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -18,7 +18,6 @@ #include "UGen/Env.h" #include "UGen/ugen_JuceEnvelopeComponent.h" #include "audio/CustomEffect.h" -#include "audio/DashedLineEffect.h" #include "audio/DelayEffect.h" #include "audio/PerspectiveEffect.h" #include "audio/PublicSynthesiser.h" @@ -90,8 +89,6 @@ public: std::shared_ptr delayEffect = std::make_shared(); - std::shared_ptr dashedLineEffect = std::make_shared(); - std::function errorCallback = [this](int lineNum, juce::String fileName, juce::String error) { notifyErrorListeners(lineNum, fileName, error); }; std::shared_ptr customEffect = std::make_shared(errorCallback, luaValues); std::shared_ptr custom = std::make_shared( diff --git a/Source/audio/DashedLineEffect.h b/Source/audio/DashedLineEffect.h index 99b0851..e48c3f3 100644 --- a/Source/audio/DashedLineEffect.h +++ b/Source/audio/DashedLineEffect.h @@ -1,34 +1,42 @@ #pragma once #include +#include "../PluginProcessor.h" class DashedLineEffect : public osci::EffectApplication { public: - osci::Point apply(int index, osci::Point vector, const std::vector>& values, double sampleRate) override { - // dash length in seconds - double dashLength = values[0] / 400; - int dashLengthSamples = (int)(dashLength * sampleRate); - dashLengthSamples = juce::jmin(dashLengthSamples, MAX_BUFFER); - - if (dashIndex >= dashLengthSamples) { - dashIndex = 0; + DashedLineEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {} + + osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { + double dashCount = juce::jmax(1.0, values[0].load()); // Dashes per cycle + double dashCoverage = juce::jlimit(0.0, 1.0, values[1].load()); + double dashOffset = values[2]; + double dashLengthSamples = (sampleRate / audioProcessor.frequency) / dashCount; + double dashPhase = framePhase * dashCount - dashOffset; + dashPhase = dashPhase - std::floor(dashPhase); // Wrap + buffer[bufferIndex] = input; + + // Linear interpolation works much better than nearest for this + double samplePos = bufferIndex - dashLengthSamples * dashPhase * (1 - dashCoverage); + samplePos = samplePos - buffer.size() * std::floor(samplePos / buffer.size()); // Wrap to [0, size] + int lowIndex = (int)std::floor(samplePos) % buffer.size(); + int highIndex = (lowIndex + 1) % buffer.size(); + double mixFactor = samplePos - std::floor(samplePos); // Fractional part + osci::Point output = (1 - mixFactor) * buffer[lowIndex] + mixFactor * buffer[highIndex]; + + bufferIndex++; + if (bufferIndex >= buffer.size()) { bufferIndex = 0; } + framePhase += audioProcessor.frequency / sampleRate; + framePhase = framePhase - std::floor(framePhase); - buffer[bufferIndex] = vector; - bufferIndex++; - - vector = buffer[dashIndex]; - - if (index % 2 == 0) { - dashIndex++; - } - - return vector; + return output; } private: + OscirenderAudioProcessor &audioProcessor; const static int MAX_BUFFER = 192000; std::vector buffer = std::vector(MAX_BUFFER); - int dashIndex = 0; int bufferIndex = 0; -}; + double framePhase = 0.0; // [0, 1] +}; \ No newline at end of file