diff --git a/Resources/svg/polygon_bitcrush.svg b/Resources/svg/polygon_bitcrush.svg
new file mode 100644
index 00000000..38e9962f
--- /dev/null
+++ b/Resources/svg/polygon_bitcrush.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp
index 2ed1f30e..9a3840a3 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/PolygonBitCrushEffect.h"
#include "audio/SpiralBitCrushEffect.h"
#include "audio/DistortEffect.h"
#include "audio/UnfoldEffect.h"
@@ -62,6 +63,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
toggleableEffects.push_back(BounceEffect().build());
toggleableEffects.push_back(TwistEffect().build());
toggleableEffects.push_back(SkewEffect().build());
+ toggleableEffects.push_back(PolygonBitCrushEffect().build());
toggleableEffects.push_back(KaleidoscopeEffect(*this).build());
toggleableEffects.push_back(VortexEffect().build());
toggleableEffects.push_back(GodRayEffect().build());
diff --git a/Source/audio/BulgeEffect.h b/Source/audio/BulgeEffect.h
index c15c9c15..e9414c16 100644
--- a/Source/audio/BulgeEffect.h
+++ b/Source/audio/BulgeEffect.h
@@ -7,11 +7,11 @@ public:
double value = values[0];
double translatedBulge = -value + 1;
- double r = sqrt(pow(input.x, 2) + pow(input.y, 2));
- double theta = atan2(input.y, input.x);
- double rn = pow(r, translatedBulge);
-
- return osci::Point(rn * cos(theta), rn * sin(theta), input.z);
+ double r = std::hypot(input.x, input.y);
+ if (r == 0) return input;
+ double rn = std::pow(r, translatedBulge);
+ double scale = rn / r;
+ return osci::Point(scale * input.x, scale * input.y, input.z);
}
std::shared_ptr build() const override {
diff --git a/Source/audio/PolygonBitCrushEffect.h b/Source/audio/PolygonBitCrushEffect.h
new file mode 100644
index 00000000..753add9b
--- /dev/null
+++ b/Source/audio/PolygonBitCrushEffect.h
@@ -0,0 +1,62 @@
+#pragma once
+#include
+#include "../MathUtil.h"
+
+// Inspired by xenontesla122
+class PolygonBitCrushEffect : public osci::EffectApplication {
+public:
+ osci::Point apply(int index, osci::Point input, const std::vector> &values, double sampleRate) override {
+ const double pi = juce::MathConstants::pi;
+ const double twoPi = juce::MathConstants::twoPi;
+ double effectScale = juce::jlimit(0.0, 1.0, values[0].load());
+ double nSides = juce::jmax(2.0, values[1].load());
+ double stripeSize = juce::jmax(1e-4, values[2].load());
+ stripeSize = std::pow(0.63 * stripeSize, 1.5); // Change range and bias toward smaller values
+ double rotation = values[3] * twoPi;
+ double stripePhase = values[4];
+
+ osci::Point output(0);
+ if (input.x != 0 || input.y != 0) {
+ // Note 90 degree rotation: Theta is treated relative to +Y rather than +X
+ double r = std::hypot(input.x, input.y);
+ double theta = std::atan2(-input.x, input.y) - rotation;
+ theta = MathUtil::wrapAngle(theta + pi) - pi; // Move branch cut to +/-pi after angle offset is applied
+ double regionCenterTheta = std::round(theta * nSides / twoPi) / nSides * twoPi;
+ double localTheta = theta - regionCenterTheta;
+ double dist = r * std::cos(localTheta);
+ double newDist = juce::jmax(0.0, (std::round(dist / stripeSize - stripePhase) + stripePhase) * stripeSize);
+ double scale = newDist / dist;
+ output.x = scale * input.x;
+ output.y = scale * input.y;
+ }
+ // Apply the same stripe quantization to abs(z) if the input is 3D
+ double absZ = std::abs(input.z);
+ if (absZ > 0.0001) {
+ double signZ = input.z > 0 ? 1 : -1;
+ output.z = signZ * juce::jmax(0.0, (std::round(absZ / stripeSize - stripePhase) + stripePhase) * stripeSize);
+ }
+ 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("Polygon Bit Crush",
+ "Constrains points to a polygon pattern.",
+ "polygonBitCrush", VERSION_HINT, 1.0, 0.0, 1.0),
+ new osci::EffectParameter("Sides", "Controls the number of sides of the polygon pattern.",
+ "polygonBitCrushSides", VERSION_HINT, 5.0, 3.0, 8.0),
+ new osci::EffectParameter("Stripe Size",
+ "Controls the spacing between the stripes of the polygon pattern.",
+ "polygonBitCrushStripeSize", VERSION_HINT, 0.5, 0.0, 1.0),
+ new osci::EffectParameter("Rotation", "Rotates the polygon pattern.",
+ "polygonBitCrushRotation", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 0.1),
+ new osci::EffectParameter("Stripe Phase", "Offsets the stripes of the polygon pattern.",
+ "polygonBitCrushStripePhase", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001, osci::LfoType::Sawtooth, 2.0)
+ }
+ );
+ eff->setIcon(BinaryData::polygon_bitcrush_svg);
+ return eff;
+ }
+};
diff --git a/osci-render.jucer b/osci-render.jucer
index 99db5fc7..8aff0e9d 100644
--- a/osci-render.jucer
+++ b/osci-render.jucer
@@ -4,7 +4,7 @@
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" pluginCharacteristicsValue="pluginWantsMidiIn"
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
cppLanguageStandard="20" projectLineFeed="
" headerPath="./include"
- version="2.6.1.1" companyName="James H Ball" companyWebsite="https://osci-render.com"
+ version="2.6.2.0" companyName="James H Ball" companyWebsite="https://osci-render.com"
companyEmail="james@ball.sh" defines="NOMINMAX=1
INTERNET_FLAG_NO_AUTO_REDIRECT=0
OSCI_PREMIUM=1
JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1
JUCE_MODAL_LOOPS_PERMITTED=1"
pluginAUMainType="'aumf'" postExportShellCommandPosix="echo "Building LuaJIT for $OSTYPE..." && DIR=%%1%% %%1%%/luajit_linux_macos.sh ">
@@ -127,6 +127,8 @@
+
@@ -180,6 +182,8 @@
+