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 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
multiplexEffect->getParameter("gridPhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
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 Z", "Scales the depth of the object.", "scaleZ", VERSION_HINT, 1.0, -3.0, 3.0),
});
scaleEffect->setName("Scale");
scaleEffect->markLockable(true);
booleanParameters.push_back(scaleEffect->linked);
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 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);
booleanParameters.push_back(distortEffect->linked);
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 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);
toggleableEffects.push_back(rippleEffect);
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 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")->lfoRate->setUnnormalisedValueNotifyingHost(0.2);
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) {
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 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),
}));
});
translateEffect->setName("Translate");
toggleableEffects.push_back(translateEffect);
toggleableEffects.push_back(std::make_shared<osci::Effect>(
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
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 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);
toggleableEffects.push_back(wobble);
toggleableEffects.push_back(std::make_shared<osci::Effect>(
auto delay = std::make_shared<osci::Effect>(
delayEffect,
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 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>(
new osci::EffectParameter("Delay Length", "Controls the time in seconds between echos.", "delayLength", VERSION_HINT, 0.5, 0.0, 1.0)
});
delay->setName("Delay");
toggleableEffects.push_back(delay);
auto dashEffect = std::make_shared<osci::Effect>(
dashedLineEffect,
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),
}));
});
dashEffect->setName("Dash");
toggleableEffects.push_back(dashEffect);
toggleableEffects.push_back(custom);
toggleableEffects.push_back(trace);
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.setTextBoxStyle(juce::Slider::TextBoxRight, false, TEXT_BOX_WIDTH, slider.getTextBoxHeight());
slider.setScrollWheelEnabled(false);
if (effect.parameters[index]->step == 1.0) {
slider.setNumDecimalPlacesToDisplay(0);
} else {
@ -39,6 +40,7 @@ EffectComponent::EffectComponent(osci::Effect& effect, int index) : effect(effec
lfoSlider.setTextValueSuffix("Hz");
lfoSlider.setColour(sliderThumbOutlineColourId, juce::Colour(0xff00ff00));
lfoSlider.setNumDecimalPlacesToDisplay(3);
lfoSlider.setScrollWheelEnabled(false);
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,6 +2,7 @@
#include "SvgButton.h"
#include "../PluginEditor.h"
#include "../LookAndFeel.h"
#include "EffectTypeGridComponent.h"
EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, osci::Effect& effect) : DraggableListBoxItem(lb, data, rn),
effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) {
@ -114,6 +115,15 @@ std::shared_ptr<juce::Component> EffectsListComponent::createComponent(osci::Eff
int EffectsListBoxModel::getRowHeight(int row) {
auto data = (AudioEffectListBoxItemData&)modelData;
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;
}
@ -122,10 +132,28 @@ bool EffectsListBoxModel::hasVariableHeightRows() const {
}
juce::Component* EffectsListBoxModel::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component *existingComponentToUpdate) {
std::unique_ptr<EffectsListComponent> item(dynamic_cast<EffectsListComponent*>(existingComponentToUpdate));
if (juce::isPositiveAndBelow(rowNumber, modelData.getNumItems())) {
auto data = (AudioEffectListBoxItemData&)modelData;
item = std::make_unique<EffectsListComponent>(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, *data.getEffect(rowNumber));
}
if (juce::isPositiveAndBelow(rowNumber, data.getNumItems() - 1)) {
// 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 nullptr;
}

Wyświetl plik

@ -5,6 +5,7 @@
#include "EffectComponent.h"
#include "ComponentList.h"
#include "SwitchButton.h"
#include "EffectTypeGridComponent.h"
#include <random>
// Application-specific data container
@ -68,7 +69,7 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
}
int getNumItems() override {
return data.size();
return data.size() + 1;
}
// CURRENTLY NOT USED

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

Wyświetl plik

@ -157,6 +157,14 @@
file="Source/components/EffectsListComponent.cpp"/>
<FILE id="dcLchL" name="EffectsListComponent.h" compile="0" resource="0"
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="Source/components/ErrorCodeEditorComponent.h"/>
<FILE id="L9DIT2" name="LabelledTextBox.h" compile="0" resource="0"