Fix bugs, improve performance, and change how multiplex and kaleidoscope react to frequency

pull/320/head
James H Ball 2025-08-30 21:29:48 +01:00
rodzic 5da472ccd6
commit c052decbe9
9 zmienionych plików z 59 dodań i 59 usunięć

Wyświetl plik

@ -130,18 +130,13 @@ void EffectsComponent::resized() {
area.removeFromTop(6); area.removeFromTop(6);
if (showingGrid) { if (showingGrid) {
grid.setBounds(area); grid.setBounds(area);
grid.setVisible(true);
addEffectButton.setVisible(false); addEffectButton.setVisible(false);
listBox.setVisible(false);
} else { } else {
// Reserve space at bottom for the add button // Reserve space at bottom for the add button
auto addBtnHeight = 44; auto addBtnHeight = 44;
auto listArea = area; auto listArea = area;
auto buttonArea = listArea.removeFromBottom(addBtnHeight); auto buttonArea = listArea.removeFromBottom(addBtnHeight);
listBox.setBounds(listArea); listBox.setBounds(listArea);
listBox.setVisible(true);
grid.setVisible(false);
listBox.updateContent();
addEffectButton.setVisible(true); addEffectButton.setVisible(true);
addEffectButton.setBounds(buttonArea.reduced(0, 4)); addEffectButton.setBounds(buttonArea.reduced(0, 4));
} }

Wyświetl plik

@ -38,21 +38,24 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
toggleableEffects.push_back(BitCrushEffect().build()); toggleableEffects.push_back(BitCrushEffect().build());
toggleableEffects.push_back(BulgeEffect().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(VectorCancellingEffect().build());
toggleableEffects.push_back(RippleEffectApp().build()); toggleableEffects.push_back(RippleEffectApp().build());
toggleableEffects.push_back(RotateEffectApp().build()); toggleableEffects.push_back(RotateEffectApp().build());
toggleableEffects.push_back(TranslateEffectApp().build()); toggleableEffects.push_back(TranslateEffectApp().build());
toggleableEffects.push_back(SwirlEffectApp().build()); toggleableEffects.push_back(SwirlEffectApp().build());
toggleableEffects.push_back(SmoothEffect().build()); toggleableEffects.push_back(SmoothEffect().build());
toggleableEffects.push_back(TwistEffect().build());
toggleableEffects.push_back(DelayEffect().build()); toggleableEffects.push_back(DelayEffect().build());
toggleableEffects.push_back(DashedLineEffect(*this).build()); toggleableEffects.push_back(DashedLineEffect(*this).build());
toggleableEffects.push_back(TraceEffect(*this).build()); toggleableEffects.push_back(TraceEffect(*this).build());
toggleableEffects.push_back(WobbleEffect(*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(); auto scaleEffect = ScaleEffectApp().build();
booleanParameters.push_back(scaleEffect->linked); booleanParameters.push_back(scaleEffect->linked);
toggleableEffects.push_back(scaleEffect); toggleableEffects.push_back(scaleEffect);

Wyświetl plik

@ -8,11 +8,12 @@
class KaleidoscopeEffect : public osci::EffectApplication { class KaleidoscopeEffect : public osci::EffectApplication {
public: public:
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override { explicit 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[0] = segments (can be fractional)
// values[1] = phase (0-1) selecting which segment is currently being drawn // 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 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 // Polar conversion
double r = std::sqrt(input.x * input.x + input.y * input.y); double r = std::sqrt(input.x * input.x + input.y * input.y);
@ -22,6 +23,8 @@ public:
int fullSegments = (int)std::floor(segments); int fullSegments = (int)std::floor(segments);
double fractionalPart = segments - fullSegments; // in [0,1) 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. // Use 'segments' for timing so partial segment gets proportionally shorter time.
double currentSegmentFloat = phase * segments; // [0, segments) double currentSegmentFloat = phase * segments; // [0, segments)
int currentSegmentIndex = (int)std::floor(currentSegmentFloat); int currentSegmentIndex = (int)std::floor(currentSegmentFloat);
@ -53,7 +56,7 @@ public:
std::shared_ptr<osci::Effect> build() const override { std::shared_ptr<osci::Effect> build() const override {
auto eff = std::make_shared<osci::Effect>( auto eff = std::make_shared<osci::Effect>(
std::make_shared<KaleidoscopeEffect>(), std::make_shared<KaleidoscopeEffect>(audioProcessor),
std::vector<osci::EffectParameter*>{ std::vector<osci::EffectParameter*>{
new osci::EffectParameter( new osci::EffectParameter(
"Kaleidoscope Segments", "Kaleidoscope Segments",
@ -67,22 +70,14 @@ public:
osci::LfoType::Sine, osci::LfoType::Sine,
0.25f // LFO frequency (Hz) – slow, visible rotation 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->setName("Kaleidoscope");
eff->setIcon(BinaryData::kaleidoscope_svg); eff->setIcon(BinaryData::kaleidoscope_svg);
return eff; return eff;
} }
private:
OscirenderAudioProcessor &audioProcessor;
double phase = 0.0;
}; };

Wyświetl plik

@ -1,25 +1,26 @@
#pragma once #pragma once
#include <JuceHeader.h> #include <JuceHeader.h>
#include <cmath> #include <cmath>
#include <numbers>
#include "../PluginProcessor.h"
class MultiplexEffect : public osci::EffectApplication { class MultiplexEffect : public osci::EffectApplication {
public: public:
explicit MultiplexEffect(OscirenderAudioProcessor &p) : audioProcessor(p) {}
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override { osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
jassert(values.size() >= 6); jassert(values.size() >= 5);
double gridX = values[0].load() + 0.0001; double gridX = values[0].load() + 0.0001;
double gridY = values[1].load() + 0.0001; double gridY = values[1].load() + 0.0001;
double gridZ = values[2].load() + 0.0001; double gridZ = values[2].load() + 0.0001;
double interpolation = values[3].load(); double interpolation = values[3].load();
double phase = values[4].load();
double gridDelay = values[5].load(); double gridDelay = values[5].load();
head++; head++;
if (head >= (int)buffer.size()) {
if (head >= buffer.size()) {
head = 0; head = 0;
} }
buffer[head] = input; buffer[head] = input;
osci::Point grid = osci::Point(gridX, gridY, gridZ); osci::Point grid = osci::Point(gridX, gridY, gridZ);
@ -33,6 +34,8 @@ public:
double position = phase * totalPositions; double position = phase * totalPositions;
double delayPosition = static_cast<int>(position) / totalPositions; double delayPosition = static_cast<int>(position) / totalPositions;
phase = nextPhase(audioProcessor.frequency / totalPositions, sampleRate) / (2.0 * std::numbers::pi);
int delayedIndex = head - static_cast<int>(delayPosition * gridDelay * sampleRate); int delayedIndex = head - static_cast<int>(delayPosition * gridDelay * sampleRate);
if (delayedIndex < 0) { if (delayedIndex < 0) {
delayedIndex += buffer.size(); delayedIndex += buffer.size();
@ -53,13 +56,12 @@ public:
std::shared_ptr<osci::Effect> build() const override { std::shared_ptr<osci::Effect> build() const override {
auto eff = std::make_shared<osci::Effect>( auto eff = std::make_shared<osci::Effect>(
std::make_shared<MultiplexEffect>(), std::make_shared<MultiplexEffect>(audioProcessor),
std::vector<osci::EffectParameter*>{ std::vector<osci::EffectParameter*>{
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 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 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 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 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), 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),
} }
); );
@ -96,6 +98,8 @@ private:
return point; return point;
} }
OscirenderAudioProcessor &audioProcessor;
double phase = 0.0; // Normalised 0..1 phase for multiplex traversal
const static int MAX_DELAY = 192000 * 10; const static int MAX_DELAY = 192000 * 10;
std::vector<osci::Point> buffer = std::vector<osci::Point>(MAX_DELAY); std::vector<osci::Point> buffer = std::vector<osci::Point>(MAX_DELAY);
int head = 0; int head = 0;

Wyświetl plik

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <JuceHeader.h> #include <JuceHeader.h>
#include "VListBox.h"
// This class is a wrapper for a component that allows it to be used in a ListBox // This class is a wrapper for a component that allows it to be used in a ListBox
// Why is this needed?!?!?!?! // Why is this needed?!?!?!?!
@ -19,15 +20,18 @@ private:
std::shared_ptr<juce::Component> component; std::shared_ptr<juce::Component> component;
}; };
class ComponentListModel : public juce::ListBoxModel class ComponentListModel : public VListBoxModel
{ {
public: public:
ComponentListModel() {} ComponentListModel(int rowHeight) : rowHeight(rowHeight) {}
~ComponentListModel() override {} ~ComponentListModel() override {}
int getNumRows() override; int getNumRows() override;
void paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) 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; juce::Component* refreshComponentForRow(int sliderNum, bool isRowSelected, juce::Component *existingComponentToUpdate) override;
int getRowHeight(int rowNumber) override {
return rowHeight;
}
void addComponent(std::shared_ptr<juce::Component> component) { void addComponent(std::shared_ptr<juce::Component> component) {
components.push_back(component); components.push_back(component);
@ -35,4 +39,5 @@ public:
private: private:
std::vector<std::shared_ptr<juce::Component>> components; std::vector<std::shared_ptr<juce::Component>> components;
int rowHeight;
}; };

Wyświetl plik

@ -41,7 +41,6 @@ effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) {
list.setColour(effectComponentBackgroundColourId, juce::Colours::transparentBlack.withAlpha(0.2f)); list.setColour(effectComponentBackgroundColourId, juce::Colours::transparentBlack.withAlpha(0.2f));
list.setModel(&listModel); list.setModel(&listModel);
list.setRowHeight(ROW_HEIGHT);
list.updateContent(); list.updateContent();
addAndMakeVisible(list); addAndMakeVisible(list);
addAndMakeVisible(enabled); addAndMakeVisible(enabled);
@ -111,11 +110,9 @@ void EffectsListComponent::paintOverChildren(juce::Graphics& g) {
g.setColour(juce::Colours::black.withAlpha(0.3f)); g.setColour(juce::Colours::black.withAlpha(0.3f));
auto bounds = list.getBounds(); auto bounds = list.getBounds();
bounds.removeFromBottom(PADDING); 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()) { if (!enabled.getToggleState()) {
g.fillPath(path); g.fillRect(bounds);
} }
} }
@ -127,6 +124,7 @@ void EffectsListComponent::resized() {
closeButton.setImageTransform(juce::AffineTransform::translation(0, -2)); closeButton.setImageTransform(juce::AffineTransform::translation(0, -2));
leftBar.removeFromLeft(20); leftBar.removeFromLeft(20);
enabled.setBounds(leftBar.withSizeKeepingCentre(30, 20)); enabled.setBounds(leftBar.withSizeKeepingCentre(30, 20));
// TODO: this is super slow
list.setBounds(area); list.setBounds(area);
} }

Wyświetl plik

@ -187,8 +187,8 @@ public:
protected: protected:
osci::Effect& effect; osci::Effect& effect;
ComponentListModel listModel; ComponentListModel listModel { ROW_HEIGHT };
juce::ListBox list; VListBox list;
jux::SwitchButton enabled = { effect.enabled }; jux::SwitchButton enabled = { effect.enabled };
SvgButton closeButton = SvgButton("closeEffect", juce::String::createStringFromData(BinaryData::close_svg, BinaryData::close_svgSize), juce::Colours::white, juce::Colours::white); SvgButton closeButton = SvgButton("closeEffect", juce::String::createStringFromData(BinaryData::close_svg, BinaryData::close_svgSize), juce::Colours::white, juce::Colours::white);
private: private:

Wyświetl plik

@ -4,7 +4,7 @@
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" pluginCharacteristicsValue="pluginWantsMidiIn" addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" pluginCharacteristicsValue="pluginWantsMidiIn"
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender" pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
cppLanguageStandard="20" projectLineFeed="&#10;" headerPath="./include" cppLanguageStandard="20" projectLineFeed="&#10;" 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&#10;INTERNET_FLAG_NO_AUTO_REDIRECT=0&#10;OSCI_PREMIUM=1&#10;JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1&#10;JUCE_MODAL_LOOPS_PERMITTED=1" companyEmail="james@ball.sh" defines="NOMINMAX=1&#10;INTERNET_FLAG_NO_AUTO_REDIRECT=0&#10;OSCI_PREMIUM=1&#10;JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1&#10;JUCE_MODAL_LOOPS_PERMITTED=1"
pluginAUMainType="'aumf'" postExportShellCommandPosix="echo &quot;Building LuaJIT for $OSTYPE...&quot; &amp;&amp; DIR=%%1%% %%1%%/luajit_linux_macos.sh "> pluginAUMainType="'aumf'" postExportShellCommandPosix="echo &quot;Building LuaJIT for $OSTYPE...&quot; &amp;&amp; DIR=%%1%% %%1%%/luajit_linux_macos.sh ">
<MAINGROUP id="j5Ge2T" name="osci-render"> <MAINGROUP id="j5Ge2T" name="osci-render">

Wyświetl plik

@ -3,7 +3,7 @@
<JUCERPROJECT id="HH2E72" name="sosci" projectType="audioplug" useAppConfig="0" <JUCERPROJECT id="HH2E72" name="sosci" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" pluginManufacturer="jameshball" addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" pluginManufacturer="jameshball"
aaxIdentifier="sh.ball.sosci" cppLanguageStandard="20" projectLineFeed="&#10;" aaxIdentifier="sh.ball.sosci" cppLanguageStandard="20" projectLineFeed="&#10;"
headerPath="./include" version="1.1.8.2" companyName="James H Ball" headerPath="./include" version="1.1.8.3" companyName="James H Ball"
companyWebsite="https://osci-render.com" companyEmail="james@ball.sh" companyWebsite="https://osci-render.com" companyEmail="james@ball.sh"
defines="NOMINMAX=1&#10;INTERNET_FLAG_NO_AUTO_REDIRECT=0&#10;OSCI_PREMIUM=1&#10;JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1&#10;JUCE_MODAL_LOOPS_PERMITTED=1" defines="NOMINMAX=1&#10;INTERNET_FLAG_NO_AUTO_REDIRECT=0&#10;OSCI_PREMIUM=1&#10;JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1&#10;JUCE_MODAL_LOOPS_PERMITTED=1"
pluginManufacturerCode="Jhba" pluginCode="Sosc" pluginAUMainType="'aufx'"> pluginManufacturerCode="Jhba" pluginCode="Sosc" pluginAUMainType="'aufx'">