From 1c63a18bb5d8d6e31e52b8de0d29708865c6d606 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Wed, 3 Sep 2025 03:33:19 -0700 Subject: [PATCH 01/18] Add spiral bitcrush effect --- Source/PluginProcessor.cpp | 2 + Source/audio/SpiralBitCrushEffect.h | 66 +++++++++++++++++++++++++++++ osci-render.jucer | 2 + 3 files changed, 70 insertions(+) create mode 100644 Source/audio/SpiralBitCrushEffect.h diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 6e25aaec..c132d4f3 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -12,6 +12,7 @@ #include "audio/BitCrushEffect.h" #include "audio/BulgeEffect.h" #include "audio/TwistEffect.h" +#include "audio/SpiralBitCrushEffect.h" #include "audio/DistortEffect.h" #include "audio/KaleidoscopeEffect.h" #include "audio/MultiplexEffect.h" @@ -54,6 +55,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse toggleableEffects.push_back(KaleidoscopeEffect(*this).build()); toggleableEffects.push_back(BounceEffect().build()); toggleableEffects.push_back(TwistEffect().build()); + toggleableEffects.push_back(SpiralBitCrushEffect().build()); #endif auto scaleEffect = ScaleEffectApp().build(); diff --git a/Source/audio/SpiralBitCrushEffect.h b/Source/audio/SpiralBitCrushEffect.h new file mode 100644 index 00000000..2fce3be5 --- /dev/null +++ b/Source/audio/SpiralBitCrushEffect.h @@ -0,0 +1,66 @@ +#pragma once +#include + +class SpiralBitCrushEffect : public osci::EffectApplication { +public: + osci::Point apply(int index, osci::Point input, const std::vector> &values, double sampleRate) override { + // Completing one revolution in input space traverses the hypotenuse of one "domain" in log-polar space + double effectScale = juce::jlimit(0.0, 1.0, values[0].load()); + double domainX = juce::jmax(2.0, std::floor(values[1].load() + 0.0001)); + double domainY = std::round(domainX * values[2].load()); + osci::Point offset(values[3].load(), values[4].load()); + + double domainHypot = std::hypot(domainX, domainY); + double domainTheta = std::atan2(domainY, domainX); + double scale = domainHypot / juce::MathConstants::twoPi; + osci::Point output(0); + + // Round to log-polar grid + if (input.x != 0 || input.y != 0) { + // Convert input point to log-polar coordinates transformed based on domain and offset + // Note 90 degree rotation: Theta is treated relative to -Y rather than +X + double r = std::hypot(input.x, input.y); + double logR = std::log(r); + double theta = std::atan2(input.x, -input.y); + osci::Point logPolarCoords(theta, logR); + logPolarCoords.rotate(0, 0, domainTheta); + logPolarCoords = logPolarCoords * scale - offset; + + // Round this point to the center of the log-polar cell the input lies in, convert back to Cartesian + logPolarCoords.x = std::round(logPolarCoords.x); + logPolarCoords.y = std::round(logPolarCoords.y); + logPolarCoords = (logPolarCoords + offset) / scale; + logPolarCoords.rotate(0, 0, -domainTheta); + double outR = std::exp(logPolarCoords.y); + double outTheta = logPolarCoords.x; + output.x = outR * std::sin(outTheta); + output.y = outR * -std::cos(outTheta); + } + + // Round z in log space using same spacing as xy log-polar grid + // Use same offset as xy's radial offset to be consistent with the appearance of zooming + if (input.z != 0) { + double signZ = input.z > 0 ? 1.0 : -1.0; + double logZ = std::log(std::abs(input.z)); + logZ = logZ * scale - offset.y; + logZ = std::round(logZ); + logZ = (logZ + offset.y) / scale; + output.z = signZ * std::exp(logZ); + } + return (1 - effectScale) * input + effectScale * output; + } + + std::shared_ptr build() const override { + auto eff = std::make_shared( + std::make_shared(), + std::vector{ + new osci::EffectParameter("Spiral Bit Crush", "Constrains points to a spiral pattern.", "spiralBitCrushEnable", VERSION_HINT, 1.0, 0.0, 1.0), + new osci::EffectParameter("Spiral Density", "Controls the density of the spiral pattern.", "spiralDensity", VERSION_HINT, 13.0, 3.0, 30.0), + new osci::EffectParameter("Spiral Twist", "Controls how much the spiral pattern twists.", "spiralTwist", VERSION_HINT, -0.6, -1.0, 1.0), + new osci::EffectParameter("Angle Offset", "Rotates the spiral pattern.", "spiralOffsetX", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.4), + new osci::EffectParameter("Radial Offset", "Zooms the spiral pattern.", "spiralOffsetY", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 1.0) + }); + eff->setIcon(BinaryData::swirl_svg); + return eff; + } +}; \ No newline at end of file diff --git a/osci-render.jucer b/osci-render.jucer index 98a4786c..5ca81275 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -165,6 +165,8 @@ + From 53c080c10f8ab97096d25534eff31030f20d6875 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Thu, 4 Sep 2025 23:05:35 -0700 Subject: [PATCH 02/18] Add radiation effect todo: test --- Source/PluginProcessor.cpp | 2 ++ Source/audio/RadiationEffect.h | 52 ++++++++++++++++++++++++++++++++++ osci-render.jucer | 2 ++ 3 files changed, 56 insertions(+) create mode 100644 Source/audio/RadiationEffect.h diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 7c577688..6743b863 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -26,6 +26,7 @@ #include "audio/SwirlEffect.h" #include "audio/BounceEffect.h" #include "audio/SkewEffect.h" +#include "audio/RadiationEffect.h" #include "parser/FileParser.h" #include "parser/FrameProducer.h" @@ -56,6 +57,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse toggleableEffects.push_back(BounceEffect().build()); toggleableEffects.push_back(TwistEffect().build()); toggleableEffects.push_back(SkewEffect().build()); + toggleableEffects.push_back(RadiationEffect().build()); #endif auto scaleEffect = ScaleEffectApp().build(); diff --git a/Source/audio/RadiationEffect.h b/Source/audio/RadiationEffect.h new file mode 100644 index 00000000..1b4868c5 --- /dev/null +++ b/Source/audio/RadiationEffect.h @@ -0,0 +1,52 @@ +#pragma once +#include + +class RadiationEffect : public osci::EffectApplication { +public: + osci::Point apply(int index, osci::Point input, const std::vector> &values, double sampleRate) override { + double noiseAmp = juce::jmax(0.0, values[0].load()); + double bias = values[1]; + double biasExponent = std::pow(8.0, std::abs(bias)); + + // TODO: Try lopsided radiation + + /* + double noise = 2.0 * (double)std::rand() / RAND_MAX - 1.0; + double noiseSign = noise >= 0.0 ? 1.0 : -1.0; + // If bias is positive, bend values toward center + // If bias is negative, bend values toward extremes + if (bias < 0.0) { + noise = noiseSign * (1 - std::pow(1 - std::abs(noise), biasExponent)); + } + else { + noise = noiseSign * std::pow(std::abs(noise), biasExponent); + } + noise = 0.5 + 0.5 * noise; + + */ + double noise = (double)std::rand() / RAND_MAX; + //double noiseSign = noise >= 0.0 ? 1.0 : -1.0; + // If bias is positive, bend values toward center + // If bias is negative, bend values toward extremes + if (bias < 0.0) { + noise = 1 - std::pow(1 - noise, biasExponent); + } + else { + noise = std::pow(noise, biasExponent); + } + + double scale = (1 - noiseAmp) + noise * noiseAmp; + return input * scale; + } + + std::shared_ptr build() const override { + auto eff = std::make_shared( + std::make_shared(), + std::vector{ + new osci::EffectParameter("Radiation", "Creates a crepuscular ray effect by adding noise. This slider controls the size of the rays.", "radiationAmp", VERSION_HINT, 1.0, 0.0, 1.0), + new osci::EffectParameter("Radiation Bias", "Controls whether the rays appear to be radiating inward or outward.", "radiationBias", VERSION_HINT, 0.6, -1.0, 1.0) + }); + eff->setIcon(BinaryData::multiplex_svg); + return eff; + } +}; \ No newline at end of file diff --git a/osci-render.jucer b/osci-render.jucer index c77d7694..7437d709 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -166,6 +166,8 @@ + Date: Fri, 5 Sep 2025 13:18:16 -0700 Subject: [PATCH 03/18] Update radiation effect values --- Source/audio/RadiationEffect.h | 41 +++++++++++----------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/Source/audio/RadiationEffect.h b/Source/audio/RadiationEffect.h index 1b4868c5..aef90536 100644 --- a/Source/audio/RadiationEffect.h +++ b/Source/audio/RadiationEffect.h @@ -6,35 +6,16 @@ public: osci::Point apply(int index, osci::Point input, const std::vector> &values, double sampleRate) override { double noiseAmp = juce::jmax(0.0, values[0].load()); double bias = values[1]; - double biasExponent = std::pow(8.0, std::abs(bias)); + double biasExponent = std::pow(12.0, std::abs(bias)); - // TODO: Try lopsided radiation - - /* - double noise = 2.0 * (double)std::rand() / RAND_MAX - 1.0; - double noiseSign = noise >= 0.0 ? 1.0 : -1.0; - // If bias is positive, bend values toward center - // If bias is negative, bend values toward extremes - if (bias < 0.0) { - noise = noiseSign * (1 - std::pow(1 - std::abs(noise), biasExponent)); - } - else { - noise = noiseSign * std::pow(std::abs(noise), biasExponent); - } - noise = 0.5 + 0.5 * noise; - - */ double noise = (double)std::rand() / RAND_MAX; - //double noiseSign = noise >= 0.0 ? 1.0 : -1.0; - // If bias is positive, bend values toward center - // If bias is negative, bend values toward extremes - if (bias < 0.0) { - noise = 1 - std::pow(1 - noise, biasExponent); - } - else { + // Bias values toward 0 or 1 based on sign + if (bias > 0.0) { noise = std::pow(noise, biasExponent); } - + else { + noise = 1 - std::pow(1 - noise, biasExponent); + } double scale = (1 - noiseAmp) + noise * noiseAmp; return input * scale; } @@ -43,10 +24,14 @@ public: auto eff = std::make_shared( std::make_shared(), std::vector{ - new osci::EffectParameter("Radiation", "Creates a crepuscular ray effect by adding noise. This slider controls the size of the rays.", "radiationAmp", VERSION_HINT, 1.0, 0.0, 1.0), - new osci::EffectParameter("Radiation Bias", "Controls whether the rays appear to be radiating inward or outward.", "radiationBias", VERSION_HINT, 0.6, -1.0, 1.0) + new osci::EffectParameter("Radiation", + "Creates a crepuscular ray effect by adding noise. This slider controls the size of the rays. Looks best with higher sample rates.", + "radiationAmp", VERSION_HINT, 1.0, 0.0, 1.0), + new osci::EffectParameter("Radiation Bias", + "Controls whether the rays appear to be radiating inward or outward.", + "radiationBias", VERSION_HINT, 0.8, -1.0, 1.0) }); - eff->setIcon(BinaryData::multiplex_svg); + eff->setIcon(BinaryData::scale_svg); return eff; } }; \ No newline at end of file From dcb6094177efb44b7dc18b648fefa44e0843302c Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Sun, 7 Sep 2025 01:27:45 -0700 Subject: [PATCH 04/18] harmonic duplicator not discrete freq --- Source/PluginProcessor.cpp | 2 ++ Source/audio/HarmonicDuplicatorEffect.h | 42 +++++++++++++++++++++++++ osci-render.jucer | 2 ++ 3 files changed, 46 insertions(+) create mode 100644 Source/audio/HarmonicDuplicatorEffect.h diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 6e25aaec..50cfb10d 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -17,6 +17,7 @@ #include "audio/MultiplexEffect.h" #include "audio/SmoothEffect.h" #include "audio/WobbleEffect.h" +#include "audio/HarmonicDuplicatorEffect.h" #include "audio/DashedLineEffect.h" #include "audio/VectorCancellingEffect.h" #include "audio/ScaleEffect.h" @@ -48,6 +49,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse toggleableEffects.push_back(DashedLineEffect(*this).build()); toggleableEffects.push_back(TraceEffect(*this).build()); toggleableEffects.push_back(WobbleEffect(*this).build()); + toggleableEffects.push_back(HarmonicDuplicatorEffect(*this).build()); #if OSCI_PREMIUM toggleableEffects.push_back(MultiplexEffect(*this).build()); diff --git a/Source/audio/HarmonicDuplicatorEffect.h b/Source/audio/HarmonicDuplicatorEffect.h new file mode 100644 index 00000000..5c3f50d9 --- /dev/null +++ b/Source/audio/HarmonicDuplicatorEffect.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include "../PluginProcessor.h" + +class HarmonicDuplicatorEffect : public osci::EffectApplication { +public: + HarmonicDuplicatorEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {} + + osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { + const double twoPi = juce::MathConstants::twoPi; + double copies = juce::jmax(1.0, values[0].load()); + double dist = juce::jlimit(0.0, 1.0, values[1].load()); + double angleOffset = values[2].load() * juce::MathConstants::twoPi; + + double theta = std::floor(framePhase * copies) / copies * twoPi + angleOffset; + osci::Point offset(std::cos(theta), std::sin(theta), 0.0); + + framePhase += audioProcessor.frequency / copies / sampleRate; + framePhase = framePhase - std::floor(framePhase); + + return (1 - dist) * input + dist * offset; + } + + std::shared_ptr build() const override { + auto eff = std::make_shared( + std::make_shared(audioProcessor), + std::vector{ + // TODO ID strings + new osci::EffectParameter("Copies", "Controls the number of copies of the input shape to draw.", "rdCopies", VERSION_HINT, 3.0, 1.0, 10.0), + new osci::EffectParameter("Distance", "Controls the distance between copies of the input shape.", "rdMix", VERSION_HINT, 0.5, 0.0, 1.0), + new osci::EffectParameter("Angle Offset", "Rotates the offsets between copies without rotating the input shape.", "rdAngle", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 1.0) + } + ); + eff->setName("Radial Duplicator"); + eff->setIcon(BinaryData::kaleidoscope_svg); + return eff; + } + +private: + OscirenderAudioProcessor &audioProcessor; + double framePhase = 0.0; // [0, 1] +}; \ No newline at end of file diff --git a/osci-render.jucer b/osci-render.jucer index 98a4786c..00da3164 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -165,6 +165,8 @@ + From aaa491b349153aefc4ab34b18c9a83ccaefecc65 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Sun, 7 Sep 2025 01:52:55 -0700 Subject: [PATCH 05/18] Fix default radiation amp (thought I did it already) --- Source/audio/RadiationEffect.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/audio/RadiationEffect.h b/Source/audio/RadiationEffect.h index aef90536..45a99a2f 100644 --- a/Source/audio/RadiationEffect.h +++ b/Source/audio/RadiationEffect.h @@ -26,7 +26,7 @@ public: std::vector{ new osci::EffectParameter("Radiation", "Creates a crepuscular ray effect by adding noise. This slider controls the size of the rays. Looks best with higher sample rates.", - "radiationAmp", VERSION_HINT, 1.0, 0.0, 1.0), + "radiationAmp", VERSION_HINT, 0.5, 0.0, 1.0), new osci::EffectParameter("Radiation Bias", "Controls whether the rays appear to be radiating inward or outward.", "radiationBias", VERSION_HINT, 0.8, -1.0, 1.0) From 5eea5daab50e82ec250371d2c4ace26b0c5663e8 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Sun, 7 Sep 2025 03:18:01 -0700 Subject: [PATCH 06/18] Make mushrom 3D and normalize --- Resources/lua/mushroom.lua | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Resources/lua/mushroom.lua b/Resources/lua/mushroom.lua index a28d5335..82e10b8b 100644 --- a/Resources/lua/mushroom.lua +++ b/Resources/lua/mushroom.lua @@ -1,9 +1,9 @@ -local X_SIZE = 0.35 +local LOOPS = 30 +local CAP_WIDTH = 0.35 local STEM_WIDTH = 0.05 local Y_SIZE = 0.02 local HEIGHT = 2.0 local OFFSET_Y = -1.0 -local LOOPS = 50 local SHROOMS = 3 -- can be 1-4 local MOVE_AMOUNT = 0.4 @@ -13,27 +13,34 @@ local SPREAD = 0.2 local railroad_switch = SHROOMS * phase / (2 * math.pi) local drawing_phase = (phase * SHROOMS) % (2 * math.pi) -local x = X_SIZE * math.cos(LOOPS * drawing_phase) -local y = Y_SIZE * math.sin(LOOPS * drawing_phase) +local x = CAP_WIDTH * math.cos(LOOPS * drawing_phase) +local z = CAP_WIDTH * math.sin(LOOPS * drawing_phase) local sawtooth = (drawing_phase % (2 * math.pi)) / (2 * math.pi) -y = y + (HEIGHT * sawtooth) + OFFSET_Y +local y = HEIGHT * sawtooth + OFFSET_Y local sine_mod = math.sin(2 * math.pi * sawtooth) if sawtooth < 0.75 then - sine_mod = 1 - x = x * (STEM_WIDTH / X_SIZE) + sine_mod = STEM_WIDTH / CAP_WIDTH end x = x * sine_mod +z = z * sine_mod local base_time = step/sample_rate * MOVE_FREQ local current_mushroom = math.floor(railroad_switch) local phase_offset = current_mushroom / SHROOMS -local wiggle = math.sin(2 * math.pi * (sawtooth + base_time + phase_offset)) * sawtooth -local outer_drift = SPREAD * sawtooth * wiggle -x = x + (MOVE_AMOUNT * wiggle + outer_drift) +local wiggle_x = math.cos(2 * math.pi * (sawtooth + base_time + phase_offset)) * sawtooth +local wiggle_z = math.sin(2 * math.pi * (sawtooth + base_time + phase_offset)) * sawtooth +local outer_drift_x = SPREAD * sawtooth * wiggle_x +local outer_drift_z = SPREAD * sawtooth * wiggle_z +x = x + (MOVE_AMOUNT * wiggle_x + outer_drift_x) +z = z + (MOVE_AMOUNT * wiggle_z + outer_drift_z) -return {x, y} \ No newline at end of file +-- normalize for default HEIGHT and OFFSET_Y +local max_xz = math.abs(MOVE_AMOUNT) + math.abs(SPREAD) +local scale = 1 / math.sqrt(1 + max_xz * max_xz) + +return {x * scale, y * scale, z * scale} From 00fbb4f1d7f2a2bc9a92e94d9892c57687609b42 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Sun, 7 Sep 2025 04:00:12 -0700 Subject: [PATCH 07/18] trailing newline --- Source/audio/SpiralBitCrushEffect.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/audio/SpiralBitCrushEffect.h b/Source/audio/SpiralBitCrushEffect.h index 2fce3be5..b3fbe897 100644 --- a/Source/audio/SpiralBitCrushEffect.h +++ b/Source/audio/SpiralBitCrushEffect.h @@ -63,4 +63,4 @@ public: eff->setIcon(BinaryData::swirl_svg); return eff; } -}; \ No newline at end of file +}; From 98030fffabb23995c4708851b5544913bb537070 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Sun, 7 Sep 2025 16:30:38 -0700 Subject: [PATCH 08/18] Hypercube and spiral tweaks --- Resources/lua/hypercube.lua | 43 ++++++++++++++++++++----------------- Resources/lua/mushroom.lua | 1 - Resources/lua/spiral.lua | 4 ++-- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Resources/lua/hypercube.lua b/Resources/lua/hypercube.lua index 211c2bd2..c0157cd9 100644 --- a/Resources/lua/hypercube.lua +++ b/Resources/lua/hypercube.lua @@ -1,5 +1,6 @@ local SCALE = 1.0 local ANIMATION_SPEED = 1.0 +local FOV_4D = math.rad(80) local function rotate4D_XY(x, y, z, w, angle) return x * math.cos(angle) - y * math.sin(angle), @@ -23,8 +24,9 @@ local function rotate4D_XW(x, y, z, w, angle) end local function project4Dto3D(x, y, z, w) - local w_factor = 1 / (3 - w) - return x * w_factor, y * w_factor, z * w_factor + local camera_w = -1 / math.sin(0.5 * FOV_4D) + local scale = 1 / (w - camera_w) / math.tan(0.5 * FOV_4D) + return x * scale, y * scale, z * scale end local vertices = { @@ -36,36 +38,37 @@ local vertices = { {-1, -1, 1, 1}, {1, -1, 1, 1}, {1, 1, 1, 1}, {-1, 1, 1, 1} } -local edges = { - -- Inner cube edges - {1, 2}, {2, 3}, {3, 4}, {4, 1}, - {5, 6}, {6, 7}, {7, 8}, {8, 5}, - {1, 5}, {2, 6}, {3, 7}, {4, 8}, - -- Outer cube edges - {9, 10}, {10, 11}, {11, 12}, {12, 9}, - {13, 14}, {14, 15}, {15, 16}, {16, 13}, - {9, 13}, {10, 14}, {11, 15}, {12, 16}, - -- Connections between cubes - {1, 9}, {2, 10}, {3, 11}, {4, 12}, - {5, 13}, {6, 14}, {7, 15}, {8, 16} +-- Eulerian cycle through vertices +local path = { + 1, 5, 8, 4, 12, 16, 15, 11, + 3, 7, 6, 14, 15, 7, 8, 16, + 13, 5, 6, 2, 10, 9, 13, 14, + 10, 11, 12, 9, 1, 2, 3, 4 } -local NUM_EDGES = #edges +local NUM_EDGES = #path local railroad_switch = NUM_EDGES * phase / (2 * math.pi) -local current_edge = math.floor(railroad_switch) + 1 +local current_vertex = math.floor(railroad_switch) + 1 +local next_vertex = current_vertex % NUM_EDGES + 1 local edge_phase = railroad_switch % 1 local time = step/sample_rate * ANIMATION_SPEED -if current_edge <= NUM_EDGES then - local v1 = vertices[edges[current_edge][1]] - local v2 = vertices[edges[current_edge][2]] +if current_vertex <= NUM_EDGES then + local v1 = vertices[path[current_vertex]] + local v2 = vertices[path[next_vertex]] local x = v1[1] + (v2[1] - v1[1]) * edge_phase local y = v1[2] + (v2[2] - v1[2]) * edge_phase local z = v1[3] + (v2[3] - v1[3]) * edge_phase local w = v1[4] + (v2[4] - v1[4]) * edge_phase + + -- Normalize + x = x * 0.5 + y = y * 0.5 + z = z * 0.5 + w = w * 0.5 local fold_angle = math.sin(time) * math.pi / 2 @@ -82,4 +85,4 @@ if current_edge <= NUM_EDGES then return {x, y, z} end -return {0, 0} \ No newline at end of file +return {0, 0} diff --git a/Resources/lua/mushroom.lua b/Resources/lua/mushroom.lua index 82e10b8b..dc56c211 100644 --- a/Resources/lua/mushroom.lua +++ b/Resources/lua/mushroom.lua @@ -1,7 +1,6 @@ local LOOPS = 30 local CAP_WIDTH = 0.35 local STEM_WIDTH = 0.05 -local Y_SIZE = 0.02 local HEIGHT = 2.0 local OFFSET_Y = -1.0 local SHROOMS = 3 -- can be 1-4 diff --git a/Resources/lua/spiral.lua b/Resources/lua/spiral.lua index 9ed752fc..f1b66f2d 100644 --- a/Resources/lua/spiral.lua +++ b/Resources/lua/spiral.lua @@ -34,7 +34,7 @@ dir = dir or 1 t = t or 0 -- This is the correct increment for t to use such -- that we hear the right frequency. -increment = 2 * math.pi * frequency / sample_rate +increment = 4 * math.pi * frequency / sample_rate -- If we get to the end of the spiral, flip the -- direction to go back. @@ -51,4 +51,4 @@ t = t + dir * increment return { 0.1 * t * math.sin(spiral_length * t), 0.1 * t * math.cos(spiral_length * t) -} \ No newline at end of file +} From 5ff9fde8f911e94a22da009251c78285005417cd Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Sun, 7 Sep 2025 21:11:29 -0700 Subject: [PATCH 09/18] Harmonic dupe integer freq, kaleidoscope boundary fix --- Source/audio/HarmonicDuplicatorEffect.h | 25 +++++++++++++++++-------- Source/audio/KaleidoscopeEffect.h | 6 +++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Source/audio/HarmonicDuplicatorEffect.h b/Source/audio/HarmonicDuplicatorEffect.h index 5c3f50d9..7b2bfa67 100644 --- a/Source/audio/HarmonicDuplicatorEffect.h +++ b/Source/audio/HarmonicDuplicatorEffect.h @@ -9,29 +9,38 @@ public: osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { const double twoPi = juce::MathConstants::twoPi; double copies = juce::jmax(1.0, values[0].load()); - double dist = juce::jlimit(0.0, 1.0, values[1].load()); + double spread = juce::jlimit(0.0, 1.0, values[1].load()); double angleOffset = values[2].load() * juce::MathConstants::twoPi; + // Ensure values extremely close to integer don't get rounded up + double fractionalPart = copies - std::floor(copies); + double ceilCopies = fractionalPart > 1e-4 ? std::ceil(copies) : std::floor(copies); + double theta = std::floor(framePhase * copies) / copies * twoPi + angleOffset; osci::Point offset(std::cos(theta), std::sin(theta), 0.0); - framePhase += audioProcessor.frequency / copies / sampleRate; + framePhase += audioProcessor.frequency / ceilCopies / sampleRate; framePhase = framePhase - std::floor(framePhase); - return (1 - dist) * input + dist * offset; + return (1 - spread) * input + spread * offset; } std::shared_ptr build() const override { auto eff = std::make_shared( std::make_shared(audioProcessor), std::vector{ - // TODO ID strings - new osci::EffectParameter("Copies", "Controls the number of copies of the input shape to draw.", "rdCopies", VERSION_HINT, 3.0, 1.0, 10.0), - new osci::EffectParameter("Distance", "Controls the distance between copies of the input shape.", "rdMix", VERSION_HINT, 0.5, 0.0, 1.0), - new osci::EffectParameter("Angle Offset", "Rotates the offsets between copies without rotating the input shape.", "rdAngle", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 1.0) + new osci::EffectParameter("Copies", + "Controls the number of copies of the input shape to draw. Splitting the shape into multiple copies creates audible harmony.", + "harmonicDuplicatorCopies", VERSION_HINT, 3.0, 1.0, 6.0), + new osci::EffectParameter("Spread", + "Controls the spread between copies of the input shape.", + "harmonicDuplicatorSpread", VERSION_HINT, 0.4, 0.0, 1.0), + new osci::EffectParameter("Angle Offset", + "Rotates the offsets between copies without rotating the input shape.", + "harmonicDuplicatorAngle", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.2) } ); - eff->setName("Radial Duplicator"); + eff->setName("Harmonic Duplicator"); eff->setIcon(BinaryData::kaleidoscope_svg); return eff; } diff --git a/Source/audio/KaleidoscopeEffect.h b/Source/audio/KaleidoscopeEffect.h index 2ac29c5a..be76c0bb 100644 --- a/Source/audio/KaleidoscopeEffect.h +++ b/Source/audio/KaleidoscopeEffect.h @@ -22,18 +22,18 @@ public: int fullSegments = (int)std::floor(segments); double fractionalPart = segments - fullSegments; // in [0,1) + fullSegments = fractionalPart > 1e-4 ? fullSegments : fullSegments - 1; phase = nextPhase(audioProcessor.frequency / (fullSegments + 1), sampleRate) / (2.0 * std::numbers::pi); // Use 'segments' for timing so partial segment gets proportionally shorter time. double currentSegmentFloat = phase * segments; // [0, segments) int currentSegmentIndex = (int)std::floor(currentSegmentFloat); - int maxIndex = fractionalPart > 1e-9 ? fullSegments : fullSegments - 1; // include partial index if exists - if (currentSegmentIndex > maxIndex) currentSegmentIndex = maxIndex; // safety + if (currentSegmentIndex > fullSegments) currentSegmentIndex = fullSegments; // safety // Base full wedge angle (all full wedges) and size of partial wedge double baseWedgeAngle = juce::MathConstants::twoPi / segments; // size of a "unit" wedge - double partialScale = (currentSegmentIndex == fullSegments && fractionalPart > 1e-9) ? fractionalPart : 1.0; + double partialScale = (currentSegmentIndex == fullSegments && fractionalPart > 1e-4) ? fractionalPart : 1.0; double wedgeAngle = baseWedgeAngle * partialScale; // Normalize theta to [0,1) for compression From c698e2a7dd0ba8eb22431a9a7500ce8ab569f9d6 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Mon, 8 Sep 2025 04:21:43 -0700 Subject: [PATCH 10/18] Polish duplication boundary handling --- Source/audio/HarmonicDuplicatorEffect.h | 10 ++++------ Source/audio/KaleidoscopeEffect.h | 4 ++-- Source/audio/MultiplexEffect.h | 10 ++++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Source/audio/HarmonicDuplicatorEffect.h b/Source/audio/HarmonicDuplicatorEffect.h index 7b2bfa67..50c1f383 100644 --- a/Source/audio/HarmonicDuplicatorEffect.h +++ b/Source/audio/HarmonicDuplicatorEffect.h @@ -9,13 +9,11 @@ public: osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { const double twoPi = juce::MathConstants::twoPi; double copies = juce::jmax(1.0, values[0].load()); + double ceilCopies = std::ceil(copies - 1e-3); double spread = juce::jlimit(0.0, 1.0, values[1].load()); double angleOffset = values[2].load() * juce::MathConstants::twoPi; - // Ensure values extremely close to integer don't get rounded up - double fractionalPart = copies - std::floor(copies); - double ceilCopies = fractionalPart > 1e-4 ? std::ceil(copies) : std::floor(copies); - + // Offset moves each time the input shape is drawn once double theta = std::floor(framePhase * copies) / copies * twoPi + angleOffset; osci::Point offset(std::cos(theta), std::sin(theta), 0.0); @@ -37,8 +35,8 @@ public: "harmonicDuplicatorSpread", VERSION_HINT, 0.4, 0.0, 1.0), new osci::EffectParameter("Angle Offset", "Rotates the offsets between copies without rotating the input shape.", - "harmonicDuplicatorAngle", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.2) - } + "harmonicDuplicatorAngle", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.1) + } ); eff->setName("Harmonic Duplicator"); eff->setIcon(BinaryData::kaleidoscope_svg); diff --git a/Source/audio/KaleidoscopeEffect.h b/Source/audio/KaleidoscopeEffect.h index be76c0bb..7d662b41 100644 --- a/Source/audio/KaleidoscopeEffect.h +++ b/Source/audio/KaleidoscopeEffect.h @@ -22,7 +22,7 @@ public: int fullSegments = (int)std::floor(segments); double fractionalPart = segments - fullSegments; // in [0,1) - fullSegments = fractionalPart > 1e-4 ? fullSegments : fullSegments - 1; + fullSegments = fractionalPart > 1e-3 ? fullSegments : fullSegments - 1; phase = nextPhase(audioProcessor.frequency / (fullSegments + 1), sampleRate) / (2.0 * std::numbers::pi); @@ -33,7 +33,7 @@ public: // Base full wedge angle (all full wedges) and size of partial wedge double baseWedgeAngle = juce::MathConstants::twoPi / segments; // size of a "unit" wedge - double partialScale = (currentSegmentIndex == fullSegments && fractionalPart > 1e-4) ? fractionalPart : 1.0; + double partialScale = (currentSegmentIndex == fullSegments && fractionalPart > 1e-3) ? fractionalPart : 1.0; double wedgeAngle = baseWedgeAngle * partialScale; // Normalize theta to [0,1) for compression diff --git a/Source/audio/MultiplexEffect.h b/Source/audio/MultiplexEffect.h index 4bef9f91..fdb9328b 100644 --- a/Source/audio/MultiplexEffect.h +++ b/Source/audio/MultiplexEffect.h @@ -11,9 +11,9 @@ public: osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { jassert(values.size() == 5); - double gridX = values[0].load() + 0.0001; - double gridY = values[1].load() + 0.0001; - double gridZ = values[2].load() + 0.0001; + double gridX = values[0].load(); + double gridY = values[1].load(); + double gridZ = values[2].load(); double interpolation = values[3].load(); double gridDelay = values[4].load(); @@ -24,7 +24,9 @@ public: buffer[head] = input; osci::Point grid = osci::Point(gridX, gridY, gridZ); - osci::Point gridFloor = osci::Point(std::floor(gridX), std::floor(gridY), std::floor(gridZ)); + osci::Point gridFloor = osci::Point(std::floor(gridX + 1e-3), + std::floor(gridY + 1e-3), + std::floor(gridZ + 1e-3)); gridFloor.x = std::max(gridFloor.x, 1.0); gridFloor.y = std::max(gridFloor.y, 1.0); From 297f518da7856bef928cfb03b8e4bb5176bb7426 Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Mon, 8 Sep 2025 15:16:46 -0700 Subject: [PATCH 11/18] trailing newline --- Source/audio/HarmonicDuplicatorEffect.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/audio/HarmonicDuplicatorEffect.h b/Source/audio/HarmonicDuplicatorEffect.h index 50c1f383..78bb2db2 100644 --- a/Source/audio/HarmonicDuplicatorEffect.h +++ b/Source/audio/HarmonicDuplicatorEffect.h @@ -46,4 +46,4 @@ public: private: OscirenderAudioProcessor &audioProcessor; double framePhase = 0.0; // [0, 1] -}; \ No newline at end of file +}; From fcbecb723449a66a6eed1ab397438acf752311bc Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Mon, 8 Sep 2025 15:44:19 -0700 Subject: [PATCH 12/18] Rename NUM_EDGES to PATH_LENGTH --- Resources/lua/hypercube.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/lua/hypercube.lua b/Resources/lua/hypercube.lua index c0157cd9..259b5054 100644 --- a/Resources/lua/hypercube.lua +++ b/Resources/lua/hypercube.lua @@ -46,16 +46,16 @@ local path = { 10, 11, 12, 9, 1, 2, 3, 4 } -local NUM_EDGES = #path +local PATH_LENGTH = #path -local railroad_switch = NUM_EDGES * phase / (2 * math.pi) +local railroad_switch = PATH_LENGTH * phase / (2 * math.pi) local current_vertex = math.floor(railroad_switch) + 1 -local next_vertex = current_vertex % NUM_EDGES + 1 +local next_vertex = current_vertex % PATH_LENGTH + 1 local edge_phase = railroad_switch % 1 local time = step/sample_rate * ANIMATION_SPEED -if current_vertex <= NUM_EDGES then +if current_vertex <= PATH_LENGTH then local v1 = vertices[path[current_vertex]] local v2 = vertices[path[next_vertex]] From 1fd774b3cb19c5d3a52cbb728b6348e7970aa74b Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Mon, 8 Sep 2025 17:22:17 -0700 Subject: [PATCH 13/18] Rename ceilCopies to freqDivisor --- Source/audio/HarmonicDuplicatorEffect.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/audio/HarmonicDuplicatorEffect.h b/Source/audio/HarmonicDuplicatorEffect.h index 78bb2db2..c1673e2a 100644 --- a/Source/audio/HarmonicDuplicatorEffect.h +++ b/Source/audio/HarmonicDuplicatorEffect.h @@ -9,7 +9,6 @@ public: osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { const double twoPi = juce::MathConstants::twoPi; double copies = juce::jmax(1.0, values[0].load()); - double ceilCopies = std::ceil(copies - 1e-3); double spread = juce::jlimit(0.0, 1.0, values[1].load()); double angleOffset = values[2].load() * juce::MathConstants::twoPi; @@ -17,7 +16,8 @@ public: double theta = std::floor(framePhase * copies) / copies * twoPi + angleOffset; osci::Point offset(std::cos(theta), std::sin(theta), 0.0); - framePhase += audioProcessor.frequency / ceilCopies / sampleRate; + double freqDivisor = std::ceil(copies - 1e-3); + framePhase += audioProcessor.frequency / freqDivisor / sampleRate; framePhase = framePhase - std::floor(framePhase); return (1 - spread) * input + spread * offset; From 07bfe8fdd81966f499ed97d775cba682fc5a000b Mon Sep 17 00:00:00 2001 From: James H Ball Date: Tue, 9 Sep 2025 21:42:40 +0100 Subject: [PATCH 14/18] Rename HarmonicDuplicator to Duplicator and add SVG --- Resources/svg/duplicator.svg | 1 + Source/PluginProcessor.cpp | 4 ++-- ...icDuplicatorEffect.h => DuplicatorEffect.h} | 18 +++++++++--------- osci-render.jucer | 5 +++-- 4 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 Resources/svg/duplicator.svg rename Source/audio/{HarmonicDuplicatorEffect.h => DuplicatorEffect.h} (71%) diff --git a/Resources/svg/duplicator.svg b/Resources/svg/duplicator.svg new file mode 100644 index 00000000..b7482d2d --- /dev/null +++ b/Resources/svg/duplicator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 0114bd93..820d424c 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -17,7 +17,7 @@ #include "audio/MultiplexEffect.h" #include "audio/SmoothEffect.h" #include "audio/WobbleEffect.h" -#include "audio/HarmonicDuplicatorEffect.h" +#include "audio/DuplicatorEffect.h" #include "audio/DashedLineEffect.h" #include "audio/VectorCancellingEffect.h" #include "audio/ScaleEffect.h" @@ -50,7 +50,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse toggleableEffects.push_back(DashedLineEffect(*this).build()); toggleableEffects.push_back(TraceEffect(*this).build()); toggleableEffects.push_back(WobbleEffect(*this).build()); - toggleableEffects.push_back(HarmonicDuplicatorEffect(*this).build()); + toggleableEffects.push_back(DuplicatorEffect(*this).build()); #if OSCI_PREMIUM toggleableEffects.push_back(MultiplexEffect(*this).build()); diff --git a/Source/audio/HarmonicDuplicatorEffect.h b/Source/audio/DuplicatorEffect.h similarity index 71% rename from Source/audio/HarmonicDuplicatorEffect.h rename to Source/audio/DuplicatorEffect.h index c1673e2a..f516f924 100644 --- a/Source/audio/HarmonicDuplicatorEffect.h +++ b/Source/audio/DuplicatorEffect.h @@ -2,9 +2,9 @@ #include #include "../PluginProcessor.h" -class HarmonicDuplicatorEffect : public osci::EffectApplication { +class DuplicatorEffect : public osci::EffectApplication { public: - HarmonicDuplicatorEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {} + DuplicatorEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {} osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { const double twoPi = juce::MathConstants::twoPi; @@ -25,21 +25,21 @@ public: std::shared_ptr build() const override { auto eff = std::make_shared( - std::make_shared(audioProcessor), + std::make_shared(audioProcessor), std::vector{ - new osci::EffectParameter("Copies", + new osci::EffectParameter("Duplicator Copies", "Controls the number of copies of the input shape to draw. Splitting the shape into multiple copies creates audible harmony.", - "harmonicDuplicatorCopies", VERSION_HINT, 3.0, 1.0, 6.0), + "duplicatorCopies", VERSION_HINT, 3.0, 1.0, 6.0), new osci::EffectParameter("Spread", "Controls the spread between copies of the input shape.", - "harmonicDuplicatorSpread", VERSION_HINT, 0.4, 0.0, 1.0), + "duplicatorSpread", VERSION_HINT, 0.4, 0.0, 1.0), new osci::EffectParameter("Angle Offset", "Rotates the offsets between copies without rotating the input shape.", - "harmonicDuplicatorAngle", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.1) + "duplicatorAngle", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.1) } ); - eff->setName("Harmonic Duplicator"); - eff->setIcon(BinaryData::kaleidoscope_svg); + eff->setName("Duplicator"); + eff->setIcon(BinaryData::duplicator_svg); return eff; } diff --git a/osci-render.jucer b/osci-render.jucer index d4634ff1..43b87678 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -95,6 +95,7 @@ + @@ -167,8 +168,8 @@ - + From b1037febba1c7c0288702d4534865878808bad3e Mon Sep 17 00:00:00 2001 From: Anthony Hall Date: Tue, 9 Sep 2025 15:17:32 -0700 Subject: [PATCH 15/18] Rename effect and parameters --- Source/PluginProcessor.cpp | 4 ++-- Source/audio/{RadiationEffect.h => GodRayEffect.h} | 14 +++++++------- osci-render.jucer | 3 +-- 3 files changed, 10 insertions(+), 11 deletions(-) rename Source/audio/{RadiationEffect.h => GodRayEffect.h} (64%) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 6743b863..b33fdb3b 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -26,7 +26,7 @@ #include "audio/SwirlEffect.h" #include "audio/BounceEffect.h" #include "audio/SkewEffect.h" -#include "audio/RadiationEffect.h" +#include "audio/GodRayEffect.h" #include "parser/FileParser.h" #include "parser/FrameProducer.h" @@ -57,7 +57,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse toggleableEffects.push_back(BounceEffect().build()); toggleableEffects.push_back(TwistEffect().build()); toggleableEffects.push_back(SkewEffect().build()); - toggleableEffects.push_back(RadiationEffect().build()); + toggleableEffects.push_back(GodRayEffect().build()); #endif auto scaleEffect = ScaleEffectApp().build(); diff --git a/Source/audio/RadiationEffect.h b/Source/audio/GodRayEffect.h similarity index 64% rename from Source/audio/RadiationEffect.h rename to Source/audio/GodRayEffect.h index 45a99a2f..53d00679 100644 --- a/Source/audio/RadiationEffect.h +++ b/Source/audio/GodRayEffect.h @@ -1,7 +1,7 @@ #pragma once #include -class RadiationEffect : public osci::EffectApplication { +class GodRayEffect : public osci::EffectApplication { public: osci::Point apply(int index, osci::Point input, const std::vector> &values, double sampleRate) override { double noiseAmp = juce::jmax(0.0, values[0].load()); @@ -22,14 +22,14 @@ public: std::shared_ptr build() const override { auto eff = std::make_shared( - std::make_shared(), + std::make_shared(), std::vector{ - new osci::EffectParameter("Radiation", - "Creates a crepuscular ray effect by adding noise. This slider controls the size of the rays. Looks best with higher sample rates.", - "radiationAmp", VERSION_HINT, 0.5, 0.0, 1.0), - new osci::EffectParameter("Radiation Bias", + new osci::EffectParameter("God Rays", + "Creates a god ray effect by adding noise. This slider controls the size of the rays. Looks best with higher sample rates.", + "godRayAmp", VERSION_HINT, 0.5, 0.0, 1.0), + new osci::EffectParameter("God Ray Position", "Controls whether the rays appear to be radiating inward or outward.", - "radiationBias", VERSION_HINT, 0.8, -1.0, 1.0) + "godRayBias", VERSION_HINT, 0.8, -1.0, 1.0) }); eff->setIcon(BinaryData::scale_svg); return eff; diff --git a/osci-render.jucer b/osci-render.jucer index 7437d709..977e77d9 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -166,8 +166,7 @@ - + Date: Tue, 9 Sep 2025 17:51:31 -0700 Subject: [PATCH 16/18] Overhaul zoom and rotation, modify parameter details --- Source/audio/SpiralBitCrushEffect.h | 52 ++++++++++++++++++----------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Source/audio/SpiralBitCrushEffect.h b/Source/audio/SpiralBitCrushEffect.h index b3fbe897..713fda1a 100644 --- a/Source/audio/SpiralBitCrushEffect.h +++ b/Source/audio/SpiralBitCrushEffect.h @@ -5,11 +5,12 @@ class SpiralBitCrushEffect : public osci::EffectApplication { public: osci::Point apply(int index, osci::Point input, const std::vector> &values, double sampleRate) override { // Completing one revolution in input space traverses the hypotenuse of one "domain" in log-polar space - double effectScale = juce::jlimit(0.0, 1.0, values[0].load()); - double domainX = juce::jmax(2.0, std::floor(values[1].load() + 0.0001)); + double effectScale = juce::jlimit(0.0, 1.0, values[0].load()); + double domainX = juce::jmax(2.0, std::floor(values[1].load() + 0.001)); double domainY = std::round(domainX * values[2].load()); - osci::Point offset(values[3].load(), values[4].load()); - + double zoom = values[3].load() * juce::MathConstants::twoPi; // Use same scale as angle + double rotation = values[4].load() * juce::MathConstants::twoPi; + double domainHypot = std::hypot(domainX, domainY); double domainTheta = std::atan2(domainY, domainX); double scale = domainHypot / juce::MathConstants::twoPi; @@ -22,17 +23,17 @@ public: double r = std::hypot(input.x, input.y); double logR = std::log(r); double theta = std::atan2(input.x, -input.y); - osci::Point logPolarCoords(theta, logR); + osci::Point logPolarCoords(theta - rotation, logR - zoom); logPolarCoords.rotate(0, 0, domainTheta); - logPolarCoords = logPolarCoords * scale - offset; + logPolarCoords = logPolarCoords * scale; // Round this point to the center of the log-polar cell the input lies in, convert back to Cartesian logPolarCoords.x = std::round(logPolarCoords.x); logPolarCoords.y = std::round(logPolarCoords.y); - logPolarCoords = (logPolarCoords + offset) / scale; + logPolarCoords = logPolarCoords / scale; logPolarCoords.rotate(0, 0, -domainTheta); - double outR = std::exp(logPolarCoords.y); - double outTheta = logPolarCoords.x; + double outR = std::exp(logPolarCoords.y + zoom); + double outTheta = logPolarCoords.x + rotation; output.x = outR * std::sin(outTheta); output.y = outR * -std::cos(outTheta); } @@ -42,24 +43,35 @@ public: if (input.z != 0) { double signZ = input.z > 0 ? 1.0 : -1.0; double logZ = std::log(std::abs(input.z)); - logZ = logZ * scale - offset.y; + logZ = (logZ - zoom) * scale; logZ = std::round(logZ); - logZ = (logZ + offset.y) / scale; + logZ = logZ / scale + zoom; output.z = signZ * std::exp(logZ); } return (1 - effectScale) * input + effectScale * output; } std::shared_ptr build() const override { - auto eff = std::make_shared( - std::make_shared(), - std::vector{ - new osci::EffectParameter("Spiral Bit Crush", "Constrains points to a spiral pattern.", "spiralBitCrushEnable", VERSION_HINT, 1.0, 0.0, 1.0), - new osci::EffectParameter("Spiral Density", "Controls the density of the spiral pattern.", "spiralDensity", VERSION_HINT, 13.0, 3.0, 30.0), - new osci::EffectParameter("Spiral Twist", "Controls how much the spiral pattern twists.", "spiralTwist", VERSION_HINT, -0.6, -1.0, 1.0), - new osci::EffectParameter("Angle Offset", "Rotates the spiral pattern.", "spiralOffsetX", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.4), - new osci::EffectParameter("Radial Offset", "Zooms the spiral pattern.", "spiralOffsetY", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 1.0) - }); + auto eff = std::make_shared( + std::make_shared(), + std::vector{ + new osci::EffectParameter("Spiral Bit Crush", + "Constrains points to a spiral pattern.", + "spiralBitCrush", VERSION_HINT, 1.0, 0.0, 1.0), + new osci::EffectParameter("Spiral Density", + "Controls the density of the spiral pattern.", + "spiralBitCrushDensity", VERSION_HINT, 13.0, 3.0, 30.0, 1.0), + new osci::EffectParameter("Spiral Twist", + "Controls how much the spiral pattern twists.", + "spiralBitCrushTwist", VERSION_HINT, 0.6, -1.0, 1.0), + new osci::EffectParameter("Zoom", + "Zooms the spiral pattern.", + "spiralBitCrushZoom", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.05), + new osci::EffectParameter("Rotation", + "Rotates the spiral pattern.", + "spiralBitCrushRotation", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::ReverseSawtooth, 0.02) + } + ); eff->setIcon(BinaryData::swirl_svg); return eff; } From d036d2b0afb7c4b6fbda6684a123fc8493420d27 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Thu, 11 Sep 2025 20:53:28 +0100 Subject: [PATCH 17/18] Update spiral bitcrush default values and add icon --- Resources/svg/spiral_bitcrush.svg | 1 + Source/audio/SpiralBitCrushEffect.h | 6 +++--- osci-render.jucer | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 Resources/svg/spiral_bitcrush.svg diff --git a/Resources/svg/spiral_bitcrush.svg b/Resources/svg/spiral_bitcrush.svg new file mode 100644 index 00000000..978a696e --- /dev/null +++ b/Resources/svg/spiral_bitcrush.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/audio/SpiralBitCrushEffect.h b/Source/audio/SpiralBitCrushEffect.h index 713fda1a..5a7c0c3a 100644 --- a/Source/audio/SpiralBitCrushEffect.h +++ b/Source/audio/SpiralBitCrushEffect.h @@ -57,7 +57,7 @@ public: std::vector{ new osci::EffectParameter("Spiral Bit Crush", "Constrains points to a spiral pattern.", - "spiralBitCrush", VERSION_HINT, 1.0, 0.0, 1.0), + "spiralBitCrush", VERSION_HINT, 0.4, 0.0, 1.0), new osci::EffectParameter("Spiral Density", "Controls the density of the spiral pattern.", "spiralBitCrushDensity", VERSION_HINT, 13.0, 3.0, 30.0, 1.0), @@ -66,13 +66,13 @@ public: "spiralBitCrushTwist", VERSION_HINT, 0.6, -1.0, 1.0), new osci::EffectParameter("Zoom", "Zooms the spiral pattern.", - "spiralBitCrushZoom", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.05), + "spiralBitCrushZoom", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.1), new osci::EffectParameter("Rotation", "Rotates the spiral pattern.", "spiralBitCrushRotation", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::ReverseSawtooth, 0.02) } ); - eff->setIcon(BinaryData::swirl_svg); + eff->setIcon(BinaryData::spiral_bitcrush_svg); return eff; } }; diff --git a/osci-render.jucer b/osci-render.jucer index 0bd03d70..2fa55d8c 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -142,6 +142,8 @@ + From 3a34dddb06e9d23c3e91bd2638142c5c7ca72abf Mon Sep 17 00:00:00 2001 From: James H Ball Date: Thu, 11 Sep 2025 21:04:45 +0100 Subject: [PATCH 18/18] Tweak parameters and add god ray icon --- Resources/svg/god_ray.svg | 1 + Source/audio/GodRayEffect.h | 8 ++++---- osci-render.jucer | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 Resources/svg/god_ray.svg diff --git a/Resources/svg/god_ray.svg b/Resources/svg/god_ray.svg new file mode 100644 index 00000000..19d69788 --- /dev/null +++ b/Resources/svg/god_ray.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/audio/GodRayEffect.h b/Source/audio/GodRayEffect.h index 53d00679..c035235c 100644 --- a/Source/audio/GodRayEffect.h +++ b/Source/audio/GodRayEffect.h @@ -24,14 +24,14 @@ public: auto eff = std::make_shared( std::make_shared(), std::vector{ - new osci::EffectParameter("God Rays", + new osci::EffectParameter("God Ray Strength", "Creates a god ray effect by adding noise. This slider controls the size of the rays. Looks best with higher sample rates.", - "godRayAmp", VERSION_HINT, 0.5, 0.0, 1.0), + "godRayStrength", VERSION_HINT, 0.5, 0.0, 1.0), new osci::EffectParameter("God Ray Position", "Controls whether the rays appear to be radiating inward or outward.", - "godRayBias", VERSION_HINT, 0.8, -1.0, 1.0) + "godRayPosition", VERSION_HINT, 0.8, -1.0, 1.0) }); - eff->setIcon(BinaryData::scale_svg); + eff->setIcon(BinaryData::god_ray_svg); return eff; } }; \ No newline at end of file diff --git a/osci-render.jucer b/osci-render.jucer index 7b95f3d4..934d31fe 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -99,6 +99,7 @@ +