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