From c052decbe9ad1c2eda87fef61af1c16579b31b43 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Sat, 30 Aug 2025 21:29:48 +0100 Subject: [PATCH] Fix bugs, improve performance, and change how multiplex and kaleidoscope react to frequency --- Source/EffectsComponent.cpp | 5 --- Source/PluginProcessor.cpp | 11 +++-- Source/audio/KaleidoscopeEffect.h | 25 +++++------ Source/audio/MultiplexEffect.h | 52 ++++++++++++---------- Source/components/ComponentList.h | 11 +++-- Source/components/EffectsListComponent.cpp | 6 +-- Source/components/EffectsListComponent.h | 4 +- osci-render.jucer | 2 +- sosci.jucer | 2 +- 9 files changed, 59 insertions(+), 59 deletions(-) diff --git a/Source/EffectsComponent.cpp b/Source/EffectsComponent.cpp index 8c59ae1d..a4b09db4 100644 --- a/Source/EffectsComponent.cpp +++ b/Source/EffectsComponent.cpp @@ -130,18 +130,13 @@ void EffectsComponent::resized() { area.removeFromTop(6); if (showingGrid) { grid.setBounds(area); - grid.setVisible(true); addEffectButton.setVisible(false); - listBox.setVisible(false); } else { // Reserve space at bottom for the add button auto addBtnHeight = 44; auto listArea = area; auto buttonArea = listArea.removeFromBottom(addBtnHeight); listBox.setBounds(listArea); - listBox.setVisible(true); - grid.setVisible(false); - listBox.updateContent(); addEffectButton.setVisible(true); addEffectButton.setBounds(buttonArea.reduced(0, 4)); } diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index e1730c58..6e25aaec 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -38,21 +38,24 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse toggleableEffects.push_back(BitCrushEffect().build()); toggleableEffects.push_back(BulgeEffect().build()); - toggleableEffects.push_back(MultiplexEffect().build()); - toggleableEffects.push_back(KaleidoscopeEffect().build()); - toggleableEffects.push_back(BounceEffect().build()); toggleableEffects.push_back(VectorCancellingEffect().build()); toggleableEffects.push_back(RippleEffectApp().build()); toggleableEffects.push_back(RotateEffectApp().build()); toggleableEffects.push_back(TranslateEffectApp().build()); toggleableEffects.push_back(SwirlEffectApp().build()); toggleableEffects.push_back(SmoothEffect().build()); - toggleableEffects.push_back(TwistEffect().build()); toggleableEffects.push_back(DelayEffect().build()); toggleableEffects.push_back(DashedLineEffect(*this).build()); toggleableEffects.push_back(TraceEffect(*this).build()); toggleableEffects.push_back(WobbleEffect(*this).build()); +#if OSCI_PREMIUM + toggleableEffects.push_back(MultiplexEffect(*this).build()); + toggleableEffects.push_back(KaleidoscopeEffect(*this).build()); + toggleableEffects.push_back(BounceEffect().build()); + toggleableEffects.push_back(TwistEffect().build()); +#endif + auto scaleEffect = ScaleEffectApp().build(); booleanParameters.push_back(scaleEffect->linked); toggleableEffects.push_back(scaleEffect); diff --git a/Source/audio/KaleidoscopeEffect.h b/Source/audio/KaleidoscopeEffect.h index 35a43925..2ac29c5a 100644 --- a/Source/audio/KaleidoscopeEffect.h +++ b/Source/audio/KaleidoscopeEffect.h @@ -8,11 +8,12 @@ class KaleidoscopeEffect : public osci::EffectApplication { public: - osci::Point apply(int /*index*/, osci::Point input, const std::vector>& values, double /*sampleRate*/) override { + explicit KaleidoscopeEffect(OscirenderAudioProcessor &p) : audioProcessor(p) {} + + osci::Point apply(int /*index*/, osci::Point input, const std::vector>& 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 - double phase = values.size() > 1 ? values[1].load() : 0.0; // Polar conversion double r = std::sqrt(input.x * input.x + input.y * input.y); @@ -22,6 +23,8 @@ public: int fullSegments = (int)std::floor(segments); double fractionalPart = segments - fullSegments; // in [0,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); @@ -53,7 +56,7 @@ public: std::shared_ptr build() const override { auto eff = std::make_shared( - std::make_shared(), + std::make_shared(audioProcessor), std::vector{ new osci::EffectParameter( "Kaleidoscope Segments", @@ -67,22 +70,14 @@ public: osci::LfoType::Sine, 0.25f // LFO frequency (Hz) – slow, visible rotation ), - new osci::EffectParameter( - "Kaleidoscope Phase", - "Selects which kaleidoscope segment is currently being drawn (time-multiplexed). Animate to sweep around the circle.", - "kaleidoscopePhase", - VERSION_HINT, - 0.0, // default - 0.0, // min - 1.0, // max - 0.0001f, // step - osci::LfoType::Sawtooth, - 55.0f // LFO frequency (Hz) – slow, visible rotation - ), } ); eff->setName("Kaleidoscope"); eff->setIcon(BinaryData::kaleidoscope_svg); return eff; } + +private: + OscirenderAudioProcessor &audioProcessor; + double phase = 0.0; }; diff --git a/Source/audio/MultiplexEffect.h b/Source/audio/MultiplexEffect.h index e4891dcb..3df402e8 100644 --- a/Source/audio/MultiplexEffect.h +++ b/Source/audio/MultiplexEffect.h @@ -1,65 +1,67 @@ #pragma once #include #include +#include +#include "../PluginProcessor.h" class MultiplexEffect : public osci::EffectApplication { public: + explicit MultiplexEffect(OscirenderAudioProcessor &p) : audioProcessor(p) {} + osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { - jassert(values.size() >= 6); - + 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 interpolation = values[3].load(); - double phase = values[4].load(); double gridDelay = values[5].load(); - + head++; - - if (head >= buffer.size()) { + if (head >= (int)buffer.size()) { head = 0; } - 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)); - + gridFloor.x = std::max(gridFloor.x, 1.0); gridFloor.y = std::max(gridFloor.y, 1.0); gridFloor.z = std::max(gridFloor.z, 1.0); - + double totalPositions = gridFloor.x * gridFloor.y * gridFloor.z; double position = phase * totalPositions; double delayPosition = static_cast(position) / totalPositions; - + + phase = nextPhase(audioProcessor.frequency / totalPositions, sampleRate) / (2.0 * std::numbers::pi); + int delayedIndex = head - static_cast(delayPosition * gridDelay * sampleRate); if (delayedIndex < 0) { delayedIndex += buffer.size(); } osci::Point delayedInput = buffer[delayedIndex % buffer.size()]; - + osci::Point nextGrid = gridFloor + 1.0; - + osci::Point current = multiplex(delayedInput, position, gridFloor); osci::Point next = multiplex(delayedInput, position, nextGrid); - + // Calculate interpolation factors osci::Point gridDiff = grid - gridFloor; osci::Point interpolationFactor = gridDiff * interpolation; - + return (1.0 - interpolationFactor) * current + interpolationFactor * next; } std::shared_ptr build() const override { auto eff = std::make_shared( - std::make_shared(), + std::make_shared(audioProcessor), std::vector{ new osci::EffectParameter("Multiplex X", "Controls the horizontal grid size for the multiplex effect.", "multiplexGridX", VERSION_HINT, 2.0, 1.0, 8.0), new osci::EffectParameter("Multiplex Y", "Controls the vertical grid size for the multiplex effect.", "multiplexGridY", VERSION_HINT, 2.0, 1.0, 8.0), new osci::EffectParameter("Multiplex Z", "Controls the depth grid size for the multiplex effect.", "multiplexGridZ", VERSION_HINT, 1.0, 1.0, 8.0), new osci::EffectParameter("Multiplex Smooth", "Controls the smoothness of transitions between grid sizes.", "multiplexSmooth", VERSION_HINT, 0.0, 0.0, 1.0), - new osci::EffectParameter("Multiplex Phase", "Controls the current phase of the multiplex grid animation.", "gridPhase", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001f, osci::LfoType::Sawtooth, 55.0f), new osci::EffectParameter("Multiplex Delay", "Controls the delay of the audio samples used in the multiplex effect.", "gridDelay", VERSION_HINT, 0.0, 0.0, 1.0), } ); @@ -71,31 +73,33 @@ public: private: osci::Point multiplex(osci::Point point, double position, osci::Point grid) { osci::Point unit = 1.0 / grid; - + point *= unit; point.x = -point.x; point.y = -point.y; - + double xPosRaw = std::floor(position); double yPosRaw = std::floor(position / grid.x); double zPosRaw = std::floor(position / (grid.x * grid.y)); - + // Use fmod for positive modulo with doubles double xPos = std::fmod(std::fmod(xPosRaw, grid.x) + grid.x, grid.x); double yPos = std::fmod(std::fmod(yPosRaw, grid.y) + grid.y, grid.y); double zPos = std::fmod(std::fmod(zPosRaw, grid.z) + grid.z, grid.z); - + point.x -= (grid.x - 1.0) / grid.x; point.y += (grid.y - 1.0) / grid.y; point.z += (grid.z - 1.0) / grid.z; - + point.x += xPos * 2.0 * unit.x; point.y -= yPos * 2.0 * unit.y; point.z -= zPos * 2.0 * unit.z; - + return point; } - + + OscirenderAudioProcessor &audioProcessor; + double phase = 0.0; // Normalised 0..1 phase for multiplex traversal const static int MAX_DELAY = 192000 * 10; std::vector buffer = std::vector(MAX_DELAY); int head = 0; diff --git a/Source/components/ComponentList.h b/Source/components/ComponentList.h index 8f23a6dc..83ef83e5 100644 --- a/Source/components/ComponentList.h +++ b/Source/components/ComponentList.h @@ -1,5 +1,6 @@ #pragma once #include +#include "VListBox.h" // This class is a wrapper for a component that allows it to be used in a ListBox // Why is this needed?!?!?!?! @@ -19,20 +20,24 @@ private: std::shared_ptr component; }; -class ComponentListModel : public juce::ListBoxModel +class ComponentListModel : public VListBoxModel { public: - ComponentListModel() {} + ComponentListModel(int rowHeight) : rowHeight(rowHeight) {} ~ComponentListModel() override {} int getNumRows() override; void paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) override; juce::Component* refreshComponentForRow(int sliderNum, bool isRowSelected, juce::Component *existingComponentToUpdate) override; - + int getRowHeight(int rowNumber) override { + return rowHeight; + } + void addComponent(std::shared_ptr component) { components.push_back(component); } private: std::vector> components; + int rowHeight; }; diff --git a/Source/components/EffectsListComponent.cpp b/Source/components/EffectsListComponent.cpp index c225fb2d..d2c3d768 100644 --- a/Source/components/EffectsListComponent.cpp +++ b/Source/components/EffectsListComponent.cpp @@ -41,7 +41,6 @@ effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) { list.setColour(effectComponentBackgroundColourId, juce::Colours::transparentBlack.withAlpha(0.2f)); list.setModel(&listModel); - list.setRowHeight(ROW_HEIGHT); list.updateContent(); addAndMakeVisible(list); addAndMakeVisible(enabled); @@ -111,11 +110,9 @@ void EffectsListComponent::paintOverChildren(juce::Graphics& g) { g.setColour(juce::Colours::black.withAlpha(0.3f)); auto bounds = list.getBounds(); bounds.removeFromBottom(PADDING); - juce::Path path; - path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, false, true, false, true); if (!enabled.getToggleState()) { - g.fillPath(path); + g.fillRect(bounds); } } @@ -127,6 +124,7 @@ void EffectsListComponent::resized() { closeButton.setImageTransform(juce::AffineTransform::translation(0, -2)); leftBar.removeFromLeft(20); enabled.setBounds(leftBar.withSizeKeepingCentre(30, 20)); + // TODO: this is super slow list.setBounds(area); } diff --git a/Source/components/EffectsListComponent.h b/Source/components/EffectsListComponent.h index 39121605..bec56a56 100644 --- a/Source/components/EffectsListComponent.h +++ b/Source/components/EffectsListComponent.h @@ -187,8 +187,8 @@ public: protected: osci::Effect& effect; - ComponentListModel listModel; - juce::ListBox list; + ComponentListModel listModel { ROW_HEIGHT }; + VListBox list; jux::SwitchButton enabled = { effect.enabled }; SvgButton closeButton = SvgButton("closeEffect", juce::String::createStringFromData(BinaryData::close_svg, BinaryData::close_svgSize), juce::Colours::white, juce::Colours::white); private: diff --git a/osci-render.jucer b/osci-render.jucer index ae0d1a9e..98a4786c 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.0" companyName="James H Ball" companyWebsite="https://osci-render.com" + version="2.6.1.1" 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 "> diff --git a/sosci.jucer b/sosci.jucer index 3a16b97f..c57d36f8 100644 --- a/sosci.jucer +++ b/sosci.jucer @@ -3,7 +3,7 @@