From c98e32e0cfb279b1f091dc93a2f98b136c05335a Mon Sep 17 00:00:00 2001 From: James H Ball Date: Sat, 10 May 2025 15:36:38 +0100 Subject: [PATCH 1/7] Add slider with a label that is rendered using the visualiser --- Source/EffectPluginEditor.cpp | 35 +++- Source/EffectPluginEditor.h | 11 +- Source/EffectPluginProcessor.cpp | 63 +++----- Source/EffectPluginProcessor.h | 15 +- Source/audio/ShapeVectorRenderer.cpp | 77 +++++++++ Source/audio/ShapeVectorRenderer.h | 35 ++++ .../components/SliderVisualiserComponent.cpp | 149 ++++++++++++++++++ Source/components/SliderVisualiserComponent.h | 52 ++++++ osci-bitcrush.jucer | 8 + 9 files changed, 379 insertions(+), 66 deletions(-) create mode 100644 Source/audio/ShapeVectorRenderer.cpp create mode 100644 Source/audio/ShapeVectorRenderer.h create mode 100644 Source/components/SliderVisualiserComponent.cpp create mode 100644 Source/components/SliderVisualiserComponent.h diff --git a/Source/EffectPluginEditor.cpp b/Source/EffectPluginEditor.cpp index 9dfe182..d5aada0 100644 --- a/Source/EffectPluginEditor.cpp +++ b/Source/EffectPluginEditor.cpp @@ -17,19 +17,30 @@ EffectPluginEditor::EffectPluginEditor(EffectAudioProcessor& p) addAndMakeVisible(visualiser); addAndMakeVisible(titleVisualiser); - addAndMakeVisible(bitCrush); + addAndMakeVisible(sliderVisualiser); titleVisualiser.setCropRectangle(juce::Rectangle(-0.1f, 0.35f, 1.2f, 0.3f)); + // Configure the slider visualiser component + sliderVisualiser.onValueChange([this]() { + audioProcessor.bitCrush->parameters[0]->setUnnormalisedValueNotifyingHost(sliderVisualiser.getValue()); + }); - bitCrush.slider.onValueChange = [this] { - audioProcessor.bitCrush->parameters[0]->setUnnormalisedValueNotifyingHost(bitCrush.slider.getValue()); - }; + // Set the label for the slider + sliderVisualiser.setLabel("bit crush"); setSize(600, 200); setResizable(false, false); tooltipDropShadow.setOwner(&tooltipWindow.get()); tooltipWindow->setMillisecondsBeforeTipAppears(0); + + audioProcessor.bitCrush->addListener(0, this); +} + +EffectPluginEditor::~EffectPluginEditor() { + audioProcessor.bitCrush->removeListener(0, this); + setLookAndFeel(nullptr); + juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr); } void EffectPluginEditor::resized() { @@ -40,10 +51,18 @@ void EffectPluginEditor::resized() { auto titleBounds = bounds.removeFromTop(100); titleVisualiser.setBounds(titleBounds); - bitCrush.setBounds(bounds); + // Set bounds for sliderVisualiser + sliderVisualiser.setBounds(bounds); } -EffectPluginEditor::~EffectPluginEditor() { - setLookAndFeel(nullptr); - juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr); +void EffectPluginEditor::parameterValueChanged(int parameterIndex, float newValue) { + if (parameterIndex == 0) { + juce::MessageManager::getInstance()->callAsync([this, newValue]() { + sliderVisualiser.setValue(newValue); + // Update visualizer directly as setValue doesn't always trigger onChange + sliderVisualiser.updateVisualiser(); + }); + } } + +void EffectPluginEditor::parameterGestureChanged(int parameterIndex, bool gestureIsStarting) {} diff --git a/Source/EffectPluginEditor.h b/Source/EffectPluginEditor.h index 2b1868b..3b6f869 100644 --- a/Source/EffectPluginEditor.h +++ b/Source/EffectPluginEditor.h @@ -5,13 +5,16 @@ #include "visualiser/VisualiserRenderer.h" #include "LookAndFeel.h" #include "components/EffectComponent.h" +#include "components/SliderVisualiserComponent.h" -class EffectPluginEditor : public juce::AudioProcessorEditor { +class EffectPluginEditor : public juce::AudioProcessorEditor, public juce::AudioProcessorParameter::Listener { public: EffectPluginEditor(EffectAudioProcessor&); ~EffectPluginEditor() override; void resized() override; + void parameterValueChanged(int parameterIndex, float newValue) override; + void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override; private: EffectAudioProcessor& audioProcessor; @@ -35,11 +38,13 @@ public: "Title" }; + SliderVisualiserComponent sliderVisualiser{ + audioProcessor + }; + juce::SharedResourcePointer tooltipWindow; juce::DropShadower tooltipDropShadow{juce::DropShadow(juce::Colours::black.withAlpha(0.5f), 6, {0,0})}; - EffectComponent bitCrush{*audioProcessor.bitCrush}; - #if JUCE_LINUX juce::OpenGLContext openGlContext; #endif diff --git a/Source/EffectPluginProcessor.cpp b/Source/EffectPluginProcessor.cpp index 87fdf5c..20170a5 100644 --- a/Source/EffectPluginProcessor.cpp +++ b/Source/EffectPluginProcessor.cpp @@ -26,8 +26,11 @@ EffectAudioProcessor::EffectAudioProcessor() } } - titleShapes = titleParser.draw(); - titleShapesLength = osci::Shape::totalLength(titleShapes); + // Initialize title shapes + juce::Font titleFont = juce::Font(1.0f, juce::Font::bold); + TextParser titleParser{"bit crush", titleFont}; + auto titleShapes = titleParser.draw(); + titleRenderer.setShapes(std::move(titleShapes)); } const juce::String EffectAudioProcessor::getName() const { @@ -86,6 +89,7 @@ void EffectAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) } currentSampleRate = sampleRate; + titleRenderer.setSampleRate(sampleRate); threadManager.prepare(sampleRate, samplesPerBlock); } @@ -126,56 +130,25 @@ void EffectAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce:: if (output.getNumChannels() > 1) { outputArray[1][sample] = point.y; } + // handle the title drawing + osci::Point titlePoint = titleRenderer.nextVector(); - // handle the title drawing - - osci::Point titlePoint = { 0.0, 0.0, 1.0f }; - if (currentTitleShape < titleShapes.size()) { - auto& shape = titleShapes[currentTitleShape]; - double length = shape->length(); - double drawingProgress = length == 0.0 ? 1 : titleShapeDrawn / length; - titlePoint = shape->nextVector(drawingProgress); - titlePoint.z = 1.0f; - - // apply bit crush without animating values, as this has already been done - titlePoint = bitCrush->apply(sample, titlePoint, 0.0, false); - } - + // apply bit crush without animating values, as this has already been done + titlePoint = bitCrush->apply(sample, titlePoint, 0.0, false); + threadManager.write(titlePoint, "VisualiserRendererTitle"); - - incrementTitleShapeDrawing(); - - if (titleFrameDrawn >= titleShapesLength) { - double currentShapeLength = 0; - if (currentTitleShape < titleShapes.size()) { - currentShapeLength = titleShapes[currentTitleShape]->len; - } - titleFrameDrawn -= titleShapesLength; - currentTitleShape = 0; + + osci::Point sliderPoint; + { + juce::SpinLock::ScopedLockType lock(sliderLock); + sliderPoint = sliderRenderer.nextVector(); } + + threadManager.write(sliderPoint, "VisualiserRendererSlider"); } } -void EffectAudioProcessor::incrementTitleShapeDrawing() { - if (titleShapes.size() <= 0) return; - double length = currentTitleShape < titleShapes.size() ? titleShapes[currentTitleShape]->len : 0.0; - double FREQUENCY = 60.0; - double lengthIncrement = titleShapesLength / (currentSampleRate / FREQUENCY); - titleFrameDrawn += lengthIncrement; - titleShapeDrawn += lengthIncrement; - // Need to skip all shapes that the lengthIncrement draws over. - // This is especially an issue when there are lots of small lines being - // drawn. - while (titleShapeDrawn > length) { - titleShapeDrawn -= length; - currentTitleShape++; - if (currentTitleShape >= titleShapes.size()) { - currentTitleShape = 0; - } - length = titleShapes[currentTitleShape]->len; - } -} void EffectAudioProcessor::getStateInformation(juce::MemoryBlock& destData) { std::unique_ptr xml = std::make_unique("project"); diff --git a/Source/EffectPluginProcessor.h b/Source/EffectPluginProcessor.h index 132a60e..c1f6e6d 100644 --- a/Source/EffectPluginProcessor.h +++ b/Source/EffectPluginProcessor.h @@ -6,6 +6,7 @@ #include "audio/BitCrushEffect.h" #include "audio/AutoGainControlEffect.h" #include "txt/TextParser.h" +#include "audio/ShapeVectorRenderer.h" class EffectAudioProcessor : public juce::AudioProcessor #if JucePlugin_Enable_ARA @@ -56,6 +57,9 @@ public: VisualiserParameters visualiserParameters; osci::AudioBackgroundThreadManager threadManager; + + juce::SpinLock sliderLock; + ShapeVectorRenderer sliderRenderer; protected: @@ -64,16 +68,7 @@ protected: private: double currentSampleRate = 44100.0; - // variables for the title - juce::Font titleFont = juce::Font(1.0f, juce::Font::bold); - TextParser titleParser{"bit crush", titleFont}; - std::vector> titleShapes; - double titleShapesLength = 0.0; - int currentTitleShape = 0; - double titleShapeDrawn = 0.0; - double titleFrameDrawn = 0.0; - - void incrementTitleShapeDrawing(); + ShapeVectorRenderer titleRenderer; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EffectAudioProcessor) diff --git a/Source/audio/ShapeVectorRenderer.cpp b/Source/audio/ShapeVectorRenderer.cpp new file mode 100644 index 0000000..477c790 --- /dev/null +++ b/Source/audio/ShapeVectorRenderer.cpp @@ -0,0 +1,77 @@ +#include "ShapeVectorRenderer.h" + +ShapeVectorRenderer::ShapeVectorRenderer(double sampleRate, double frequency) + : currentSampleRate(sampleRate), frequency(frequency) { +} + +void ShapeVectorRenderer::setShapes(std::vector> newShapes) { + shapes.clear(); + + // Move the shapes from the input vector to our internal vector + for (auto& shape : newShapes) { + shapes.push_back(std::move(shape)); + } + + // Calculate the total length of all shapes + shapesLength = osci::Shape::totalLength(shapes); + + // Reset the drawing state + currentShape = 0; + shapeDrawn = 0.0; + frameDrawn = 0.0; +} + +osci::Point ShapeVectorRenderer::nextVector() { + osci::Point point = { 0.0, 0.0, 1.0f }; + + if (shapes.size() <= 0) { + return point; + } + + if (currentShape < shapes.size()) { + auto& shape = shapes[currentShape]; + double length = shape->length(); + double drawingProgress = length == 0.0 ? 1.0 : shapeDrawn / length; + point = shape->nextVector(drawingProgress); + point.z = 1.0f; + } + + incrementShapeDrawing(); + + if (frameDrawn >= shapesLength) { + frameDrawn -= shapesLength; + currentShape = 0; + } + + return point; +} + +void ShapeVectorRenderer::setSampleRate(double newSampleRate) { + currentSampleRate = newSampleRate; +} + +void ShapeVectorRenderer::setFrequency(double newFrequency) { + frequency = newFrequency; +} + +void ShapeVectorRenderer::incrementShapeDrawing() { + if (shapes.size() <= 0) return; + + double length = currentShape < shapes.size() ? shapes[currentShape]->len : 0.0; + double lengthIncrement = shapesLength / (currentSampleRate / frequency); + + frameDrawn += lengthIncrement; + shapeDrawn += lengthIncrement; + + // Need to skip all shapes that the lengthIncrement draws over. + // This is especially an issue when there are lots of small lines being + // drawn. + while (shapeDrawn > length) { + shapeDrawn -= length; + currentShape++; + if (currentShape >= shapes.size()) { + currentShape = 0; + } + length = shapes[currentShape]->len; + } +} diff --git a/Source/audio/ShapeVectorRenderer.h b/Source/audio/ShapeVectorRenderer.h new file mode 100644 index 0000000..510558f --- /dev/null +++ b/Source/audio/ShapeVectorRenderer.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include "../visualiser/VisualiserParameters.h" + +class ShapeVectorRenderer { +public: + ShapeVectorRenderer(double sampleRate = 44100.0, double frequency = 60.0); + + // Set new shapes to render + void setShapes(std::vector> newShapes); + + // Get the next point in the shape vector sequence + osci::Point nextVector(); + + // Update sample rate if it changes + void setSampleRate(double newSampleRate); + + // Set the frequency at which shapes should be drawn + void setFrequency(double newFrequency); + +private: + double currentSampleRate = 44100.0; + double frequency = 60.0; + + std::vector> shapes; + double shapesLength = 0.0; + int currentShape = 0; + double shapeDrawn = 0.0; + double frameDrawn = 0.0; + + void incrementShapeDrawing(); +}; diff --git a/Source/components/SliderVisualiserComponent.cpp b/Source/components/SliderVisualiserComponent.cpp new file mode 100644 index 0000000..d9008cd --- /dev/null +++ b/Source/components/SliderVisualiserComponent.cpp @@ -0,0 +1,149 @@ +#include "SliderVisualiserComponent.h" + +SliderVisualiserComponent::SliderVisualiserComponent(EffectAudioProcessor& processor) + : audioProcessor(processor) +{ + sliderVisualiser.setCropRectangle(juce::Rectangle(0.0f, 0.35f, 1.0f, 0.25f)); + sliderVisualiser.setInterceptsMouseClicks(false, false); + + // Configure slider to be invisible but functional + slider.setLookAndFeel(nullptr); // Remove any look and feel + slider.setColour(juce::Slider::trackColourId, juce::Colours::transparentBlack); + slider.setColour(juce::Slider::backgroundColourId, juce::Colours::transparentBlack); + slider.setColour(juce::Slider::thumbColourId, juce::Colours::transparentBlack); + slider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentBlack); + slider.setColour(juce::Slider::textBoxTextColourId, juce::Colours::transparentBlack); + slider.setColour(juce::Slider::textBoxBackgroundColourId, juce::Colours::transparentBlack); + slider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0); + slider.setRange(0.0, 1.0, 0.001); + + // Setup default callback + slider.onValueChange = [this] { updateVisualiser(); }; + + // Set default label + setLabel("Param"); + + addAndMakeVisible(sliderVisualiser); + addAndMakeVisible(slider); +} + +SliderVisualiserComponent::~SliderVisualiserComponent() +{ +} + +void SliderVisualiserComponent::resized() +{ + auto bounds = getLocalBounds(); + sliderVisualiser.setBounds(bounds); + + // Scale down the slider to match the visual representation + // It should only occupy the portion where the slider visuals appear (not the label area) + int labelWidth = bounds.getWidth() * labelWidthProportion; + auto sliderBounds = bounds.withTrimmedLeft(labelWidth - 5); + slider.setBounds(sliderBounds.withTrimmedRight(10)); // Add some padding on the right +} + +void SliderVisualiserComponent::setValue(double newValue) +{ + slider.setValue(newValue); +} + +double SliderVisualiserComponent::getValue() const +{ + return slider.getValue(); +} + +void SliderVisualiserComponent::setRange(double newMinimum, double newMaximum, double newInterval) +{ + slider.setRange(newMinimum, newMaximum, newInterval); +} + +void SliderVisualiserComponent::addListener(juce::Slider::Listener* listener) +{ + slider.addListener(listener); +} + +void SliderVisualiserComponent::removeListener(juce::Slider::Listener* listener) +{ + slider.removeListener(listener); +} + +void SliderVisualiserComponent::onValueChange(std::function callback) +{ + slider.onValueChange = [this, callback]() { + updateVisualiser(); + callback(); + }; +} + +void SliderVisualiserComponent::setLabel(const juce::String& labelText) +{ + // Only update if the label has changed + if (label != labelText) { + label = labelText; + updateLabelShapes(); + updateVisualiser(); + } +} + +void SliderVisualiserComponent::updateLabelShapes() +{ + // Create text shapes using TextParser + if (label.isNotEmpty()) { + TextParser labelParser{label, labelFont}; + labelShapes = labelParser.draw(); + + // Calculate the center of the label area + double labelCenter = -1.0 + (labelWidthProportion * 1.0); // Center of the label area in oscilloscope space + + // Scale and position the label shapes to appear on the left side + for (auto& shape : labelShapes) { + // Scale down the text to fit nicely + shape->scale(0.3, 0.3, 1.0); + // Position centered in the label area + shape->translate(labelCenter, 0.0, 0.0); + } + } else { + labelShapes.clear(); + } +} + +void SliderVisualiserComponent::updateVisualiser() +{ + // Create shapes for the slider indicator + std::vector> combinedShapes; + + // First add the label shapes + for (auto& shape : labelShapes) { + combinedShapes.push_back(shape->clone()); + } + + // Calculate the visualizer coordinates that exactly match our component layout + // Convert component coordinates to oscilloscope coordinates (-1.0 to 1.0) + double sliderStart = -1.0 + (labelWidthProportion * 2.0); // Scaled from component space to oscilloscope space + double sliderEnd = 1.0; + double sliderRange = sliderEnd - sliderStart; + + double value = slider.getValue(); + double circleRadius = 0.07; + + // Map the normalized slider value (0-1) to our visual slider range + double xPos = sliderStart + (value * sliderRange); + + // Add the left line only if there's space for it (circle not at left edge) + if (xPos - circleRadius > sliderStart) { + combinedShapes.push_back(std::make_unique(sliderStart, 0.0, xPos - circleRadius, 0.0)); // Left line + } + + // Add the right line only if there's space for it (circle not at right edge) + if (xPos + circleRadius < sliderEnd) { + combinedShapes.push_back(std::make_unique(xPos + circleRadius, 0.0, sliderEnd, 0.0)); // Right line + } + + // Add circle + combinedShapes.push_back(std::make_unique(xPos, 0.0, circleRadius, circleRadius, 0.0, 2.0 * M_PI)); + + juce::SpinLock::ScopedLockType lock(audioProcessor.sliderLock); + // Set the shapes in the renderer + audioProcessor.sliderRenderer.setShapes(std::move(combinedShapes)); +} diff --git a/Source/components/SliderVisualiserComponent.h b/Source/components/SliderVisualiserComponent.h new file mode 100644 index 0000000..208421d --- /dev/null +++ b/Source/components/SliderVisualiserComponent.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include "../visualiser/VisualiserRenderer.h" +#include "../EffectPluginProcessor.h" +#include "../txt/TextParser.h" + +class SliderVisualiserComponent : public juce::Component +{ +public: + SliderVisualiserComponent(EffectAudioProcessor& processor); + ~SliderVisualiserComponent() override; + + void resized() override; + void setValue(double newValue); + double getValue() const; + + // Forwards to underlying slider + void setRange(double newMinimum, double newMaximum, double newInterval = 0.0); + void addListener(juce::Slider::Listener* listener); + void removeListener(juce::Slider::Listener* listener); + void onValueChange(std::function callback); + + // Set the label text to be displayed next to the slider + void setLabel(const juce::String& labelText); + + // Update the visualiser display + void updateVisualiser(); + +private: + EffectAudioProcessor& audioProcessor; + + VisualiserRenderer sliderVisualiser{ + audioProcessor.visualiserParameters, + audioProcessor.threadManager, + 512, + 60.0, + "Slider" + }; + + juce::Slider slider; + juce::String label; + std::vector> labelShapes; + juce::Font labelFont{0.7f}; + + // Proportion of the width allocated for the label + const double labelWidthProportion = 0.35; + + void updateLabelShapes(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SliderVisualiserComponent) +}; diff --git a/osci-bitcrush.jucer b/osci-bitcrush.jucer index 7dfc7e4..4cb31bc 100644 --- a/osci-bitcrush.jucer +++ b/osci-bitcrush.jucer @@ -81,6 +81,10 @@ + + @@ -93,6 +97,10 @@ file="Source/components/MainMenuBarModel.cpp"/> + + From ce5f735271678cac38de290b3489de823312392d Mon Sep 17 00:00:00 2001 From: James H Ball Date: Sat, 10 May 2025 15:59:12 +0100 Subject: [PATCH 2/7] Make the custom syphon/spout name visible and fix the double text boxes --- Source/CommonPluginEditor.cpp | 2 +- Source/CommonPluginEditor.h | 2 +- Source/FrameSettingsComponent.cpp | 8 ++++++++ Source/components/DoubleTextBox.h | 1 + osci-render.jucer | 2 +- sosci.jucer | 2 +- 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/CommonPluginEditor.cpp b/Source/CommonPluginEditor.cpp index 8209474..828d3f2 100644 --- a/Source/CommonPluginEditor.cpp +++ b/Source/CommonPluginEditor.cpp @@ -45,7 +45,7 @@ CommonPluginEditor::CommonPluginEditor(CommonAudioProcessor& p, juce::String app visualiserSettings.setColour(juce::ResizableWindow::backgroundColourId, Colours::dark); recordingSettings.setLookAndFeel(&getLookAndFeel()); - recordingSettings.setSize(300, 280); + recordingSettings.setSize(300, 330); #if JUCE_WINDOWS // if not standalone, use native title bar for compatibility with DAWs recordingSettingsWindow.setUsingNativeTitleBar(processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone); diff --git a/Source/CommonPluginEditor.h b/Source/CommonPluginEditor.h index adff077..4576e92 100644 --- a/Source/CommonPluginEditor.h +++ b/Source/CommonPluginEditor.h @@ -51,7 +51,7 @@ public: VisualiserSettings visualiserSettings = VisualiserSettings(audioProcessor.visualiserParameters, 3); RecordingSettings recordingSettings = RecordingSettings(audioProcessor.recordingParameters); - SettingsWindow recordingSettingsWindow = SettingsWindow("Recording Settings", recordingSettings, 330, 350, 330, 350); + SettingsWindow recordingSettingsWindow = SettingsWindow("Recording Settings", recordingSettings, 330, 360, 330, 360); VisualiserComponent visualiser{ audioProcessor, *this, diff --git a/Source/FrameSettingsComponent.cpp b/Source/FrameSettingsComponent.cpp index e0e298b..be2deaf 100644 --- a/Source/FrameSettingsComponent.cpp +++ b/Source/FrameSettingsComponent.cpp @@ -36,8 +36,16 @@ FrameSettingsComponent::FrameSettingsComponent(OscirenderAudioProcessor& p, Osci audioProcessor.animationOffset->setUnnormalisedValueNotifyingHost(offsetBox.getValue()); }; + rateBox.onReturnKey = updateAnimation; rateBox.onFocusLost = updateAnimation; + offsetBox.onFocusLost = updateAnimation; + offsetBox.onReturnKey = updateAnimation; + + animate.setClickingTogglesState(true); + animate.onClick = [this]() { + audioProcessor.animateFrames->setValue(animate.getToggleState()); + }; threshold.slider.onValueChange = [this]() { audioProcessor.imageThreshold->setValue(threshold.slider.getValue()); diff --git a/Source/components/DoubleTextBox.h b/Source/components/DoubleTextBox.h index cc451e4..9ffc042 100644 --- a/Source/components/DoubleTextBox.h +++ b/Source/components/DoubleTextBox.h @@ -8,6 +8,7 @@ public: setMultiLine(false); setJustification(juce::Justification::centred); setFont(juce::Font(15.0f, juce::Font::plain)); + setSelectAllWhenFocused(true); onTextChange = [this]() { setText(getText(), false); }; diff --git a/osci-render.jucer b/osci-render.jucer index 5e1d4d2..d1098b7 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.5.0.7" companyName="James H Ball" companyWebsite="https://osci-render.com" + version="2.5.0.8" 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'"> diff --git a/sosci.jucer b/sosci.jucer index fe10bf2..4309ce3 100644 --- a/sosci.jucer +++ b/sosci.jucer @@ -3,7 +3,7 @@ From b7114dbed00f31a65ea5ef2b490966ec1e60b164 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Mon, 12 May 2025 20:55:10 +0100 Subject: [PATCH 3/7] Add parameter linking, remove debug messages in opengl on windows, make visualiser settings resizable --- Resources/svg/link.svg | 1 + Source/CommonPluginEditor.h | 2 +- Source/EffectsComponent.cpp | 4 -- Source/FrameSettingsComponent.cpp | 8 --- Source/PerspectiveComponent.cpp | 3 - Source/PluginEditor.h | 2 +- Source/PluginProcessor.cpp | 20 ++++--- Source/components/EffectComponent.cpp | 60 +++++++++++++++----- Source/components/EffectComponent.h | 26 ++++++--- Source/components/EffectsListComponent.cpp | 5 -- Source/components/LuaListComponent.cpp | 4 -- Source/parser/FileParser.cpp | 4 +- Source/visualiser/RecordingSettings.cpp | 3 - Source/visualiser/VisualiserParameters.h | 3 + Source/visualiser/VisualiserRenderer.cpp | 4 ++ Source/visualiser/VisualiserSettings.cpp | 10 ++-- Source/visualiser/VisualiserSettings.h | 66 ++++++++++++---------- modules/osci_render_core | 2 +- osci-render.jucer | 3 +- sosci.jucer | 3 +- 20 files changed, 132 insertions(+), 101 deletions(-) create mode 100644 Resources/svg/link.svg diff --git a/Resources/svg/link.svg b/Resources/svg/link.svg new file mode 100644 index 0000000..38ec037 --- /dev/null +++ b/Resources/svg/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/CommonPluginEditor.h b/Source/CommonPluginEditor.h index 4576e92..d973ffd 100644 --- a/Source/CommonPluginEditor.h +++ b/Source/CommonPluginEditor.h @@ -44,7 +44,7 @@ public: #endif #if OSCI_PREMIUM - int VISUALISER_SETTINGS_HEIGHT = 1200; + int VISUALISER_SETTINGS_HEIGHT = 1230; #else int VISUALISER_SETTINGS_HEIGHT = 700; #endif diff --git a/Source/EffectsComponent.cpp b/Source/EffectsComponent.cpp index d39a452..69b9148 100644 --- a/Source/EffectsComponent.cpp +++ b/Source/EffectsComponent.cpp @@ -11,10 +11,6 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP frequency.slider.setTextValueSuffix("Hz"); frequency.slider.setValue(audioProcessor.frequencyEffect->getValue(), juce::dontSendNotification); - frequency.slider.onValueChange = [this] { - audioProcessor.frequencyEffect->parameters[0]->setUnnormalisedValueNotifyingHost(frequency.slider.getValue()); - }; - /*addBtn.setButtonText("Add Item..."); addBtn.onClick = [this]() { diff --git a/Source/FrameSettingsComponent.cpp b/Source/FrameSettingsComponent.cpp index be2deaf..77d119b 100644 --- a/Source/FrameSettingsComponent.cpp +++ b/Source/FrameSettingsComponent.cpp @@ -46,14 +46,6 @@ FrameSettingsComponent::FrameSettingsComponent(OscirenderAudioProcessor& p, Osci animate.onClick = [this]() { audioProcessor.animateFrames->setValue(animate.getToggleState()); }; - - threshold.slider.onValueChange = [this]() { - audioProcessor.imageThreshold->setValue(threshold.slider.getValue()); - }; - - stride.slider.onValueChange = [this]() { - audioProcessor.imageStride->setValue(stride.slider.getValue()); - }; audioProcessor.animationRate->addListener(this); audioProcessor.animationOffset->addListener(this); diff --git a/Source/PerspectiveComponent.cpp b/Source/PerspectiveComponent.cpp index f418c6d..c266066 100644 --- a/Source/PerspectiveComponent.cpp +++ b/Source/PerspectiveComponent.cpp @@ -7,9 +7,6 @@ PerspectiveComponent::PerspectiveComponent(OscirenderAudioProcessor& p, Oscirend addAndMakeVisible(perspective); addAndMakeVisible(focalLength); - - perspective.setSliderOnValueChange(); - focalLength.setSliderOnValueChange(); } PerspectiveComponent::~PerspectiveComponent() {} diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 5d6379b..53cf8f6 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -54,7 +54,7 @@ public: juce::ComponentAnimator codeEditorAnimator; LuaComponent lua{audioProcessor, *this}; - SettingsWindow visualiserSettingsWindow = SettingsWindow("Visualiser Settings", visualiserSettings, 550, 500, 550, VISUALISER_SETTINGS_HEIGHT); + SettingsWindow visualiserSettingsWindow = SettingsWindow("Visualiser Settings", visualiserSettings, 550, 500, 1500, VISUALISER_SETTINGS_HEIGHT); LuaConsole console; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index c1160fc..1ed5417 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -34,16 +34,19 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse toggleableEffects.push_back(std::make_shared( std::make_shared(), new osci::EffectParameter("Vector Cancelling", "Inverts the audio and image every few samples to 'cancel out' the audio, making the audio quiet, and distorting the image.", "vectorCancelling", VERSION_HINT, 0.1111111, 0.0, 1.0))); - toggleableEffects.push_back(std::make_shared( + auto scaleEffect = std::make_shared( [this](int index, osci::Point input, const std::vector>& values, double sampleRate) { return input * osci::Point(values[0], values[1], values[2]); }, std::vector{ - new osci::EffectParameter("Scale X", "Scales the object in the horizontal direction.", "scaleX", VERSION_HINT, 1.0, -5.0, 5.0), - new osci::EffectParameter("Scale Y", "Scales the object in the vertical direction.", "scaleY", VERSION_HINT, 1.0, -5.0, 5.0), - new osci::EffectParameter("Scale Z", "Scales the depth of the object.", "scaleZ", VERSION_HINT, 1.0, -5.0, 5.0), - })); - toggleableEffects.push_back(std::make_shared( + new osci::EffectParameter("Scale X", "Scales the object in the horizontal direction.", "scaleX", VERSION_HINT, 1.0, -3.0, 3.0), + new osci::EffectParameter("Scale Y", "Scales the object in the vertical direction.", "scaleY", VERSION_HINT, 1.0, -3.0, 3.0), + new osci::EffectParameter("Scale Z", "Scales the depth of the object.", "scaleZ", VERSION_HINT, 1.0, -3.0, 3.0), + }); + scaleEffect->markLockable(true); + booleanParameters.push_back(scaleEffect->linked); + toggleableEffects.push_back(scaleEffect); + auto distortEffect = std::make_shared( [this](int index, osci::Point input, const std::vector>& values, double sampleRate) { int flip = index % 2 == 0 ? 1 : -1; osci::Point jitter = osci::Point(flip * values[0], flip * values[1], flip * values[2]); @@ -53,7 +56,10 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse new osci::EffectParameter("Distort X", "Distorts the image in the horizontal direction by jittering the audio sample being drawn.", "distortX", VERSION_HINT, 0.0, 0.0, 1.0), new osci::EffectParameter("Distort Y", "Distorts the image in the vertical direction by jittering the audio sample being drawn.", "distortY", VERSION_HINT, 0.0, 0.0, 1.0), new osci::EffectParameter("Distort Z", "Distorts the depth of the image by jittering the audio sample being drawn.", "distortZ", VERSION_HINT, 0.1, 0.0, 1.0), - })); + }); + distortEffect->markLockable(false); + booleanParameters.push_back(distortEffect->linked); + toggleableEffects.push_back(distortEffect); auto rippleEffect = std::make_shared( [this](int index, osci::Point input, const std::vector>& values, double sampleRate) { double phase = values[1] * std::numbers::pi; diff --git a/Source/components/EffectComponent.cpp b/Source/components/EffectComponent.cpp index cb8a6dd..ba5bd37 100644 --- a/Source/components/EffectComponent.cpp +++ b/Source/components/EffectComponent.cpp @@ -1,4 +1,5 @@ #include "EffectComponent.h" + #include "../LookAndFeel.h" EffectComponent::EffectComponent(osci::Effect& effect, int index) : effect(effect), index(index) { @@ -15,6 +16,16 @@ EffectComponent::EffectComponent(osci::Effect& effect, int index) : effect(effec addAndMakeVisible(*sidechainButton); } + if (effect.linked != nullptr && index == 0) { + linkButton.setTooltip("When enabled, parameters are linked and changes to one parameter will affect all parameters."); + linkButton.onClick = [this]() { + if (this->effect.linked != nullptr) { + this->effect.linked->setBoolValueNotifyingHost(!this->effect.linked->getBoolValue()); + } + }; + addAndMakeVisible(linkButton); + } + slider.setSliderStyle(juce::Slider::LinearHorizontal); slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, TEXT_BOX_WIDTH, slider.getTextBoxHeight()); if (effect.parameters[index]->step == 1.0) { @@ -57,8 +68,8 @@ EffectComponent::EffectComponent(osci::Effect& effect) : EffectComponent(effect, void EffectComponent::setSliderValueIfChanged(osci::FloatParameter* parameter, juce::Slider& slider) { juce::String newSliderValue = juce::String(parameter->getValueUnnormalised(), 3); - juce::String oldSliderValue = juce::String((float) slider.getValue(), 3); - + juce::String oldSliderValue = juce::String((float)slider.getValue(), 3); + // only set the slider value if the parameter value is different so that we prefer the more // precise slider value. if (newSliderValue != oldSliderValue) { @@ -70,7 +81,17 @@ void EffectComponent::setupComponent() { osci::EffectParameter* parameter = effect.parameters[index]; setEnabled(effect.enabled == nullptr || effect.enabled->getBoolValue()); - + + if (effect.linked != nullptr && index == 0) { + linkButton.setToggleState(effect.linked->getBoolValue(), juce::dontSendNotification); + + if (effect.linked->getBoolValue()) { + for (int i = 1; i < effect.parameters.size(); i++) { + effect.setValue(i, effect.parameters[0]->getValueUnnormalised()); + } + } + } + if (updateToggleState != nullptr) { updateToggleState(); } @@ -83,6 +104,20 @@ void EffectComponent::setupComponent() { setSliderValueIfChanged(parameter, slider); slider.setDoubleClickReturnValue(true, parameter->defaultValue); + // Set the new slider value change handler + slider.onValueChange = [this] { + // Update the effect parameter with this slider's value + effect.setValue(index, slider.getValue()); + + if (effect.linked != nullptr && effect.linked->getBoolValue()) { + for (int i = 0; i < effect.parameters.size(); i++) { + if (i != index) { + effect.setValue(i, slider.getValue()); + } + } + } + }; + lfoEnabled = parameter->lfo != nullptr && parameter->lfoRate != nullptr; if (lfoEnabled) { lfo.setSelectedId(parameter->lfo->getValueUnnormalised(), juce::dontSendNotification); @@ -125,7 +160,6 @@ void EffectComponent::setupComponent() { effect.parameters[index]->sidechain->setBoolValueNotifyingHost(!effect.parameters[index]->sidechain->getBoolValue()); }; } - if (sidechainEnabled && effect.parameters[index]->sidechain->getBoolValue()) { slider.setEnabled(false); @@ -138,7 +172,6 @@ void EffectComponent::setupComponent() { } } - EffectComponent::~EffectComponent() { effect.removeListener(index, this); } @@ -146,14 +179,17 @@ EffectComponent::~EffectComponent() { void EffectComponent::resized() { auto bounds = getLocalBounds(); auto componentBounds = bounds.removeFromRight(25); - if (component != nullptr) { + + if (effect.linked != nullptr) { + linkButton.setBounds(componentBounds); + } else if (component != nullptr) { component->setBounds(componentBounds); } if (sidechainEnabled) { sidechainButton->setBounds(bounds.removeFromRight(20)); } - + if (settingsButton.isVisible()) { settingsButton.setBounds(bounds.removeFromRight(20)); } @@ -165,7 +201,7 @@ void EffectComponent::resized() { } bounds.removeFromRight(2); - + bounds.removeFromLeft(5); label.setBounds(bounds.removeFromLeft(drawingSmall ? SMALL_TEXT_WIDTH : TEXT_WIDTH)); @@ -205,12 +241,6 @@ void EffectComponent::setRangeEnabled(bool enabled) { } void EffectComponent::setComponent(std::shared_ptr component) { - this->component = component; + this->component = component; addAndMakeVisible(component.get()); } - -void EffectComponent::setSliderOnValueChange() { - slider.onValueChange = [this] { - effect.setValue(index, slider.getValue()); - }; -} diff --git a/Source/components/EffectComponent.h b/Source/components/EffectComponent.h index dfb72f9..0031c2a 100644 --- a/Source/components/EffectComponent.h +++ b/Source/components/EffectComponent.h @@ -1,5 +1,6 @@ #pragma once #include + #include "LabelledTextBox.h" #include "SvgButton.h" @@ -18,14 +19,13 @@ public: void setRangeEnabled(bool enabled); void setComponent(std::shared_ptr component); - void setSliderOnValueChange(); juce::Slider slider; juce::Slider lfoSlider; osci::Effect& effect; int index = 0; juce::ComboBox lfo; - + class EffectSettingsComponent : public juce::Component { public: EffectSettingsComponent(EffectComponent* parent) { @@ -38,9 +38,9 @@ public: addAndMakeVisible(lfoEndSlider); addAndMakeVisible(smoothValueChangeLabel); addAndMakeVisible(smoothValueChangeSlider); - + osci::EffectParameter* parameter = parent->effect.parameters[parent->index]; - + min.textBox.setValue(parameter->min, juce::dontSendNotification); max.textBox.setValue(parameter->max, juce::dontSendNotification); @@ -87,7 +87,7 @@ public: lfoEndSlider.onValueChange = [this, parameter]() { parameter->lfoEndPercent->setUnnormalisedValueNotifyingHost(lfoEndSlider.getValue()); }; - + smoothValueChangeLabel.setText("Smooth Value Change Speed", juce::dontSendNotification); smoothValueChangeLabel.setJustificationType(juce::Justification::centred); smoothValueChangeLabel.setFont(juce::Font(14.0f, juce::Font::bold)); @@ -102,7 +102,7 @@ public: popupLabel.setJustificationType(juce::Justification::centred); popupLabel.setFont(juce::Font(14.0f, juce::Font::bold)); } - + void resized() override { auto bounds = getLocalBounds(); popupLabel.setBounds(bounds.removeFromTop(30)); @@ -115,7 +115,7 @@ public: smoothValueChangeLabel.setBounds(bounds.removeFromTop(20)); smoothValueChangeSlider.setBounds(bounds.removeFromTop(40)); } - + private: juce::Label popupLabel; LabelledTextBox min{"Min"}; @@ -127,7 +127,7 @@ public: juce::Label smoothValueChangeLabel; juce::Slider smoothValueChangeSlider; }; - + std::function updateToggleState; private: @@ -145,9 +145,17 @@ private: std::unique_ptr sidechainButton; + SvgButton linkButton = SvgButton(effect.parameters[index]->name + " Link", + BinaryData::link_svg, + juce::Colours::white, + juce::Colours::red, + nullptr, + BinaryData::link_svg + ); + juce::Label label; - SvgButton settingsButton = { "settingsButton", BinaryData::cog_svg, juce::Colours::white }; + SvgButton settingsButton = {"settingsButton", BinaryData::cog_svg, juce::Colours::white}; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectComponent) }; diff --git a/Source/components/EffectsListComponent.cpp b/Source/components/EffectsListComponent.cpp index 06c71af..7c48aaa 100644 --- a/Source/components/EffectsListComponent.cpp +++ b/Source/components/EffectsListComponent.cpp @@ -12,11 +12,6 @@ effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) { // using weak_ptr to avoid circular reference and memory leak std::weak_ptr weakEffectComponent = effectComponent; effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification); - effectComponent->slider.onValueChange = [this, i, weakEffectComponent] { - if (auto effectComponent = weakEffectComponent.lock()) { - this->effect.setValue(i, effectComponent->slider.getValue()); - } - }; list.setEnabled(selected.getToggleState()); selected.onClick = [this, weakEffectComponent] { diff --git a/Source/components/LuaListComponent.cpp b/Source/components/LuaListComponent.cpp index 9b13c2c..afa9cde 100644 --- a/Source/components/LuaListComponent.cpp +++ b/Source/components/LuaListComponent.cpp @@ -3,10 +3,6 @@ LuaListComponent::LuaListComponent(OscirenderAudioProcessor& p, osci::Effect& effect) { effectComponent = std::make_shared(effect); - effectComponent->slider.onValueChange = [this, &effect, &p] { - effect.setValue(effectComponent->slider.getValue()); - }; - addAndMakeVisible(*effectComponent); } diff --git a/Source/parser/FileParser.cpp b/Source/parser/FileParser.cpp index 18547ee..c146921 100644 --- a/Source/parser/FileParser.cpp +++ b/Source/parser/FileParser.cpp @@ -15,9 +15,7 @@ void FileParser::showFileSizeWarning(juce::String fileName, int64_t totalBytes, } const double fileSizeMB = totalBytes / (1024.0 * 1024.0); - juce::String message = juce::String::formatted( - "The %s file '%s' you're trying to open is %.2f MB in size, and may time a long time to open. " - "Would you like to continue loading it?", fileType.toRawUTF8(), fileName.toRawUTF8(), fileSizeMB); + juce::String message = "The " + fileType + " file '" + fileName + "' you're trying to open is " + juce::String(fileSizeMB, 2) + " MB in size, and may time a long time to open.\n\nWould you like to continue loading it?"; juce::MessageManager::callAsync([this, message, callback]() { juce::AlertWindow::showOkCancelBox( diff --git a/Source/visualiser/RecordingSettings.cpp b/Source/visualiser/RecordingSettings.cpp index 2bcb605..e11cd0d 100644 --- a/Source/visualiser/RecordingSettings.cpp +++ b/Source/visualiser/RecordingSettings.cpp @@ -18,11 +18,8 @@ RecordingSettings::RecordingSettings(RecordingParameters& ps) : parameters(ps) { addAndMakeVisible(customSharedTextureOutputLabel); addAndMakeVisible(customSharedTextureOutputEditor); - quality.setSliderOnValueChange(); quality.setRangeEnabled(false); - resolution.setSliderOnValueChange(); resolution.setRangeEnabled(false); - frameRate.setSliderOnValueChange(); frameRate.setRangeEnabled(false); recordAudio.onClick = [this] { diff --git a/Source/visualiser/VisualiserParameters.h b/Source/visualiser/VisualiserParameters.h index 99f2f0f..f07cbcd 100644 --- a/Source/visualiser/VisualiserParameters.h +++ b/Source/visualiser/VisualiserParameters.h @@ -86,6 +86,9 @@ public: class VisualiserParameters { public: + VisualiserParameters() { + scaleEffect->markLockable(true); + } double getIntensity() { return intensityEffect->getActualValue() / 100; diff --git a/Source/visualiser/VisualiserRenderer.cpp b/Source/visualiser/VisualiserRenderer.cpp index 521f9f5..33d0d3f 100644 --- a/Source/visualiser/VisualiserRenderer.cpp +++ b/Source/visualiser/VisualiserRenderer.cpp @@ -203,6 +203,10 @@ void VisualiserRenderer::newOpenGLContextCreated() { juce::CriticalSection::ScopedLockType lock(samplesLock); +#if JUCE_WINDOWS && JUCE_DEBUG + glDisable(GL_DEBUG_OUTPUT); +#endif + glColorMask(true, true, true, true); viewportChanged(viewportArea); diff --git a/Source/visualiser/VisualiserSettings.cpp b/Source/visualiser/VisualiserSettings.cpp index c734a39..24ca639 100644 --- a/Source/visualiser/VisualiserSettings.cpp +++ b/Source/visualiser/VisualiserSettings.cpp @@ -15,7 +15,8 @@ VisualiserSettings::VisualiserSettings(VisualiserParameters& p, int numChannels) addAndMakeVisible(screenOverlayLabel); addAndMakeVisible(screenOverlay); #if OSCI_PREMIUM - addAndMakeVisible(positionSize); + addAndMakeVisible(scale); + addAndMakeVisible(position); addAndMakeVisible(screenColour); addAndMakeVisible(flipVerticalToggle); addAndMakeVisible(flipHorizontalToggle); @@ -31,9 +32,6 @@ VisualiserSettings::VisualiserSettings(VisualiserParameters& p, int numChannels) parameters.screenOverlay->setUnnormalisedValueNotifyingHost(screenOverlay.getSelectedId()); }; - sweepMs.setSliderOnValueChange(); - triggerValue.setSliderOnValueChange(); - sweepMs.setEnabled(sweepToggle.getToggleState()); triggerValue.setEnabled(sweepToggle.getToggleState()); @@ -79,7 +77,9 @@ void VisualiserSettings::resized() { #if OSCI_PREMIUM area.removeFromTop(10); - positionSize.setBounds(area.removeFromTop(positionSize.getHeight())); + scale.setBounds(area.removeFromTop(scale.getHeight())); + area.removeFromTop(10); + position.setBounds(area.removeFromTop(position.getHeight())); area.removeFromTop(10); flipVerticalToggle.setBounds(area.removeFromTop(rowHeight)); flipHorizontalToggle.setBounds(area.removeFromTop(rowHeight)); diff --git a/Source/visualiser/VisualiserSettings.h b/Source/visualiser/VisualiserSettings.h index ca4fc87..dc0f630 100644 --- a/Source/visualiser/VisualiserSettings.h +++ b/Source/visualiser/VisualiserSettings.h @@ -3,12 +3,13 @@ #define VERSION_HINT 2 #include -#include "../components/EffectComponent.h" -#include "../components/SvgButton.h" + #include "../LookAndFeel.h" -#include "../components/SwitchButton.h" #include "../audio/SmoothEffect.h" #include "../audio/StereoEffect.h" +#include "../components/EffectComponent.h" +#include "../components/SvgButton.h" +#include "../components/SwitchButton.h" #include "VisualiserParameters.h" class GroupedSettings : public juce::GroupComponent { @@ -16,29 +17,28 @@ public: GroupedSettings(std::vector> effects, juce::String label) : effects(effects), juce::GroupComponent(label, label) { for (auto effect : effects) { addAndMakeVisible(effect.get()); - effect->setSliderOnValueChange(); } - + setColour(groupComponentBackgroundColourId, Colours::veryDark.withMultipliedBrightness(3.0)); } - + void resized() override { auto area = getLocalBounds(); area.removeFromTop(35); double rowHeight = 30; - + for (auto effect : effects) { effect->setBounds(area.removeFromTop(rowHeight)); } } - + int getHeight() { return 40 + effects.size() * 30; } private: std::vector> effects; - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GroupedSettings) }; @@ -62,9 +62,8 @@ private: std::make_shared(*parameters.lineSaturationEffect), std::make_shared(*parameters.intensityEffect), }, - "Line Colour" - }; - + "Line Colour"}; + #if OSCI_PREMIUM GroupedSettings screenColour{ std::vector>{ @@ -72,10 +71,9 @@ private: std::make_shared(*parameters.screenSaturationEffect), std::make_shared(*parameters.ambientEffect), }, - "Screen Colour" - }; + "Screen Colour"}; #endif - + GroupedSettings lightEffects{ std::vector>{ std::make_shared(*parameters.persistenceEffect), @@ -88,16 +86,14 @@ private: std::make_shared(*parameters.ambientEffect), #endif }, - "Light Effects" - }; - + "Light Effects"}; + GroupedSettings videoEffects{ std::vector>{ std::make_shared(*parameters.noiseEffect), }, - "Video Effects" - }; - + "Video Effects"}; + GroupedSettings lineEffects{ std::vector>{ std::make_shared(*parameters.smoothEffect), @@ -105,28 +101,31 @@ private: std::make_shared(*parameters.stereoEffect), #endif }, - "Line Effects" - }; - + "Line Effects"}; + EffectComponent sweepMs{*parameters.sweepMsEffect}; EffectComponent triggerValue{*parameters.triggerValueEffect}; - + juce::Label screenOverlayLabel{"Screen Overlay", "Screen Overlay"}; juce::ComboBox screenOverlay; - + jux::SwitchButton upsamplingToggle{parameters.upsamplingEnabled}; jux::SwitchButton sweepToggle{parameters.sweepEnabled}; #if OSCI_PREMIUM - GroupedSettings positionSize{ + GroupedSettings scale{ std::vector>{ std::make_shared(*parameters.scaleEffect, 0), std::make_shared(*parameters.scaleEffect, 1), + }, + "Image Scale"}; + + GroupedSettings position{ + std::vector>{ std::make_shared(*parameters.offsetEffect, 0), std::make_shared(*parameters.offsetEffect, 1), }, - "Line Position & Scale" - }; + "Image Position"}; jux::SwitchButton flipVerticalToggle{parameters.flipVertical}; jux::SwitchButton flipHorizontalToggle{parameters.flipHorizontal}; @@ -160,7 +159,7 @@ private: class SettingsWindow : public juce::DialogWindow { public: - SettingsWindow(juce::String name, juce::Component& component, int windowWidth, int windowHeight, int componentWidth, int componentHeight) : juce::DialogWindow(name, Colours::darker, true, true), component(component) { + SettingsWindow(juce::String name, juce::Component& component, int windowWidth, int windowHeight, int componentWidth, int componentHeight) : juce::DialogWindow(name, Colours::darker, true, true), component(component), componentHeight(componentHeight) { setContentComponent(&viewport); centreWithSize(windowWidth, windowHeight); setResizeLimits(windowWidth, windowHeight, componentWidth, componentHeight); @@ -175,7 +174,14 @@ public: setVisible(false); } + void resized() override { + DialogWindow::resized(); + // Update the component width to match the viewport width while maintaining its height + component.setSize(viewport.getWidth(), componentHeight); + } + private: juce::Viewport viewport; juce::Component& component; + int componentHeight; }; diff --git a/modules/osci_render_core b/modules/osci_render_core index 3e2d2da..4ddac1f 160000 --- a/modules/osci_render_core +++ b/modules/osci_render_core @@ -1 +1 @@ -Subproject commit 3e2d2daa7e07e4317bdd096bcdb3dbb721241efa +Subproject commit 4ddac1f6ff0853c989bf3ab619e0f1e0264f5a45 diff --git a/osci-render.jucer b/osci-render.jucer index d1098b7..4cab792 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.5.0.8" companyName="James H Ball" companyWebsite="https://osci-render.com" + version="2.5.1.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'"> @@ -53,6 +53,7 @@ file="Resources/svg/fixed_rotate.svg"/> + diff --git a/sosci.jucer b/sosci.jucer index 4309ce3..1f4b48c 100644 --- a/sosci.jucer +++ b/sosci.jucer @@ -3,7 +3,7 @@ @@ -57,6 +57,7 @@ file="Resources/svg/fixed_rotate.svg"/> + From ed62df01b4f6bf22dce1e7c575a4395571e858f2 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Mon, 12 May 2025 21:06:13 +0100 Subject: [PATCH 4/7] Fix compilation on free version --- Source/visualiser/VisualiserParameters.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/visualiser/VisualiserParameters.h b/Source/visualiser/VisualiserParameters.h index f07cbcd..216b9cf 100644 --- a/Source/visualiser/VisualiserParameters.h +++ b/Source/visualiser/VisualiserParameters.h @@ -87,7 +87,9 @@ public: class VisualiserParameters { public: VisualiserParameters() { +#if OSCI_PREMIUM scaleEffect->markLockable(true); +#endif } double getIntensity() { From 8d475d7946799187147f4bde5d3de2ab559a26bc Mon Sep 17 00:00:00 2001 From: James H Ball Date: Sun, 18 May 2025 13:15:59 +0100 Subject: [PATCH 5/7] Flip syphon texture on mac --- Source/img/ImageParser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/img/ImageParser.cpp b/Source/img/ImageParser.cpp index b44e695..1c4a120 100644 --- a/Source/img/ImageParser.cpp +++ b/Source/img/ImageParser.cpp @@ -338,7 +338,12 @@ float ImageParser::getPixelValue(int x, int y, bool invert) { if (usingLiveImage) { if (liveImage.isValid()) { if (x < 0 || x >= width || y < 0 || y >= height) return 0; +#if JUCE_MAC + juce::Colour pixel = liveImage.getPixelAt(x, y); +#else juce::Colour pixel = liveImage.getPixelAt(x, height - y - 1); +#endif + float value = pixel.getBrightness(); if (invert && value > 0) value = 1.0f - value; return value; From 475da7a4ade9f74ca36e4e6164879f43862380a5 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Mon, 19 May 2025 22:17:26 +0100 Subject: [PATCH 6/7] Update build.yaml --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0c71d95..6ab3adc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,7 +2,6 @@ name: Build on: push: branches: - - main - develop workflow_dispatch: jobs: From 986a1cfd13dbe0a449e4330b6c0aa25801e59afc Mon Sep 17 00:00:00 2001 From: James H Ball Date: Mon, 26 May 2025 17:37:48 +0100 Subject: [PATCH 7/7] Fix issue with sample rate not being set correctly when recording audio --- Source/audio/AudioRecorder.h | 13 ++++++++++++- Source/visualiser/VisualiserComponent.cpp | 2 ++ Source/visualiser/VisualiserParameters.h | 1 + Source/visualiser/VisualiserRenderer.cpp | 1 - osci-render.jucer | 2 +- sosci.jucer | 2 +- 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Source/audio/AudioRecorder.h b/Source/audio/AudioRecorder.h index 98fe9ae..4ab640c 100644 --- a/Source/audio/AudioRecorder.h +++ b/Source/audio/AudioRecorder.h @@ -43,6 +43,17 @@ public: activeWriter = threadedWriter.get(); } } + } else { + // Error: Invalid sample rate + juce::AlertWindow::showMessageBoxAsync( + juce::AlertWindow::WarningIcon, + "Recording Error", + "Cannot start recording: Invalid sample rate (" + juce::String(sampleRate) + "). Sample rate must be greater than 0.", + "OK" + ); + stop(); + stopCallback(); + return; } } @@ -91,7 +102,7 @@ private: juce::int64 nextSampleNum = 0; double recordingLength = 99999999999.0; - double sampleRate = 192000; + double sampleRate = -1; juce::CriticalSection writerLock; std::atomic activeWriter { nullptr }; diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp index 3e9dc24..91c9875 100644 --- a/Source/visualiser/VisualiserComponent.cpp +++ b/Source/visualiser/VisualiserComponent.cpp @@ -26,6 +26,8 @@ VisualiserComponent::VisualiserComponent( visualiserOnly(visualiserOnly), parent(parent), editor(pluginEditor) { + setShouldBeRunning(true); + #if OSCI_PREMIUM addAndMakeVisible(editor.ffmpegDownloader); #endif diff --git a/Source/visualiser/VisualiserParameters.h b/Source/visualiser/VisualiserParameters.h index 216b9cf..8da10c5 100644 --- a/Source/visualiser/VisualiserParameters.h +++ b/Source/visualiser/VisualiserParameters.h @@ -89,6 +89,7 @@ public: VisualiserParameters() { #if OSCI_PREMIUM scaleEffect->markLockable(true); + booleans.push_back(scaleEffect->linked); #endif } diff --git a/Source/visualiser/VisualiserRenderer.cpp b/Source/visualiser/VisualiserRenderer.cpp index 33d0d3f..e42380f 100644 --- a/Source/visualiser/VisualiserRenderer.cpp +++ b/Source/visualiser/VisualiserRenderer.cpp @@ -30,7 +30,6 @@ VisualiserRenderer::VisualiserRenderer( { openGLContext.setRenderer(this); openGLContext.attachTo(*this); - setShouldBeRunning(true); } VisualiserRenderer::~VisualiserRenderer() { diff --git a/osci-render.jucer b/osci-render.jucer index 4cab792..7e42d3b 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.5.1.0" companyName="James H Ball" companyWebsite="https://osci-render.com" + version="2.5.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'"> diff --git a/sosci.jucer b/sosci.jucer index 1f4b48c..f46a379 100644 --- a/sosci.jucer +++ b/sosci.jucer @@ -3,7 +3,7 @@