kopia lustrzana https://github.com/jameshball/osci-render
Merge pull request #328 from ahall99/mirror-kaleidoscope
Add Mirror Kaleidoscope, rename old "Kaleidoscope" to "Bloom"pull/330/head
commit
b713516888
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16,4L20,8V4M20,16L16,20H20M8,20L4,16V20M4,8L8,4H4M16.95,7.05C14.22,4.32 9.78,4.32 7.05,7.05C4.32,9.78 4.32,14.22 7.05,16.95C9.78,19.68 14.22,19.68 16.95,16.95C19.68,14.22 19.68,9.79 16.95,7.05M15.85,15.85C13.72,18 10.28,18 8.15,15.85C6,13.72 6,10.28 8.15,8.15C10.28,6 13.72,6 15.85,8.15C18,10.28 18,13.72 15.85,15.85Z" /></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M120-120v-240h80v104l124-124 56 56-124 124h104v80H120Zm480 0v-80h104L580-324l56-56 124 124v-104h80v240H600ZM324-580 200-704v104h-80v-240h240v80H256l124 124-56 56Zm312 0-56-56 124-124H600v-80h240v240h-80v-104L636-580ZM480-400q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z"/></svg>
|
||||
|
Przed Szerokość: | Wysokość: | Rozmiar: 397 B Po Szerokość: | Wysokość: | Rozmiar: 443 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M480-28 346-160H160v-186L28-480l132-134v-186h186l134-132 134 132h186v186l132 134-132 134v186H614L480-28Zm0-112 100-100h140v-140l100-100-100-100v-140H580L480-820 380-720H240v140L140-480l100 100v140h140l100 100Zm0-340Z"/></svg>
|
||||
|
Po Szerokość: | Wysokość: | Rozmiar: 341 B |
|
|
@ -15,7 +15,7 @@
|
|||
#include "audio/PolygonBitCrushEffect.h"
|
||||
#include "audio/SpiralBitCrushEffect.h"
|
||||
#include "audio/DistortEffect.h"
|
||||
#include "audio/KaleidoscopeEffect.h"
|
||||
#include "audio/UnfoldEffect.h"
|
||||
#include "audio/MultiplexEffect.h"
|
||||
#include "audio/SmoothEffect.h"
|
||||
#include "audio/WobbleEffect.h"
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
#include "audio/SwirlEffect.h"
|
||||
#include "audio/BounceEffect.h"
|
||||
#include "audio/SkewEffect.h"
|
||||
#include "audio/KaleidoscopeEffect.h"
|
||||
#include "audio/VortexEffect.h"
|
||||
#include "audio/GodRayEffect.h"
|
||||
#include "parser/FileParser.h"
|
||||
|
|
@ -58,11 +59,12 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
|
|||
|
||||
#if OSCI_PREMIUM
|
||||
toggleableEffects.push_back(MultiplexEffect(*this).build());
|
||||
toggleableEffects.push_back(KaleidoscopeEffect(*this).build());
|
||||
toggleableEffects.push_back(UnfoldEffect(*this).build());
|
||||
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());
|
||||
toggleableEffects.push_back(SpiralBitCrushEffect().build());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
// Deprecated header retained temporarily for backward compatibility.
|
||||
// Use UnfoldEffect.h instead. This file will be removed in a future cleanup.
|
||||
#pragma once
|
||||
#include "UnfoldEffect.h"
|
||||
|
|
@ -12,8 +12,7 @@ public:
|
|||
// Bias values toward 0 or 1 based on sign
|
||||
if (bias > 0.0) {
|
||||
noise = std::pow(noise, biasExponent);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
noise = 1 - std::pow(1 - noise, biasExponent);
|
||||
}
|
||||
double scale = (1 - noiseAmp) + noise * noiseAmp;
|
||||
|
|
|
|||
|
|
@ -1,75 +1,86 @@
|
|||
// KaleidoscopeEffect.h
|
||||
// Repeats and mirrors the input around the origin to create a kaleidoscope pattern.
|
||||
// The effect supports a floating point number of segments, allowing smooth morphing
|
||||
// between different symmetry counts.
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "../PluginProcessor.h"
|
||||
|
||||
class KaleidoscopeEffect : public osci::EffectApplication {
|
||||
public:
|
||||
explicit KaleidoscopeEffect(OscirenderAudioProcessor &p) : audioProcessor(p) {}
|
||||
KaleidoscopeEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {}
|
||||
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
// values[0] = segments (can be fractional)
|
||||
// values[1] = phase (0-1) selecting which segment is currently being drawn
|
||||
double segments = juce::jmax(values[0].load(), 1.0); // ensure at least 1 segment
|
||||
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
const double pi = juce::MathConstants<double>::pi;
|
||||
const double twoPi = juce::MathConstants<double>::twoPi;
|
||||
double segments = juce::jmax(1.0, values[0].load());
|
||||
double mirror = values[1].load();
|
||||
double spread = juce::jlimit(0.0, 1.0, values[2].load());
|
||||
double clip = juce::jlimit(0.0, 1.0, values[3].load());
|
||||
|
||||
// Polar conversion
|
||||
double r = std::sqrt(input.x * input.x + input.y * input.y);
|
||||
if (r < 1e-12) return input;
|
||||
double theta = std::atan2(input.y, input.x);
|
||||
// To start, treat everything as if we are on the +X (theta = 0) segment
|
||||
// Rotate input shape 90 deg CW so the shape is always "upright"
|
||||
// relative to the radius
|
||||
// Then apply spread
|
||||
input = osci::Point(input.y, -input.x, input.z);
|
||||
osci::Point output = (1 - spread) * input;
|
||||
output.x += spread;
|
||||
|
||||
int fullSegments = (int)std::floor(segments);
|
||||
double fractionalPart = segments - fullSegments; // in [0,1)
|
||||
fullSegments = fractionalPart > 1e-3 ? 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);
|
||||
if (currentSegmentIndex > fullSegments) currentSegmentIndex = fullSegments; // safety
|
||||
|
||||
// Base full wedge angle (all full wedges) and size of partial wedge
|
||||
double baseWedgeAngle = juce::MathConstants<double>::twoPi / segments; // size of a "unit" wedge
|
||||
double partialScale = (currentSegmentIndex == fullSegments && fractionalPart > 1e-3) ? fractionalPart : 1.0;
|
||||
double wedgeAngle = baseWedgeAngle * partialScale;
|
||||
|
||||
// Normalize theta to [0,1) for compression
|
||||
double thetaNorm = (theta + juce::MathConstants<double>::pi) / juce::MathConstants<double>::twoPi; // 0..1
|
||||
|
||||
// Offset for this segment: each preceding full segment occupies baseWedgeAngle
|
||||
double segmentOffset = 0.0;
|
||||
if (currentSegmentIndex < fullSegments) {
|
||||
segmentOffset = currentSegmentIndex * baseWedgeAngle;
|
||||
} else { // partial segment
|
||||
segmentOffset = fullSegments * baseWedgeAngle;
|
||||
// Mirror the y of every other segment if enabled
|
||||
double currentSegment = std::floor(framePhase * segments);
|
||||
if ((int)currentSegment % 2 == 1) {
|
||||
output.y *= (1 - 2 * mirror);
|
||||
}
|
||||
// Map entire original angle range into [segmentOffset, segmentOffset + wedgeAngle) so edges line up exactly.
|
||||
double finalTheta = segmentOffset + thetaNorm * wedgeAngle - juce::MathConstants<double>::pi; // constant 180° rotation
|
||||
|
||||
double newX = r * std::cos(finalTheta);
|
||||
double newY = r * std::sin(finalTheta);
|
||||
return osci::Point(newX, newY, input.z);
|
||||
// Clip the shape to remain within this radial segment
|
||||
double segmentSize = twoPi / segments; // Angular size
|
||||
osci::Point upperPlaneNormal(std::sin(0.5 * segmentSize), -std::cos(0.5 * segmentSize), 0);
|
||||
osci::Point lowerPlaneNormal(upperPlaneNormal.x, -upperPlaneNormal.y, 0);
|
||||
osci::Point clippedOutput;
|
||||
if (segmentSize < pi) {
|
||||
clippedOutput = clipToPlane(output, upperPlaneNormal);
|
||||
clippedOutput = clipToPlane(clippedOutput, lowerPlaneNormal);
|
||||
// Point could still lie left of the origin along the lower plane
|
||||
if (clippedOutput.x < 0) {
|
||||
clippedOutput.x = 0;
|
||||
clippedOutput.y = 0;
|
||||
}
|
||||
} else {
|
||||
// segmentSize is wider than 180 degrees
|
||||
// If the point is clipped to both planes like above, the actual result region
|
||||
// will be less than 180 degrees
|
||||
// So only clip to one plane at a time
|
||||
if (output.y > 0) {
|
||||
clippedOutput = clipToPlane(output, upperPlaneNormal);
|
||||
} else {
|
||||
clippedOutput = clipToPlane(output, lowerPlaneNormal);
|
||||
}
|
||||
}
|
||||
output = (1 - clip) * output + clip * clippedOutput;
|
||||
|
||||
// Finally, rotate this radial segment to its actual location
|
||||
double rotTheta = (currentSegment / segments) * twoPi;
|
||||
output.rotate(0, 0, rotTheta);
|
||||
|
||||
double freqDivisor = std::ceil(segments - 1e-3);
|
||||
framePhase += audioProcessor.frequency / freqDivisor / sampleRate;
|
||||
framePhase = framePhase - std::floor(framePhase);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<KaleidoscopeEffect>(audioProcessor),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter(
|
||||
"Kaleidoscope Segments",
|
||||
"Controls how many times the image is rotationally repeated around the centre. Fractional values smoothly morph the repetition.",
|
||||
"kaleidoscopeSegments",
|
||||
VERSION_HINT,
|
||||
3.0, // default
|
||||
1.0, // min
|
||||
5.0, // max
|
||||
0.0001f, // step
|
||||
osci::LfoType::Sine,
|
||||
0.25f // LFO frequency (Hz) – slow, visible rotation
|
||||
),
|
||||
new osci::EffectParameter("Kaeidoscope Segments",
|
||||
"Controls how many times the input shape is rotationally duplicated around the centre.",
|
||||
"kaleidoscopeSegments", VERSION_HINT, 6.0, 1.0, 6.0, 0.0001, osci::LfoType::Sine, 0.2),
|
||||
new osci::EffectParameter("Mirror",
|
||||
"Mirrors every other segment like a real kaleidoscope. Best used in combination with an even number of segments.",
|
||||
"kaleidoscopeMirror", VERSION_HINT, 1.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("Spread",
|
||||
"Controls the radial spread of each segment.",
|
||||
"kaleidoscopeSpread", VERSION_HINT, 0.4, 0.0, 1.0),
|
||||
new osci::EffectParameter("Clip",
|
||||
"Clips each copy of the input shape within its own segment.",
|
||||
"kaleidoscopeClip", VERSION_HINT, 1.0, 0.0, 1.0)
|
||||
}
|
||||
);
|
||||
eff->setName("Kaleidoscope");
|
||||
|
|
@ -79,5 +90,15 @@ public:
|
|||
|
||||
private:
|
||||
OscirenderAudioProcessor &audioProcessor;
|
||||
double phase = 0.0;
|
||||
double framePhase = 0.0; // [0, 1]
|
||||
|
||||
// Clips points behind the plane to the plane
|
||||
// Leaves points in front of the plane untouched
|
||||
osci::Point clipToPlane(osci::Point input, osci::Point planeNormal)
|
||||
{
|
||||
double distToPlane = input.innerProduct(planeNormal);
|
||||
double clippedDist = std::max(0.0, distToPlane);
|
||||
double distAdjustment = clippedDist - distToPlane;
|
||||
return input + distAdjustment * planeNormal;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
// UnfoldEffect.h
|
||||
// Repeats and mirrors the input around the origin to create a kaleidoscope pattern.
|
||||
// Previously named BloomEffect. Renamed to UnfoldEffect.
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class UnfoldEffect : public osci::EffectApplication {
|
||||
public:
|
||||
explicit UnfoldEffect(OscirenderAudioProcessor &p) : audioProcessor(p) {}
|
||||
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
// values[0] = segments (can be fractional)
|
||||
// values[1] = phase (0-1) selecting which segment is currently being drawn
|
||||
double segments = juce::jmax(values[0].load(), 1.0); // ensure at least 1 segment
|
||||
|
||||
// Polar conversion
|
||||
double r = std::sqrt(input.x * input.x + input.y * input.y);
|
||||
if (r < 1e-12) return input;
|
||||
double theta = std::atan2(input.y, input.x);
|
||||
|
||||
int fullSegments = (int)std::floor(segments);
|
||||
double fractionalPart = segments - fullSegments; // in [0,1)
|
||||
fullSegments = fractionalPart > 1e-3 ? 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);
|
||||
if (currentSegmentIndex > fullSegments) currentSegmentIndex = fullSegments; // safety
|
||||
|
||||
// Base full wedge angle (all full wedges) and size of partial wedge
|
||||
double baseWedgeAngle = juce::MathConstants<double>::twoPi / segments; // size of a "unit" wedge
|
||||
double partialScale = (currentSegmentIndex == fullSegments && fractionalPart > 1e-3) ? fractionalPart : 1.0;
|
||||
double wedgeAngle = baseWedgeAngle * partialScale;
|
||||
|
||||
// Normalize theta to [0,1) for compression
|
||||
double thetaNorm = (theta + juce::MathConstants<double>::pi) / juce::MathConstants<double>::twoPi; // 0..1
|
||||
|
||||
// Offset for this segment: each preceding full segment occupies baseWedgeAngle
|
||||
double segmentOffset = 0.0;
|
||||
if (currentSegmentIndex < fullSegments) {
|
||||
segmentOffset = currentSegmentIndex * baseWedgeAngle;
|
||||
} else { // partial segment
|
||||
segmentOffset = fullSegments * baseWedgeAngle;
|
||||
}
|
||||
// Map entire original angle range into [segmentOffset, segmentOffset + wedgeAngle) so edges line up exactly.
|
||||
double finalTheta = segmentOffset + thetaNorm * wedgeAngle - juce::MathConstants<double>::pi; // constant 180° rotation
|
||||
|
||||
double newX = r * std::cos(finalTheta);
|
||||
double newY = r * std::sin(finalTheta);
|
||||
return osci::Point(newX, newY, input.z);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<UnfoldEffect>(audioProcessor),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter(
|
||||
"Unfold Segments",
|
||||
"Controls how many times the image is rotationally repeated around the centre. Fractional values smoothly morph the repetition.",
|
||||
"unfoldSegments",
|
||||
VERSION_HINT,
|
||||
3.0, // default
|
||||
1.0, // min
|
||||
5.0, // max
|
||||
0.0001f, // step
|
||||
osci::LfoType::Sine,
|
||||
0.25f // LFO frequency (Hz) – slow, visible rotation
|
||||
),
|
||||
}
|
||||
);
|
||||
eff->setName("Unfold");
|
||||
eff->setIcon(BinaryData::unfold_svg);
|
||||
return eff;
|
||||
}
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor &audioProcessor;
|
||||
double phase = 0.0;
|
||||
};
|
||||
|
|
@ -155,6 +155,7 @@
|
|||
<FILE id="ESR7bv" name="trace.svg" compile="0" resource="1" file="Resources/svg/trace.svg"/>
|
||||
<FILE id="ID1vTS" name="translate.svg" compile="0" resource="1" file="Resources/svg/translate.svg"/>
|
||||
<FILE id="Sw4WWb" name="twist.svg" compile="0" resource="1" file="Resources/svg/twist.svg"/>
|
||||
<FILE id="Y90Viq" name="unfold.svg" compile="0" resource="1" file="Resources/svg/unfold.svg"/>
|
||||
<FILE id="praXUY" name="vector-cancelling.svg" compile="0" resource="1"
|
||||
file="Resources/svg/vector-cancelling.svg"/>
|
||||
<FILE id="qC6QiP" name="volume.svg" compile="0" resource="1" file="Resources/svg/volume.svg"/>
|
||||
|
|
@ -173,6 +174,9 @@
|
|||
</GROUP>
|
||||
<GROUP id="{75439074-E50C-362F-1EDF-8B4BE9011259}" name="Source">
|
||||
<GROUP id="{85A33213-D880-BD92-70D8-1901DA6D23F0}" name="audio">
|
||||
<FILE id="XSUjDz" name="KaleidoscopeEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/KaleidoscopeEffect.h"/>
|
||||
<FILE id="mREEpc" name="UnfoldEffect.h" compile="0" resource="0" file="Source/audio/UnfoldEffect.h"/>
|
||||
<FILE id="jfRtxu" name="VortexEffect.h" compile="0" resource="0" file="Source/audio/VortexEffect.h"/>
|
||||
<FILE id="de5H36" name="GodRayEffect.h" compile="0" resource="0" file="Source/audio/GodRayEffect.h"/>
|
||||
<FILE id="tU2pQl" name="SkewEffect.h" compile="0" resource="0" file="Source/audio/SkewEffect.h"/>
|
||||
|
|
@ -195,8 +199,6 @@
|
|||
<FILE id="ux2dO2" name="DistortEffect.h" compile="0" resource="0" file="Source/audio/DistortEffect.h"/>
|
||||
<FILE id="SWC0tN" name="MultiplexEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/MultiplexEffect.h"/>
|
||||
<FILE id="kAlEiD" name="KaleidoscopeEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/KaleidoscopeEffect.h"/>
|
||||
<FILE id="bNcEfx" name="BounceEffect.h" compile="0" resource="0" file="Source/audio/BounceEffect.h"/>
|
||||
<FILE id="h0dMim" name="PerspectiveEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/PerspectiveEffect.h"/>
|
||||
|
|
@ -916,4 +918,3 @@
|
|||
<MODULE id="osci_render_core" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
|
||||
</MODULES>
|
||||
</JUCERPROJECT>
|
||||
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue