Add initial dummy effect grid

pull/307/head
James H Ball 2025-08-01 22:48:42 +01:00
rodzic 47ed50a96c
commit 83927204b8
10 zmienionych plików z 408 dodań i 88 usunięć

Wyświetl plik

@ -42,6 +42,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
new osci::EffectParameter("Multiplex Phase", "Controls the current phase of the multiplex grid animation.", "gridPhase", 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),
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),
}); });
multiplexEffect->setName("Multiplex");
// Set up the Grid Phase parameter with sawtooth LFO at 100Hz // Set up the Grid Phase parameter with sawtooth LFO at 100Hz
multiplexEffect->getParameter("gridPhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); multiplexEffect->getParameter("gridPhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
multiplexEffect->getParameter("gridPhase")->lfoRate->setUnnormalisedValueNotifyingHost(100.0); multiplexEffect->getParameter("gridPhase")->lfoRate->setUnnormalisedValueNotifyingHost(100.0);
@ -58,6 +59,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
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 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), new osci::EffectParameter("Scale Z", "Scales the depth of the object.", "scaleZ", VERSION_HINT, 1.0, -3.0, 3.0),
}); });
scaleEffect->setName("Scale");
scaleEffect->markLockable(true); scaleEffect->markLockable(true);
booleanParameters.push_back(scaleEffect->linked); booleanParameters.push_back(scaleEffect->linked);
toggleableEffects.push_back(scaleEffect); toggleableEffects.push_back(scaleEffect);
@ -72,6 +74,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
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 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), 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->setName("Distort");
distortEffect->markLockable(false); distortEffect->markLockable(false);
booleanParameters.push_back(distortEffect->linked); booleanParameters.push_back(distortEffect->linked);
toggleableEffects.push_back(distortEffect); toggleableEffects.push_back(distortEffect);
@ -87,6 +90,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
new osci::EffectParameter("Ripple Phase", "Controls the position of the ripple. Animate this to see a moving ripple effect.", "ripplePhase", VERSION_HINT, 0.0, -1.0, 1.0), new osci::EffectParameter("Ripple Phase", "Controls the position of the ripple. Animate this to see a moving ripple effect.", "ripplePhase", VERSION_HINT, 0.0, -1.0, 1.0),
new osci::EffectParameter("Ripple Amount", "Controls how many ripples are applied to the image.", "rippleAmount", VERSION_HINT, 0.1, 0.0, 1.0), new osci::EffectParameter("Ripple Amount", "Controls how many ripples are applied to the image.", "rippleAmount", VERSION_HINT, 0.1, 0.0, 1.0),
}); });
rippleEffect->setName("Ripple");
rippleEffect->getParameter("ripplePhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); rippleEffect->getParameter("ripplePhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
toggleableEffects.push_back(rippleEffect); toggleableEffects.push_back(rippleEffect);
auto rotateEffect = std::make_shared<osci::Effect>( auto rotateEffect = std::make_shared<osci::Effect>(
@ -99,10 +103,11 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
new osci::EffectParameter("Rotate Y", "Controls the rotation of the object in the Y axis.", "rotateY", VERSION_HINT, 0.0, -1.0, 1.0), new osci::EffectParameter("Rotate Y", "Controls the rotation of the object in the Y axis.", "rotateY", VERSION_HINT, 0.0, -1.0, 1.0),
new osci::EffectParameter("Rotate Z", "Controls the rotation of the object in the Z axis.", "rotateZ", VERSION_HINT, 0.0, -1.0, 1.0), new osci::EffectParameter("Rotate Z", "Controls the rotation of the object in the Z axis.", "rotateZ", VERSION_HINT, 0.0, -1.0, 1.0),
}); });
rotateEffect->setName("Rotate");
rotateEffect->getParameter("rotateY")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); rotateEffect->getParameter("rotateY")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
rotateEffect->getParameter("rotateY")->lfoRate->setUnnormalisedValueNotifyingHost(0.2); rotateEffect->getParameter("rotateY")->lfoRate->setUnnormalisedValueNotifyingHost(0.2);
toggleableEffects.push_back(rotateEffect); toggleableEffects.push_back(rotateEffect);
toggleableEffects.push_back(std::make_shared<osci::Effect>( auto translateEffect = std::make_shared<osci::Effect>(
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) { [this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
return input + osci::Point(values[0], values[1], values[2]); return input + osci::Point(values[0], values[1], values[2]);
}, },
@ -110,7 +115,9 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
new osci::EffectParameter("Translate X", "Moves the object horizontally.", "translateX", VERSION_HINT, 0.0, -1.0, 1.0), new osci::EffectParameter("Translate X", "Moves the object horizontally.", "translateX", VERSION_HINT, 0.0, -1.0, 1.0),
new osci::EffectParameter("Translate Y", "Moves the object vertically.", "translateY", VERSION_HINT, 0.0, -1.0, 1.0), new osci::EffectParameter("Translate Y", "Moves the object vertically.", "translateY", VERSION_HINT, 0.0, -1.0, 1.0),
new osci::EffectParameter("Translate Z", "Moves the object away from the camera.", "translateZ", VERSION_HINT, 0.0, -1.0, 1.0), new osci::EffectParameter("Translate Z", "Moves the object away from the camera.", "translateZ", VERSION_HINT, 0.0, -1.0, 1.0),
})); });
translateEffect->setName("Translate");
toggleableEffects.push_back(translateEffect);
toggleableEffects.push_back(std::make_shared<osci::Effect>( toggleableEffects.push_back(std::make_shared<osci::Effect>(
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) { [this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
double length = 10 * values[0] * input.magnitude(); double length = 10 * values[0] * input.magnitude();
@ -130,18 +137,24 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
new osci::EffectParameter("Wobble Amount", "Adds a sine wave of the prominent frequency in the audio currently playing. The sine wave's frequency is slightly offset to create a subtle 'wobble' in the image. Increasing the slider increases the strength of the wobble.", "wobble", VERSION_HINT, 0.3, 0.0, 1.0), new osci::EffectParameter("Wobble Amount", "Adds a sine wave of the prominent frequency in the audio currently playing. The sine wave's frequency is slightly offset to create a subtle 'wobble' in the image. Increasing the slider increases the strength of the wobble.", "wobble", VERSION_HINT, 0.3, 0.0, 1.0),
new osci::EffectParameter("Wobble Phase", "Controls the phase of the wobble.", "wobblePhase", VERSION_HINT, 0.0, -1.0, 1.0), new osci::EffectParameter("Wobble Phase", "Controls the phase of the wobble.", "wobblePhase", VERSION_HINT, 0.0, -1.0, 1.0),
}); });
wobble->setName("Wobble");
wobble->getParameter("wobblePhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); wobble->getParameter("wobblePhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
toggleableEffects.push_back(wobble); toggleableEffects.push_back(wobble);
toggleableEffects.push_back(std::make_shared<osci::Effect>( auto delay = std::make_shared<osci::Effect>(
delayEffect, delayEffect,
std::vector<osci::EffectParameter*>{ std::vector<osci::EffectParameter*>{
new osci::EffectParameter("Delay Decay", "Adds repetitions, delays, or echos to the audio. This slider controls the volume of the echo.", "delayDecay", VERSION_HINT, 0.4, 0.0, 1.0), new osci::EffectParameter("Delay Decay", "Adds repetitions, delays, or echos to the audio. This slider controls the volume of the echo.", "delayDecay", VERSION_HINT, 0.4, 0.0, 1.0),
new osci::EffectParameter("Delay Length", "Controls the time in seconds between echos.", "delayLength", VERSION_HINT, 0.5, 0.0, 1.0)})); new osci::EffectParameter("Delay Length", "Controls the time in seconds between echos.", "delayLength", VERSION_HINT, 0.5, 0.0, 1.0)
toggleableEffects.push_back(std::make_shared<osci::Effect>( });
delay->setName("Delay");
toggleableEffects.push_back(delay);
auto dashEffect = std::make_shared<osci::Effect>(
dashedLineEffect, dashedLineEffect,
std::vector<osci::EffectParameter*>{ std::vector<osci::EffectParameter*>{
new osci::EffectParameter("Dash Length", "Controls the length of the dashed line.", "dashLength", VERSION_HINT, 0.2, 0.0, 1.0), new osci::EffectParameter("Dash Length", "Controls the length of the dashed line.", "dashLength", VERSION_HINT, 0.2, 0.0, 1.0),
})); });
dashEffect->setName("Dash");
toggleableEffects.push_back(dashEffect);
toggleableEffects.push_back(custom); toggleableEffects.push_back(custom);
toggleableEffects.push_back(trace); toggleableEffects.push_back(trace);
trace->getParameter("traceLength")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); trace->getParameter("traceLength")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);

Wyświetl plik

@ -28,6 +28,7 @@ EffectComponent::EffectComponent(osci::Effect& effect, int index) : effect(effec
slider.setSliderStyle(juce::Slider::LinearHorizontal); slider.setSliderStyle(juce::Slider::LinearHorizontal);
slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, TEXT_BOX_WIDTH, slider.getTextBoxHeight()); slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, TEXT_BOX_WIDTH, slider.getTextBoxHeight());
slider.setScrollWheelEnabled(false);
if (effect.parameters[index]->step == 1.0) { if (effect.parameters[index]->step == 1.0) {
slider.setNumDecimalPlacesToDisplay(0); slider.setNumDecimalPlacesToDisplay(0);
} else { } else {
@ -39,6 +40,7 @@ EffectComponent::EffectComponent(osci::Effect& effect, int index) : effect(effec
lfoSlider.setTextValueSuffix("Hz"); lfoSlider.setTextValueSuffix("Hz");
lfoSlider.setColour(sliderThumbOutlineColourId, juce::Colour(0xff00ff00)); lfoSlider.setColour(sliderThumbOutlineColourId, juce::Colour(0xff00ff00));
lfoSlider.setNumDecimalPlacesToDisplay(3); lfoSlider.setNumDecimalPlacesToDisplay(3);
lfoSlider.setScrollWheelEnabled(false);
label.setFont(juce::Font(14.0f)); label.setFont(juce::Font(14.0f));

Wyświetl plik

@ -0,0 +1,81 @@
#include "EffectTypeGridComponent.h"
#include "../LookAndFeel.h"
EffectTypeGridComponent::EffectTypeGridComponent(OscirenderAudioProcessor& processor)
: audioProcessor(processor)
{
setupEffectItems();
setSize(400, 200); // Default size, will be resized by parent
setMouseCursor(juce::MouseCursor::PointingHandCursor);
}
EffectTypeGridComponent::~EffectTypeGridComponent() = default;
void EffectTypeGridComponent::setupEffectItems()
{
// Clear existing items
effectItems.clear();
// Get effect types directly from the audio processor's toggleableEffects
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
for (const auto& effect : audioProcessor.toggleableEffects)
{
// Extract effect name from the effect ID or first parameter name
juce::String effectName = effect->getName();
// Create new item component
auto* item = new EffectTypeItemComponent(effectName, effect->getId());
// Set up callback to forward effect selection
item->onEffectSelected = [this](const juce::String& effectId) {
if (onEffectSelected)
onEffectSelected(effectId);
};
effectItems.add(item);
addAndMakeVisible(item);
}
}
void EffectTypeGridComponent::paint(juce::Graphics& g)
{
// No background - make component transparent
}
void EffectTypeGridComponent::resized()
{
auto bounds = getLocalBounds();
// Create FlexBox for responsive grid layout
flexBox = juce::FlexBox();
flexBox.flexWrap = juce::FlexBox::Wrap::wrap;
flexBox.justifyContent = juce::FlexBox::JustifyContent::spaceBetween;
flexBox.alignContent = juce::FlexBox::AlignContent::flexStart;
flexBox.flexDirection = juce::FlexBox::Direction::row;
// Add each effect item as a FlexItem with flex-grow to fill available space
for (auto* item : effectItems)
{
flexBox.items.add(juce::FlexItem(*item)
.withMinWidth(MIN_ITEM_WIDTH)
.withHeight(ITEM_HEIGHT)
.withFlex(1.0f) // Allow items to grow to fill available space
.withMargin(juce::FlexItem::Margin(0)));
}
flexBox.performLayout(bounds.toFloat());
}
int EffectTypeGridComponent::calculateRequiredHeight(int availableWidth) const
{
if (effectItems.isEmpty())
return ITEM_HEIGHT;
// Calculate how many items can fit per row
int itemsPerRow = juce::jmax(1, availableWidth / MIN_ITEM_WIDTH);
// Calculate number of rows needed
int numRows = (effectItems.size() + itemsPerRow - 1) / itemsPerRow; // Ceiling division
return numRows * ITEM_HEIGHT;
}

Wyświetl plik

@ -0,0 +1,29 @@
#pragma once
#include <JuceHeader.h>
#include "../PluginProcessor.h"
#include "EffectTypeItemComponent.h"
class EffectTypeGridComponent : public juce::Component
{
public:
EffectTypeGridComponent(OscirenderAudioProcessor& processor);
~EffectTypeGridComponent() override;
void paint(juce::Graphics& g) override;
void resized() override;
int calculateRequiredHeight(int availableWidth) const;
std::function<void(const juce::String& effectId)> onEffectSelected;
private:
OscirenderAudioProcessor& audioProcessor;
juce::OwnedArray<EffectTypeItemComponent> effectItems;
juce::FlexBox flexBox;
static constexpr int ITEM_HEIGHT = 80;
static constexpr int MIN_ITEM_WIDTH = 180;
void setupEffectItems();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectTypeGridComponent)
};

Wyświetl plik

@ -0,0 +1,120 @@
#include "EffectTypeItemComponent.h"
EffectTypeItemComponent::EffectTypeItemComponent(const juce::String& name, const juce::String& id)
: effectName(name), effectId(id),
hoverAnimator(juce::ValueAnimatorBuilder{}
.withEasing(juce::Easings::createEaseOut())
.withDurationMs(200)
.withValueChangedCallback([this](auto value) {
animationProgress = static_cast<float>(value);
repaint();
})
.build()),
unhoverAnimator(juce::ValueAnimatorBuilder{}
.withEasing(juce::Easings::createEaseOut())
.withDurationMs(200)
.withValueChangedCallback([this](auto value) {
animationProgress = 1.0f - static_cast<float>(value);
repaint();
})
.build())
{
setupAnimators();
}
EffectTypeItemComponent::~EffectTypeItemComponent() = default;
void EffectTypeItemComponent::setupAnimators()
{
animatorUpdater.addAnimator(hoverAnimator);
animatorUpdater.addAnimator(unhoverAnimator);
}
void EffectTypeItemComponent::animateHover(bool isHovering)
{
if (isHovering)
{
unhoverAnimator.complete();
hoverAnimator.start();
}
else
{
hoverAnimator.complete();
unhoverAnimator.start();
}
}
void EffectTypeItemComponent::paint(juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat().reduced(10);
// Apply upward shift based on animation progress
auto yOffset = -animationProgress * HOVER_LIFT_AMOUNT;
bounds = bounds.translated(0, yOffset);
// Draw drop shadow
if (animationProgress > 0.01f) {
juce::DropShadow shadow;
shadow.colour = juce::Colours::lime.withAlpha(animationProgress * 0.2f);
shadow.radius = 15 * animationProgress;
shadow.offset = juce::Point<int>(0, 4);
juce::Path shadowPath;
shadowPath.addRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
shadow.drawForPath(g, shadowPath);
}
// Draw background with rounded corners - interpolate between normal and hover colors
juce::Colour normalBgColour = juce::Colour::fromRGB(25, 25, 25);
juce::Colour hoverBgColour = juce::Colour::fromRGB(40, 40, 40);
juce::Colour bgColour = normalBgColour.interpolatedWith(hoverBgColour, animationProgress);
g.setColour(bgColour);
g.fillRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
// Draw colored outline
juce::Colour outlineColour = juce::Colour::fromRGB(160, 160, 160);
g.setColour(outlineColour.withAlpha(0.9f));
g.drawRoundedRectangle(bounds.toFloat(), CORNER_RADIUS, 1.0f);
// Create areas for text (left) and icon (right)
auto textArea = bounds.reduced(8, 4);
auto iconArea = textArea.removeFromRight(20); // Reserve 20px for icon on right
textArea = textArea.withTrimmedRight(4); // Add small gap between text and icon
g.setColour(juce::Colours::white);
g.setFont(juce::FontOptions(16.0f, juce::Font::plain));
g.drawText(effectName, textArea, juce::Justification::centred, true);
// Draw placeholder icon (simple circle with "+" symbol)
g.setColour(juce::Colours::white.withAlpha(0.7f));
auto iconSize = juce::jmin(iconArea.getWidth(), iconArea.getHeight()) - 4;
auto iconBounds = iconArea.withSizeKeepingCentre(iconSize, iconSize);
g.drawEllipse(iconBounds.toFloat(), 1.5f);
// Draw "+" symbol in the circle
auto centerX = iconBounds.getCentreX();
auto centerY = iconBounds.getCentreY();
auto halfSize = iconSize * 0.25f;
g.drawLine(centerX - halfSize, centerY, centerX + halfSize, centerY, 2.0f);
g.drawLine(centerX, centerY - halfSize, centerX, centerY + halfSize, 2.0f);
}
void EffectTypeItemComponent::mouseEnter(const juce::MouseEvent& event)
{
isHovered = true;
animateHover(true);
}
void EffectTypeItemComponent::mouseExit(const juce::MouseEvent& event)
{
isHovered = false;
animateHover(false);
}
void EffectTypeItemComponent::mouseDown(const juce::MouseEvent& event)
{
if (onEffectSelected)
onEffectSelected(effectId);
}

Wyświetl plik

@ -0,0 +1,38 @@
#pragma once
#include <JuceHeader.h>
class EffectTypeItemComponent : public juce::Component
{
public:
EffectTypeItemComponent(const juce::String& name, const juce::String& id);
~EffectTypeItemComponent() override;
void paint(juce::Graphics& g) override;
void mouseEnter(const juce::MouseEvent& event) override;
void mouseExit(const juce::MouseEvent& event) override;
void mouseDown(const juce::MouseEvent& event) override;
const juce::String& getEffectId() const { return effectId; }
const juce::String& getEffectName() const { return effectName; }
std::function<void(const juce::String& effectId)> onEffectSelected;
private:
juce::String effectName;
juce::String effectId;
float animationProgress = 0.0f;
bool isHovered = false;
// Animation components
juce::VBlankAnimatorUpdater animatorUpdater { this };
juce::Animator hoverAnimator;
juce::Animator unhoverAnimator;
static constexpr int CORNER_RADIUS = 8;
static constexpr float HOVER_LIFT_AMOUNT = 2.0f;
void setupAnimators();
void animateHover(bool isHovering);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectTypeItemComponent)
};

Wyświetl plik

@ -2,27 +2,28 @@
#include "SvgButton.h" #include "SvgButton.h"
#include "../PluginEditor.h" #include "../PluginEditor.h"
#include "../LookAndFeel.h" #include "../LookAndFeel.h"
#include "EffectTypeGridComponent.h"
EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, osci::Effect& effect) : DraggableListBoxItem(lb, data, rn), EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, osci::Effect& effect) : DraggableListBoxItem(lb, data, rn),
effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) { effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) {
auto parameters = effect.parameters; auto parameters = effect.parameters;
for (int i = 0; i < parameters.size(); i++) { for (int i = 0; i < parameters.size(); i++) {
std::shared_ptr<EffectComponent> effectComponent = std::make_shared<EffectComponent>(effect, i); std::shared_ptr<EffectComponent> effectComponent = std::make_shared<EffectComponent>(effect, i);
selected.setToggleState(effect.enabled == nullptr || effect.enabled->getValue(), juce::dontSendNotification); selected.setToggleState(effect.enabled == nullptr || effect.enabled->getValue(), juce::dontSendNotification);
// using weak_ptr to avoid circular reference and memory leak // using weak_ptr to avoid circular reference and memory leak
std::weak_ptr<EffectComponent> weakEffectComponent = effectComponent; std::weak_ptr<EffectComponent> weakEffectComponent = effectComponent;
effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification); effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification);
list.setEnabled(selected.getToggleState()); list.setEnabled(selected.getToggleState());
selected.onClick = [this, weakEffectComponent] { selected.onClick = [this, weakEffectComponent] {
if (auto effectComponent = weakEffectComponent.lock()) { if (auto effectComponent = weakEffectComponent.lock()) {
auto data = (AudioEffectListBoxItemData&)modelData; auto data = (AudioEffectListBoxItemData&)modelData;
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
data.setSelected(rowNum, selected.getToggleState()); data.setSelected(rowNum, selected.getToggleState());
list.setEnabled(selected.getToggleState()); list.setEnabled(selected.getToggleState());
} }
repaint(); repaint();
}; };
effectComponent->updateToggleState = [this, i, weakEffectComponent] { effectComponent->updateToggleState = [this, i, weakEffectComponent] {
if (auto effectComponent = weakEffectComponent.lock()) { if (auto effectComponent = weakEffectComponent.lock()) {
selected.setToggleState(effectComponent->effect.enabled == nullptr || effectComponent->effect.enabled->getValue(), juce::dontSendNotification); selected.setToggleState(effectComponent->effect.enabled == nullptr || effectComponent->effect.enabled->getValue(), juce::dontSendNotification);
@ -31,20 +32,20 @@ effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) {
repaint(); repaint();
}; };
auto component = createComponent(parameters[i]); auto component = createComponent(parameters[i]);
if (component != nullptr) { if (component != nullptr) {
effectComponent->setComponent(component); effectComponent->setComponent(component);
} }
listModel.addComponent(effectComponent); listModel.addComponent(effectComponent);
} }
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.setRowHeight(ROW_HEIGHT);
list.updateContent(); list.updateContent();
addAndMakeVisible(list); addAndMakeVisible(list);
addAndMakeVisible(selected); addAndMakeVisible(selected);
} }
EffectsListComponent::~EffectsListComponent() { EffectsListComponent::~EffectsListComponent() {
@ -52,26 +53,26 @@ EffectsListComponent::~EffectsListComponent() {
} }
void EffectsListComponent::paint(juce::Graphics& g) { void EffectsListComponent::paint(juce::Graphics& g) {
auto bounds = getLocalBounds().removeFromLeft(LEFT_BAR_WIDTH); auto bounds = getLocalBounds().removeFromLeft(LEFT_BAR_WIDTH);
g.setColour(findColour(effectComponentHandleColourId)); g.setColour(findColour(effectComponentHandleColourId));
bounds.removeFromBottom(PADDING); bounds.removeFromBottom(PADDING);
juce::Path path; juce::Path path;
path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, true, false, true, false); path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, true, false, true, false);
g.fillPath(path); g.fillPath(path);
g.setColour(juce::Colours::white); g.setColour(juce::Colours::white);
// draw drag and drop handle using circles // draw drag and drop handle using circles
double size = 4; double size = 4;
double leftPad = 4; double leftPad = 4;
double spacing = 7; double spacing = 7;
double topPad = 6; double topPad = 6;
double y = bounds.getHeight() / 2 - 15; double y = bounds.getHeight() / 2 - 15;
g.fillEllipse(leftPad, y + topPad, size, size); g.fillEllipse(leftPad, y + topPad, size, size);
g.fillEllipse(leftPad, y + topPad + spacing, size, size); g.fillEllipse(leftPad, y + topPad + spacing, size, size);
g.fillEllipse(leftPad, y + topPad + 2 * spacing, size, size); g.fillEllipse(leftPad, y + topPad + 2 * spacing, size, size);
g.fillEllipse(leftPad + spacing, y + topPad, size, size); g.fillEllipse(leftPad + spacing, y + topPad, size, size);
g.fillEllipse(leftPad + spacing, y + topPad + spacing, size, size); g.fillEllipse(leftPad + spacing, y + topPad + spacing, size, size);
g.fillEllipse(leftPad + spacing, y + topPad + 2 * spacing, size, size); g.fillEllipse(leftPad + spacing, y + topPad + 2 * spacing, size, size);
DraggableListBoxItem::paint(g); DraggableListBoxItem::paint(g);
} }
void EffectsListComponent::paintOverChildren(juce::Graphics& g) { void EffectsListComponent::paintOverChildren(juce::Graphics& g) {
@ -81,51 +82,78 @@ void EffectsListComponent::paintOverChildren(juce::Graphics& g) {
juce::Path path; juce::Path path;
path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, false, true, false, true); path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, false, true, false, true);
if (!selected.getToggleState()) { if (!selected.getToggleState()) {
g.fillPath(path); g.fillPath(path);
} }
} }
void EffectsListComponent::resized() { void EffectsListComponent::resized() {
auto area = getLocalBounds(); auto area = getLocalBounds();
auto leftBar = area.removeFromLeft(LEFT_BAR_WIDTH); auto leftBar = area.removeFromLeft(LEFT_BAR_WIDTH);
leftBar.removeFromLeft(20); leftBar.removeFromLeft(20);
area.removeFromRight(PADDING); area.removeFromRight(PADDING);
selected.setBounds(leftBar.withSizeKeepingCentre(30, 20)); selected.setBounds(leftBar.withSizeKeepingCentre(30, 20));
list.setBounds(area); list.setBounds(area);
} }
std::shared_ptr<juce::Component> EffectsListComponent::createComponent(osci::EffectParameter* parameter) { std::shared_ptr<juce::Component> EffectsListComponent::createComponent(osci::EffectParameter* parameter) {
if (parameter->paramID == "customEffectStrength") { if (parameter->paramID == "customEffectStrength") {
std::shared_ptr<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::pencil_svg, juce::Colours::white, juce::Colours::red); std::shared_ptr<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::pencil_svg, juce::Colours::white, juce::Colours::red);
std::weak_ptr<SvgButton> weakButton = button; std::weak_ptr<SvgButton> weakButton = button;
button->setEdgeIndent(5); button->setEdgeIndent(5);
button->setToggleState(editor.editingCustomFunction, juce::dontSendNotification); button->setToggleState(editor.editingCustomFunction, juce::dontSendNotification);
button->setTooltip("Toggles whether the text editor is editing the currently open file, or the custom Lua effect."); button->setTooltip("Toggles whether the text editor is editing the currently open file, or the custom Lua effect.");
button->onClick = [this, weakButton] { button->onClick = [this, weakButton] {
if (auto button = weakButton.lock()) { if (auto button = weakButton.lock()) {
editor.editCustomFunction(button->getToggleState()); editor.editCustomFunction(button->getToggleState());
} }
}; };
return button; return button;
} }
return nullptr; return nullptr;
} }
int EffectsListBoxModel::getRowHeight(int row) { int EffectsListBoxModel::getRowHeight(int row) {
auto data = (AudioEffectListBoxItemData&)modelData; auto data = (AudioEffectListBoxItemData&)modelData;
return data.getEffect(row)->parameters.size() * EffectsListComponent::ROW_HEIGHT + EffectsListComponent::PADDING; if (row == data.getNumItems() - 1) {
// Effect type grid row - calculate dynamic height based on layout
// Get the available width from the listbox
int availableWidth = listBox.getWidth() - 20; // Account for scrollbar and margins
// Create a temporary grid component to calculate required height
EffectTypeGridComponent tempGrid(data.audioProcessor);
return tempGrid.calculateRequiredHeight(availableWidth);
}
return data.getEffect(row)->parameters.size() * EffectsListComponent::ROW_HEIGHT + EffectsListComponent::PADDING;
} }
bool EffectsListBoxModel::hasVariableHeightRows() const { bool EffectsListBoxModel::hasVariableHeightRows() const {
return true; return true;
} }
juce::Component* EffectsListBoxModel::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component *existingComponentToUpdate) { juce::Component* EffectsListBoxModel::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component *existingComponentToUpdate) {
std::unique_ptr<EffectsListComponent> item(dynamic_cast<EffectsListComponent*>(existingComponentToUpdate)); auto data = (AudioEffectListBoxItemData&)modelData;
if (juce::isPositiveAndBelow(rowNumber, modelData.getNumItems())) {
auto data = (AudioEffectListBoxItemData&)modelData; if (juce::isPositiveAndBelow(rowNumber, data.getNumItems() - 1)) {
item = std::make_unique<EffectsListComponent>(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, *data.getEffect(rowNumber)); // Regular effect component
std::unique_ptr<EffectsListComponent> item(dynamic_cast<EffectsListComponent*>(existingComponentToUpdate));
item = std::make_unique<EffectsListComponent>(listBox, data, rowNumber, *data.getEffect(rowNumber));
return item.release();
} else if (rowNumber == data.getNumItems() - 1) {
// Create the effect type grid component
std::unique_ptr<EffectTypeGridComponent> gridComponent(dynamic_cast<EffectTypeGridComponent*>(existingComponentToUpdate));
if (gridComponent == nullptr) {
gridComponent = std::make_unique<EffectTypeGridComponent>(data.audioProcessor);
// Set up callback for when an effect is selected
gridComponent->onEffectSelected = [&data](const juce::String& effectId) {
// TODO: Implement adding new effect instance based on effectId
// This would need to be implemented based on how effects are created in the system
DBG("Effect selected: " + effectId);
};
}
return gridComponent.release();
} }
return item.release();
return nullptr;
} }

Wyświetl plik

@ -5,6 +5,7 @@
#include "EffectComponent.h" #include "EffectComponent.h"
#include "ComponentList.h" #include "ComponentList.h"
#include "SwitchButton.h" #include "SwitchButton.h"
#include "EffectTypeGridComponent.h"
#include <random> #include <random>
// Application-specific data container // Application-specific data container
@ -22,9 +23,9 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
void randomise() { void randomise() {
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
auto effect = data[i]; auto effect = data[i];
auto id = effect->getId().toLowerCase(); auto id = effect->getId().toLowerCase();
if (id.contains("scale") || id.contains("translate") || id.contains("trace")) { if (id.contains("scale") || id.contains("translate") || id.contains("trace")) {
continue; continue;
@ -42,19 +43,19 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
} }
} }
} }
effect->enabled->setValueNotifyingHost(juce::Random::getSystemRandom().nextFloat() > 0.7); effect->enabled->setValueNotifyingHost(juce::Random::getSystemRandom().nextFloat() > 0.7);
} }
// shuffle precedence // shuffle precedence
std::random_device rd; std::random_device rd;
std::mt19937 g(rd()); std::mt19937 g(rd());
std::shuffle(data.begin(), data.end(), g); std::shuffle(data.begin(), data.end(), g);
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
data[i]->setPrecedence(i); data[i]->setPrecedence(i);
} }
audioProcessor.updateEffectPrecedence(); audioProcessor.updateEffectPrecedence();
} }
void resetData() { void resetData() {
@ -68,12 +69,12 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
} }
int getNumItems() override { int getNumItems() override {
return data.size(); return data.size() + 1;
} }
// CURRENTLY NOT USED // CURRENTLY NOT USED
void deleteItem(int indexOfItemToDelete) override { void deleteItem(int indexOfItemToDelete) override {
// data.erase(data.begin() + indexOfItemToDelete); // data.erase(data.begin() + indexOfItemToDelete);
} }
// CURRENTLY NOT USED // CURRENTLY NOT USED
@ -81,10 +82,10 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
// data.push_back(juce::String("Yahoo")); // data.push_back(juce::String("Yahoo"));
} }
void moveBefore(int indexOfItemToMove, int indexOfItemToPlaceBefore) override { void moveBefore(int indexOfItemToMove, int indexOfItemToPlaceBefore) override {
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
auto effect = data[indexOfItemToMove]; auto effect = data[indexOfItemToMove];
if (indexOfItemToMove < indexOfItemToPlaceBefore) { if (indexOfItemToMove < indexOfItemToPlaceBefore) {
move(data, indexOfItemToMove, indexOfItemToPlaceBefore - 1); move(data, indexOfItemToMove, indexOfItemToPlaceBefore - 1);
@ -92,12 +93,12 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
move(data, indexOfItemToMove, indexOfItemToPlaceBefore); move(data, indexOfItemToMove, indexOfItemToPlaceBefore);
} }
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
data[i]->setPrecedence(i); data[i]->setPrecedence(i);
} }
audioProcessor.updateEffectPrecedence(); audioProcessor.updateEffectPrecedence();
} }
void moveAfter(int indexOfItemToMove, int indexOfItemToPlaceAfter) override { void moveAfter(int indexOfItemToMove, int indexOfItemToPlaceAfter) override {
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);

@ -1 +1 @@
Subproject commit 913c3b052404818c45ad93c5e6022244d57df5e9 Subproject commit 56a625fcead4d7fd428a75afddf541363a0adf63

Wyświetl plik

@ -157,6 +157,14 @@
file="Source/components/EffectsListComponent.cpp"/> file="Source/components/EffectsListComponent.cpp"/>
<FILE id="dcLchL" name="EffectsListComponent.h" compile="0" resource="0" <FILE id="dcLchL" name="EffectsListComponent.h" compile="0" resource="0"
file="Source/components/EffectsListComponent.h"/> file="Source/components/EffectsListComponent.h"/>
<FILE id="xQQo5D" name="EffectTypeGridComponent.cpp" compile="1" resource="0"
file="Source/components/EffectTypeGridComponent.cpp"/>
<FILE id="Fb2Qci" name="EffectTypeGridComponent.h" compile="0" resource="0"
file="Source/components/EffectTypeGridComponent.h"/>
<FILE id="PPK1iQ" name="EffectTypeItemComponent.cpp" compile="1" resource="0"
file="Source/components/EffectTypeItemComponent.cpp"/>
<FILE id="kGAfsx" name="EffectTypeItemComponent.h" compile="0" resource="0"
file="Source/components/EffectTypeItemComponent.h"/>
<FILE id="aEprcE" name="ErrorCodeEditorComponent.h" compile="0" resource="0" <FILE id="aEprcE" name="ErrorCodeEditorComponent.h" compile="0" resource="0"
file="Source/components/ErrorCodeEditorComponent.h"/> file="Source/components/ErrorCodeEditorComponent.h"/>
<FILE id="L9DIT2" name="LabelledTextBox.h" compile="0" resource="0" <FILE id="L9DIT2" name="LabelledTextBox.h" compile="0" resource="0"