|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 10V8H20V10H18M18 12V10H16V12H18M18 8V6H16V8H18M16 2.84V4H18C17.37 3.54 16.71 3.15 16 2.84M18 4V6H20C19.42 5.25 18.75 4.58 18 4M20 6V8H21.16C20.85 7.29 20.46 6.63 20 6M22 12C22 11.32 21.93 10.65 21.8 10H20V12H22M16 6V4H14V6H16M16 16H18V14H16V16M18 18H20L20 18V16H18V18M16 20H18L18 20V18H16V20M14 21.8C14.7 21.66 15.36 21.44 16 21.16V20H14V21.8M18 14H20V12H18V14M16 8H14V10H16V8M20 16H21.16C21.44 15.36 21.66 14.7 21.8 14H20V16M16 12H14V14H16V12M12 18V16H14V14H12V12H14V10H12V8H14V6H12V4H14V2.2C13.35 2.07 12.69 2 12 2C6.5 2 2 6.5 2 12S6.5 22 12 22V20H14V18H12M14 18H16V16H14V18Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 660 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19.59,7L12,14.59L6.41,9H11V7H3V15H5V10.41L12,17.41L21,8.41" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 138 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16,4L20,8V4M20,16L16,20H20M8,20L4,16V20M4,8L8,4H4M16.95,7.05C14.22,4.32 9.78,4.32 7.05,7.05C4.32,9.78 4.32,14.22 7.05,16.95C9.78,19.68 14.22,19.68 16.95,16.95C19.68,14.22 19.68,9.79 16.95,7.05M15.85,15.85C13.72,18 10.28,18 8.15,15.85C6,13.72 6,10.28 8.15,8.15C10.28,6 13.72,6 15.85,8.15C18,10.28 18,13.72 15.85,15.85Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 397 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 188 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2,4C2,2.89 2.9,2 4,2H7V4H4V7H2V4M22,4V7H20V4H17V2H20A2,2 0 0,1 22,4M20,20V17H22V20C22,21.11 21.1,22 20,22H17V20H20M2,20V17H4V20H7V22H4A2,2 0 0,1 2,20M10,2H14V4H10V2M10,20H14V22H10V20M20,10H22V14H20V10M2,10H4V14H2V10Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 296 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6,13H18V11H6M3,6V8H21V6M10,18H14V16H10V18Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 122 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.5 4.5L5.4 9.5L8.5 14.7L5.2 20.5L3.4 19.6L6.1 14.7L3 9.5L6.7 3.6L8.5 4.5M14.7 4.4L11.6 9.5L14.7 14.5L11.4 20.3L9.6 19.4L12.3 14.5L9.2 9.5L12.9 3.5L14.7 4.4M21 4.4L17.9 9.5L21 14.5L17.7 20.3L15.9 19.4L18.6 14.5L15.5 9.5L19.2 3.5L21 4.4" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 315 B |
Po Szerokość: | Wysokość: | Rozmiar: 5.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.5,5A8.5,8.5 0 0,0 2,13.5A8.5,8.5 0 0,0 10.5,22A8.5,8.5 0 0,0 19,13.5A8.5,8.5 0 0,0 10.5,5M13.5,13A2.5,2.5 0 0,1 11,10.5A2.5,2.5 0 0,1 13.5,8A2.5,2.5 0 0,1 16,10.5A2.5,2.5 0 0,1 13.5,13M19.5,2A2.5,2.5 0 0,0 17,4.5A2.5,2.5 0 0,0 19.5,7A2.5,2.5 0 0,0 22,4.5A2.5,2.5 0 0,0 19.5,2" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 358 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 16C13.1 16 14 16.9 14 18S13.1 20 12 20 10 19.1 10 18 10.9 16 12 16M12 10C13.1 10 14 10.9 14 12S13.1 14 12 14 10 13.1 10 12 10.9 10 12 10M12 4C13.1 4 14 4.9 14 6S13.1 8 12 8 10 7.1 10 6 10.9 4 12 4M6 16C7.1 16 8 16.9 8 18S7.1 20 6 20 4 19.1 4 18 4.9 16 6 16M6 10C7.1 10 8 10.9 8 12S7.1 14 6 14 4 13.1 4 12 4.9 10 6 10M6 4C7.1 4 8 4.9 8 6S7.1 8 6 8 4 7.1 4 6 4.9 4 6 4M18 16C19.1 16 20 16.9 20 18S19.1 20 18 20 16 19.1 16 18 16.9 16 18 16M18 10C19.1 10 20 10.9 20 12S19.1 14 18 14 16 13.1 16 12 16.9 10 18 10M18 4C19.1 4 20 4.9 20 6S19.1 8 18 8 16 7.1 16 6 16.9 4 18 4Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 650 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,6A11,11 0 0,0 1,17H3C3,12.04 7.04,8 12,8C16.96,8 21,12.04 21,17H23A11,11 0 0,0 12,6M12,10C8.14,10 5,13.14 5,17H7A5,5 0 0,1 12,12A5,5 0 0,1 17,17H19C19,13.14 15.86,10 12,10Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 255 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7C6.5 7 2 9.2 2 12C2 14.2 4.9 16.1 9 16.8V20L13 16L9 12V14.7C5.8 14.1 4 12.8 4 12C4 10.9 7 9 12 9S20 10.9 20 12C20 12.7 18.5 13.9 16 14.5V16.6C19.5 15.8 22 14.1 22 12C22 9.2 17.5 7 12 7Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 269 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,2L16,6H13V13.85L19.53,17.61L21,15.03L22.5,20.5L17,21.96L18.53,19.35L12,15.58L5.47,19.35L7,21.96L1.5,20.5L3,15.03L4.47,17.61L11,13.85V6H8L12,2Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 225 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 3H16C18.76 3 21 5.24 21 8V16C21 18.76 18.76 21 16 21H8C5.24 21 3 18.76 3 16V8C3 5.24 5.24 3 8 3M8 5C6.34 5 5 6.34 5 8V16C5 17.66 6.34 19 8 19H16C17.66 19 19 17.66 19 16V8C19 6.34 17.66 5 16 5H8Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 276 B |
|
@ -0,0 +1,7 @@
|
|||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="Laravelnova--Streamline-Simple-Icons" height="24" width="24">
|
||||
<desc>
|
||||
Laravelnova Streamline Icon: https://streamlinehq.com
|
||||
</desc>
|
||||
<title>Laravel Nova</title>
|
||||
<path d="M21.333 4.319C16.56 0.386 9.453 0.632 4.973 5.057a7.571 7.571 0 0 0 0 10.8c3.018 2.982 7.912 2.982 10.931 0a3.245 3.245 0 0 0 0 -4.628 3.342 3.342 0 0 0 -4.685 0 1.114 1.114 0 0 1 -1.561 0 1.082 1.082 0 0 1 0 -1.543 5.57 5.57 0 0 1 7.808 0 5.408 5.408 0 0 1 0 7.714c-3.881 3.834 -10.174 3.834 -14.055 0a9.734 9.734 0 0 1 -0.015 -13.87C5.596 1.35 8.638 0 12 0c3.75 0 7.105 1.68 9.333 4.319zm-0.714 16.136A12.184 12.184 0 0 1 12 24a12.18 12.18 0 0 1 -9.333 -4.319c4.772 3.933 11.88 3.687 16.36 -0.738a7.571 7.571 0 0 0 0 -10.8c-3.018 -2.982 -7.912 -2.982 -10.931 0a3.245 3.245 0 0 0 0 4.628 3.342 3.342 0 0 0 4.685 0 1.114 1.114 0 0 1 1.561 0 1.082 1.082 0 0 1 0 1.543 5.57 5.57 0 0 1 -7.808 0 5.408 5.408 0 0 1 0 -7.714c3.881 -3.834 10.174 -3.834 14.055 0a9.734 9.734 0 0 1 0.03 13.855z" fill="#000000" stroke-width="1"></path>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 1.1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.75 20.85C11.53 20.15 11.14 18.22 10.24 17C9.35 15.75 8.12 14.89 6.88 14.06C6 13.5 5.19 12.8 4.54 12C4.26 11.67 3.69 11.06 4.27 10.94C4.86 10.82 5.88 11.4 6.4 11.62C7.31 12 8.21 12.44 9.05 12.96L10.06 11.26C8.5 10.23 6.5 9.32 4.64 9.05C3.58 8.89 2.46 9.11 2.1 10.26C1.78 11.25 2.29 12.25 2.87 13.03C4.24 14.86 6.37 15.74 7.96 17.32C8.3 17.65 8.71 18.04 8.91 18.5C9.12 18.94 9.07 18.97 8.6 18.97C7.36 18.97 5.81 18 4.8 17.36L3.79 19.06C5.32 20 7.88 21.47 9.75 20.85M20.84 5.25C21.06 5.03 21.06 4.67 20.84 4.46L19.54 3.16C19.33 2.95 18.97 2.95 18.76 3.16L17.74 4.18L19.82 6.26M11 10.92V13H13.08L19.23 6.85L17.15 4.77L11 10.92Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 705 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6.45,17.45L1,12L6.45,6.55L7.86,7.96L4.83,11H19.17L16.14,7.96L17.55,6.55L23,12L17.55,17.45L16.14,16.04L19.17,13H4.83L7.86,16.04L6.45,17.45Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 218 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13.41,19.31L16.59,22.5L18,21.07L14.83,17.9M15.54,11.53H15.53L12,15.07L8.47,11.53H8.46V11.53C7.56,10.63 7,9.38 7,8A5,5 0 0,1 12,3A5,5 0 0,1 17,8C17,9.38 16.44,10.63 15.54,11.53M16.9,13C18.2,11.73 19,9.96 19,8A7,7 0 0,0 12,1A7,7 0 0,0 5,8C5,9.96 5.81,11.73 7.1,13V13L10.59,16.5L6,21.07L7.41,22.5L16.9,13Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 382 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 14H9L5 18L1 14H4C4 11.3 5.7 6.6 11 6.1V8.1C7.6 8.6 6 11.9 6 14M20 14C20 11.3 18.3 6.6 13 6.1V8.1C16.4 8.7 18 11.9 18 14H15L19 18L23 14H20Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 220 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.5,21C13.5,21 12.31,16.76 11.05,12.28C10.14,9.04 9,5 7.5,5C4.11,5 4,11.93 4,12H2C2,11.63 2.06,3 7.5,3C10.5,3 11.71,7.25 12.97,11.74C13.83,14.8 15,19 16.5,19C19.94,19 20.03,12.07 20.03,12H22.03C22.03,12.37 21.97,21 16.5,21Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 304 B |
|
@ -60,7 +60,7 @@ CommonPluginEditor::CommonPluginEditor(CommonAudioProcessor& p, juce::String app
|
|||
setResizeLimits(250, 250, 999999, 999999);
|
||||
|
||||
tooltipDropShadow.setOwner(&tooltipWindow.get());
|
||||
tooltipWindow->setMillisecondsBeforeTipAppears(0);
|
||||
tooltipWindow->setMillisecondsBeforeTipAppears(100);
|
||||
|
||||
updateTitle();
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ EffectPluginEditor::EffectPluginEditor(EffectAudioProcessor& p)
|
|||
setResizable(false, false);
|
||||
|
||||
tooltipDropShadow.setOwner(&tooltipWindow.get());
|
||||
tooltipWindow->setMillisecondsBeforeTipAppears(0);
|
||||
tooltipWindow->setMillisecondsBeforeTipAppears(100);
|
||||
|
||||
audioProcessor.bitCrush->addListener(0, this);
|
||||
}
|
||||
|
|
|
@ -40,19 +40,9 @@ public:
|
|||
int getCurrentProgram() override;
|
||||
void setCurrentProgram(int index) override;
|
||||
const juce::String getProgramName(int index) override;
|
||||
void changeProgramName(int index, const juce::String& newName) override; std::shared_ptr<osci::Effect> bitCrush = std::make_shared<osci::Effect>(
|
||||
std::make_shared<BitCrushEffect>(),
|
||||
new osci::EffectParameter("Bit Crush", "Limits the resolution of points drawn to the screen, making the object look pixelated, and making the audio sound more 'digital' and distorted.", "bitCrush", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
);
|
||||
void changeProgramName(int index, const juce::String& newName) override; std::shared_ptr<osci::Effect> bitCrush = BitCrushEffect().build();
|
||||
|
||||
std::shared_ptr<osci::Effect> autoGain = std::make_shared<osci::Effect>(
|
||||
std::make_shared<AutoGainControlEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Intensity", "Controls how aggressively the gain adjustment is applied", "agcIntensity", VERSION_HINT, 1.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("Target Level", "Target output level for the automatic gain control", "agcTarget", VERSION_HINT, 0.6, 0.0, 1.0),
|
||||
new osci::EffectParameter("Response", "How quickly the effect responds to level changes (lower is slower)", "agcResponse", VERSION_HINT, 0.0001, 0.0, 1.0)
|
||||
}
|
||||
);
|
||||
std::shared_ptr<osci::Effect> autoGain = AutoGainControlEffect().build();
|
||||
|
||||
VisualiserParameters visualiserParameters;
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
#include "audio/BitCrushEffect.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), itemData(p, editor), listBoxModel(listBox, itemData) {
|
||||
EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor)
|
||||
: audioProcessor(p), itemData(p, editor), listBoxModel(listBox, itemData), grid(p) {
|
||||
setText("Audio Effects");
|
||||
|
||||
addAndMakeVisible(frequency);
|
||||
|
@ -11,14 +12,6 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP
|
|||
frequency.slider.setTextValueSuffix("Hz");
|
||||
frequency.slider.setValue(audioProcessor.frequencyEffect->getValue(), juce::dontSendNotification);
|
||||
|
||||
/*addBtn.setButtonText("Add Item...");
|
||||
addBtn.onClick = [this]()
|
||||
{
|
||||
itemData.data.push_back(juce::String("Item " + juce::String(1 + itemData.getNumItems())));
|
||||
listBox.updateContent();
|
||||
};
|
||||
addAndMakeVisible(addBtn);*/
|
||||
|
||||
addAndMakeVisible(randomiseButton);
|
||||
|
||||
randomiseButton.setTooltip("Randomise all effect parameter values, randomise which effects are enabled, and randomise their order.");
|
||||
|
@ -33,8 +26,93 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP
|
|||
audioProcessor.broadcaster.addChangeListener(this);
|
||||
}
|
||||
|
||||
// Wire list model to notify when user wants to add
|
||||
itemData.onAddNewEffectRequested = [this]() {
|
||||
showingGrid = true;
|
||||
grid.setVisible(true);
|
||||
grid.refreshDisabledStates();
|
||||
listBox.setVisible(false);
|
||||
resized();
|
||||
repaint();
|
||||
};
|
||||
|
||||
// Decide initial view: show grid only if there are no selected effects
|
||||
bool anySelected = false;
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
for (const auto& eff : audioProcessor.toggleableEffects) {
|
||||
const bool isSelected = (eff->selected == nullptr) ? true : eff->selected->getBoolValue();
|
||||
if (isSelected) { anySelected = true; break; }
|
||||
}
|
||||
}
|
||||
showingGrid = !anySelected;
|
||||
grid.onEffectSelected = [this](const juce::String& effectId) {
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
// Mark the chosen effect as selected and enabled, and move it to the end
|
||||
std::shared_ptr<osci::Effect> chosen;
|
||||
for (auto& eff : audioProcessor.toggleableEffects) {
|
||||
if (eff->getId() == effectId) {
|
||||
eff->selected->setBoolValueNotifyingHost(true);
|
||||
eff->enabled->setBoolValueNotifyingHost(true);
|
||||
chosen = eff;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Place chosen effect at the end of the visible (selected) list and update precedence
|
||||
if (chosen != nullptr) {
|
||||
int idx = 0;
|
||||
for (auto& e : itemData.data) {
|
||||
if (e != chosen) e->setPrecedence(idx++);
|
||||
}
|
||||
chosen->setPrecedence(idx++);
|
||||
audioProcessor.updateEffectPrecedence();
|
||||
}
|
||||
}
|
||||
// Refresh list content to include newly selected
|
||||
itemData.resetData();
|
||||
listBox.updateContent();
|
||||
showingGrid = false;
|
||||
listBox.setVisible(true);
|
||||
grid.setVisible(false);
|
||||
resized();
|
||||
repaint();
|
||||
};
|
||||
grid.onCanceled = [this]() {
|
||||
// If canceled while default grid, just show list
|
||||
showingGrid = false;
|
||||
listBox.setVisible(true);
|
||||
grid.setVisible(false);
|
||||
resized();
|
||||
repaint();
|
||||
};
|
||||
|
||||
listBox.setModel(&listBoxModel);
|
||||
addAndMakeVisible(listBox);
|
||||
// Add a small top spacer so the drop indicator can be visible above the first row
|
||||
{
|
||||
auto spacer = std::make_unique<juce::Component>();
|
||||
spacer->setSize(1, LIST_SPACER); // top padding
|
||||
listBox.setHeaderComponent(std::move(spacer));
|
||||
}
|
||||
// Setup scroll fade mixin
|
||||
initScrollFade(*this);
|
||||
attachToListBox(listBox);
|
||||
// Wire "+ Add new effect" button below the list
|
||||
addEffectButton.onClick = [this]() {
|
||||
if (itemData.onAddNewEffectRequested) itemData.onAddNewEffectRequested();
|
||||
};
|
||||
addAndMakeVisible(addEffectButton);
|
||||
addAndMakeVisible(grid);
|
||||
// Keep disabled states in sync whenever grid is shown
|
||||
if (showingGrid) {
|
||||
grid.setVisible(true);
|
||||
grid.refreshDisabledStates();
|
||||
listBox.setVisible(false);
|
||||
} else {
|
||||
grid.setVisible(false);
|
||||
listBox.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
EffectsComponent::~EffectsComponent() {
|
||||
|
@ -52,10 +130,28 @@ void EffectsComponent::resized() {
|
|||
frequency.setBounds(area.removeFromTop(30));
|
||||
|
||||
area.removeFromTop(6);
|
||||
listBox.setBounds(area);
|
||||
if (showingGrid) {
|
||||
grid.setBounds(area);
|
||||
addEffectButton.setVisible(false);
|
||||
// Hide fade when grid is shown
|
||||
setScrollFadeVisible(false);
|
||||
} else {
|
||||
// Reserve space at bottom for the add button
|
||||
auto addBtnHeight = 44;
|
||||
auto listArea = area;
|
||||
auto buttonArea = listArea.removeFromBottom(addBtnHeight);
|
||||
listBox.setBounds(listArea);
|
||||
// Layout bottom fade overlay; visible if list is scrollable
|
||||
layoutScrollFade(listArea.withTrimmedTop(LIST_SPACER), true, 48);
|
||||
addEffectButton.setVisible(true);
|
||||
addEffectButton.setBounds(buttonArea.reduced(0, 4));
|
||||
}
|
||||
}
|
||||
|
||||
void EffectsComponent::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
||||
itemData.resetData();
|
||||
listBox.updateContent();
|
||||
// Re-layout scroll fades after content changes
|
||||
if (! showingGrid)
|
||||
layoutScrollFade(listBox.getBounds().withTrimmedTop(LIST_SPACER), true, 48);
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
#include "PluginProcessor.h"
|
||||
#include "components/DraggableListBox.h"
|
||||
#include "components/EffectsListComponent.h"
|
||||
#include "components/ScrollFadeMixin.h"
|
||||
#include "components/EffectTypeGridComponent.h"
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class EffectsComponent : public juce::GroupComponent, public juce::ChangeListener {
|
||||
class EffectsComponent : public juce::GroupComponent, public juce::ChangeListener, private ScrollFadeMixin {
|
||||
public:
|
||||
EffectsComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&);
|
||||
~EffectsComponent() override;
|
||||
|
@ -26,6 +28,11 @@ private:
|
|||
AudioEffectListBoxItemData itemData;
|
||||
EffectsListBoxModel listBoxModel;
|
||||
DraggableListBox listBox;
|
||||
juce::TextButton addEffectButton { "+ Add new effect" }; // Separate button under the list
|
||||
EffectTypeGridComponent grid { audioProcessor };
|
||||
bool showingGrid = true; // show grid by default
|
||||
|
||||
const int LIST_SPACER = 4; // Space above the list to show drop indicator
|
||||
|
||||
EffectComponent frequency = EffectComponent(*audioProcessor.frequencyEffect, false);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ OscirenderLookAndFeel::OscirenderLookAndFeel() {
|
|||
setColour(juce::PopupMenu::backgroundColourId, Colours::darker);
|
||||
setColour(juce::PopupMenu::highlightedBackgroundColourId, Colours::grey);
|
||||
setColour(juce::TooltipWindow::backgroundColourId, Colours::darker);
|
||||
setColour(juce::TooltipWindow::outlineColourId, juce::Colours::white);
|
||||
setColour(juce::TooltipWindow::outlineColourId, Colours::darker);
|
||||
setColour(juce::TextButton::buttonOnColourId, Colours::darker);
|
||||
setColour(juce::AlertWindow::outlineColourId, Colours::darker);
|
||||
setColour(juce::AlertWindow::backgroundColourId, Colours::darker);
|
||||
|
|
|
@ -17,7 +17,7 @@ void OscirenderAudioProcessorEditor::registerFileRemovedCallback() {
|
|||
});
|
||||
}
|
||||
|
||||
OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p) : CommonPluginEditor(p, "osci-render", "osci", 1100, 750), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white) {
|
||||
OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p) : CommonPluginEditor(p, "osci-render", "osci", 1100, 770), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white) {
|
||||
// Register the file removal callback
|
||||
registerFileRemovedCallback();
|
||||
|
||||
|
|
|
@ -13,11 +13,18 @@
|
|||
#include "audio/BulgeEffect.h"
|
||||
#include "audio/TwistEffect.h"
|
||||
#include "audio/DistortEffect.h"
|
||||
#include "audio/KaleidoscopeEffect.h"
|
||||
#include "audio/MultiplexEffect.h"
|
||||
#include "audio/SmoothEffect.h"
|
||||
#include "audio/WobbleEffect.h"
|
||||
#include "audio/DashedLineEffect.h"
|
||||
#include "audio/VectorCancellingEffect.h"
|
||||
#include "audio/ScaleEffect.h"
|
||||
#include "audio/RotateEffect.h"
|
||||
#include "audio/TranslateEffect.h"
|
||||
#include "audio/RippleEffect.h"
|
||||
#include "audio/SwirlEffect.h"
|
||||
#include "audio/BounceEffect.h"
|
||||
#include "parser/FileParser.h"
|
||||
#include "parser/FrameProducer.h"
|
||||
|
||||
|
@ -29,138 +36,39 @@
|
|||
OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(BusesProperties().withInput("Input", juce::AudioChannelSet::namedChannelSet(2), true).withOutput("Output", juce::AudioChannelSet::stereo(), true)) {
|
||||
// locking isn't necessary here because we are in the constructor
|
||||
|
||||
toggleableEffects.push_back(std::make_shared<osci::Effect>(
|
||||
std::make_shared<BitCrushEffect>(),
|
||||
new osci::EffectParameter("Bit Crush", "Limits the resolution of points drawn to the screen, making the object look pixelated, and making the audio sound more 'digital' and distorted.", "bitCrush", VERSION_HINT, 0.6, 0.0, 1.0)));
|
||||
toggleableEffects.push_back(std::make_shared<osci::Effect>(
|
||||
std::make_shared<BulgeEffect>(),
|
||||
new osci::EffectParameter("Bulge", "Applies a bulge that makes the centre of the image larger, and squishes the edges of the image. This applies a distortion to the audio.", "bulge", VERSION_HINT, 0.5, 0.0, 1.0)));
|
||||
auto multiplexEffect = std::make_shared<osci::Effect>(
|
||||
std::make_shared<MultiplexEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Multiplex X", "Controls the horizontal grid size for the multiplex effect.", "multiplexGridX", VERSION_HINT, 1.0, 1.0, 8.0),
|
||||
new osci::EffectParameter("Multiplex Y", "Controls the vertical grid size for the multiplex effect.", "multiplexGridY", VERSION_HINT, 1.0, 1.0, 8.0),
|
||||
new osci::EffectParameter("Multiplex Z", "Controls the depth grid size for the multiplex effect.", "multiplexGridZ", VERSION_HINT, 1.0, 1.0, 8.0),
|
||||
new osci::EffectParameter("Multiplex Smooth", "Controls the smoothness of transitions between grid sizes.", "multiplexSmooth", VERSION_HINT, 0.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("Multiplex 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),
|
||||
});
|
||||
// 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);
|
||||
toggleableEffects.push_back(multiplexEffect);
|
||||
toggleableEffects.push_back(std::make_shared<osci::Effect>(
|
||||
std::make_shared<VectorCancellingEffect>(),
|
||||
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)));
|
||||
auto scaleEffect = 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]);
|
||||
},
|
||||
std::vector<osci::EffectParameter*>{
|
||||
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);
|
||||
toggleableEffects.push_back(BitCrushEffect().build());
|
||||
toggleableEffects.push_back(BulgeEffect().build());
|
||||
toggleableEffects.push_back(MultiplexEffect().build());
|
||||
toggleableEffects.push_back(KaleidoscopeEffect().build());
|
||||
toggleableEffects.push_back(BounceEffect().build());
|
||||
toggleableEffects.push_back(VectorCancellingEffect().build());
|
||||
toggleableEffects.push_back(RippleEffectApp().build());
|
||||
toggleableEffects.push_back(RotateEffectApp().build());
|
||||
toggleableEffects.push_back(TranslateEffectApp().build());
|
||||
toggleableEffects.push_back(SwirlEffectApp().build());
|
||||
toggleableEffects.push_back(SmoothEffect().build());
|
||||
toggleableEffects.push_back(TwistEffect().build());
|
||||
toggleableEffects.push_back(DelayEffect().build());
|
||||
toggleableEffects.push_back(DashedLineEffect(*this).build());
|
||||
toggleableEffects.push_back(TraceEffect(*this).build());
|
||||
toggleableEffects.push_back(WobbleEffect(*this).build());
|
||||
|
||||
auto scaleEffect = ScaleEffectApp().build();
|
||||
booleanParameters.push_back(scaleEffect->linked);
|
||||
toggleableEffects.push_back(scaleEffect);
|
||||
auto distortEffect = std::make_shared<osci::Effect>(
|
||||
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
|
||||
int flip = index % 2 == 0 ? 1 : -1;
|
||||
osci::Point jitter = osci::Point(flip * values[0], flip * values[1], flip * values[2]);
|
||||
return input + jitter;
|
||||
},
|
||||
std::vector<osci::EffectParameter*>{
|
||||
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);
|
||||
|
||||
auto distortEffect = DistortEffect().build();
|
||||
booleanParameters.push_back(distortEffect->linked);
|
||||
toggleableEffects.push_back(distortEffect);
|
||||
auto rippleEffect = std::make_shared<osci::Effect>(
|
||||
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
|
||||
double phase = values[1] * std::numbers::pi;
|
||||
double distance = 100 * values[2] * (input.x * input.x + input.y * input.y);
|
||||
input.z += values[0] * std::sin(phase + distance);
|
||||
return input;
|
||||
},
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Ripple Depth", "Controls how large the ripples applied to the image are.", "rippleDepth", VERSION_HINT, 0.2, 0.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),
|
||||
});
|
||||
rippleEffect->getParameter("ripplePhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
|
||||
toggleableEffects.push_back(rippleEffect);
|
||||
auto rotateEffect = std::make_shared<osci::Effect>(
|
||||
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
|
||||
input.rotate(values[0] * std::numbers::pi, values[1] * std::numbers::pi, values[2] * std::numbers::pi);
|
||||
return input;
|
||||
},
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Rotate X", "Controls the rotation of the object in the X axis.", "rotateX", 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),
|
||||
});
|
||||
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>(
|
||||
[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]);
|
||||
},
|
||||
std::vector<osci::EffectParameter*>{
|
||||
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),
|
||||
}));
|
||||
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();
|
||||
double newX = input.x * std::cos(length) - input.y * std::sin(length);
|
||||
double newY = input.x * std::sin(length) + input.y * std::cos(length);
|
||||
return osci::Point(newX, newY, input.z);
|
||||
},
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Swirl", "Swirls the image in a spiral pattern.", "swirl", VERSION_HINT, 0.3, -1.0, 1.0),
|
||||
}));
|
||||
toggleableEffects.push_back(std::make_shared<osci::Effect>(
|
||||
std::make_shared<TwistEffect>(),
|
||||
std::vector<osci::EffectParameter *>{
|
||||
new osci::EffectParameter("Twist", "Twists the image in a corkscrew pattern.", "twist", VERSION_HINT, 0.5, -1.0, 1.0),
|
||||
}));
|
||||
toggleableEffects.push_back(std::make_shared<osci::Effect>(
|
||||
std::make_shared<SmoothEffect>(),
|
||||
new osci::EffectParameter("Smoothing", "This works as a low-pass frequency filter that removes high frequencies, making the image look smoother, and audio sound less harsh.", "smoothing", VERSION_HINT, 0.75, 0.0, 1.0)));
|
||||
std::shared_ptr<osci::Effect> wobble = std::make_shared<osci::Effect>(
|
||||
std::make_shared<WobbleEffect>(*this),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
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->getParameter("wobblePhase")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
|
||||
toggleableEffects.push_back(wobble);
|
||||
toggleableEffects.push_back(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)}));
|
||||
auto dashedLineEffect = std::make_shared<osci::Effect>(
|
||||
std::make_shared<DashedLineEffect>(*this),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Dash Count", "Controls the number of dashed lines in the drawing.", "dashCount", VERSION_HINT, 16.0, 1.0, 32.0),
|
||||
new osci::EffectParameter("Dash Coverage", "Controls the fraction of each dash unit that is drawn.", "dashCoverage", VERSION_HINT, 0.5, 0.0, 1.0),
|
||||
new osci::EffectParameter("Dash Offset", "Offsets the location of the dashed lines.", "dashOffset", VERSION_HINT, 0.0, 0.0, 1.0),
|
||||
});
|
||||
dashedLineEffect->getParameter("dashOffset")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
|
||||
dashedLineEffect->getParameter("dashOffset")->lfoRate->setUnnormalisedValueNotifyingHost(1.0);
|
||||
toggleableEffects.push_back(dashedLineEffect);
|
||||
|
||||
custom->setIcon(BinaryData::lua_svg);
|
||||
toggleableEffects.push_back(custom);
|
||||
toggleableEffects.push_back(trace);
|
||||
trace->getParameter("traceLength")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth);
|
||||
|
||||
for (int i = 0; i < toggleableEffects.size(); i++) {
|
||||
auto effect = toggleableEffects[i];
|
||||
effect->markSelectable(false);
|
||||
booleanParameters.push_back(effect->selected);
|
||||
effect->selected->setValueNotifyingHost(false);
|
||||
effect->markEnableable(false);
|
||||
booleanParameters.push_back(effect->enabled);
|
||||
effect->enabled->setValueNotifyingHost(false);
|
||||
|
@ -175,6 +83,9 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
|
|||
|
||||
for (int i = 0; i < 26; i++) {
|
||||
addLuaSlider();
|
||||
if (i < luaEffects.size()) {
|
||||
luaEffects[i]->setIcon(BinaryData::lua_svg);
|
||||
}
|
||||
}
|
||||
|
||||
effects.insert(effects.end(), toggleableEffects.begin(), toggleableEffects.end());
|
||||
|
@ -467,6 +378,17 @@ void OscirenderAudioProcessor::setObjectServerPort(int port) {
|
|||
objectServer.reload();
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::setPreviewEffectId(const juce::String& effectId) {
|
||||
previewEffect.reset();
|
||||
for (auto& eff : toggleableEffects) {
|
||||
if (eff->getId() == effectId) { previewEffect = eff; break; }
|
||||
}
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::clearPreviewEffect() {
|
||||
previewEffect.reset();
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
|
||||
juce::ScopedNoDenormals noDenormals;
|
||||
// Audio info variables
|
||||
|
@ -629,13 +551,25 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
|||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
if (volume > EPSILON) {
|
||||
for (auto& effect : toggleableEffects) {
|
||||
if (effect->enabled->getValue()) {
|
||||
bool isEnabled = effect->enabled != nullptr && effect->enabled->getValue();
|
||||
bool isSelected = effect->selected == nullptr ? true : effect->selected->getBoolValue();
|
||||
if (isEnabled && isSelected) {
|
||||
if (effect->getId() == custom->getId()) {
|
||||
effect->setExternalInput(osci::Point{ left, right });
|
||||
}
|
||||
channels = effect->apply(sample, channels, currentVolume);
|
||||
}
|
||||
}
|
||||
// Apply preview effect if present and not already active in the main chain
|
||||
if (previewEffect) {
|
||||
const bool prevEnabled = (previewEffect->enabled != nullptr) && previewEffect->enabled->getValue();
|
||||
const bool prevSelected = (previewEffect->selected == nullptr) ? true : previewEffect->selected->getBoolValue();
|
||||
if (!(prevEnabled && prevSelected)) {
|
||||
if (previewEffect->getId() == custom->getId())
|
||||
previewEffect->setExternalInput(osci::Point{ left, right });
|
||||
channels = previewEffect->apply(sample, channels, currentVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& effect : permanentEffects) {
|
||||
channels = effect->apply(sample, channels, currentVolume);
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#define VERSION_HINT 2
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
||||
#include <numbers>
|
||||
|
@ -59,6 +57,8 @@ public:
|
|||
|
||||
std::vector<std::shared_ptr<osci::Effect>> toggleableEffects;
|
||||
std::vector<std::shared_ptr<osci::Effect>> luaEffects;
|
||||
// Temporary preview effect applied while hovering effects in the grid (guarded by effectsLock)
|
||||
std::shared_ptr<osci::Effect> previewEffect;
|
||||
std::atomic<double> luaValues[26] = {0.0};
|
||||
|
||||
std::shared_ptr<osci::Effect> frequencyEffect = std::make_shared<osci::Effect>(
|
||||
|
@ -72,20 +72,6 @@ public:
|
|||
"frequency",
|
||||
VERSION_HINT, 220.0, 0.0, 4200.0));
|
||||
|
||||
std::shared_ptr<osci::Effect> trace = std::make_shared<osci::Effect>(
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter(
|
||||
"Trace Start",
|
||||
"Defines how far into the frame the drawing is started at. This has the effect of 'tracing' out the image from a single dot when animated. By default, we start drawing from the beginning of the frame, so this value is 0.0.",
|
||||
"traceStart",
|
||||
VERSION_HINT, 0.0, 0.0, 1.0, 0.001),
|
||||
new osci::EffectParameter(
|
||||
"Trace Length",
|
||||
"Defines how much of the frame is drawn per cycle. This has the effect of 'tracing' out the image from a single dot when animated. By default, we draw the whole frame, corresponding to a value of 1.0.",
|
||||
"traceLength",
|
||||
VERSION_HINT, 1.0, 0.0, 1.0, 0.001),
|
||||
});
|
||||
|
||||
std::shared_ptr<DelayEffect> delayEffect = std::make_shared<DelayEffect>();
|
||||
|
||||
std::function<void(int, juce::String, juce::String)> errorCallback = [this](int lineNum, juce::String fileName, juce::String error) { notifyErrorListeners(lineNum, fileName, error); };
|
||||
|
@ -94,13 +80,7 @@ public:
|
|||
customEffect,
|
||||
new osci::EffectParameter("Lua Effect", "Controls the strength of the custom Lua effect applied. You can write your own custom effect using Lua by pressing the edit button on the right.", "customEffectStrength", VERSION_HINT, 1.0, 0.0, 1.0));
|
||||
|
||||
std::shared_ptr<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>();
|
||||
std::shared_ptr<osci::Effect> perspective = std::make_shared<osci::Effect>(
|
||||
perspectiveEffect,
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Perspective", "Controls the strength of the 3D perspective projection.", "perspectiveStrength", VERSION_HINT, 1.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("FOV", "Controls the camera's field of view in degrees. A lower field of view makes the image look more flat, and a higher field of view makes the image look more 3D.", "perspectiveFov", VERSION_HINT, 50.0, 5.0, 130.0),
|
||||
});
|
||||
std::shared_ptr<osci::Effect> perspective = PerspectiveEffect().build();
|
||||
|
||||
osci::BooleanParameter* midiEnabled = new osci::BooleanParameter("MIDI Enabled", "midiEnabled", VERSION_HINT, false, "Enable MIDI input for the synth. If disabled, the synth will play a constant tone, as controlled by the frequency slider.");
|
||||
osci::BooleanParameter* inputEnabled = new osci::BooleanParameter("Audio Input Enabled", "inputEnabled", VERSION_HINT, false, "Enable to use input audio, instead of the generated audio.");
|
||||
|
@ -200,6 +180,10 @@ public:
|
|||
void removeErrorListener(ErrorListener* listener);
|
||||
void notifyErrorListeners(int lineNumber, juce::String id, juce::String error);
|
||||
|
||||
// Preview API: set/clear a temporary effect by ID for hover auditioning
|
||||
void setPreviewEffectId(const juce::String& effectId);
|
||||
void clearPreviewEffect();
|
||||
|
||||
// Setter for the callback
|
||||
void setFileRemovedCallback(std::function<void(int)> callback);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ SettingsComponent::SettingsComponent(OscirenderAudioProcessor& p, OscirenderAudi
|
|||
addChildComponent(frame);
|
||||
|
||||
double midiLayoutPreferredSize = std::any_cast<double>(audioProcessor.getProperty("midiLayoutPreferredSize", pluginEditor.CLOSED_PREF_SIZE));
|
||||
double mainLayoutPreferredSize = std::any_cast<double>(audioProcessor.getProperty("mainLayoutPreferredSize", -0.4));
|
||||
double mainLayoutPreferredSize = std::any_cast<double>(audioProcessor.getProperty("mainLayoutPreferredSize", -0.5));
|
||||
|
||||
midiLayout.setItemLayout(0, -0.1, -1.0, -(1.0 + midiLayoutPreferredSize));
|
||||
midiLayout.setItemLayout(1, pluginEditor.RESIZER_BAR_SIZE, pluginEditor.RESIZER_BAR_SIZE, pluginEditor.RESIZER_BAR_SIZE);
|
||||
|
@ -46,8 +46,6 @@ void SettingsComponent::resized() {
|
|||
mainLayout.layOutComponents(columns, 3, dummy.getX(), dummy.getY(), dummy.getWidth(), dummy.getHeight(), false, true);
|
||||
|
||||
auto bounds = dummy2.getBounds();
|
||||
perspective.setBounds(bounds.removeFromBottom(120));
|
||||
bounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
|
||||
main.setBounds(bounds);
|
||||
|
||||
juce::Component* effectSettings = nullptr;
|
||||
|
@ -65,6 +63,9 @@ void SettingsComponent::resized() {
|
|||
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
|
||||
}
|
||||
|
||||
perspective.setBounds(dummyBounds.removeFromBottom(120));
|
||||
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
|
||||
|
||||
effects.setBounds(dummyBounds);
|
||||
|
||||
if (isVisible() && getWidth() > 0 && getHeight() > 0) {
|
||||
|
|
|
@ -1,118 +1,4 @@
|
|||
#include <JuceHeader.h>
|
||||
#include "obj/Camera.h"
|
||||
#include "mathter/Common/Approx.hpp"
|
||||
|
||||
class FrustumTest : public juce::UnitTest {
|
||||
public:
|
||||
FrustumTest() : juce::UnitTest("Frustum Culling") {}
|
||||
|
||||
void runTest() override {
|
||||
double focalLength = 1;
|
||||
|
||||
Camera camera;
|
||||
camera.setFocalLength(focalLength);
|
||||
Vec3 position = Vec3(0, 0, -focalLength);
|
||||
camera.setPosition(position);
|
||||
Frustum frustum = camera.getFrustum();
|
||||
|
||||
beginTest("Focal Plane Frustum In-Bounds");
|
||||
|
||||
// Focal plane is at z = 0
|
||||
Vec3 vecs[] = {
|
||||
Vec3(0, 0, 0), Vec3(0, 0, 0),
|
||||
Vec3(1, 1, 0), Vec3(1, 1, 0),
|
||||
Vec3(-1, -1, 0), Vec3(-1, -1, 0),
|
||||
Vec3(1, -1, 0), Vec3(1, -1, 0),
|
||||
Vec3(-1, 1, 0), Vec3(-1, 1, 0),
|
||||
Vec3(0.5, 0.5, 0), Vec3(0.5, 0.5, 0),
|
||||
};
|
||||
|
||||
testFrustumClippedEqualsExpected(vecs, camera, 6);
|
||||
|
||||
beginTest("Focal Plane Frustum Out-Of-Bounds");
|
||||
|
||||
// Focal plane is at z = 0
|
||||
Vec3 vecs2[] = {
|
||||
Vec3(1.1, 1.1, 0), Vec3(1, 1, 0),
|
||||
Vec3(-1.1, -1.1, 0), Vec3(-1, -1, 0),
|
||||
Vec3(1.1, -1.1, 0), Vec3(1, -1, 0),
|
||||
Vec3(-1.1, 1.1, 0), Vec3(-1, 1, 0),
|
||||
Vec3(1.1, 0.5, 0), Vec3(1, 0.5, 0),
|
||||
Vec3(-1.1, 0.5, 0), Vec3(-1, 0.5, 0),
|
||||
Vec3(0.5, -1.1, 0), Vec3(0.5, -1, 0),
|
||||
Vec3(0.5, 1.1, 0), Vec3(0.5, 1, 0),
|
||||
Vec3(10, 10, 0), Vec3(1, 1, 0),
|
||||
Vec3(-10, -10, 0), Vec3(-1, -1, 0),
|
||||
Vec3(10, -10, 0), Vec3(1, -1, 0),
|
||||
Vec3(-10, 10, 0), Vec3(-1, 1, 0),
|
||||
};
|
||||
|
||||
testFrustumClippedEqualsExpected(vecs2, camera, 12);
|
||||
|
||||
beginTest("Behind Camera Out-Of-Bounds");
|
||||
|
||||
double minZWorldCoords = -focalLength + camera.getFrustum().nearDistance;
|
||||
|
||||
Vec3 vecs3[] = {
|
||||
Vec3(0, 0, -focalLength), Vec3(0, 0, minZWorldCoords),
|
||||
Vec3(0, 0, -100), Vec3(0, 0, minZWorldCoords),
|
||||
Vec3(0.5, 0.5, -focalLength), Vec3(0.1, 0.1, minZWorldCoords),
|
||||
Vec3(10, -10, -focalLength), Vec3(0.1, -0.1, minZWorldCoords),
|
||||
Vec3(-0.5, 0.5, -100), Vec3(-0.1, 0.1, minZWorldCoords),
|
||||
Vec3(-10, 10, -100), Vec3(-0.1, 0.1, minZWorldCoords),
|
||||
};
|
||||
|
||||
testFrustumClippedEqualsExpected(vecs3, camera, 6);
|
||||
|
||||
beginTest("3D Point Out-Of-Bounds");
|
||||
|
||||
Vec3 vecs4[] = {
|
||||
Vec3(1, 1, -0.1),
|
||||
Vec3(-1, -1, -0.1),
|
||||
Vec3(1, -1, -0.1),
|
||||
Vec3(-1, 1, -0.1),
|
||||
Vec3(0.5, 0.5, minZWorldCoords),
|
||||
};
|
||||
|
||||
testFrustumClipOccurs(vecs4, camera, 5);
|
||||
}
|
||||
|
||||
Vec3 project(Vec3& p, double focalLength) {
|
||||
return Vec3(
|
||||
p.x * focalLength / p.z,
|
||||
p.y * focalLength / p.z,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
juce::String vec3ToString(Vec3& p) {
|
||||
return "(" + juce::String(p.x) + ", " + juce::String(p.y) + ", " + juce::String(p.z) + ")";
|
||||
}
|
||||
|
||||
juce::String errorMessage(Vec3& actual, Vec3& expected) {
|
||||
return "Expected: " + vec3ToString(expected) + ", Actual: " + vec3ToString(actual);
|
||||
}
|
||||
|
||||
void testFrustumClippedEqualsExpected(Vec3 vecs[], Camera& camera, int length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
Vec3 p = vecs[2 * i];
|
||||
p = camera.toCameraSpace(p);
|
||||
camera.getFrustum().clipToFrustum(p);
|
||||
p = camera.toWorldSpace(p);
|
||||
expect(mathter::AlmostEqual(p, vecs[2 * i + 1]), errorMessage(p, vecs[2 * i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
void testFrustumClipOccurs(Vec3 vecs[], Camera& camera, int length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
Vec3 p = vecs[i];
|
||||
p = camera.toCameraSpace(p);
|
||||
camera.getFrustum().clipToFrustum(p);
|
||||
p = camera.toWorldSpace(p);
|
||||
expect(!mathter::AlmostEqual(p, vecs[i]), errorMessage(p, vecs[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ProducerThread : public juce::Thread {
|
||||
public:
|
||||
|
@ -183,7 +69,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static FrustumTest frustumTest;
|
||||
static BufferConsumerTest bufferConsumerTest;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
|
|
@ -40,6 +40,18 @@ public:
|
|||
return input * gainAdjust;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<AutoGainControlEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Intensity", "Controls how aggressively the gain adjustment is applied", "agcIntensity", VERSION_HINT, 1.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("Target Level", "Target output level for the automatic gain control", "agcTarget", VERSION_HINT, 0.6, 0.0, 1.0),
|
||||
new osci::EffectParameter("Response", "How quickly the effect responds to level changes (lower is slower)", "agcResponse", VERSION_HINT, 0.0001, 0.0, 1.0)
|
||||
}
|
||||
);
|
||||
return eff;
|
||||
}
|
||||
|
||||
private:
|
||||
const double EPSILON = 0.00001;
|
||||
double smoothedLevel = 0.01; // Start with a small non-zero value to avoid division by zero
|
||||
|
|
|
@ -15,4 +15,12 @@ public:
|
|||
double dequant = 1.0f / quant;
|
||||
return osci::Point(dequant * (int)(input.x * quant), dequant * (int)(input.y * quant), dequant * (int)(input.z * quant));
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<BitCrushEffect>(),
|
||||
new osci::EffectParameter("Bit Crush", "Limits the resolution of points drawn to the screen, making the object look pixelated, and making the audio sound more 'digital' and distorted.", "bitCrush", VERSION_HINT, 0.7, 0.0, 1.0));
|
||||
eff->setIcon(BinaryData::bitcrush_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// BounceEffect.h (Simplified DVD-style 2D bounce)
|
||||
// Scales the original shape, then translates it within [-1,1] x [-1,1] using
|
||||
// constant-velocity motion that bounces off the edges. Z coordinate is unchanged.
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class BounceEffect : public osci::EffectApplication {
|
||||
public:
|
||||
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
// values[0] = size (0.05..1.0)
|
||||
// values[1] = speed (0..2)
|
||||
// values[2] = angle (0..1 -> 0..2π)
|
||||
double size = juce::jlimit(0.05, 1.0, values[0].load());
|
||||
double speed = juce::jlimit(0.0, 2.0, values[1].load());
|
||||
double angle = values[2].load() * juce::MathConstants<double>::twoPi;
|
||||
|
||||
// Base direction from user
|
||||
double dirX = std::cos(angle);
|
||||
double dirY = std::sin(angle);
|
||||
if (flipX) dirX = -dirX;
|
||||
if (flipY) dirY = -dirY;
|
||||
|
||||
double dt = 1.0 / sampleRate;
|
||||
position.x += dirX * speed * dt;
|
||||
position.y += dirY * speed * dt;
|
||||
|
||||
double maxP = 1.0 - size;
|
||||
double minP = -1.0 + size;
|
||||
if (position.x > maxP) { position.x = maxP; flipX = !flipX; }
|
||||
else if (position.x < minP) { position.x = minP; flipX = !flipX; }
|
||||
if (position.y > maxP) { position.y = maxP; flipY = !flipY; }
|
||||
else if (position.y < minP) { position.y = minP; flipY = !flipY; }
|
||||
|
||||
osci::Point scaled = input * size;
|
||||
osci::Point out = scaled + osci::Point(position.x, position.y, 0.0);
|
||||
out.z = input.z; // preserve original Z
|
||||
return out;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<BounceEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Bounce Size", "Size (scale) of the bouncing object.", "bounceSize", VERSION_HINT, 0.3, 0.05, 1.0),
|
||||
new osci::EffectParameter("Bounce Speed", "Speed of motion.", "bounceSpeed", VERSION_HINT, 5.0, 0.0, 10.0),
|
||||
new osci::EffectParameter("Bounce Angle", juce::String(juce::CharPointer_UTF8("Direction of travel (0..1 -> 0..360°).")), "bounceAngle", VERSION_HINT, 0.16, 0.0, 1.0),
|
||||
}
|
||||
);
|
||||
eff->setName("Bounce");
|
||||
eff->setIcon(BinaryData::bounce_svg);
|
||||
return eff;
|
||||
}
|
||||
|
||||
private:
|
||||
osci::Point position { 0.0, 0.0, 0.0 };
|
||||
bool flipX = false;
|
||||
bool flipY = false;
|
||||
};
|
|
@ -13,4 +13,12 @@ public:
|
|||
|
||||
return osci::Point(rn * cos(theta), rn * sin(theta), input.z);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<BulgeEffect>(),
|
||||
new osci::EffectParameter("Bulge", "Applies a bulge that makes the centre of the image larger, and squishes the edges of the image. This applies a distortion to the audio.", "bulge", VERSION_HINT, 0.5, 0.0, 1.0));
|
||||
eff->setIcon(BinaryData::bulge_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,6 +18,15 @@ public:
|
|||
|
||||
double frequency = 0;
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
// Note: callers needing CustomEffect with callback/vars should construct directly.
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<CustomEffect>([](int, juce::String, juce::String) {}, nullptr),
|
||||
new osci::EffectParameter("Lua Effect", "Controls the strength of the custom Lua effect applied. You can write your own custom effect using Lua by pressing the edit button on the right.", "customEffectStrength", VERSION_HINT, 1.0, 0.0, 1.0));
|
||||
eff->setIcon(BinaryData::lua_svg);
|
||||
return eff;
|
||||
}
|
||||
|
||||
private:
|
||||
const juce::String DEFAULT_SCRIPT = "return { x, y, z }";
|
||||
juce::String code = DEFAULT_SCRIPT;
|
||||
|
|
|
@ -7,9 +7,18 @@ public:
|
|||
DashedLineEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {}
|
||||
|
||||
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
double dashCount = juce::jmax(1.0, values[0].load()); // Dashes per cycle
|
||||
double dashCoverage = juce::jlimit(0.0, 1.0, values[1].load());
|
||||
double dashOffset = values[2];
|
||||
// if only 2 parameters are provided, this is being used as a 'trace effect'
|
||||
// where the dash count is 1.
|
||||
double dashCount = 1.0;
|
||||
int i = 0;
|
||||
|
||||
if (values.size() > 2) {
|
||||
dashCount = juce::jmax(1.0, values[i++].load()); // Dashes per cycle
|
||||
}
|
||||
|
||||
double dashOffset = values[i++];
|
||||
double dashCoverage = juce::jlimit(0.0, 1.0, values[i++].load());
|
||||
|
||||
double dashLengthSamples = (sampleRate / audioProcessor.frequency) / dashCount;
|
||||
double dashPhase = framePhase * dashCount - dashOffset;
|
||||
dashPhase = dashPhase - std::floor(dashPhase); // Wrap
|
||||
|
@ -33,10 +42,53 @@ public:
|
|||
return output;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<DashedLineEffect>(audioProcessor),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Dash Count", "Controls the number of dashed lines in the drawing.", "dashCount", VERSION_HINT, 16.0, 1.0, 32.0),
|
||||
new osci::EffectParameter("Dash Offset", "Offsets the location of the dashed lines.", "dashOffset", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001f, osci::LfoType::Sawtooth, 1.0f),
|
||||
new osci::EffectParameter("Dash Width", "Controls how much each dash unit is drawn.", "dashWidth", VERSION_HINT, 0.5, 0.0, 1.0),
|
||||
}
|
||||
);
|
||||
eff->setName("Dash");
|
||||
eff->setIcon(BinaryData::dash_svg);
|
||||
return eff;
|
||||
}
|
||||
|
||||
protected:
|
||||
OscirenderAudioProcessor &audioProcessor;
|
||||
private:
|
||||
const static int MAX_BUFFER = 192000;
|
||||
std::vector<osci::Point> buffer = std::vector<osci::Point>(MAX_BUFFER);
|
||||
int bufferIndex = 0;
|
||||
double framePhase = 0.0; // [0, 1]
|
||||
};
|
||||
};
|
||||
|
||||
class TraceEffect : public DashedLineEffect {
|
||||
public:
|
||||
TraceEffect(OscirenderAudioProcessor& p) : DashedLineEffect(p) {}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<TraceEffect>(audioProcessor),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter(
|
||||
"Trace Start",
|
||||
"Defines how far into the frame the drawing is started at. This has the effect of 'tracing' out the image from a single dot when animated. By default, we start drawing from the beginning of the frame, so this value is 0.0.",
|
||||
"traceStart",
|
||||
VERSION_HINT, 0.0, 0.0, 1.0, 0.001
|
||||
),
|
||||
new osci::EffectParameter(
|
||||
"Trace Length",
|
||||
"Defines how much of the frame is drawn per cycle. This has the effect of 'tracing' out the image from a single dot when animated. By default, we draw the whole frame, corresponding to a value of 1.0.",
|
||||
"traceLength",
|
||||
VERSION_HINT, 1.0, 0.0, 1.0, 0.001, osci::LfoType::Sawtooth
|
||||
)
|
||||
}
|
||||
);
|
||||
eff->setName("Trace");
|
||||
eff->setIcon(BinaryData::trace_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -37,6 +37,19 @@ public:
|
|||
return vector;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<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)
|
||||
}
|
||||
);
|
||||
eff->setName("Delay");
|
||||
eff->setIcon(BinaryData::delay_svg);
|
||||
return eff;
|
||||
}
|
||||
|
||||
private:
|
||||
const static int MAX_DELAY = 192000 * 10;
|
||||
std::vector<osci::Point> delayBuffer = std::vector<osci::Point>(MAX_DELAY);
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
|
||||
class DistortEffect : public osci::EffectApplication {
|
||||
public:
|
||||
DistortEffect(bool vertical) : vertical(vertical) {}
|
||||
|
||||
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
double value = values[0];
|
||||
int vertical = (int)this->vertical;
|
||||
if (index % 2 == 0) {
|
||||
input.translate((1 - vertical) * value, vertical * value, 0);
|
||||
} else {
|
||||
input.translate((1 - vertical) * -value, vertical * -value, 0);
|
||||
}
|
||||
return input;
|
||||
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override {
|
||||
int flip = index % 2 == 0 ? 1 : -1;
|
||||
osci::Point jitter = osci::Point(flip * values[0], flip * values[1], flip * values[2]);
|
||||
return input + jitter;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<DistortEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
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.4, 0.0, 1.0),
|
||||
}
|
||||
);
|
||||
eff->setName("Distort");
|
||||
eff->setIcon(BinaryData::distort_svg);
|
||||
eff->markLockable(false);
|
||||
return eff;
|
||||
}
|
||||
private:
|
||||
bool vertical;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
// KaleidoscopeEffect.h
|
||||
// Repeats and mirrors the input around the origin to create a kaleidoscope pattern.
|
||||
// The effect supports a floating point number of segments, allowing smooth morphing
|
||||
// between different symmetry counts.
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class KaleidoscopeEffect : public osci::EffectApplication {
|
||||
public:
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override {
|
||||
// values[0] = segments (can be fractional)
|
||||
// values[1] = phase (0-1) selecting which segment is currently being drawn
|
||||
double segments = juce::jmax(values[0].load(), 1.0); // ensure at least 1 segment
|
||||
double phase = values.size() > 1 ? values[1].load() : 0.0;
|
||||
|
||||
// Polar conversion
|
||||
double r = std::sqrt(input.x * input.x + input.y * input.y);
|
||||
if (r < 1e-12) return input;
|
||||
double theta = std::atan2(input.y, input.x);
|
||||
|
||||
int fullSegments = (int)std::floor(segments);
|
||||
double fractionalPart = segments - fullSegments; // in [0,1)
|
||||
|
||||
// Use 'segments' for timing so partial segment gets proportionally shorter time.
|
||||
double currentSegmentFloat = phase * segments; // [0, segments)
|
||||
int currentSegmentIndex = (int)std::floor(currentSegmentFloat);
|
||||
int maxIndex = fractionalPart > 1e-9 ? fullSegments : fullSegments - 1; // include partial index if exists
|
||||
if (currentSegmentIndex > maxIndex) currentSegmentIndex = maxIndex; // safety
|
||||
|
||||
// Base full wedge angle (all full wedges) and size of partial wedge
|
||||
double baseWedgeAngle = juce::MathConstants<double>::twoPi / segments; // size of a "unit" wedge
|
||||
double partialScale = (currentSegmentIndex == fullSegments && fractionalPart > 1e-9) ? fractionalPart : 1.0;
|
||||
double wedgeAngle = baseWedgeAngle * partialScale;
|
||||
|
||||
// Normalize theta to [0,1) for compression
|
||||
double thetaNorm = (theta + juce::MathConstants<double>::pi) / juce::MathConstants<double>::twoPi; // 0..1
|
||||
|
||||
// Offset for this segment: each preceding full segment occupies baseWedgeAngle
|
||||
double segmentOffset = 0.0;
|
||||
if (currentSegmentIndex < fullSegments) {
|
||||
segmentOffset = currentSegmentIndex * baseWedgeAngle;
|
||||
} else { // partial segment
|
||||
segmentOffset = fullSegments * baseWedgeAngle;
|
||||
}
|
||||
// Map entire original angle range into [segmentOffset, segmentOffset + wedgeAngle) so edges line up exactly.
|
||||
double finalTheta = segmentOffset + thetaNorm * wedgeAngle - juce::MathConstants<double>::pi; // constant 180° rotation
|
||||
|
||||
double newX = r * std::cos(finalTheta);
|
||||
double newY = r * std::sin(finalTheta);
|
||||
return osci::Point(newX, newY, input.z);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<KaleidoscopeEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter(
|
||||
"Kaleidoscope Segments",
|
||||
"Controls how many times the image is rotationally repeated around the centre. Fractional values smoothly morph the repetition.",
|
||||
"kaleidoscopeSegments",
|
||||
VERSION_HINT,
|
||||
3.0, // default
|
||||
1.0, // min
|
||||
10.0, // max
|
||||
0.0001f, // step
|
||||
osci::LfoType::Sine,
|
||||
0.25f // LFO frequency (Hz) – slow, visible rotation
|
||||
),
|
||||
new osci::EffectParameter(
|
||||
"Kaleidoscope Phase",
|
||||
"Selects which kaleidoscope segment is currently being drawn (time-multiplexed). Animate to sweep around the circle.",
|
||||
"kaleidoscopePhase",
|
||||
VERSION_HINT,
|
||||
0.0, // default
|
||||
0.0, // min
|
||||
1.0, // max
|
||||
0.0001f, // step
|
||||
osci::LfoType::Sawtooth,
|
||||
55.0f // LFO frequency (Hz) – slow, visible rotation
|
||||
),
|
||||
}
|
||||
);
|
||||
eff->setName("Kaleidoscope");
|
||||
eff->setIcon(BinaryData::kaleidoscope_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
|
@ -7,9 +7,9 @@ public:
|
|||
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
jassert(values.size() >= 6);
|
||||
|
||||
double gridX = values[0].load();
|
||||
double gridY = values[1].load();
|
||||
double gridZ = values[2].load();
|
||||
double gridX = values[0].load() + 0.0001;
|
||||
double gridY = values[1].load() + 0.0001;
|
||||
double gridZ = values[2].load() + 0.0001;
|
||||
double interpolation = values[3].load();
|
||||
double phase = values[4].load();
|
||||
double gridDelay = values[5].load();
|
||||
|
@ -51,6 +51,23 @@ public:
|
|||
return (1.0 - interpolationFactor) * current + interpolationFactor * next;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<MultiplexEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Multiplex X", "Controls the horizontal grid size for the multiplex effect.", "multiplexGridX", VERSION_HINT, 2.0, 1.0, 8.0),
|
||||
new osci::EffectParameter("Multiplex Y", "Controls the vertical grid size for the multiplex effect.", "multiplexGridY", VERSION_HINT, 2.0, 1.0, 8.0),
|
||||
new osci::EffectParameter("Multiplex Z", "Controls the depth grid size for the multiplex effect.", "multiplexGridZ", VERSION_HINT, 1.0, 1.0, 8.0),
|
||||
new osci::EffectParameter("Multiplex Smooth", "Controls the smoothness of transitions between grid sizes.", "multiplexSmooth", VERSION_HINT, 0.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("Multiplex Phase", "Controls the current phase of the multiplex grid animation.", "gridPhase", VERSION_HINT, 0.0, 0.0, 1.0, 0.0001f, osci::LfoType::Sawtooth, 55.0f),
|
||||
new osci::EffectParameter("Multiplex Delay", "Controls the delay of the audio samples used in the multiplex effect.", "gridDelay", VERSION_HINT, 0.0, 0.0, 1.0),
|
||||
}
|
||||
);
|
||||
eff->setName("Multiplex");
|
||||
eff->setIcon(BinaryData::multiplex_svg);
|
||||
return eff;
|
||||
}
|
||||
|
||||
private:
|
||||
osci::Point multiplex(osci::Point point, double position, osci::Point grid) {
|
||||
osci::Point unit = 1.0 / grid;
|
||||
|
|
|
@ -25,6 +25,17 @@ public:
|
|||
);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<PerspectiveEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Perspective", "Controls the strength of the 3D perspective projection.", "perspectiveStrength", VERSION_HINT, 1.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("FOV", "Controls the camera's field of view in degrees. A lower field of view makes the image look more flat, and a higher field of view makes the image look more 3D.", "perspectiveFov", VERSION_HINT, 50.0, 5.0, 130.0),
|
||||
}
|
||||
);
|
||||
return eff;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Camera camera;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class RippleEffectApp : public osci::EffectApplication {
|
||||
public:
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override {
|
||||
double phase = values[1] * std::numbers::pi;
|
||||
double distance = 100 * values[2] * (input.x * input.x + input.y * input.y);
|
||||
input.z += values[0] * std::sin(phase + distance);
|
||||
return input;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<RippleEffectApp>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Ripple Depth", "Controls how large the ripples applied to the image are.", "rippleDepth", VERSION_HINT, 0.2, 0.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, 0.0001f, osci::LfoType::Sawtooth, 1.0f),
|
||||
new osci::EffectParameter("Ripple Amount", "Controls how many ripples are applied to the image.", "rippleAmount", VERSION_HINT, 0.1, 0.0, 1.0),
|
||||
}
|
||||
);
|
||||
eff->setName("Ripple");
|
||||
eff->setIcon(BinaryData::ripple_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class RotateEffectApp : public osci::EffectApplication {
|
||||
public:
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override {
|
||||
input.rotate(values[0] * std::numbers::pi, values[1] * std::numbers::pi, values[2] * std::numbers::pi);
|
||||
return input;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<RotateEffectApp>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Rotate X", "Controls the rotation of the object in the X axis.", "rotateX", 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, 0.0001f, osci::LfoType::Sawtooth, 0.2f),
|
||||
new osci::EffectParameter("Rotate Z", "Controls the rotation of the object in the Z axis.", "rotateZ", VERSION_HINT, 0.0, -1.0, 1.0),
|
||||
}
|
||||
);
|
||||
eff->setName("Rotate");
|
||||
eff->setIcon(BinaryData::rotate_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class ScaleEffectApp : public osci::EffectApplication {
|
||||
public:
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override {
|
||||
return input * osci::Point(values[0], values[1], values[2]);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<ScaleEffectApp>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Scale X", "Scales the object in the horizontal direction.", "scaleX", VERSION_HINT, 1.2, -3.0, 3.0),
|
||||
new osci::EffectParameter("Scale Y", "Scales the object in the vertical direction.", "scaleY", VERSION_HINT, 1.2, -3.0, 3.0),
|
||||
new osci::EffectParameter("Scale Z", "Scales the depth of the object.", "scaleZ", VERSION_HINT, 1.2, -3.0, 3.0),
|
||||
}
|
||||
);
|
||||
eff->setName("Scale");
|
||||
eff->setIcon(BinaryData::scale_svg);
|
||||
eff->markLockable(true);
|
||||
return eff;
|
||||
}
|
||||
};
|
|
@ -1,10 +1,7 @@
|
|||
#include "ShapeVoice.h"
|
||||
#include "../PluginProcessor.h"
|
||||
|
||||
ShapeVoice::ShapeVoice(OscirenderAudioProcessor& p, juce::AudioSampleBuffer& externalAudio) : audioProcessor(p), externalAudio(externalAudio) {
|
||||
actualTraceStart = audioProcessor.trace->getValue(0);
|
||||
actualTraceLength = audioProcessor.trace->getValue(1);
|
||||
}
|
||||
ShapeVoice::ShapeVoice(OscirenderAudioProcessor& p, juce::AudioSampleBuffer& externalAudio) : audioProcessor(p), externalAudio(externalAudio) {}
|
||||
|
||||
bool ShapeVoice::canPlaySound(juce::SynthesiserSound* sound) {
|
||||
return dynamic_cast<ShapeSound*> (sound) != nullptr;
|
||||
|
@ -93,13 +90,7 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
|
|||
}
|
||||
|
||||
for (auto sample = startSample; sample < startSample + numSamples; ++sample) {
|
||||
bool traceEnabled = audioProcessor.trace->enabled->getBoolValue();
|
||||
|
||||
// update length increment
|
||||
double traceLen = traceEnabled ? actualTraceLength : 1.0;
|
||||
double traceMin = traceEnabled ? actualTraceStart : 0.0;
|
||||
double proportionalLength = std::max(0.001, traceLen) * frameLength;
|
||||
lengthIncrement = juce::jmax(proportionalLength / (audioProcessor.currentSampleRate / actualFrequency), MIN_LENGTH_INCREMENT);
|
||||
lengthIncrement = juce::jmax(frameLength / (audioProcessor.currentSampleRate / actualFrequency), MIN_LENGTH_INCREMENT);
|
||||
|
||||
osci::Point channels;
|
||||
double x = 0.0;
|
||||
|
@ -163,27 +154,11 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
|
|||
outputBuffer.addSample(0, sample, x * gain);
|
||||
}
|
||||
|
||||
double traceStartValue = audioProcessor.trace->getActualValue(0);
|
||||
double traceLengthValue = audioProcessor.trace->getActualValue(1);
|
||||
traceLengthValue = traceEnabled ? traceLengthValue : 1.0;
|
||||
traceStartValue = traceEnabled ? traceStartValue : 0.0;
|
||||
actualTraceLength = std::max(0.01, traceLengthValue);
|
||||
actualTraceStart = traceStartValue;
|
||||
if (actualTraceStart < 0) {
|
||||
actualTraceStart = 0;
|
||||
}
|
||||
|
||||
if (!renderingSample) {
|
||||
incrementShapeDrawing();
|
||||
}
|
||||
|
||||
double drawnFrameLength = frameLength;
|
||||
bool willLoopOver = false;
|
||||
if (traceEnabled) {
|
||||
drawnFrameLength *= actualTraceLength + actualTraceStart;
|
||||
}
|
||||
|
||||
if (!renderingSample && frameDrawn >= drawnFrameLength) {
|
||||
if (!renderingSample && frameDrawn >= frameLength) {
|
||||
double currentShapeLength = 0;
|
||||
if (currentShape < frame.size()) {
|
||||
currentShapeLength = frame[currentShape]->len;
|
||||
|
@ -191,22 +166,9 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
|
|||
if (sound.load() != nullptr && currentlyPlaying) {
|
||||
frameLength = sound.load()->updateFrame(frame);
|
||||
}
|
||||
frameDrawn -= drawnFrameLength;
|
||||
if (traceEnabled) {
|
||||
shapeDrawn = juce::jlimit(0.0, currentShapeLength, frameDrawn);
|
||||
}
|
||||
double prevFrameLength = frameLength;
|
||||
frameDrawn -= prevFrameLength;
|
||||
currentShape = 0;
|
||||
|
||||
// TODO: updateFrame already iterates over all the shapes,
|
||||
// so we can improve performance by calculating frameDrawn
|
||||
// and shapeDrawn directly. frameDrawn is simply actualTraceStart * frameLength
|
||||
// but shapeDrawn is the amount of the current shape that has been drawn so
|
||||
// we need to iterate over all the shapes to calculate it.
|
||||
if (traceEnabled) {
|
||||
while (frameDrawn < actualTraceStart * frameLength) {
|
||||
incrementShapeDrawing();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,11 @@ public:
|
|||
|
||||
bool renderingSample = false;
|
||||
private:
|
||||
const double MIN_TRACE = 0.005;
|
||||
const double MIN_LENGTH_INCREMENT = 0.000001;
|
||||
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
std::vector<std::unique_ptr<osci::Shape>> frame;
|
||||
std::atomic<ShapeSound*> sound = nullptr;
|
||||
double actualTraceStart;
|
||||
double actualTraceLength;
|
||||
|
||||
double frameLength = 0.0;
|
||||
int currentShape = 0;
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
class SmoothEffect : public osci::EffectApplication {
|
||||
public:
|
||||
SmoothEffect() = default;
|
||||
explicit SmoothEffect(juce::String prefix, float defaultValue = 0.75f) : idPrefix(prefix), smoothingDefault(defaultValue) {}
|
||||
|
||||
osci::Point apply(int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) override {
|
||||
double weight = juce::jmax(values[0].load(), 0.00001);
|
||||
weight *= 0.95;
|
||||
|
@ -13,6 +16,18 @@ public:
|
|||
|
||||
return avg;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto id = idPrefix.isEmpty() ? juce::String("smoothing") : (idPrefix + "Smoothing");
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<SmoothEffect>(id),
|
||||
new osci::EffectParameter("Smoothing", "This works as a low-pass frequency filter that removes high frequencies, making the image look smoother, and audio sound less harsh.", id, VERSION_HINT, smoothingDefault, 0.0, 1.0)
|
||||
);
|
||||
eff->setIcon(BinaryData::smoothing_svg);
|
||||
return eff;
|
||||
}
|
||||
private:
|
||||
osci::Point avg;
|
||||
juce::String idPrefix;
|
||||
float smoothingDefault = 0.5f;
|
||||
};
|
||||
|
|
|
@ -27,6 +27,18 @@ public:
|
|||
return osci::Point(input.x, buffer[readHead].y, input.z);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
return std::make_shared<osci::Effect>(
|
||||
std::make_shared<StereoEffect>(),
|
||||
new osci::EffectParameter(
|
||||
"Stereo",
|
||||
"Turns mono audio that is uninteresting to visualise into stereo audio that is interesting to visualise.",
|
||||
"stereo",
|
||||
VERSION_HINT, 0.0, 0.0, 1.0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void initialiseBuffer(double sampleRate) {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class SwirlEffectApp : public osci::EffectApplication {
|
||||
public:
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override {
|
||||
double length = 10 * values[0] * input.magnitude();
|
||||
double newX = input.x * std::cos(length) - input.y * std::sin(length);
|
||||
double newY = input.x * std::sin(length) + input.y * std::cos(length);
|
||||
return osci::Point(newX, newY, input.z);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<SwirlEffectApp>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Swirl", "Swirls the image in a spiral pattern.", "swirl", VERSION_HINT, 0.4, -1.0, 1.0),
|
||||
}
|
||||
);
|
||||
eff->setIcon(BinaryData::swirl_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class TranslateEffectApp : public osci::EffectApplication {
|
||||
public:
|
||||
osci::Point apply(int /*index*/, osci::Point input, const std::vector<std::atomic<double>>& values, double /*sampleRate*/) override {
|
||||
return input + osci::Point(values[0], values[1], values[2]);
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<TranslateEffectApp>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Translate X", "Moves the object horizontally.", "translateX", VERSION_HINT, 0.3, -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),
|
||||
}
|
||||
);
|
||||
eff->setName("Translate");
|
||||
eff->setIcon(BinaryData::translate_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
|
@ -10,4 +10,13 @@ public:
|
|||
input.rotate(0.0, twistTheta, 0.0);
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<TwistEffect>(),
|
||||
new osci::EffectParameter("Twist", "Twists the image in a corkscrew pattern.", "twist", VERSION_HINT, 0.5, 0.0, 1.0, 0.0001, osci::LfoType::Sine, 0.5)
|
||||
);
|
||||
eff->setIcon(BinaryData::twist_svg);
|
||||
return eff;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,6 +20,14 @@ public:
|
|||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto eff = std::make_shared<osci::Effect>(
|
||||
std::make_shared<VectorCancellingEffect>(),
|
||||
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.5, 0.0, 1.0));
|
||||
eff->setIcon(BinaryData::vectorcancelling_svg);
|
||||
return eff;
|
||||
}
|
||||
private:
|
||||
int lastIndex = 0;
|
||||
double nextInvert = 0;
|
||||
|
|
|
@ -14,6 +14,18 @@ public:
|
|||
return input + delta;
|
||||
}
|
||||
|
||||
std::shared_ptr<osci::Effect> build() const override {
|
||||
auto wobble = std::make_shared<osci::Effect>(
|
||||
std::make_shared<WobbleEffect>(audioProcessor),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
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, 0.0001f, osci::LfoType::Sawtooth, 1.0f),
|
||||
});
|
||||
wobble->setName("Wobble");
|
||||
wobble->setIcon(BinaryData::wobble_svg);
|
||||
return wobble;
|
||||
}
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
double smoothedFrequency = 0;
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
#include "DraggableListBox.h"
|
||||
#include <cmath>
|
||||
|
||||
DraggableListBoxItemData::~DraggableListBoxItemData() {};
|
||||
|
||||
void DraggableListBoxItem::paint(juce::Graphics& g)
|
||||
{
|
||||
if (insertAfter)
|
||||
{
|
||||
g.setColour(juce::Colour(0xff00ff00));
|
||||
g.fillRect(0, getHeight() - 4, getWidth(), 4);
|
||||
}
|
||||
else if (insertBefore)
|
||||
{
|
||||
g.setColour(juce::Colour(0xff00ff00));
|
||||
g.fillRect(0, 0, getWidth(), 4);
|
||||
}
|
||||
// Per-item insertion lines are suppressed in favour of a single overlay drawn by DraggableListBox.
|
||||
juce::ignoreUnused(g);
|
||||
}
|
||||
|
||||
void DraggableListBoxItem::mouseEnter(const juce::MouseEvent&)
|
||||
|
@ -31,7 +24,10 @@ void DraggableListBoxItem::mouseDrag(const juce::MouseEvent&)
|
|||
{
|
||||
if (juce::DragAndDropContainer* container = juce::DragAndDropContainer::findParentDragContainerFor(this))
|
||||
{
|
||||
container->startDragging("DraggableListBoxItem", this);
|
||||
auto* obj = new juce::DynamicObject();
|
||||
obj->setProperty("type", juce::var("DraggableListBoxItem"));
|
||||
obj->setProperty("row", juce::var(rowNum));
|
||||
container->startDragging(juce::var(obj), this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,31 +56,247 @@ void DraggableListBoxItem::hideInsertLines()
|
|||
void DraggableListBoxItem::itemDragEnter(const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
updateInsertLines(dragSourceDetails);
|
||||
updateAutoScroll(dragSourceDetails);
|
||||
// Update the global overlay on the parent list box
|
||||
auto ptGlobal = localPointToGlobal(dragSourceDetails.localPosition);
|
||||
auto ptInLB = listBox.getLocalPoint(nullptr, ptGlobal);
|
||||
listBox.updateDropIndicatorAt(ptInLB);
|
||||
}
|
||||
|
||||
void DraggableListBoxItem::itemDragMove(const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
updateInsertLines(dragSourceDetails);
|
||||
updateAutoScroll(dragSourceDetails);
|
||||
auto ptGlobal = localPointToGlobal(dragSourceDetails.localPosition);
|
||||
auto ptInLB = listBox.getLocalPoint(nullptr, ptGlobal);
|
||||
listBox.updateDropIndicatorAt(ptInLB);
|
||||
}
|
||||
|
||||
void DraggableListBoxItem::itemDragExit(const SourceDetails& /*dragSourceDetails*/)
|
||||
{
|
||||
hideInsertLines();
|
||||
stopAutoScroll();
|
||||
listBox.clearDropIndicator();
|
||||
}
|
||||
|
||||
void DraggableListBoxItem::itemDropped(const juce::DragAndDropTarget::SourceDetails &dragSourceDetails)
|
||||
{
|
||||
hideInsertLines();
|
||||
stopAutoScroll();
|
||||
listBox.clearDropIndicator();
|
||||
if (DraggableListBoxItem* item = dynamic_cast<DraggableListBoxItem*>(dragSourceDetails.sourceComponent.get()))
|
||||
{
|
||||
if (dragSourceDetails.localPosition.y < getHeight() / 2)
|
||||
modelData.moveBefore(item->rowNum, rowNum);
|
||||
else
|
||||
modelData.moveAfter(item->rowNum, rowNum);
|
||||
if (auto* vp = listBox.getViewport())
|
||||
{
|
||||
// Compute the global insertion index using the list box, not the item local midpoint
|
||||
auto ptInThis = dragSourceDetails.localPosition;
|
||||
auto ptGlobal = localPointToGlobal(ptInThis);
|
||||
auto ptInLB = listBox.getLocalPoint(nullptr, ptGlobal);
|
||||
int insertIndex = listBox.getInsertionIndexForPosition(ptInLB.x, ptInLB.y);
|
||||
insertIndex = juce::jlimit(0, modelData.getNumItems(), insertIndex);
|
||||
|
||||
// If dragging an item that appears before the insertion point and we're moving it down,
|
||||
// account for the removal shifting indices.
|
||||
const int fromIndex = item->rowNum;
|
||||
int toIndex = insertIndex;
|
||||
if (toIndex > fromIndex) toIndex -= 1;
|
||||
|
||||
if (toIndex < 0) toIndex = 0;
|
||||
if (toIndex >= modelData.getNumItems())
|
||||
modelData.moveAfter(fromIndex, modelData.getNumItems() - 1);
|
||||
else if (toIndex <= 0)
|
||||
modelData.moveBefore(fromIndex, 0);
|
||||
else
|
||||
modelData.moveBefore(fromIndex, toIndex);
|
||||
}
|
||||
listBox.updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
void DraggableListBoxItem::updateAutoScroll(const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
// Determine pointer position within the list's viewport
|
||||
if (auto* vp = listBox.getViewport())
|
||||
{
|
||||
// Convert the drag position to the viewport's local coordinates
|
||||
auto ptInThis = dragSourceDetails.localPosition;
|
||||
auto ptInLB = localPointToGlobal(ptInThis);
|
||||
ptInLB = listBox.getLocalPoint(nullptr, ptInLB); // convert from screen to listbox
|
||||
|
||||
auto viewBounds = vp->getLocalBounds();
|
||||
auto viewTop = viewBounds.getY();
|
||||
auto viewBottom = viewBounds.getBottom();
|
||||
|
||||
// Position of pointer within the viewport component
|
||||
auto ptInVP = vp->getLocalPoint(&listBox, ptInLB);
|
||||
const int yInVP = ptInVP.y;
|
||||
|
||||
const int edgeZone = juce::jmax(12, viewBounds.getHeight() / 6); // scrolling zone near edges
|
||||
double velocity = 0.0;
|
||||
|
||||
if (yInVP < viewTop + edgeZone)
|
||||
{
|
||||
// Near top: scroll up. Speed increases closer to edge.
|
||||
const double t = juce::jlimit(0.0, 1.0, (double)(edgeZone - (yInVP - viewTop)) / (double)edgeZone);
|
||||
velocity = - (1.0 + 14.0 * t * t); // pixels per tick (quadratic ramp), negative = up
|
||||
}
|
||||
else if (yInVP > viewBottom - edgeZone)
|
||||
{
|
||||
// Near bottom: scroll down.
|
||||
const double d = (double)(yInVP - (viewBottom - edgeZone));
|
||||
const double t = juce::jlimit(0.0, 1.0, d / (double)edgeZone);
|
||||
velocity = (1.0 + 14.0 * t * t); // pixels per tick, positive = down
|
||||
}
|
||||
|
||||
scrollPixelsPerTick = velocity;
|
||||
|
||||
if (std::abs(scrollPixelsPerTick) > 0.0)
|
||||
{
|
||||
if (!autoScrollActive)
|
||||
{
|
||||
autoScrollActive = true;
|
||||
startTimerHz(60); // smooth-ish scrolling tied to UI thread
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stopAutoScroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DraggableListBoxItem::stopAutoScroll()
|
||||
{
|
||||
autoScrollActive = false;
|
||||
stopTimer();
|
||||
scrollPixelsPerTick = 0.0;
|
||||
}
|
||||
|
||||
void DraggableListBoxItem::timerCallback()
|
||||
{
|
||||
if (!autoScrollActive || scrollPixelsPerTick == 0.0)
|
||||
return;
|
||||
|
||||
if (auto* vp = listBox.getViewport())
|
||||
{
|
||||
auto current = vp->getViewPosition();
|
||||
const int contentH = vp->getViewedComponent() != nullptr ? vp->getViewedComponent()->getHeight() : listBox.getHeight();
|
||||
const int maxY = juce::jmax(0, contentH - vp->getHeight());
|
||||
int newY = juce::jlimit(0, maxY, current.y + (int)std::lround(scrollPixelsPerTick));
|
||||
if (newY != current.y)
|
||||
{
|
||||
vp->setViewPosition(current.x, newY);
|
||||
}
|
||||
|
||||
// Update the global drop indicator position based on current mouse position (even if the mouse isn't moving)
|
||||
auto screenPos = juce::Desktop::getInstance().getMainMouseSource().getScreenPosition();
|
||||
auto posInLB = listBox.getLocalPoint(nullptr, screenPos.toInt());
|
||||
listBox.updateDropIndicatorAt(posInLB);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== DraggableListBox overlay indicator =====================
|
||||
|
||||
void DraggableListBox::updateDropIndicator(const SourceDetails& details)
|
||||
{
|
||||
// localPosition is already in this component's coordinate space
|
||||
const auto pt = details.localPosition;
|
||||
int index = getInsertionIndexForPosition(pt.x, pt.y);
|
||||
if (index < 0) index = 0; // allow showing at very top (over header spacer)
|
||||
dropInsertIndex = index;
|
||||
showDropIndicator = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DraggableListBox::clearDropIndicator()
|
||||
{
|
||||
showDropIndicator = false;
|
||||
dropInsertIndex = -1;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DraggableListBox::updateDropIndicatorAt(const juce::Point<int>& listLocalPos)
|
||||
{
|
||||
int index = getInsertionIndexForPosition(listLocalPos.x, listLocalPos.y);
|
||||
if (index < 0) index = 0; // allow showing at very top (over header spacer)
|
||||
dropInsertIndex = index;
|
||||
showDropIndicator = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DraggableListBox::paintOverChildren(juce::Graphics& g)
|
||||
{
|
||||
VListBox::paintOverChildren(g);
|
||||
if (!showDropIndicator) return;
|
||||
|
||||
const int numRows = getModel() != nullptr ? getModel()->getNumRows() : 0;
|
||||
if (dropInsertIndex < 0 || dropInsertIndex > numRows) return;
|
||||
|
||||
auto* vp = getViewport();
|
||||
if (vp == nullptr) return;
|
||||
|
||||
// Determine the y position between rows to draw the indicator line
|
||||
int y = 0;
|
||||
if (dropInsertIndex == 0)
|
||||
{
|
||||
// Top of first row (below header)
|
||||
if (numRows > 0)
|
||||
y = getRowPosition(0, true).getY();
|
||||
else
|
||||
y = 0;
|
||||
}
|
||||
else if (dropInsertIndex >= numRows)
|
||||
{
|
||||
auto lastRowBounds = getRowPosition(numRows - 1, true);
|
||||
y = lastRowBounds.getBottom();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prevBounds = getRowPosition(dropInsertIndex - 1, true);
|
||||
y = prevBounds.getBottom();
|
||||
}
|
||||
|
||||
// Draw a prominent indicator spanning the visible row width
|
||||
const int x = 0;
|
||||
const int w = getVisibleRowWidth();
|
||||
const int thickness = 3;
|
||||
const juce::Colour colour = juce::Colours::lime.withAlpha(0.9f);
|
||||
|
||||
const float yOffset = -2.5f; // Offset to center the line visually
|
||||
|
||||
g.setColour(colour);
|
||||
g.fillRoundedRectangle(x, y - thickness / 2 + yOffset, w, thickness, 2.0f);
|
||||
}
|
||||
|
||||
void DraggableListBox::itemDropped(const SourceDetails& details)
|
||||
{
|
||||
// Background drop: compute insertion index and use model to move
|
||||
int insertIndex = -1;
|
||||
// localPosition is already relative to this list
|
||||
insertIndex = getInsertionIndexForPosition(details.localPosition.x, details.localPosition.y);
|
||||
if (insertIndex < 0) insertIndex = 0; // clamp to top when over header spacer
|
||||
|
||||
if (auto* m = dynamic_cast<DraggableListBoxModel*>(getModel()))
|
||||
{
|
||||
int fromIndex = -1;
|
||||
const juce::var& desc = details.description;
|
||||
if (desc.isObject())
|
||||
{
|
||||
auto* obj = desc.getDynamicObject();
|
||||
if (obj != nullptr)
|
||||
{
|
||||
auto v = obj->getProperty("row");
|
||||
if (v.isInt()) fromIndex = (int)v;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromIndex >= 0 && insertIndex >= 0)
|
||||
m->moveByInsertIndex(fromIndex, insertIndex);
|
||||
}
|
||||
|
||||
clearDropIndicator();
|
||||
}
|
||||
|
||||
juce::Component* DraggableListBoxModel::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component *existingComponentToUpdate)
|
||||
{
|
||||
std::unique_ptr<DraggableListBoxItem> item(dynamic_cast<DraggableListBoxItem*>(existingComponentToUpdate));
|
||||
|
|
|
@ -18,14 +18,39 @@ struct DraggableListBoxItemData
|
|||
virtual void addItemAtEnd() {};
|
||||
};
|
||||
|
||||
// DraggableListBox is basically just a ListBox, that inherits from DragAndDropContainer.
|
||||
// Declare your list box using this type.
|
||||
class DraggableListBox : public juce::jc::ListBox, public juce::DragAndDropContainer
|
||||
// DraggableListBox extends VListBox to both initiate drags and act as a target, so
|
||||
// it can paint a clean, consistent drop indicator between row components.
|
||||
class DraggableListBox : public VListBox,
|
||||
public juce::DragAndDropContainer,
|
||||
public juce::DragAndDropTarget
|
||||
{
|
||||
public:
|
||||
using VListBox::VListBox;
|
||||
|
||||
// DragAndDropTarget
|
||||
bool isInterestedInDragSource(const SourceDetails&) override { return true; }
|
||||
void itemDragEnter(const SourceDetails& details) override { updateDropIndicator(details); }
|
||||
void itemDragMove(const SourceDetails& details) override { updateDropIndicator(details); }
|
||||
void itemDragExit(const SourceDetails&) override { clearDropIndicator(); }
|
||||
void itemDropped(const SourceDetails& details) override;
|
||||
bool shouldDrawDragImageWhenOver() override { return true; }
|
||||
|
||||
// Paint a global drop indicator between rows
|
||||
void paintOverChildren(juce::Graphics& g) override;
|
||||
|
||||
// Allow children to drive indicator positioning
|
||||
void updateDropIndicatorAt(const juce::Point<int>& listLocalPos);
|
||||
void clearDropIndicator();
|
||||
|
||||
private:
|
||||
void updateDropIndicator(const SourceDetails& details);
|
||||
|
||||
bool showDropIndicator = false;
|
||||
int dropInsertIndex = -1; // index to insert before; may be getNumRows() for end
|
||||
};
|
||||
|
||||
// Everything below this point should be generic.
|
||||
class DraggableListBoxItem : public juce::Component, public juce::DragAndDropTarget
|
||||
class DraggableListBoxItem : public juce::Component, public juce::DragAndDropTarget, public juce::Timer
|
||||
{
|
||||
public:
|
||||
DraggableListBoxItem(DraggableListBox& lb, DraggableListBoxItemData& data, int rn)
|
||||
|
@ -49,6 +74,9 @@ public:
|
|||
protected:
|
||||
void updateInsertLines(const SourceDetails &dragSourceDetails);
|
||||
void hideInsertLines();
|
||||
void updateAutoScroll(const SourceDetails& dragSourceDetails);
|
||||
void stopAutoScroll();
|
||||
void timerCallback() override;
|
||||
|
||||
int rowNum;
|
||||
DraggableListBoxItemData& modelData;
|
||||
|
@ -57,9 +85,13 @@ protected:
|
|||
juce::MouseCursor savedCursor;
|
||||
bool insertAfter = false;
|
||||
bool insertBefore = false;
|
||||
|
||||
// Auto-scroll state while dragging near viewport edges
|
||||
double scrollPixelsPerTick = 0.0; // positive = scroll down, negative = up
|
||||
bool autoScrollActive = false;
|
||||
};
|
||||
|
||||
class DraggableListBoxModel : public juce::jc::ListBoxModel
|
||||
class DraggableListBoxModel : public VListBoxModel
|
||||
{
|
||||
public:
|
||||
DraggableListBoxModel(DraggableListBox& lb, DraggableListBoxItemData& md)
|
||||
|
@ -70,6 +102,27 @@ public:
|
|||
|
||||
juce::Component* refreshComponentForRow(int, bool, juce::Component*) override;
|
||||
|
||||
// Convenience: move an item using an insertion index (before position). Handles index shifting.
|
||||
void moveByInsertIndex(int fromIndex, int insertIndex)
|
||||
{
|
||||
const int count = modelData.getNumItems();
|
||||
if (count <= 0) return;
|
||||
insertIndex = juce::jlimit(0, count, insertIndex);
|
||||
int toIndex = insertIndex;
|
||||
if (toIndex > fromIndex) toIndex -= 1;
|
||||
|
||||
if (count == 1 || fromIndex == toIndex) return;
|
||||
|
||||
if (toIndex <= 0)
|
||||
modelData.moveBefore(fromIndex, 0);
|
||||
else if (toIndex >= count)
|
||||
modelData.moveAfter(fromIndex, count - 1);
|
||||
else
|
||||
modelData.moveBefore(fromIndex, toIndex);
|
||||
|
||||
listBox.updateContent();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Draggable model has a reference to its owner ListBox, so it can tell it to update after DnD.
|
||||
DraggableListBox &listBox;
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
@ -213,14 +215,8 @@ void EffectComponent::resized() {
|
|||
}
|
||||
|
||||
void EffectComponent::paint(juce::Graphics& g) {
|
||||
auto bounds = getLocalBounds();
|
||||
auto length = effect.parameters.size();
|
||||
auto isEnd = index == length - 1;
|
||||
auto isStart = index == 0;
|
||||
g.setColour(findColour(effectComponentBackgroundColourId, true));
|
||||
juce::Path path;
|
||||
path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, false, isStart, false, isEnd);
|
||||
g.fillPath(path);
|
||||
g.fillRect(getLocalBounds());
|
||||
}
|
||||
|
||||
void EffectComponent::parameterValueChanged(int parameterIndex, float newValue) {
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
#include "EffectTypeGridComponent.h"
|
||||
#include "../LookAndFeel.h"
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
EffectTypeGridComponent::EffectTypeGridComponent(OscirenderAudioProcessor& processor)
|
||||
: audioProcessor(processor)
|
||||
{
|
||||
// Setup scrollable viewport and content
|
||||
addAndMakeVisible(viewport);
|
||||
viewport.setViewedComponent(&content, false);
|
||||
viewport.setScrollBarsShown(true, false); // vertical only
|
||||
// Setup reusable bottom fade
|
||||
initScrollFade(*this);
|
||||
attachToViewport(viewport);
|
||||
setupEffectItems();
|
||||
setSize(400, 200);
|
||||
addAndMakeVisible(cancelButton);
|
||||
cancelButton.onClick = [this]() {
|
||||
if (onCanceled) onCanceled();
|
||||
};
|
||||
refreshDisabledStates();
|
||||
}
|
||||
|
||||
EffectTypeGridComponent::~EffectTypeGridComponent() = default;
|
||||
|
||||
void EffectTypeGridComponent::setupEffectItems()
|
||||
{
|
||||
// Clear existing items
|
||||
effectItems.clear();
|
||||
content.removeAllChildren();
|
||||
|
||||
// Get effect types directly from the audio processor's toggleableEffects
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
const int n = (int) audioProcessor.toggleableEffects.size();
|
||||
std::vector<int> order(n);
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
std::sort(order.begin(), order.end(), [this](int a, int b) {
|
||||
auto ea = audioProcessor.toggleableEffects[a];
|
||||
auto eb = audioProcessor.toggleableEffects[b];
|
||||
const int cmp = ea->getName().compareIgnoreCase(eb->getName());
|
||||
if (cmp != 0)
|
||||
return cmp < 0; // ascending alphabetical, case-insensitive
|
||||
// Stable tie-breaker to ensure deterministic layout
|
||||
return ea->getId().compare(eb->getId()) < 0;
|
||||
});
|
||||
|
||||
for (int idx : order)
|
||||
{
|
||||
auto effect = audioProcessor.toggleableEffects[idx];
|
||||
// Extract effect name from the effect
|
||||
juce::String effectName = effect->getName();
|
||||
|
||||
// Create new item component
|
||||
auto* item = new EffectTypeItemComponent(effectName, effect->getIcon(), effect->getId());
|
||||
|
||||
// Set up callback to forward effect selection
|
||||
item->onEffectSelected = [this](const juce::String& effectId) {
|
||||
if (onEffectSelected)
|
||||
onEffectSelected(effectId);
|
||||
};
|
||||
// Hover preview: request temporary preview of this effect while hovered
|
||||
item->onHoverStart = [this](const juce::String& effectId) {
|
||||
if (audioProcessor.getGlobalBoolValue("previewEffectOnHover", true)) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
audioProcessor.setPreviewEffectId(effectId);
|
||||
}
|
||||
};
|
||||
item->onHoverEnd = [this]() {
|
||||
if (audioProcessor.getGlobalBoolValue("previewEffectOnHover", true)) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
audioProcessor.clearPreviewEffect();
|
||||
}
|
||||
};
|
||||
|
||||
effectItems.add(item);
|
||||
content.addAndMakeVisible(item);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectTypeGridComponent::refreshDisabledStates()
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
// Build a quick lookup of selected ids
|
||||
std::unordered_set<std::string> selectedIds;
|
||||
selectedIds.reserve((size_t) audioProcessor.toggleableEffects.size());
|
||||
bool anySelected = false;
|
||||
for (const auto& eff : audioProcessor.toggleableEffects) {
|
||||
const bool isSelected = (eff->selected == nullptr) ? true : eff->selected->getBoolValue();
|
||||
if (isSelected) {
|
||||
anySelected = true;
|
||||
selectedIds.insert(eff->getId().toStdString());
|
||||
}
|
||||
}
|
||||
for (auto* item : effectItems) {
|
||||
const bool disable = selectedIds.find(item->getEffectId().toStdString()) != selectedIds.end();
|
||||
item->setEnabled(! disable);
|
||||
}
|
||||
cancelButton.setVisible(anySelected);
|
||||
// Update fade visibility/layout in case scrollability changed
|
||||
layoutScrollFade(viewport.getBounds(), true, 48);
|
||||
}
|
||||
|
||||
void EffectTypeGridComponent::paint(juce::Graphics& g)
|
||||
{
|
||||
// No background - make component transparent
|
||||
}
|
||||
|
||||
void EffectTypeGridComponent::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
auto topBar = bounds.removeFromTop(30);
|
||||
cancelButton.setBounds(topBar.removeFromRight(80).reduced(4));
|
||||
viewport.setBounds(bounds);
|
||||
auto contentArea = viewport.getLocalBounds();
|
||||
// Lock content width to viewport width to avoid horizontal scrolling
|
||||
content.setSize(contentArea.getWidth(), content.getHeight());
|
||||
|
||||
// Create FlexBox for responsive grid layout within content
|
||||
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;
|
||||
|
||||
// Determine fixed per-item width for this viewport width
|
||||
const int viewW = contentArea.getWidth();
|
||||
const int viewH = contentArea.getHeight();
|
||||
const int itemsPerRow = juce::jmax(1, viewW / MIN_ITEM_WIDTH);
|
||||
const int fixedItemWidth = (itemsPerRow > 0 ? viewW / itemsPerRow : viewW);
|
||||
|
||||
// Add each effect item with a fixed width, and pad the final row with placeholders so it's centered
|
||||
const int total = effectItems.size();
|
||||
const int fullRows = (itemsPerRow > 0 ? total / itemsPerRow : 0);
|
||||
const int remainder = (itemsPerRow > 0 ? total % itemsPerRow : 0);
|
||||
|
||||
auto addItemFlex = [&](juce::Component* c)
|
||||
{
|
||||
flexBox.items.add(juce::FlexItem(*c)
|
||||
.withMinWidth((float) fixedItemWidth)
|
||||
.withMaxWidth((float) fixedItemWidth)
|
||||
.withHeight((float) ITEM_HEIGHT)
|
||||
.withFlex(1.0f) // keep existing flex behaviour; fixed max width holds size
|
||||
.withMargin(juce::FlexItem::Margin(0)));
|
||||
};
|
||||
|
||||
auto addPlaceholder = [&]()
|
||||
{
|
||||
// Placeholder occupies a slot visually but has no component; ensures last row is centered
|
||||
juce::FlexItem placeholder((float) fixedItemWidth, (float) ITEM_HEIGHT);
|
||||
placeholder.flexGrow = 1.0f; // match item flex for consistent spacing
|
||||
placeholder.margin = juce::FlexItem::Margin(0);
|
||||
flexBox.items.add(std::move(placeholder));
|
||||
};
|
||||
|
||||
int index = 0;
|
||||
// Add complete rows
|
||||
for (int r = 0; r < fullRows; ++r)
|
||||
for (int c = 0; c < itemsPerRow; ++c)
|
||||
addItemFlex(effectItems.getUnchecked(index++));
|
||||
|
||||
// Add last row centered with balanced placeholders
|
||||
if (remainder > 0)
|
||||
{
|
||||
const int missing = itemsPerRow - remainder;
|
||||
const int leftPad = missing / 2;
|
||||
const int rightPad = missing - leftPad;
|
||||
|
||||
for (int i = 0; i < leftPad; ++i) addPlaceholder();
|
||||
for (int i = 0; i < remainder; ++i) addItemFlex(effectItems.getUnchecked(index++));
|
||||
for (int i = 0; i < rightPad; ++i) addPlaceholder();
|
||||
}
|
||||
|
||||
// Compute required content height
|
||||
const int requiredHeight = calculateRequiredHeight(viewW);
|
||||
|
||||
// If content is shorter than viewport, make content at least as tall as viewport
|
||||
int yOffset = 0;
|
||||
if (requiredHeight < viewH) {
|
||||
content.setSize(viewW, viewH);
|
||||
yOffset = (viewH - requiredHeight) / 2;
|
||||
} else {
|
||||
content.setSize(viewW, requiredHeight);
|
||||
}
|
||||
// Layout items within content at the computed offset
|
||||
flexBox.performLayout(juce::Rectangle<float>(0.0f, (float) yOffset, (float) viewW, (float) requiredHeight));
|
||||
|
||||
// Layout bottom scroll fade over the viewport area
|
||||
layoutScrollFade(viewport.getBounds(), true, 48);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../PluginProcessor.h"
|
||||
#include "EffectTypeItemComponent.h"
|
||||
#include "ScrollFadeMixin.h"
|
||||
|
||||
class EffectTypeGridComponent : public juce::Component, private ScrollFadeMixin
|
||||
{
|
||||
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;
|
||||
std::function<void()> onCanceled; // optional cancel handler
|
||||
void refreshDisabledStates(); // grey-out items that are already selected
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
juce::Viewport viewport; // scroll container
|
||||
juce::Component content; // holds the grid items
|
||||
juce::OwnedArray<EffectTypeItemComponent> effectItems;
|
||||
juce::FlexBox flexBox;
|
||||
juce::TextButton cancelButton { "Cancel" };
|
||||
|
||||
static constexpr int ITEM_HEIGHT = 80;
|
||||
static constexpr int MIN_ITEM_WIDTH = 180;
|
||||
|
||||
void setupEffectItems();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectTypeGridComponent)
|
||||
};
|
|
@ -0,0 +1,123 @@
|
|||
#include "EffectTypeItemComponent.h"
|
||||
|
||||
EffectTypeItemComponent::EffectTypeItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id)
|
||||
: effectName(name), effectId(id)
|
||||
{
|
||||
juce::String iconSvg = icon;
|
||||
if (icon.isEmpty()) {
|
||||
// Default icon if none is provided
|
||||
iconSvg = juce::String::createStringFromData(BinaryData::rotate_svg, BinaryData::rotate_svgSize);
|
||||
}
|
||||
iconButton = std::make_unique<SvgButton>(
|
||||
"effectIcon",
|
||||
iconSvg,
|
||||
juce::Colours::white.withAlpha(0.7f)
|
||||
);
|
||||
|
||||
// Make the icon non-interactive since this is just a visual element
|
||||
iconButton->setInterceptsMouseClicks(false, false);
|
||||
addAndMakeVisible(*iconButton);
|
||||
}
|
||||
|
||||
EffectTypeItemComponent::~EffectTypeItemComponent() = default;
|
||||
|
||||
void EffectTypeItemComponent::paint(juce::Graphics& g)
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat().reduced(10);
|
||||
|
||||
// Get animation progress from inherited HoverAnimationMixin (disabled => no hover)
|
||||
auto animationProgress = isEnabled() ? getAnimationProgress() : 0.0f;
|
||||
|
||||
// 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);
|
||||
|
||||
if (shadow.radius > 0) {
|
||||
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 = Colours::veryDark;
|
||||
juce::Colour hoverBgColour = normalBgColour.brighter(0.05f);
|
||||
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 text area - now accounting for icon space on the left
|
||||
auto textArea = bounds.reduced(8, 4);
|
||||
textArea.removeFromLeft(28); // Remove space for icon (24px + 4px gap)
|
||||
|
||||
g.setColour(juce::Colours::white);
|
||||
g.setFont(juce::FontOptions(16.0f, juce::Font::plain));
|
||||
g.drawText(effectName, textArea, juce::Justification::centred, true);
|
||||
|
||||
// If disabled, draw a dark transparent overlay over the rounded rect to simplify visuals
|
||||
if (! isEnabled()) {
|
||||
g.setColour(juce::Colours::black.withAlpha(0.35f));
|
||||
g.fillRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds().reduced(10);
|
||||
|
||||
// Reserve space for the icon on the left
|
||||
auto iconArea = bounds.removeFromLeft(60); // 24px for icon
|
||||
iconArea = iconArea.withSizeKeepingCentre(40, 40); // Make icon 20x20px
|
||||
|
||||
iconButton->setBounds(iconArea);
|
||||
|
||||
// Get animation progress and calculate Y offset
|
||||
auto animationProgress = isEnabled() ? getAnimationProgress() : 0.0f;
|
||||
auto yOffset = -animationProgress * HOVER_LIFT_AMOUNT;
|
||||
|
||||
iconButton->setTransform(juce::AffineTransform::translation(0, yOffset));
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::mouseDown(const juce::MouseEvent& event)
|
||||
{
|
||||
if (! isEnabled()) return;
|
||||
// Extend base behavior to keep hover press animation
|
||||
HoverAnimationMixin::mouseDown(event);
|
||||
// Ensure any hover preview is cleared before permanently selecting/enabling the effect
|
||||
if (onHoverEnd) onHoverEnd();
|
||||
if (onEffectSelected) {
|
||||
onEffectSelected(effectId);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::mouseMove(const juce::MouseEvent& event) {
|
||||
setMouseCursor(isEnabled() ? juce::MouseCursor::PointingHandCursor : juce::MouseCursor::NormalCursor);
|
||||
juce::Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::mouseEnter(const juce::MouseEvent& event)
|
||||
{
|
||||
HoverAnimationMixin::mouseEnter(event);
|
||||
if (isEnabled() && onHoverStart)
|
||||
onHoverStart(effectId);
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::mouseExit(const juce::MouseEvent& event)
|
||||
{
|
||||
HoverAnimationMixin::mouseExit(event);
|
||||
if (onHoverEnd)
|
||||
onHoverEnd();
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../LookAndFeel.h"
|
||||
#include "HoverAnimationMixin.h"
|
||||
#include "SvgButton.h"
|
||||
|
||||
class EffectTypeItemComponent : public HoverAnimationMixin
|
||||
{
|
||||
public:
|
||||
EffectTypeItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id);
|
||||
~EffectTypeItemComponent() override;
|
||||
|
||||
void paint(juce::Graphics& g) override;
|
||||
void resized() override;
|
||||
void mouseDown(const juce::MouseEvent& event) override;
|
||||
void mouseMove(const juce::MouseEvent& event) override;
|
||||
void mouseEnter(const juce::MouseEvent& event) override;
|
||||
void mouseExit(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;
|
||||
std::function<void(const juce::String& effectId)> onHoverStart;
|
||||
std::function<void()> onHoverEnd;
|
||||
|
||||
private:
|
||||
juce::String effectName;
|
||||
juce::String effectId;
|
||||
|
||||
// Icon for the effect
|
||||
std::unique_ptr<SvgButton> iconButton;
|
||||
|
||||
static constexpr int CORNER_RADIUS = 8;
|
||||
static constexpr float HOVER_LIFT_AMOUNT = 2.0f;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectTypeItemComponent)
|
||||
};
|
|
@ -6,45 +6,70 @@
|
|||
EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, osci::Effect& effect) : DraggableListBoxItem(lb, data, rn),
|
||||
effect(effect), audioProcessor(data.audioProcessor), editor(data.editor) {
|
||||
auto parameters = effect.parameters;
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
std::shared_ptr<EffectComponent> effectComponent = std::make_shared<EffectComponent>(effect, i);
|
||||
selected.setToggleState(effect.enabled == nullptr || effect.enabled->getValue(), juce::dontSendNotification);
|
||||
// using weak_ptr to avoid circular reference and memory leak
|
||||
std::weak_ptr<EffectComponent> weakEffectComponent = effectComponent;
|
||||
effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification);
|
||||
|
||||
list.setEnabled(selected.getToggleState());
|
||||
selected.onClick = [this, weakEffectComponent] {
|
||||
if (auto effectComponent = weakEffectComponent.lock()) {
|
||||
auto data = (AudioEffectListBoxItemData&)modelData;
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
data.setSelected(rowNum, selected.getToggleState());
|
||||
list.setEnabled(selected.getToggleState());
|
||||
}
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
std::shared_ptr<EffectComponent> effectComponent = std::make_shared<EffectComponent>(effect, i);
|
||||
enabled.setToggleState(effect.enabled == nullptr || effect.enabled->getValue(), juce::dontSendNotification);
|
||||
// using weak_ptr to avoid circular reference and memory leak
|
||||
std::weak_ptr<EffectComponent> weakEffectComponent = effectComponent;
|
||||
effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification);
|
||||
|
||||
list.setEnabled(enabled.getToggleState());
|
||||
enabled.onClick = [this, weakEffectComponent] {
|
||||
if (auto effectComponent = weakEffectComponent.lock()) {
|
||||
auto data = (AudioEffectListBoxItemData&)modelData;
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
data.setSelected(rowNum, enabled.getToggleState());
|
||||
list.setEnabled(enabled.getToggleState());
|
||||
}
|
||||
repaint();
|
||||
};
|
||||
};
|
||||
effectComponent->updateToggleState = [this, i, weakEffectComponent] {
|
||||
if (auto effectComponent = weakEffectComponent.lock()) {
|
||||
selected.setToggleState(effectComponent->effect.enabled == nullptr || effectComponent->effect.enabled->getValue(), juce::dontSendNotification);
|
||||
list.setEnabled(selected.getToggleState());
|
||||
enabled.setToggleState(effectComponent->effect.enabled == nullptr || effectComponent->effect.enabled->getValue(), juce::dontSendNotification);
|
||||
list.setEnabled(enabled.getToggleState());
|
||||
}
|
||||
repaint();
|
||||
};
|
||||
|
||||
auto component = createComponent(parameters[i]);
|
||||
if (component != nullptr) {
|
||||
auto component = createComponent(parameters[i]);
|
||||
if (component != nullptr) {
|
||||
effectComponent->setComponent(component);
|
||||
}
|
||||
|
||||
listModel.addComponent(effectComponent);
|
||||
}
|
||||
listModel.addComponent(effectComponent);
|
||||
}
|
||||
|
||||
list.setColour(effectComponentBackgroundColourId, juce::Colours::transparentBlack.withAlpha(0.2f));
|
||||
list.setModel(&listModel);
|
||||
list.setRowHeight(ROW_HEIGHT);
|
||||
list.updateContent();
|
||||
addAndMakeVisible(list);
|
||||
addAndMakeVisible(selected);
|
||||
list.setModel(&listModel);
|
||||
list.setRowHeight(ROW_HEIGHT);
|
||||
list.updateContent();
|
||||
addAndMakeVisible(list);
|
||||
addAndMakeVisible(enabled);
|
||||
|
||||
closeButton.setEdgeIndent(2);
|
||||
closeButton.onClick = [this]() {
|
||||
// Flip flags under lock
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
if (this->effect.enabled) this->effect.enabled->setValueNotifyingHost(false);
|
||||
if (this->effect.selected) this->effect.selected->setValueNotifyingHost(false);
|
||||
// Reset all parameters/flags for this effect back to defaults on removal
|
||||
this->effect.resetToDefault();
|
||||
}
|
||||
// Defer model reset and outer list refresh to avoid re-entrancy on current row
|
||||
juce::MessageManager::callAsync([this]() {
|
||||
auto& data = static_cast<AudioEffectListBoxItemData&>(modelData);
|
||||
data.resetData();
|
||||
// Update the outer DraggableListBox, not the inner parameter list
|
||||
this->listBox.updateContent();
|
||||
// If there are no effects visible, open the grid
|
||||
if (data.getNumItems() == 0) {
|
||||
if (data.onAddNewEffectRequested)
|
||||
data.onAddNewEffectRequested();
|
||||
}
|
||||
});
|
||||
};
|
||||
addAndMakeVisible(closeButton);
|
||||
}
|
||||
|
||||
EffectsListComponent::~EffectsListComponent() {
|
||||
|
@ -52,26 +77,34 @@ EffectsListComponent::~EffectsListComponent() {
|
|||
}
|
||||
|
||||
void EffectsListComponent::paint(juce::Graphics& g) {
|
||||
auto bounds = getLocalBounds().removeFromLeft(LEFT_BAR_WIDTH);
|
||||
auto bounds = getLocalBounds().removeFromLeft(LEFT_BAR_WIDTH);
|
||||
g.setColour(findColour(effectComponentHandleColourId));
|
||||
bounds.removeFromBottom(PADDING);
|
||||
juce::Path path;
|
||||
path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, true, false, true, false);
|
||||
g.fillPath(path);
|
||||
g.setColour(juce::Colours::white);
|
||||
// draw drag and drop handle using circles
|
||||
double size = 4;
|
||||
double leftPad = 4;
|
||||
double spacing = 7;
|
||||
double topPad = 6;
|
||||
double y = bounds.getHeight() / 2 - 15;
|
||||
g.fillEllipse(leftPad, y + topPad, size, size);
|
||||
g.fillEllipse(leftPad, y + topPad + 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 + spacing, size, size);
|
||||
g.fillEllipse(leftPad + spacing, y + topPad + 2 * spacing, size, size);
|
||||
DraggableListBoxItem::paint(g);
|
||||
g.setColour(juce::Colours::white);
|
||||
// draw drag and drop handle using circles
|
||||
double size = 4;
|
||||
double leftPad = 4;
|
||||
double spacing = 7;
|
||||
double topPad = 6;
|
||||
double y = bounds.getHeight() / 2 - 15;
|
||||
g.fillEllipse(leftPad, y + topPad, size, size);
|
||||
g.fillEllipse(leftPad, y + topPad + 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 + spacing, size, size);
|
||||
g.fillEllipse(leftPad + spacing, y + topPad + 2 * spacing, size, size);
|
||||
|
||||
auto rightBar = getLocalBounds().removeFromRight(RIGHT_BAR_WIDTH);
|
||||
rightBar.removeFromBottom(PADDING);
|
||||
g.setColour(findColour(effectComponentHandleColourId));
|
||||
juce::Path rightPath;
|
||||
rightPath.addRoundedRectangle(rightBar.getX(), rightBar.getY(), rightBar.getWidth(), rightBar.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, false, true, false, true);
|
||||
g.fillPath(rightPath);
|
||||
|
||||
DraggableListBoxItem::paint(g);
|
||||
}
|
||||
|
||||
void EffectsListComponent::paintOverChildren(juce::Graphics& g) {
|
||||
|
@ -81,51 +114,53 @@ void EffectsListComponent::paintOverChildren(juce::Graphics& g) {
|
|||
juce::Path path;
|
||||
path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), OscirenderLookAndFeel::RECT_RADIUS, OscirenderLookAndFeel::RECT_RADIUS, false, true, false, true);
|
||||
|
||||
if (!selected.getToggleState()) {
|
||||
if (!enabled.getToggleState()) {
|
||||
g.fillPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectsListComponent::resized() {
|
||||
auto area = getLocalBounds();
|
||||
auto area = getLocalBounds();
|
||||
auto leftBar = area.removeFromLeft(LEFT_BAR_WIDTH);
|
||||
auto buttonBounds = area.removeFromRight(RIGHT_BAR_WIDTH);
|
||||
closeButton.setBounds(buttonBounds);
|
||||
closeButton.setImageTransform(juce::AffineTransform::translation(0, -2));
|
||||
leftBar.removeFromLeft(20);
|
||||
area.removeFromRight(PADDING);
|
||||
selected.setBounds(leftBar.withSizeKeepingCentre(30, 20));
|
||||
list.setBounds(area);
|
||||
enabled.setBounds(leftBar.withSizeKeepingCentre(30, 20));
|
||||
list.setBounds(area);
|
||||
}
|
||||
|
||||
std::shared_ptr<juce::Component> EffectsListComponent::createComponent(osci::EffectParameter* parameter) {
|
||||
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::weak_ptr<SvgButton> weakButton = button;
|
||||
button->setEdgeIndent(5);
|
||||
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->onClick = [this, weakButton] {
|
||||
if (auto button = weakButton.lock()) {
|
||||
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::weak_ptr<SvgButton> weakButton = button;
|
||||
button->setEdgeIndent(5);
|
||||
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->onClick = [this, weakButton] {
|
||||
if (auto button = weakButton.lock()) {
|
||||
editor.editCustomFunction(button->getToggleState());
|
||||
}
|
||||
};
|
||||
return button;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
return button;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int EffectsListBoxModel::getRowHeight(int row) {
|
||||
auto data = (AudioEffectListBoxItemData&)modelData;
|
||||
return data.getEffect(row)->parameters.size() * EffectsListComponent::ROW_HEIGHT + EffectsListComponent::PADDING;
|
||||
auto data = (AudioEffectListBoxItemData&)modelData;
|
||||
return data.getEffect(row)->parameters.size() * EffectsListComponent::ROW_HEIGHT + EffectsListComponent::PADDING;
|
||||
}
|
||||
|
||||
bool EffectsListBoxModel::hasVariableHeightRows() const {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
juce::Component* EffectsListBoxModel::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component *existingComponentToUpdate) {
|
||||
auto data = (AudioEffectListBoxItemData&)modelData;
|
||||
if (! juce::isPositiveAndBelow(rowNumber, data.getNumItems()))
|
||||
return nullptr;
|
||||
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));
|
||||
}
|
||||
item = std::make_unique<EffectsListComponent>(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, *data.getEffect(rowNumber));
|
||||
return item.release();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "EffectComponent.h"
|
||||
#include "ComponentList.h"
|
||||
#include "SwitchButton.h"
|
||||
#include "EffectTypeGridComponent.h"
|
||||
#include "SvgButton.h"
|
||||
#include <random>
|
||||
|
||||
// Application-specific data container
|
||||
|
@ -14,6 +16,7 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
|||
std::vector<std::shared_ptr<osci::Effect>> data;
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
OscirenderAudioProcessorEditor& editor;
|
||||
std::function<void()> onAddNewEffectRequested; // callback hooked by parent to open the grid
|
||||
|
||||
AudioEffectListBoxItemData(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), editor(editor) {
|
||||
resetData();
|
||||
|
@ -21,11 +24,31 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
|||
|
||||
void randomise() {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
auto effect = data[i];
|
||||
auto id = effect->getId().toLowerCase();
|
||||
|
||||
// Decide how many effects to select (1..5 or up to available)
|
||||
int total = (int) audioProcessor.toggleableEffects.size();
|
||||
int maxPick = juce::jmin(5, total);
|
||||
int numPick = juce::jmax(1, juce::Random::getSystemRandom().nextInt({1, maxPick + 1}));
|
||||
|
||||
// Build indices [0..total)
|
||||
std::vector<int> indices(total);
|
||||
std::iota(indices.begin(), indices.end(), 0);
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
std::shuffle(indices.begin(), indices.end(), g);
|
||||
|
||||
// First, deselect and disable all
|
||||
for (auto& effect : audioProcessor.toggleableEffects) {
|
||||
effect->markSelectable(false);
|
||||
effect->markEnableable(false);
|
||||
}
|
||||
|
||||
// Pick numPick to select & enable, and randomise params
|
||||
for (int k = 0; k < numPick && k < indices.size(); ++k) {
|
||||
auto& effect = audioProcessor.toggleableEffects[indices[k]];
|
||||
effect->markSelectable(true);
|
||||
effect->markEnableable(true);
|
||||
|
||||
auto id = effect->getId().toLowerCase();
|
||||
if (id.contains("scale") || id.contains("translate") || id.contains("trace")) {
|
||||
continue;
|
||||
}
|
||||
|
@ -35,26 +58,23 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
|||
if (parameter->lfo != nullptr) {
|
||||
parameter->lfo->setUnnormalisedValueNotifyingHost((int) osci::LfoType::Static);
|
||||
parameter->lfoRate->setUnnormalisedValueNotifyingHost(1);
|
||||
|
||||
if (juce::Random::getSystemRandom().nextFloat() > 0.8) {
|
||||
parameter->lfo->setUnnormalisedValueNotifyingHost((int)(juce::Random::getSystemRandom().nextFloat() * (int) osci::LfoType::Noise));
|
||||
parameter->lfoRate->setValueNotifyingHost(juce::Random::getSystemRandom().nextFloat() * 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
effect->enabled->setValueNotifyingHost(juce::Random::getSystemRandom().nextFloat() > 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
// shuffle precedence
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
std::shuffle(data.begin(), data.end(), g);
|
||||
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
data[i]->setPrecedence(i);
|
||||
}
|
||||
|
||||
audioProcessor.updateEffectPrecedence();
|
||||
// Refresh local data with only selected effects
|
||||
resetData();
|
||||
|
||||
// shuffle precedence of the selected subset
|
||||
std::shuffle(data.begin(), data.end(), g);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
data[i]->setPrecedence(i);
|
||||
}
|
||||
audioProcessor.updateEffectPrecedence();
|
||||
}
|
||||
|
||||
void resetData() {
|
||||
|
@ -63,17 +83,22 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
|||
for (int i = 0; i < audioProcessor.toggleableEffects.size(); i++) {
|
||||
auto effect = audioProcessor.toggleableEffects[i];
|
||||
effect->setValue(effect->getValue());
|
||||
data.push_back(effect);
|
||||
// Ensure 'selected' exists and defaults to true for older projects
|
||||
effect->markSelectable(effect->selected == nullptr ? true : effect->selected->getBoolValue());
|
||||
if (effect->selected == nullptr || effect->selected->getBoolValue()) {
|
||||
data.push_back(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getNumItems() override {
|
||||
return data.size();
|
||||
// Only the effects themselves are rows; the "+ Add new effect" button is a separate control below the list
|
||||
return (int) data.size();
|
||||
}
|
||||
|
||||
// CURRENTLY NOT USED
|
||||
void deleteItem(int indexOfItemToDelete) override {
|
||||
// data.erase(data.begin() + indexOfItemToDelete);
|
||||
// data.erase(data.begin() + indexOfItemToDelete);
|
||||
}
|
||||
|
||||
// CURRENTLY NOT USED
|
||||
|
@ -81,10 +106,10 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
|||
// 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);
|
||||
|
||||
auto effect = data[indexOfItemToMove];
|
||||
auto effect = data[indexOfItemToMove];
|
||||
|
||||
if (indexOfItemToMove < indexOfItemToPlaceBefore) {
|
||||
move(data, indexOfItemToMove, indexOfItemToPlaceBefore - 1);
|
||||
|
@ -92,12 +117,12 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
|||
move(data, indexOfItemToMove, indexOfItemToPlaceBefore);
|
||||
}
|
||||
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
data[i]->setPrecedence(i);
|
||||
}
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
data[i]->setPrecedence(i);
|
||||
}
|
||||
|
||||
audioProcessor.updateEffectPrecedence();
|
||||
}
|
||||
}
|
||||
|
||||
void moveAfter(int indexOfItemToMove, int indexOfItemToPlaceAfter) override {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
|
@ -150,6 +175,7 @@ public:
|
|||
void resized() override;
|
||||
|
||||
static const int LEFT_BAR_WIDTH = 50;
|
||||
static const int RIGHT_BAR_WIDTH = 15; // space for close button
|
||||
static const int ROW_HEIGHT = 30;
|
||||
static const int PADDING = 4;
|
||||
|
||||
|
@ -157,7 +183,8 @@ protected:
|
|||
osci::Effect& effect;
|
||||
ComponentListModel listModel;
|
||||
juce::ListBox list;
|
||||
jux::SwitchButton selected = { effect.enabled };
|
||||
jux::SwitchButton enabled = { effect.enabled };
|
||||
SvgButton closeButton = SvgButton("closeEffect", juce::String::createStringFromData(BinaryData::close_svg, BinaryData::close_svgSize), juce::Colours::white, juce::Colours::white);
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
OscirenderAudioProcessorEditor& editor;
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
#include "HoverAnimationMixin.h"
|
||||
|
||||
HoverAnimationMixin::HoverAnimationMixin()
|
||||
: animatorUpdater(this),
|
||||
hoverAnimator(juce::ValueAnimatorBuilder{}
|
||||
.withEasing(getEasingFunction())
|
||||
.withDurationMs(getHoverAnimationDurationMs())
|
||||
.withValueChangedCallback([this](auto value) {
|
||||
animationProgress = static_cast<float>(value);
|
||||
repaint();
|
||||
resized();
|
||||
})
|
||||
.build()),
|
||||
unhoverAnimator(juce::ValueAnimatorBuilder{}
|
||||
.withEasing(getEasingFunction())
|
||||
.withDurationMs(getHoverAnimationDurationMs())
|
||||
.withValueChangedCallback([this](auto value) {
|
||||
animationProgress = 1.0f - static_cast<float>(value);
|
||||
repaint();
|
||||
resized();
|
||||
})
|
||||
.build())
|
||||
{
|
||||
setupAnimators();
|
||||
}
|
||||
|
||||
void HoverAnimationMixin::setupAnimators()
|
||||
{
|
||||
animatorUpdater.addAnimator(hoverAnimator);
|
||||
animatorUpdater.addAnimator(unhoverAnimator);
|
||||
}
|
||||
|
||||
void HoverAnimationMixin::animateHover(bool isHovering)
|
||||
{
|
||||
if (isHovering)
|
||||
{
|
||||
unhoverAnimator.complete();
|
||||
hoverAnimator.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
hoverAnimator.complete();
|
||||
unhoverAnimator.start();
|
||||
}
|
||||
}
|
||||
|
||||
void HoverAnimationMixin::mouseEnter(const juce::MouseEvent&)
|
||||
{
|
||||
isHovered = true;
|
||||
animateHover(true);
|
||||
}
|
||||
|
||||
void HoverAnimationMixin::mouseExit(const juce::MouseEvent&)
|
||||
{
|
||||
isHovered = false;
|
||||
// Fixed logic to prevent getting stuck in hovered state
|
||||
animateHover(false);
|
||||
}
|
||||
|
||||
void HoverAnimationMixin::mouseDown(const juce::MouseEvent&)
|
||||
{
|
||||
animateHover(false);
|
||||
}
|
||||
|
||||
void HoverAnimationMixin::mouseUp(const juce::MouseEvent& event)
|
||||
{
|
||||
// Only animate hover if the mouse is still within the component bounds
|
||||
if (getLocalBounds().contains(event.getEventRelativeTo(this).getPosition()))
|
||||
animateHover(true);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
// Base Component providing animated hover behavior via JUCE mouse overrides.
|
||||
class HoverAnimationMixin : public juce::Component
|
||||
{
|
||||
public:
|
||||
HoverAnimationMixin();
|
||||
~HoverAnimationMixin() override = default;
|
||||
|
||||
// Animation control (available for programmatic triggers if needed)
|
||||
void animateHover(bool isHovering);
|
||||
|
||||
// Getters
|
||||
float getAnimationProgress() const { return animationProgress; }
|
||||
bool getIsHovered() const { return isHovered; }
|
||||
|
||||
protected:
|
||||
// Customization hooks
|
||||
virtual int getHoverAnimationDurationMs() const { return 200; }
|
||||
virtual std::function<float(float)> getEasingFunction() const { return juce::Easings::createEaseOut(); }
|
||||
|
||||
public:
|
||||
// juce::Component overrides for mouse events - keep public so children can call base explicitly
|
||||
void mouseEnter(const juce::MouseEvent& event) override;
|
||||
void mouseExit(const juce::MouseEvent& event) override;
|
||||
void mouseDown(const juce::MouseEvent& event) override;
|
||||
void mouseUp(const juce::MouseEvent& event) override;
|
||||
|
||||
private:
|
||||
float animationProgress = 0.0f;
|
||||
bool isHovered = false;
|
||||
|
||||
// Animation components
|
||||
juce::VBlankAnimatorUpdater animatorUpdater;
|
||||
juce::Animator hoverAnimator;
|
||||
juce::Animator unhoverAnimator;
|
||||
|
||||
void setupAnimators();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HoverAnimationMixin)
|
||||
};
|
|
@ -6,12 +6,17 @@ MainMenuBarModel::~MainMenuBarModel() {}
|
|||
|
||||
void MainMenuBarModel::addTopLevelMenu(const juce::String& name) {
|
||||
topLevelMenuNames.add(name);
|
||||
menuItems.push_back(std::vector<std::pair<juce::String, std::function<void()>>>());
|
||||
menuItems.push_back({});
|
||||
menuItemsChanged();
|
||||
}
|
||||
|
||||
void MainMenuBarModel::addMenuItem(int topLevelMenuIndex, const juce::String& name, std::function<void()> action) {
|
||||
menuItems[topLevelMenuIndex].push_back(std::make_pair(name, action));
|
||||
menuItems[topLevelMenuIndex].push_back({ name, std::move(action), {}, false });
|
||||
menuItemsChanged();
|
||||
}
|
||||
|
||||
void MainMenuBarModel::addToggleMenuItem(int topLevelMenuIndex, const juce::String& name, std::function<void()> action, std::function<bool()> isTicked) {
|
||||
menuItems[topLevelMenuIndex].push_back({ name, std::move(action), std::move(isTicked), true });
|
||||
menuItemsChanged();
|
||||
}
|
||||
|
||||
|
@ -26,8 +31,13 @@ juce::PopupMenu MainMenuBarModel::getMenuForIndex(int topLevelMenuIndex, const j
|
|||
customMenuLogic(menu, topLevelMenuIndex);
|
||||
}
|
||||
|
||||
for (int i = 0; i < menuItems[topLevelMenuIndex].size(); i++) {
|
||||
menu.addItem(i + 1, menuItems[topLevelMenuIndex][i].first);
|
||||
for (int i = 0; i < (int) menuItems[topLevelMenuIndex].size(); i++) {
|
||||
auto& mi = menuItems[topLevelMenuIndex][i];
|
||||
juce::PopupMenu::Item item(mi.name);
|
||||
item.itemID = i + 1;
|
||||
if (mi.hasTick && mi.isTicked)
|
||||
item.setTicked(mi.isTicked());
|
||||
menu.addItem(item);
|
||||
}
|
||||
|
||||
return menu;
|
||||
|
@ -37,7 +47,9 @@ void MainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
|
|||
if (customMenuSelectedLogic && customMenuSelectedLogic(menuItemID, topLevelMenuIndex)) {
|
||||
return;
|
||||
}
|
||||
menuItems[topLevelMenuIndex][menuItemID - 1].second();
|
||||
auto& mi = menuItems[topLevelMenuIndex][menuItemID - 1];
|
||||
if (mi.action)
|
||||
mi.action();
|
||||
}
|
||||
|
||||
void MainMenuBarModel::menuBarActivated(bool isActive) {}
|
||||
|
|
|
@ -8,6 +8,8 @@ public:
|
|||
|
||||
void addTopLevelMenu(const juce::String& name);
|
||||
void addMenuItem(int topLevelMenuIndex, const juce::String& name, std::function<void()> action);
|
||||
// Adds a toggle (ticked) menu item whose tick state is provided dynamically via isTicked()
|
||||
void addToggleMenuItem(int topLevelMenuIndex, const juce::String& name, std::function<void()> action, std::function<bool()> isTicked);
|
||||
void resetMenuItems();
|
||||
|
||||
std::function<void(juce::PopupMenu&, int)> customMenuLogic;
|
||||
|
@ -19,6 +21,14 @@ private:
|
|||
void menuItemSelected(int menuItemID, int topLevelMenuIndex) override;
|
||||
void menuBarActivated(bool isActive);
|
||||
|
||||
struct MenuItem
|
||||
{
|
||||
juce::String name;
|
||||
std::function<void()> action;
|
||||
std::function<bool()> isTicked; // optional tick state
|
||||
bool hasTick = false;
|
||||
};
|
||||
|
||||
juce::StringArray topLevelMenuNames;
|
||||
std::vector<std::vector<std::pair<juce::String, std::function<void()>>>> menuItems;
|
||||
std::vector<std::vector<MenuItem>> menuItems;
|
||||
};
|
||||
|
|
|
@ -10,12 +10,13 @@ OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, Oscirend
|
|||
void OsciMainMenuBarModel::resetMenuItems() {
|
||||
MainMenuBarModel::resetMenuItems();
|
||||
|
||||
addTopLevelMenu("File");
|
||||
addTopLevelMenu("About");
|
||||
addTopLevelMenu("Video");
|
||||
addTopLevelMenu("File"); // index 0
|
||||
addTopLevelMenu("About"); // index 1
|
||||
addTopLevelMenu("Video"); // index 2
|
||||
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
|
||||
addTopLevelMenu("Audio");
|
||||
addTopLevelMenu("Audio"); // index 3 (only if standalone)
|
||||
}
|
||||
addTopLevelMenu("Interface"); // index 3 (if not standalone) or 4 (if standalone)
|
||||
|
||||
addMenuItem(0, "Open Project", [this] { editor.openProject(); });
|
||||
addMenuItem(0, "Save Project", [this] { editor.saveProject(); });
|
||||
|
@ -60,11 +61,7 @@ void OsciMainMenuBarModel::resetMenuItems() {
|
|||
});
|
||||
addMenuItem(1, "Randomize Blender Port", [this] {
|
||||
audioProcessor.setObjectServerPort(juce::Random::getSystemRandom().nextInt(juce::Range<int>(51600, 51700)));
|
||||
});
|
||||
addMenuItem(1, audioProcessor.getAcceptsKeys() ? "Disable Special Keys" : "Enable Special Keys", [this] {
|
||||
audioProcessor.setAcceptsKeys(!audioProcessor.getAcceptsKeys());
|
||||
resetMenuItems();
|
||||
});
|
||||
});
|
||||
|
||||
#if !OSCI_PREMIUM
|
||||
addMenuItem(1, "Purchase osci-render premium!", [this] {
|
||||
|
@ -92,6 +89,26 @@ void OsciMainMenuBarModel::resetMenuItems() {
|
|||
editor.openAudioSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Interface menu index depends on whether Audio menu exists
|
||||
int interfaceMenuIndex = (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) ? 4 : 3;
|
||||
addToggleMenuItem(interfaceMenuIndex, "Preview effect on hover", [this] {
|
||||
bool current = audioProcessor.getGlobalBoolValue("previewEffectOnHover", true);
|
||||
bool newValue = ! current;
|
||||
audioProcessor.setGlobalValue("previewEffectOnHover", newValue);
|
||||
audioProcessor.saveGlobalSettings();
|
||||
if (! newValue) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
audioProcessor.clearPreviewEffect();
|
||||
}
|
||||
resetMenuItems(); // update tick state
|
||||
}, [this] { return audioProcessor.getGlobalBoolValue("previewEffectOnHover", true);
|
||||
});
|
||||
|
||||
addToggleMenuItem(interfaceMenuIndex, "Listen for Special Keys", [this] {
|
||||
audioProcessor.setAcceptsKeys(! audioProcessor.getAcceptsKeys());
|
||||
resetMenuItems();
|
||||
}, [this] { return audioProcessor.getAcceptsKeys(); });
|
||||
}
|
||||
|
||||
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "VListBox.h"
|
||||
#include "../LookAndFeel.h"
|
||||
|
||||
// Overlay component that can render top and/or bottom scroll fades with adaptive strength.
|
||||
class ScrollFadeOverlay : public juce::Component {
|
||||
public:
|
||||
ScrollFadeOverlay() {
|
||||
setInterceptsMouseClicks(false, false);
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
void setFadeHeight(int hTopAndBottom) {
|
||||
fadeHeightTop = fadeHeightBottom = juce::jmax(4, hTopAndBottom);
|
||||
}
|
||||
|
||||
void setFadeHeights(int topH, int bottomH) {
|
||||
fadeHeightTop = juce::jmax(4, topH);
|
||||
fadeHeightBottom = juce::jmax(4, bottomH);
|
||||
}
|
||||
|
||||
void setSidesEnabled(bool top, bool bottom) {
|
||||
enableTop = top;
|
||||
enableBottom = bottom;
|
||||
}
|
||||
|
||||
// Position the overlay to fully cover the scrollable viewport area (owner coordinates)
|
||||
void layoutOver(const juce::Rectangle<int>& listBounds) {
|
||||
setBounds(listBounds);
|
||||
}
|
||||
|
||||
// Toggle per-side visibility/strength based on viewport scroll and enable flag.
|
||||
void updateVisibilityFromViewport(juce::Viewport* vp, bool enabled) {
|
||||
showTop = showBottom = false;
|
||||
strengthTop = strengthBottom = 0.0f;
|
||||
|
||||
if (enabled && vp != nullptr && vp->getVerticalScrollBar().isVisible()) {
|
||||
auto& sb = vp->getVerticalScrollBar();
|
||||
const double start = sb.getCurrentRangeStart();
|
||||
const double size = sb.getCurrentRangeSize();
|
||||
const double max = sb.getMaximumRangeLimit();
|
||||
|
||||
// Top fade strength scales with how far from top we are
|
||||
const bool atTop = start <= 0.5;
|
||||
const double topDist = start; // pixels scrolled down
|
||||
strengthTop = (float) juce::jlimit(0.0, 1.0, topDist / (double) juce::jmax(1, fadeHeightTop));
|
||||
showTop = enableTop && !atTop && strengthTop > 0.01f;
|
||||
|
||||
// Bottom fade strength scales with how far from bottom we are
|
||||
const double remaining = (max - (start + size));
|
||||
const bool atBottom = remaining <= 0.5;
|
||||
strengthBottom = (float) juce::jlimit(0.0, 1.0, remaining / (double) juce::jmax(1, fadeHeightBottom));
|
||||
showBottom = enableBottom && !atBottom && strengthBottom > 0.01f;
|
||||
}
|
||||
|
||||
const bool anyVisible = (showTop || showBottom);
|
||||
setVisible(anyVisible);
|
||||
if (anyVisible)
|
||||
repaint();
|
||||
}
|
||||
|
||||
void paint(juce::Graphics& g) override {
|
||||
auto area = getLocalBounds();
|
||||
const auto bg = findColour(groupComponentBackgroundColourId);
|
||||
|
||||
if (showTop && fadeHeightTop > 0) {
|
||||
const int h = juce::jmin(fadeHeightTop, area.getHeight());
|
||||
auto topRect = area.removeFromTop(h);
|
||||
juce::ColourGradient gradTop(bg.withAlpha(strengthTop),
|
||||
(float) topRect.getX(), (float) topRect.getY(),
|
||||
bg.withAlpha(0.0f),
|
||||
(float) topRect.getX(), (float) topRect.getBottom(),
|
||||
false);
|
||||
g.setGradientFill(gradTop);
|
||||
g.fillRect(topRect);
|
||||
}
|
||||
|
||||
// Reset area for bottom drawing
|
||||
area = getLocalBounds();
|
||||
if (showBottom && fadeHeightBottom > 0) {
|
||||
const int h = juce::jmin(fadeHeightBottom, area.getHeight());
|
||||
auto bottomRect = area.removeFromBottom(h);
|
||||
juce::ColourGradient gradBottom(bg.withAlpha(strengthBottom),
|
||||
(float) bottomRect.getX(), (float) bottomRect.getBottom(),
|
||||
bg.withAlpha(0.0f),
|
||||
(float) bottomRect.getX(), (float) bottomRect.getY(),
|
||||
false);
|
||||
g.setGradientFill(gradBottom);
|
||||
g.fillRect(bottomRect);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int fadeHeightTop { 48 };
|
||||
int fadeHeightBottom { 48 };
|
||||
bool enableTop { true };
|
||||
bool enableBottom { true };
|
||||
bool showTop { false };
|
||||
bool showBottom { false };
|
||||
float strengthTop { 0.0f };
|
||||
float strengthBottom { 0.0f };
|
||||
};
|
||||
|
||||
// Mixin to attach a bottom fade overlay for any scrollable area (ListBox or Viewport).
|
||||
class ScrollFadeMixin {
|
||||
public:
|
||||
virtual ~ScrollFadeMixin() {
|
||||
detachScrollListeners();
|
||||
}
|
||||
|
||||
protected:
|
||||
void initScrollFade(juce::Component& owner) {
|
||||
if (! scrollFade)
|
||||
scrollFade = std::make_unique<ScrollFadeOverlay>();
|
||||
if (scrollFade->getParentComponent() != &owner)
|
||||
owner.addAndMakeVisible(*scrollFade);
|
||||
|
||||
scrollListener.owner = this;
|
||||
}
|
||||
|
||||
void attachToListBox(VListBox& list) {
|
||||
detachScrollListeners();
|
||||
scrollViewport = list.getViewport();
|
||||
attachScrollListeners();
|
||||
}
|
||||
|
||||
void attachToViewport(juce::Viewport& vp) {
|
||||
detachScrollListeners();
|
||||
scrollViewport = &vp;
|
||||
attachScrollListeners();
|
||||
}
|
||||
|
||||
// Call from owner's resized(). listBounds must be in the owner's coordinate space.
|
||||
void layoutScrollFade(const juce::Rectangle<int>& listBounds, bool enabled = true, int fadeHeight = 48) {
|
||||
if (! scrollFade)
|
||||
return;
|
||||
lastListBounds = listBounds;
|
||||
lastEnabled = enabled;
|
||||
lastFadeHeight = fadeHeight;
|
||||
scrollFade->setFadeHeight(fadeHeight);
|
||||
scrollFade->layoutOver(listBounds);
|
||||
scrollFade->toFront(false);
|
||||
scrollFade->updateVisibilityFromViewport(getViewport(), enabled);
|
||||
}
|
||||
|
||||
// Explicitly hide/show (e.g., when switching views)
|
||||
void setScrollFadeVisible(bool shouldBeVisible) {
|
||||
lastEnabled = shouldBeVisible;
|
||||
if (scrollFade)
|
||||
scrollFade->setVisible(shouldBeVisible);
|
||||
}
|
||||
|
||||
// Allow configuring which sides to render (default is both true)
|
||||
void setScrollFadeSides(bool enableTop, bool enableBottom) {
|
||||
if (scrollFade) {
|
||||
scrollFade->setSidesEnabled(enableTop, enableBottom);
|
||||
// Recompute since sides changed
|
||||
scrollFade->updateVisibilityFromViewport(getViewport(), lastEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<ScrollFadeOverlay> scrollFade;
|
||||
juce::Component::SafePointer<juce::Viewport> scrollViewport;
|
||||
juce::Viewport* getViewport() const noexcept { return static_cast<juce::Viewport*>(scrollViewport.getComponent()); }
|
||||
|
||||
private:
|
||||
// Listen to vertical scrollbar to update fade visibility while scrolling
|
||||
struct VScrollListener : juce::ScrollBar::Listener {
|
||||
ScrollFadeMixin* owner { nullptr };
|
||||
void scrollBarMoved(juce::ScrollBar*, double) override {
|
||||
if (owner && owner->scrollFade) {
|
||||
// Recompute visibility using last-known enabled state
|
||||
owner->scrollFade->updateVisibilityFromViewport(owner->getViewport(), owner->lastEnabled);
|
||||
}
|
||||
}
|
||||
} scrollListener;
|
||||
|
||||
void attachScrollListeners() {
|
||||
if (auto* vp = getViewport()) {
|
||||
vp->getVerticalScrollBar().addListener(&scrollListener);
|
||||
}
|
||||
}
|
||||
|
||||
void detachScrollListeners() {
|
||||
if (auto* vp = getViewport()) {
|
||||
vp->getVerticalScrollBar().removeListener(&scrollListener);
|
||||
}
|
||||
}
|
||||
|
||||
juce::Rectangle<int> lastListBounds;
|
||||
bool lastEnabled { true };
|
||||
int lastFadeHeight { 48 };
|
||||
};
|
|
@ -96,10 +96,6 @@ void SosciMainMenuBarModel::resetMenuItems() {
|
|||
|
||||
juce::DialogWindow* dw = options.launchAsync();
|
||||
});
|
||||
addMenuItem(1, processor.getAcceptsKeys() ? "Disable Special Keys" : "Enable Special Keys", [this] {
|
||||
processor.setAcceptsKeys(!processor.getAcceptsKeys());
|
||||
resetMenuItems();
|
||||
});
|
||||
|
||||
addMenuItem(2, "Settings...", [this] {
|
||||
editor.openRecordingSettings();
|
||||
|
@ -113,4 +109,11 @@ void SosciMainMenuBarModel::resetMenuItems() {
|
|||
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
|
||||
addMenuItem(3, "Settings...", [&]() { editor.openAudioSettings(); });
|
||||
}
|
||||
|
||||
// Interface menu index depends on whether Audio menu exists
|
||||
int interfaceMenuIndex = (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) ? 4 : 3;
|
||||
addToggleMenuItem(interfaceMenuIndex, "Listen for Special Keys", [this] {
|
||||
processor.setAcceptsKeys(! processor.getAcceptsKeys());
|
||||
resetMenuItems();
|
||||
}, [this] { return processor.getAcceptsKeys(); });
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ class SosciAudioProcessor;
|
|||
class SosciMainMenuBarModel : public MainMenuBarModel {
|
||||
public:
|
||||
SosciMainMenuBarModel(SosciPluginEditor& editor, SosciAudioProcessor& processor);
|
||||
|
||||
void resetMenuItems();
|
||||
|
||||
SosciPluginEditor& editor;
|
||||
|
|
|
@ -8,12 +8,12 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
|||
|
||||
changeSvgColour(doc.get(), colour);
|
||||
normalImage = juce::Drawable::createFromSVG(*doc);
|
||||
changeSvgColour(doc.get(), colour.withBrightness(0.7f));
|
||||
overImage = juce::Drawable::createFromSVG(*doc);
|
||||
changeSvgColour(doc.get(), colour.withBrightness(0.5f));
|
||||
downImage = juce::Drawable::createFromSVG(*doc);
|
||||
changeSvgColour(doc.get(), colour.withBrightness(0.3f));
|
||||
disabledImage = juce::Drawable::createFromSVG(*doc);
|
||||
changeSvgColour(doc.get(), colour.withBrightness(0.7f));
|
||||
overImage = juce::Drawable::createFromSVG(*doc);
|
||||
changeSvgColour(doc.get(), colour.withBrightness(0.5f));
|
||||
downImage = juce::Drawable::createFromSVG(*doc);
|
||||
changeSvgColour(doc.get(), colour.withBrightness(0.3f));
|
||||
disabledImage = juce::Drawable::createFromSVG(*doc);
|
||||
|
||||
// If a toggled SVG is provided, use it for the "on" state images
|
||||
if (toggledSvg.isNotEmpty()) {
|
||||
|
@ -44,7 +44,8 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
|||
if (colour != colourOn) {
|
||||
setClickingTogglesState(true);
|
||||
}
|
||||
setImages(normalImage.get(), overImage.get(), downImage.get(), disabledImage.get(), normalImageOn.get(), overImageOn.get(), downImageOn.get(), disabledImageOn.get());
|
||||
|
||||
setImages(normalImage.get(), overImage.get(), downImage.get(), disabledImage.get(), normalImageOn.get(), overImageOn.get(), downImageOn.get(), disabledImageOn.get());
|
||||
|
||||
if (toggle != nullptr) {
|
||||
toggle->addListener(this);
|
||||
|
@ -94,8 +95,7 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
|||
juce::DrawableButton::resized();
|
||||
if (pulseUsed) {
|
||||
resizedPath = basePath;
|
||||
// scale path to fit image
|
||||
resizedPath.applyTransform(resizedPath.getTransformToScaleToFit(getImageBounds(), true));
|
||||
resizedPath.applyTransform(getImageTransform());
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
@ -122,13 +122,13 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
|||
|
||||
private:
|
||||
std::unique_ptr<juce::Drawable> normalImage;
|
||||
std::unique_ptr<juce::Drawable> overImage;
|
||||
std::unique_ptr<juce::Drawable> downImage;
|
||||
std::unique_ptr<juce::Drawable> disabledImage;
|
||||
std::unique_ptr<juce::Drawable> overImage;
|
||||
std::unique_ptr<juce::Drawable> downImage;
|
||||
std::unique_ptr<juce::Drawable> disabledImage;
|
||||
|
||||
std::unique_ptr<juce::Drawable> normalImageOn;
|
||||
std::unique_ptr<juce::Drawable> overImageOn;
|
||||
std::unique_ptr<juce::Drawable> downImageOn;
|
||||
std::unique_ptr<juce::Drawable> overImageOn;
|
||||
std::unique_ptr<juce::Drawable> downImageOn;
|
||||
std::unique_ptr<juce::Drawable> disabledImageOn;
|
||||
|
||||
osci::BooleanParameter* toggle;
|
||||
|
@ -139,6 +139,7 @@ private:
|
|||
bool prevToggleState = false;
|
||||
juce::Path basePath;
|
||||
juce::Path resizedPath;
|
||||
juce::AffineTransform imageTransform; // Transform applied to all state images
|
||||
juce::Animator pulse = juce::ValueAnimatorBuilder {}
|
||||
.withEasing([] (float t) { return std::sin(3.14159 * t) / 2 + 0.5; })
|
||||
.withDurationMs(500)
|
||||
|
@ -154,4 +155,38 @@ private:
|
|||
xmlnode->setAttribute("fill", '#' + colour.toDisplayString(false));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// Allows callers to adjust the placement/scale/rotation of the SVG within the button.
|
||||
void setImageTransform(const juce::AffineTransform& t) {
|
||||
imageTransform = juce::RectanglePlacement(juce::RectanglePlacement::centred).getTransformToFit(normalImage->getDrawableBounds(), getImageBounds()).followedBy(t);
|
||||
if (getLocalBounds().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
setButtonStyle(juce::DrawableButton::ButtonStyle::ImageRaw);
|
||||
applyImageTransform();
|
||||
// Keep the pulse overlay in sync
|
||||
resized();
|
||||
}
|
||||
|
||||
juce::AffineTransform getImageTransform() const { return imageTransform; }
|
||||
|
||||
private:
|
||||
void applyImageTransform() {
|
||||
auto apply = [this](std::unique_ptr<juce::Drawable>& d) {
|
||||
if (d != nullptr) {
|
||||
d->setTransform(imageTransform);
|
||||
}
|
||||
};
|
||||
apply(normalImage);
|
||||
apply(overImage);
|
||||
apply(downImage);
|
||||
apply(disabledImage);
|
||||
apply(normalImageOn);
|
||||
apply(overImageOn);
|
||||
apply(downImageOn);
|
||||
apply(disabledImageOn);
|
||||
|
||||
setImages(normalImage.get(), overImage.get(), downImage.get(), disabledImage.get(), normalImageOn.get(), overImageOn.get(), downImageOn.get(), disabledImageOn.get());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -25,16 +25,12 @@
|
|||
|
||||
#include "VListBox.h"
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace jc
|
||||
{
|
||||
class ListBox::RowComponent : public Component, public TooltipClient
|
||||
class VListBox::RowComponent : public juce::Component, public TooltipClient
|
||||
{
|
||||
public:
|
||||
RowComponent (ListBox& lb) : owner (lb) {}
|
||||
RowComponent (VListBox& lb) : owner (lb) {}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
void paint (juce::Graphics& g) override
|
||||
{
|
||||
if (auto* m = owner.getModel())
|
||||
m->paintListBoxItem (row, g, getWidth(), getHeight(), selected);
|
||||
|
@ -63,7 +59,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void performSelection (const MouseEvent& e, bool isMouseUp)
|
||||
void performSelection (const juce::MouseEvent& e, bool isMouseUp)
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, isMouseUp);
|
||||
|
||||
|
@ -79,7 +75,7 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
void mouseDown (const juce::MouseEvent& e) override
|
||||
{
|
||||
isDragging = false;
|
||||
isDraggingToScroll = false;
|
||||
|
@ -94,31 +90,31 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void mouseUp (const MouseEvent& e) override
|
||||
void mouseUp (const juce::MouseEvent& e) override
|
||||
{
|
||||
if (isEnabled() && selectRowOnMouseUp && ! (isDragging || isDraggingToScroll))
|
||||
performSelection (e, true);
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent& e) override
|
||||
void mouseDoubleClick (const juce::MouseEvent& e) override
|
||||
{
|
||||
if (isEnabled())
|
||||
if (auto* m = owner.getModel())
|
||||
m->listBoxItemDoubleClicked (row, e);
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
void mouseDrag (const juce::MouseEvent& e) override
|
||||
{
|
||||
if (auto* m = owner.getModel())
|
||||
{
|
||||
if (isEnabled() && e.mouseWasDraggedSinceMouseDown() && ! isDragging)
|
||||
{
|
||||
SparseSet<int> rowsToDrag;
|
||||
juce::SparseSet<int> rowsToDrag;
|
||||
|
||||
if (owner.selectOnMouseDown || owner.isRowSelected (row))
|
||||
rowsToDrag = owner.getSelectedRows();
|
||||
else
|
||||
rowsToDrag.addRange (Range<int>::withStartAndLength (row, 1));
|
||||
rowsToDrag.addRange (juce::Range<int>::withStartAndLength (row, 1));
|
||||
|
||||
if (rowsToDrag.size() > 0)
|
||||
{
|
||||
|
@ -144,7 +140,7 @@ public:
|
|||
customComponent->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
String getTooltip() override
|
||||
juce::String getTooltip() override
|
||||
{
|
||||
if (auto* m = owner.getModel())
|
||||
return m->getTooltipForRow (row);
|
||||
|
@ -152,8 +148,8 @@ public:
|
|||
return {};
|
||||
}
|
||||
|
||||
ListBox& owner;
|
||||
std::unique_ptr<Component> customComponent;
|
||||
VListBox& owner;
|
||||
std::unique_ptr<juce::Component> customComponent;
|
||||
int row = -1;
|
||||
bool selected = false, isDragging = false, isDraggingToScroll = false, selectRowOnMouseUp = false;
|
||||
|
||||
|
@ -161,21 +157,21 @@ public:
|
|||
};
|
||||
|
||||
//==============================================================================
|
||||
class ListBox::ListViewport : public Viewport
|
||||
class VListBox::ListViewport : public juce::Viewport
|
||||
{
|
||||
public:
|
||||
ListViewport (ListBox& lb) : owner (lb)
|
||||
ListViewport (VListBox& lb) : owner (lb)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
|
||||
auto content = new Component();
|
||||
auto content = new juce::Component();
|
||||
setViewedComponent (content);
|
||||
content->setWantsKeyboardFocus (false);
|
||||
|
||||
updateAllRows();
|
||||
}
|
||||
|
||||
RowComponent* getComponentForRow (const int row) const noexcept { return rows[row % jmax (1, rows.size())]; }
|
||||
RowComponent* getComponentForRow (const int row) const noexcept { return rows[row % juce::jmax (1, rows.size())]; }
|
||||
|
||||
RowComponent* getComponentForRowIfOnscreen (const int row) const noexcept
|
||||
{
|
||||
|
@ -204,19 +200,19 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
int getRowNumberOfComponent (Component* const rowComponent) const noexcept
|
||||
int getRowNumberOfComponent (juce::Component* const rowComponent) const noexcept
|
||||
{
|
||||
const int index = getViewedComponent()->getIndexOfChildComponent (rowComponent);
|
||||
const int num = rows.size();
|
||||
|
||||
for (int i = num; --i >= 0;)
|
||||
if (((firstIndex + i) % jmax (1, num)) == index)
|
||||
if (((firstIndex + i) % juce::jmax (1, num)) == index)
|
||||
return firstIndex + i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void visibleAreaChanged (const Rectangle<int>&) override
|
||||
void visibleAreaChanged (const juce::Rectangle<int>&) override
|
||||
{
|
||||
updateVisibleArea (true);
|
||||
|
||||
|
@ -231,7 +227,7 @@ public:
|
|||
auto& content = *getViewedComponent();
|
||||
auto newX = content.getX();
|
||||
auto newY = content.getY();
|
||||
auto newW = jmax (owner.minimumRowWidth, getMaximumVisibleWidth());
|
||||
auto newW = juce::jmax (owner.minimumRowWidth, getMaximumVisibleWidth());
|
||||
auto newH = owner.getContentHeight();
|
||||
|
||||
if (newY + newH < getMaximumVisibleHeight() && newH > getMaximumVisibleHeight())
|
||||
|
@ -262,7 +258,7 @@ public:
|
|||
++firstWholeIndex;
|
||||
}
|
||||
|
||||
auto lastRow = jmin (owner.getRowForPosition(y + getMaximumVisibleHeight()), owner.totalItems - 1);
|
||||
auto lastRow = juce::jmin (owner.getRowForPosition(y + getMaximumVisibleHeight()), owner.totalItems - 1);
|
||||
|
||||
lastWholeIndex = lastRow - 1;
|
||||
|
||||
|
@ -280,7 +276,7 @@ public:
|
|||
}
|
||||
|
||||
if (owner.headerComponent != nullptr) {
|
||||
auto width = jmax(owner.getWidth() - owner.outlineThickness * 2, content.getWidth());
|
||||
auto width = juce::jmax(owner.getWidth() - owner.outlineThickness * 2, content.getWidth());
|
||||
owner.headerComponent->setBounds(owner.outlineThickness + content.getX(), owner.outlineThickness, width, owner.headerComponent->getHeight());
|
||||
}
|
||||
}
|
||||
|
@ -301,14 +297,14 @@ public:
|
|||
{
|
||||
// put row at the top of the screen, or as close as we can make it. but this position is already constrained by
|
||||
// setViewPosition's internals.
|
||||
// auto y = jlimit (0, jmax (0, totalRows - rowsOnScreen), row) * rowH;
|
||||
// auto y = juce::jlimit (0, juce::jmax (0, totalRows - rowsOnScreen), row) * rowH;
|
||||
setViewPosition (getViewPositionX(), owner.getPositionForRow (row));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto p = owner.getPositionForRow (row);
|
||||
auto rh = owner.getRowHeight (row);
|
||||
setViewPosition (getViewPositionX(), jmax (0, p + rh - getMaximumVisibleHeight()));
|
||||
setViewPosition (getViewPositionX(), juce::jmax (0, p + rh - getMaximumVisibleHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,21 +322,21 @@ public:
|
|||
{
|
||||
auto p = owner.getPositionForRow (row);
|
||||
auto rh = owner.getRowHeight (row);
|
||||
setViewPosition (getViewPositionX(), jmax (0, p + rh - getMaximumVisibleHeight()));
|
||||
setViewPosition (getViewPositionX(), juce::jmax (0, p + rh - getMaximumVisibleHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
void paint (juce::Graphics& g) override
|
||||
{
|
||||
if (isOpaque())
|
||||
g.fillAll (owner.findColour (ListBox::backgroundColourId));
|
||||
g.fillAll (owner.findColour (VListBox::backgroundColourId));
|
||||
}
|
||||
|
||||
bool keyPressed (const KeyPress& key) override
|
||||
bool keyPressed (const juce::KeyPress& key) override
|
||||
{
|
||||
if (Viewport::respondsToKey (key))
|
||||
if (juce::Viewport::respondsToKey (key))
|
||||
{
|
||||
const int allowableMods = owner.multipleSelection ? ModifierKeys::shiftModifier : 0;
|
||||
const int allowableMods = owner.multipleSelection ? juce::ModifierKeys::shiftModifier : 0;
|
||||
|
||||
if ((key.getModifiers().getRawFlags() & ~allowableMods) == 0)
|
||||
{
|
||||
|
@ -350,12 +346,12 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
return Viewport::keyPressed (key);
|
||||
return juce::Viewport::keyPressed (key);
|
||||
}
|
||||
|
||||
private:
|
||||
ListBox& owner;
|
||||
OwnedArray<RowComponent> rows;
|
||||
VListBox& owner;
|
||||
juce::OwnedArray<RowComponent> rows;
|
||||
int firstIndex = 0, firstWholeIndex = 0, lastWholeIndex = 0;
|
||||
bool hasUpdated = false;
|
||||
|
||||
|
@ -363,25 +359,25 @@ private:
|
|||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ListBoxMouseMoveSelector : public MouseListener
|
||||
struct ListBoxMouseMoveSelector : public juce::MouseListener
|
||||
{
|
||||
ListBoxMouseMoveSelector (ListBox& lb) : owner (lb) { owner.addMouseListener (this, true); }
|
||||
ListBoxMouseMoveSelector (VListBox& lb) : owner (lb) { owner.addMouseListener (this, true); }
|
||||
|
||||
~ListBoxMouseMoveSelector() override { owner.removeMouseListener (this); }
|
||||
|
||||
void mouseMove (const MouseEvent& e) override
|
||||
void mouseMove (const juce::MouseEvent& e) override
|
||||
{
|
||||
auto pos = e.getEventRelativeTo (&owner).position.toInt();
|
||||
owner.selectRow (owner.getRowContainingPosition (pos.x, pos.y), true);
|
||||
}
|
||||
|
||||
void mouseExit (const MouseEvent& e) override { mouseMove (e); }
|
||||
void mouseExit (const juce::MouseEvent& e) override { mouseMove (e); }
|
||||
|
||||
ListBox& owner;
|
||||
VListBox& owner;
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBoxMouseMoveSelector)
|
||||
};
|
||||
|
||||
int ListBoxModel::getRowForPosition (int yPos)
|
||||
int VListBoxModel::getRowForPosition (int yPos)
|
||||
{
|
||||
if (! hasVariableHeightRows())
|
||||
return yPos / getRowHeight (0);
|
||||
|
@ -397,10 +393,10 @@ int ListBoxModel::getRowForPosition (int yPos)
|
|||
return r;
|
||||
}
|
||||
|
||||
return numRows;
|
||||
return numRows - 1;
|
||||
}
|
||||
|
||||
int ListBoxModel::getPositionForRow (int rowNumber)
|
||||
int VListBoxModel::getPositionForRow (int rowNumber)
|
||||
{
|
||||
if (! hasVariableHeightRows())
|
||||
return rowNumber * getRowHeight (0);
|
||||
|
@ -412,22 +408,22 @@ int ListBoxModel::getPositionForRow (int rowNumber)
|
|||
|
||||
return y;
|
||||
} //==============================================================================
|
||||
ListBox::ListBox (const String& name, ListBoxModel* const m) : Component (name), model (m)
|
||||
VListBox::VListBox (const juce::String& name, VListBoxModel* const m) : juce::Component (name), model (m)
|
||||
{
|
||||
viewport.reset (new ListViewport (*this));
|
||||
addAndMakeVisible (viewport.get());
|
||||
|
||||
ListBox::setWantsKeyboardFocus (true);
|
||||
ListBox::colourChanged();
|
||||
VListBox::setWantsKeyboardFocus (true);
|
||||
VListBox::colourChanged();
|
||||
}
|
||||
|
||||
ListBox::~ListBox()
|
||||
VListBox::~VListBox()
|
||||
{
|
||||
headerComponent.reset();
|
||||
viewport.reset();
|
||||
}
|
||||
|
||||
void ListBox::setModel (ListBoxModel* const newModel)
|
||||
void VListBox::setModel (VListBoxModel* const newModel)
|
||||
{
|
||||
if (model != newModel)
|
||||
{
|
||||
|
@ -437,20 +433,20 @@ void ListBox::setModel (ListBoxModel* const newModel)
|
|||
}
|
||||
}
|
||||
|
||||
void ListBox::setMultipleSelectionEnabled (bool b) noexcept
|
||||
void VListBox::setMultipleSelectionEnabled (bool b) noexcept
|
||||
{
|
||||
multipleSelection = b;
|
||||
}
|
||||
void ListBox::setClickingTogglesRowSelection (bool b) noexcept
|
||||
void VListBox::setClickingTogglesRowSelection (bool b) noexcept
|
||||
{
|
||||
alwaysFlipSelection = b;
|
||||
}
|
||||
void ListBox::setRowSelectedOnMouseDown (bool b) noexcept
|
||||
void VListBox::setRowSelectedOnMouseDown (bool b) noexcept
|
||||
{
|
||||
selectOnMouseDown = b;
|
||||
}
|
||||
|
||||
void ListBox::setMouseMoveSelectsRows (bool b)
|
||||
void VListBox::setMouseMoveSelectsRows (bool b)
|
||||
{
|
||||
if (b)
|
||||
{
|
||||
|
@ -464,7 +460,7 @@ void ListBox::setMouseMoveSelectsRows (bool b)
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void ListBox::paint (Graphics& g)
|
||||
void VListBox::paint (juce::Graphics& g)
|
||||
{
|
||||
if (! hasDoneInitialUpdate)
|
||||
updateContent();
|
||||
|
@ -472,7 +468,7 @@ void ListBox::paint (Graphics& g)
|
|||
g.fillAll (findColour (backgroundColourId));
|
||||
}
|
||||
|
||||
void ListBox::paintOverChildren (Graphics& g)
|
||||
void VListBox::paintOverChildren (juce::Graphics& g)
|
||||
{
|
||||
if (outlineThickness > 0)
|
||||
{
|
||||
|
@ -481,9 +477,9 @@ void ListBox::paintOverChildren (Graphics& g)
|
|||
}
|
||||
}
|
||||
|
||||
void ListBox::resized()
|
||||
void VListBox::resized()
|
||||
{
|
||||
viewport->setBoundsInset (BorderSize<int> (outlineThickness + (headerComponent != nullptr ? headerComponent->getHeight() : 0),
|
||||
viewport->setBoundsInset (juce::BorderSize<int> (outlineThickness + (headerComponent != nullptr ? headerComponent->getHeight() : 0),
|
||||
outlineThickness,
|
||||
outlineThickness,
|
||||
outlineThickness));
|
||||
|
@ -493,18 +489,18 @@ void ListBox::resized()
|
|||
viewport->updateVisibleArea (false);
|
||||
}
|
||||
|
||||
void ListBox::visibilityChanged()
|
||||
void VListBox::visibilityChanged()
|
||||
{
|
||||
viewport->updateVisibleArea (true);
|
||||
}
|
||||
|
||||
Viewport* ListBox::getViewport() const noexcept
|
||||
juce::Viewport* VListBox::getViewport() const noexcept
|
||||
{
|
||||
return viewport.get();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ListBox::updateContent() {
|
||||
void VListBox::updateContent() {
|
||||
hasDoneInitialUpdate = true;
|
||||
totalItems = (model != nullptr) ? model->getNumRows() : 0;
|
||||
|
||||
|
@ -526,19 +522,19 @@ void ListBox::updateContent() {
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void ListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst)
|
||||
void VListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst)
|
||||
{
|
||||
selectRowInternal (row, dontScroll, deselectOthersFirst, false);
|
||||
}
|
||||
|
||||
void ListBox::selectRowInternal (const int row, bool dontScroll, bool deselectOthersFirst, bool isMouseClick)
|
||||
void VListBox::selectRowInternal (const int row, bool dontScroll, bool deselectOthersFirst, bool isMouseClick)
|
||||
{
|
||||
if (! multipleSelection)
|
||||
deselectOthersFirst = true;
|
||||
|
||||
if ((! isRowSelected (row)) || (deselectOthersFirst && getNumSelectedRows() > 1))
|
||||
{
|
||||
if (isPositiveAndBelow (row, totalItems))
|
||||
if (juce::isPositiveAndBelow (row, totalItems))
|
||||
{
|
||||
if (deselectOthersFirst)
|
||||
selected.clear();
|
||||
|
@ -561,7 +557,7 @@ void ListBox::selectRowInternal (const int row, bool dontScroll, bool deselectOt
|
|||
}
|
||||
}
|
||||
|
||||
void ListBox::deselectRow (const int row)
|
||||
void VListBox::deselectRow (const int row)
|
||||
{
|
||||
if (selected.contains (row))
|
||||
{
|
||||
|
@ -575,7 +571,7 @@ void ListBox::deselectRow (const int row)
|
|||
}
|
||||
}
|
||||
|
||||
void ListBox::setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected, const NotificationType sendNotificationEventToModel)
|
||||
void VListBox::setSelectedRows (const juce::SparseSet<int>& setOfRowsToBeSelected, const juce::NotificationType sendNotificationEventToModel)
|
||||
{
|
||||
selected = setOfRowsToBeSelected;
|
||||
selected.removeRange ({ totalItems, std::numeric_limits<int>::max() });
|
||||
|
@ -585,24 +581,24 @@ void ListBox::setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected, cons
|
|||
|
||||
viewport->updateContents();
|
||||
|
||||
if (model != nullptr && sendNotificationEventToModel == sendNotification)
|
||||
if (model != nullptr && sendNotificationEventToModel == juce::sendNotification)
|
||||
model->selectedRowsChanged (lastRowSelected);
|
||||
}
|
||||
|
||||
SparseSet<int> ListBox::getSelectedRows() const
|
||||
juce::SparseSet<int> VListBox::getSelectedRows() const
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
|
||||
void ListBox::selectRangeOfRows (int firstRow, int lastRow, bool dontScrollToShowThisRange)
|
||||
void VListBox::selectRangeOfRows (int firstRow, int lastRow, bool dontScrollToShowThisRange)
|
||||
{
|
||||
if (multipleSelection && (firstRow != lastRow))
|
||||
{
|
||||
const int numRows = totalItems - 1;
|
||||
firstRow = jlimit (0, jmax (0, numRows), firstRow);
|
||||
lastRow = jlimit (0, jmax (0, numRows), lastRow);
|
||||
firstRow = juce::jlimit (0, juce::jmax (0, numRows), firstRow);
|
||||
lastRow = juce::jlimit (0, juce::jmax (0, numRows), lastRow);
|
||||
|
||||
selected.addRange ({ jmin (firstRow, lastRow), jmax (firstRow, lastRow) + 1 });
|
||||
selected.addRange ({ juce::jmin (firstRow, lastRow), juce::jmax (firstRow, lastRow) + 1 });
|
||||
|
||||
selected.removeRange ({ lastRow, lastRow + 1 });
|
||||
}
|
||||
|
@ -610,7 +606,7 @@ void ListBox::selectRangeOfRows (int firstRow, int lastRow, bool dontScrollToSho
|
|||
selectRowInternal (lastRow, dontScrollToShowThisRange, false, true);
|
||||
}
|
||||
|
||||
void ListBox::flipRowSelection (const int row)
|
||||
void VListBox::flipRowSelection (const int row)
|
||||
{
|
||||
if (isRowSelected (row))
|
||||
deselectRow (row);
|
||||
|
@ -618,7 +614,7 @@ void ListBox::flipRowSelection (const int row)
|
|||
selectRowInternal (row, false, false, true);
|
||||
}
|
||||
|
||||
void ListBox::deselectAllRows()
|
||||
void VListBox::deselectAllRows()
|
||||
{
|
||||
if (! selected.isEmpty())
|
||||
{
|
||||
|
@ -632,7 +628,7 @@ void ListBox::deselectAllRows()
|
|||
}
|
||||
}
|
||||
|
||||
void ListBox::selectRowsBasedOnModifierKeys (const int row, ModifierKeys mods, const bool isMouseUpEvent)
|
||||
void VListBox::selectRowsBasedOnModifierKeys (const int row, juce::ModifierKeys mods, const bool isMouseUpEvent)
|
||||
{
|
||||
if (multipleSelection && (mods.isCommandDown() || alwaysFlipSelection))
|
||||
{
|
||||
|
@ -648,41 +644,41 @@ void ListBox::selectRowsBasedOnModifierKeys (const int row, ModifierKeys mods, c
|
|||
}
|
||||
}
|
||||
|
||||
int ListBox::getNumSelectedRows() const
|
||||
int VListBox::getNumSelectedRows() const
|
||||
{
|
||||
return selected.size();
|
||||
}
|
||||
|
||||
int ListBox::getSelectedRow (const int index) const
|
||||
int VListBox::getSelectedRow (const int index) const
|
||||
{
|
||||
return (isPositiveAndBelow (index, selected.size())) ? selected[index] : -1;
|
||||
return (juce::isPositiveAndBelow (index, selected.size())) ? selected[index] : -1;
|
||||
}
|
||||
|
||||
bool ListBox::isRowSelected (const int row) const
|
||||
bool VListBox::isRowSelected (const int row) const
|
||||
{
|
||||
return selected.contains (row);
|
||||
}
|
||||
|
||||
int ListBox::getLastRowSelected() const
|
||||
int VListBox::getLastRowSelected() const
|
||||
{
|
||||
return isRowSelected (lastRowSelected) ? lastRowSelected : -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int ListBox::getRowContainingPosition (const int x, const int y) const noexcept
|
||||
int VListBox::getRowContainingPosition (const int x, const int y) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (x, getWidth()))
|
||||
if (juce::isPositiveAndBelow (x, getWidth()))
|
||||
{
|
||||
const int row = getRowForPosition (viewport->getViewPositionY() + y - viewport->getY());
|
||||
|
||||
if (isPositiveAndBelow (row, totalItems))
|
||||
if (juce::isPositiveAndBelow (row, totalItems))
|
||||
return row;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ListBox::getRowHeight (int row) const
|
||||
int VListBox::getRowHeight (int row) const
|
||||
{
|
||||
if (model == nullptr)
|
||||
return 0;
|
||||
|
@ -690,9 +686,9 @@ int ListBox::getRowHeight (int row) const
|
|||
return model->getRowHeight (row);
|
||||
}
|
||||
|
||||
int ListBox::getInsertionIndexForPosition (const int x, const int y) const noexcept
|
||||
int VListBox::getInsertionIndexForPosition (const int x, const int y) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (x, getWidth()))
|
||||
if (juce::isPositiveAndBelow (x, getWidth()))
|
||||
{
|
||||
auto cursorY = y + viewport->getViewPositionY() - viewport->getY();
|
||||
auto row = getRowForPosition (cursorY);
|
||||
|
@ -702,13 +698,13 @@ int ListBox::getInsertionIndexForPosition (const int x, const int y) const noexc
|
|||
if (rowCentre < cursorY)
|
||||
++row;
|
||||
|
||||
return jlimit (0, totalItems, row);
|
||||
return juce::jlimit (0, totalItems, row);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Component* ListBox::getComponentForRowNumber (const int row) const noexcept
|
||||
juce::Component* VListBox::getComponentForRowNumber (const int row) const noexcept
|
||||
{
|
||||
if (auto* listRowComp = viewport->getComponentForRowIfOnscreen (row))
|
||||
return listRowComp->customComponent.get();
|
||||
|
@ -716,12 +712,12 @@ Component* ListBox::getComponentForRowNumber (const int row) const noexcept
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int ListBox::getRowNumberOfComponent (Component* const rowComponent) const noexcept
|
||||
int VListBox::getRowNumberOfComponent (juce::Component* const rowComponent) const noexcept
|
||||
{
|
||||
return viewport->getRowNumberOfComponent (rowComponent);
|
||||
}
|
||||
|
||||
Rectangle<int> ListBox::getRowPosition (int rowNumber, bool relativeToComponentTopLeft) const noexcept
|
||||
juce::Rectangle<int> VListBox::getRowPosition (int rowNumber, bool relativeToComponentTopLeft) const noexcept
|
||||
{
|
||||
auto y = viewport->getY() + getPositionForRow (rowNumber);
|
||||
|
||||
|
@ -731,52 +727,52 @@ Rectangle<int> ListBox::getRowPosition (int rowNumber, bool relativeToComponentT
|
|||
return { viewport->getX(), y, viewport->getViewedComponent()->getWidth(), getRowHeight (rowNumber) };
|
||||
}
|
||||
|
||||
void ListBox::setVerticalPosition (const double proportion)
|
||||
void VListBox::setVerticalPosition (const double proportion)
|
||||
{
|
||||
auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
|
||||
|
||||
viewport->setViewPosition (viewport->getViewPositionX(), jmax (0, roundToInt (proportion * offscreen)));
|
||||
viewport->setViewPosition (viewport->getViewPositionX(), juce::jmax (0, juce::roundToInt (proportion * offscreen)));
|
||||
}
|
||||
|
||||
double ListBox::getVerticalPosition() const
|
||||
double VListBox::getVerticalPosition() const
|
||||
{
|
||||
auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
|
||||
|
||||
return offscreen > 0 ? viewport->getViewPositionY() / (double) offscreen : 0;
|
||||
}
|
||||
|
||||
int ListBox::getVisibleRowWidth() const noexcept
|
||||
int VListBox::getVisibleRowWidth() const noexcept
|
||||
{
|
||||
return viewport->getViewWidth();
|
||||
}
|
||||
|
||||
void ListBox::scrollToEnsureRowIsOnscreen (const int row)
|
||||
void VListBox::scrollToEnsureRowIsOnscreen (const int row)
|
||||
{
|
||||
viewport->scrollToEnsureRowIsOnscreen (row);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ListBox::keyPressed (const KeyPress& key)
|
||||
bool VListBox::keyPressed (const juce::KeyPress& key)
|
||||
{
|
||||
const bool multiple = multipleSelection && lastRowSelected >= 0 && key.getModifiers().isShiftDown();
|
||||
|
||||
if (key.isKeyCode (KeyPress::upKey))
|
||||
if (key.isKeyCode (juce::KeyPress::upKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, lastRowSelected - 1);
|
||||
else
|
||||
selectRow (jmax (0, lastRowSelected - 1));
|
||||
selectRow (juce::jmax (0, lastRowSelected - 1));
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::downKey))
|
||||
else if (key.isKeyCode (juce::KeyPress::downKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, lastRowSelected + 1);
|
||||
else
|
||||
selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected + 1)));
|
||||
selectRow (juce::jmin (totalItems - 1, juce::jmax (0, lastRowSelected + 1)));
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::pageUpKey))
|
||||
else if (key.isKeyCode (juce::KeyPress::pageUpKey))
|
||||
{
|
||||
auto rowToSelect = jmax(0, lastRowSelected);
|
||||
auto rowToSelect = juce::jmax(0, lastRowSelected);
|
||||
auto pageHeight = viewport->getMaximumVisibleHeight();
|
||||
|
||||
while (pageHeight > 0 && rowToSelect > 0)
|
||||
|
@ -792,9 +788,9 @@ bool ListBox::keyPressed (const KeyPress& key)
|
|||
else
|
||||
selectRow (rowToSelect);
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::pageDownKey))
|
||||
else if (key.isKeyCode (juce::KeyPress::pageDownKey))
|
||||
{
|
||||
auto rowToSelect = jmax(0, lastRowSelected);
|
||||
auto rowToSelect = juce::jmax(0, lastRowSelected);
|
||||
auto pageHeight= viewport->getMaximumVisibleHeight();
|
||||
|
||||
while (pageHeight > 0 && rowToSelect < totalItems - 1)
|
||||
|
@ -810,31 +806,31 @@ bool ListBox::keyPressed (const KeyPress& key)
|
|||
else
|
||||
selectRow (rowToSelect);
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::homeKey))
|
||||
else if (key.isKeyCode (juce::KeyPress::homeKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, 0);
|
||||
else
|
||||
selectRow (0);
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::endKey))
|
||||
else if (key.isKeyCode (juce::KeyPress::endKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, totalItems - 1);
|
||||
else
|
||||
selectRow (totalItems - 1);
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::returnKey) && isRowSelected (lastRowSelected))
|
||||
else if (key.isKeyCode (juce::KeyPress::returnKey) && isRowSelected (lastRowSelected))
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->returnKeyPressed (lastRowSelected);
|
||||
}
|
||||
else if ((key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey)) && isRowSelected (lastRowSelected))
|
||||
else if ((key.isKeyCode (juce::KeyPress::deleteKey) || key.isKeyCode (juce::KeyPress::backspaceKey)) && isRowSelected (lastRowSelected))
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->deleteKeyPressed (lastRowSelected);
|
||||
}
|
||||
else if (multipleSelection && key == KeyPress ('a', ModifierKeys::commandModifier, 0))
|
||||
else if (multipleSelection && key == juce::KeyPress ('a', juce::ModifierKeys::commandModifier, 0))
|
||||
{
|
||||
selectRangeOfRows (0, std::numeric_limits<int>::max());
|
||||
}
|
||||
|
@ -846,16 +842,16 @@ bool ListBox::keyPressed (const KeyPress& key)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ListBox::keyStateChanged (const bool isKeyDown)
|
||||
bool VListBox::keyStateChanged (const bool isKeyDown)
|
||||
{
|
||||
return isKeyDown
|
||||
&& (KeyPress::isKeyCurrentlyDown (KeyPress::upKey) || KeyPress::isKeyCurrentlyDown (KeyPress::pageUpKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::downKey) || KeyPress::isKeyCurrentlyDown (KeyPress::pageDownKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::homeKey) || KeyPress::isKeyCurrentlyDown (KeyPress::endKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::returnKey));
|
||||
&& (juce::KeyPress::isKeyCurrentlyDown (juce::KeyPress::upKey) || juce::KeyPress::isKeyCurrentlyDown (juce::KeyPress::pageUpKey)
|
||||
|| juce::KeyPress::isKeyCurrentlyDown (juce::KeyPress::downKey) || juce::KeyPress::isKeyCurrentlyDown (juce::KeyPress::pageDownKey)
|
||||
|| juce::KeyPress::isKeyCurrentlyDown (juce::KeyPress::homeKey) || juce::KeyPress::isKeyCurrentlyDown (juce::KeyPress::endKey)
|
||||
|| juce::KeyPress::isKeyCurrentlyDown (juce::KeyPress::returnKey));
|
||||
}
|
||||
|
||||
void ListBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
|
||||
void VListBox::mouseWheelMove (const juce::MouseEvent& e, const juce::MouseWheelDetails& wheel)
|
||||
{
|
||||
bool eventWasUsed = false;
|
||||
|
||||
|
@ -872,10 +868,10 @@ void ListBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& whee
|
|||
}
|
||||
|
||||
if (! eventWasUsed)
|
||||
Component::mouseWheelMove (e, wheel);
|
||||
juce::Component::mouseWheelMove (e, wheel);
|
||||
}
|
||||
|
||||
void ListBox::mouseUp (const MouseEvent& e)
|
||||
void VListBox::mouseUp (const juce::MouseEvent& e)
|
||||
{
|
||||
if (e.mouseWasClicked() && model != nullptr)
|
||||
model->backgroundClicked (e);
|
||||
|
@ -883,7 +879,7 @@ void ListBox::mouseUp (const MouseEvent& e)
|
|||
|
||||
//==============================================================================
|
||||
|
||||
int ListBox::getNumRowsOnScreen() const noexcept
|
||||
int VListBox::getNumRowsOnScreen() const noexcept
|
||||
{
|
||||
// todo: not clear this function can work as previously intended
|
||||
|
||||
|
@ -897,59 +893,59 @@ int ListBox::getNumRowsOnScreen() const noexcept
|
|||
return rowNumber - start;
|
||||
}
|
||||
|
||||
void ListBox::setMinimumContentWidth (const int newMinimumWidth)
|
||||
void VListBox::setMinimumContentWidth (const int newMinimumWidth)
|
||||
{
|
||||
minimumRowWidth = newMinimumWidth;
|
||||
updateContent();
|
||||
}
|
||||
|
||||
int ListBox::getVisibleContentWidth() const noexcept
|
||||
int VListBox::getVisibleContentWidth() const noexcept
|
||||
{
|
||||
return viewport->getMaximumVisibleWidth();
|
||||
}
|
||||
|
||||
ScrollBar& ListBox::getVerticalScrollBar() const noexcept
|
||||
juce::ScrollBar& VListBox::getVerticalScrollBar() const noexcept
|
||||
{
|
||||
return viewport->getVerticalScrollBar();
|
||||
}
|
||||
ScrollBar& ListBox::getHorizontalScrollBar() const noexcept
|
||||
juce::ScrollBar& VListBox::getHorizontalScrollBar() const noexcept
|
||||
{
|
||||
return viewport->getHorizontalScrollBar();
|
||||
}
|
||||
|
||||
void ListBox::colourChanged()
|
||||
void VListBox::colourChanged()
|
||||
{
|
||||
setOpaque (findColour (backgroundColourId).isOpaque());
|
||||
viewport->setOpaque (isOpaque());
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ListBox::parentHierarchyChanged()
|
||||
void VListBox::parentHierarchyChanged()
|
||||
{
|
||||
colourChanged();
|
||||
}
|
||||
|
||||
void ListBox::setOutlineThickness (int newThickness)
|
||||
void VListBox::setOutlineThickness (int newThickness)
|
||||
{
|
||||
outlineThickness = newThickness;
|
||||
resized();
|
||||
}
|
||||
|
||||
void ListBox::setHeaderComponent (std::unique_ptr<Component> newHeaderComponent)
|
||||
void VListBox::setHeaderComponent (std::unique_ptr<juce::Component> newHeaderComponent)
|
||||
{
|
||||
headerComponent = std::move (newHeaderComponent);
|
||||
addAndMakeVisible (headerComponent.get());
|
||||
ListBox::resized();
|
||||
VListBox::resized();
|
||||
}
|
||||
|
||||
void ListBox::repaintRow (const int rowNumber) noexcept
|
||||
void VListBox::repaintRow (const int rowNumber) noexcept
|
||||
{
|
||||
repaint (getRowPosition (rowNumber, true));
|
||||
}
|
||||
|
||||
Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, int& imageY)
|
||||
juce::Image VListBox::createSnapshotOfRows (const juce::SparseSet<int>& rows, int& imageX, int& imageY)
|
||||
{
|
||||
Rectangle<int> imageArea;
|
||||
juce::Rectangle<int> imageArea;
|
||||
auto firstRow = getRowContainingPosition (0, viewport->getY());
|
||||
|
||||
for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
|
||||
|
@ -958,7 +954,7 @@ Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, in
|
|||
{
|
||||
if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
|
||||
{
|
||||
auto pos = getLocalPoint (rowComp, Point<int>());
|
||||
auto pos = getLocalPoint (rowComp, juce::Point<int>());
|
||||
|
||||
imageArea = imageArea.getUnion ({ pos.x, pos.y, rowComp->getWidth(), rowComp->getHeight() });
|
||||
}
|
||||
|
@ -969,9 +965,9 @@ Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, in
|
|||
imageX = imageArea.getX();
|
||||
imageY = imageArea.getY();
|
||||
|
||||
auto listScale = Component::getApproximateScaleFactorForComponent (this);
|
||||
Image snapshot (
|
||||
Image::ARGB, roundToInt ((float) imageArea.getWidth() * listScale), roundToInt ((float) imageArea.getHeight() * listScale), true);
|
||||
auto listScale = juce::Component::getApproximateScaleFactorForComponent (this);
|
||||
juce::Image snapshot (
|
||||
juce::Image::ARGB, juce::roundToInt ((float) imageArea.getWidth() * listScale), juce::roundToInt ((float) imageArea.getHeight() * listScale), true);
|
||||
|
||||
for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
|
||||
{
|
||||
|
@ -979,15 +975,15 @@ Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, in
|
|||
{
|
||||
if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
|
||||
{
|
||||
Graphics g (snapshot);
|
||||
g.setOrigin (getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition());
|
||||
juce::Graphics g (snapshot);
|
||||
g.setOrigin (getLocalPoint (rowComp, juce::Point<int>()) - imageArea.getPosition());
|
||||
|
||||
auto rowScale = Component::getApproximateScaleFactorForComponent (rowComp);
|
||||
auto rowScale = juce::Component::getApproximateScaleFactorForComponent (rowComp);
|
||||
|
||||
if (g.reduceClipRegion (rowComp->getLocalBounds() * rowScale))
|
||||
{
|
||||
g.beginTransparencyLayer (0.6f);
|
||||
g.addTransform (AffineTransform::scale (rowScale));
|
||||
g.addTransform (juce::AffineTransform::scale (rowScale));
|
||||
rowComp->paintEntireComponent (g, false);
|
||||
g.endTransparencyLayer();
|
||||
}
|
||||
|
@ -998,28 +994,28 @@ Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, in
|
|||
return snapshot;
|
||||
}
|
||||
|
||||
void ListBox::startDragAndDrop (const MouseEvent& e,
|
||||
const SparseSet<int>& rowsToDrag,
|
||||
const var& dragDescription,
|
||||
void VListBox::startDragAndDrop (const juce::MouseEvent& e,
|
||||
const juce::SparseSet<int>& rowsToDrag,
|
||||
const juce::var& dragDescription,
|
||||
bool allowDraggingToOtherWindows)
|
||||
{
|
||||
if (auto* dragContainer = DragAndDropContainer::findParentDragContainerFor (this))
|
||||
if (auto* dragContainer = juce::DragAndDropContainer::findParentDragContainerFor (this))
|
||||
{
|
||||
int x, y;
|
||||
auto dragImage = createSnapshotOfRows (rowsToDrag, x, y);
|
||||
|
||||
auto p = Point<int> (x, y) - e.getEventRelativeTo (this).position.toInt();
|
||||
auto p = juce::Point<int> (x, y) - e.getEventRelativeTo (this).position.toInt();
|
||||
dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p, &e.source);
|
||||
}
|
||||
else
|
||||
{
|
||||
// to be able to do a drag-and-drop operation, the listbox needs to
|
||||
// be inside a component which is also a DragAndDropContainer.
|
||||
// be inside a component which is also a juce::DragAndDropContainer.
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
int ListBox::getContentHeight() const
|
||||
int VListBox::getContentHeight() const
|
||||
{
|
||||
if (model == nullptr || totalItems == 0)
|
||||
return 0;
|
||||
|
@ -1027,7 +1023,7 @@ int ListBox::getContentHeight() const
|
|||
return getPositionForRow (totalItems - 1) + getRowHeight (totalItems - 1);
|
||||
}
|
||||
|
||||
int ListBox::getRowForPosition (int y) const
|
||||
int VListBox::getRowForPosition (int y) const
|
||||
{
|
||||
if (model == nullptr || y < 0)
|
||||
return 0;
|
||||
|
@ -1035,7 +1031,7 @@ int ListBox::getRowForPosition (int y) const
|
|||
return model->getRowForPosition (y);
|
||||
}
|
||||
|
||||
int ListBox::getPositionForRow (int row) const
|
||||
int VListBox::getPositionForRow (int row) const
|
||||
{
|
||||
if (model == nullptr)
|
||||
return 0;
|
||||
|
@ -1046,32 +1042,29 @@ int ListBox::getPositionForRow (int row) const
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
|
||||
juce::Component* VListBoxModel::refreshComponentForRow (int, bool, juce::Component* existingComponentToUpdate)
|
||||
{
|
||||
ignoreUnused (existingComponentToUpdate);
|
||||
jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
|
||||
void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
|
||||
void ListBoxModel::backgroundClicked (const MouseEvent&) {}
|
||||
void ListBoxModel::selectedRowsChanged (int) {}
|
||||
void ListBoxModel::deleteKeyPressed (int) {}
|
||||
void ListBoxModel::returnKeyPressed (int) {}
|
||||
void ListBoxModel::listWasScrolled() {}
|
||||
var ListBoxModel::getDragSourceDescription (const SparseSet<int>&)
|
||||
void VListBoxModel::listBoxItemClicked (int, const juce::MouseEvent&) {}
|
||||
void VListBoxModel::listBoxItemDoubleClicked (int, const juce::MouseEvent&) {}
|
||||
void VListBoxModel::backgroundClicked (const juce::MouseEvent&) {}
|
||||
void VListBoxModel::selectedRowsChanged (int) {}
|
||||
void VListBoxModel::deleteKeyPressed (int) {}
|
||||
void VListBoxModel::returnKeyPressed (int) {}
|
||||
void VListBoxModel::listWasScrolled() {}
|
||||
juce::var VListBoxModel::getDragSourceDescription (const juce::SparseSet<int>&)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
String ListBoxModel::getTooltipForRow (int)
|
||||
juce::String VListBoxModel::getTooltipForRow (int)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
MouseCursor ListBoxModel::getMouseCursorForRow (int)
|
||||
juce::MouseCursor VListBoxModel::getMouseCursorForRow (int)
|
||||
{
|
||||
return MouseCursor::NormalCursor;
|
||||
return juce::MouseCursor::NormalCursor;
|
||||
}
|
||||
|
||||
} // namespace jc
|
||||
} // namespace juce
|
|
@ -222,16 +222,7 @@ public:
|
|||
VERSION_HINT, 0.5, 0.0, 1.0
|
||||
)
|
||||
);
|
||||
std::shared_ptr<StereoEffect> stereoEffectApplication = std::make_shared<StereoEffect>();
|
||||
std::shared_ptr<osci::Effect> stereoEffect = std::make_shared<osci::Effect>(
|
||||
stereoEffectApplication,
|
||||
new osci::EffectParameter(
|
||||
"Stereo",
|
||||
"Turns mono audio that is uninteresting to visualise into stereo audio that is interesting to visualise.",
|
||||
"stereo",
|
||||
VERSION_HINT, 0.0, 0.0, 1.0
|
||||
)
|
||||
);
|
||||
std::shared_ptr<osci::Effect> stereoEffect = StereoEffect().build();
|
||||
std::shared_ptr<osci::Effect> scaleEffect = std::make_shared<osci::Effect>(
|
||||
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
|
||||
input.scale(values[0].load(), values[1].load(), 1.0);
|
||||
|
@ -243,12 +234,12 @@ public:
|
|||
"xScale",
|
||||
VERSION_HINT, 1.0, -3.0, 3.0
|
||||
),
|
||||
new osci::EffectParameter(
|
||||
"Y Scale",
|
||||
"Controls the vertical scale of the oscilloscope display.",
|
||||
"yScale",
|
||||
VERSION_HINT, 1.0, -3.0, 3.0
|
||||
),
|
||||
new osci::EffectParameter(
|
||||
"Y Scale",
|
||||
"Controls the vertical scale of the oscilloscope display.",
|
||||
"yScale",
|
||||
VERSION_HINT, 1.0, -3.0, 3.0
|
||||
),
|
||||
});
|
||||
std::shared_ptr<osci::Effect> offsetEffect = std::make_shared<osci::Effect>(
|
||||
[this](int index, osci::Point input, const std::vector<std::atomic<double>>& values, double sampleRate) {
|
||||
|
@ -331,18 +322,10 @@ public:
|
|||
"Ambient Light",
|
||||
"Controls how much ambient light is added to the oscilloscope display.",
|
||||
"ambient",
|
||||
VERSION_HINT, 0.7, 0.0, 5.0
|
||||
)
|
||||
);
|
||||
std::shared_ptr<osci::Effect> smoothEffect = std::make_shared<osci::Effect>(
|
||||
std::make_shared<SmoothEffect>(),
|
||||
new osci::EffectParameter(
|
||||
"Smoothing",
|
||||
"This works as a low-pass frequency filter, effectively reducing the sample rate of the audio being visualised.",
|
||||
"visualiserSmoothing",
|
||||
VERSION_HINT, 0, 0.0, 1.0
|
||||
VERSION_HINT, 0.0, 0.0, 5.0
|
||||
)
|
||||
);
|
||||
std::shared_ptr<osci::Effect> smoothEffect = SmoothEffect("visualiser", 0.0f).build();
|
||||
std::shared_ptr<osci::Effect> sweepMsEffect = std::make_shared<osci::Effect>(
|
||||
new osci::EffectParameter(
|
||||
"Sweep (ms)",
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 913c3b052404818c45ad93c5e6022244d57df5e9
|
||||
Subproject commit fedc2463e64e13f1348d49ae6504d31c9db41cc0
|
|
@ -51,14 +51,23 @@
|
|||
file="Resources/sosci/vector_display.sosci"/>
|
||||
</GROUP>
|
||||
<GROUP id="{82BCD6F1-A8BF-F30B-5587-81EE70168883}" name="svg">
|
||||
<FILE id="FIXHhD" name="bit-crush.svg" compile="0" resource="1" file="Resources/svg/bit-crush.svg"/>
|
||||
<FILE id="eAm6Vd" name="bulge.svg" compile="0" resource="1" file="Resources/svg/bulge.svg"/>
|
||||
<FILE id="gqROYg" name="close.svg" compile="0" resource="1" file="Resources/svg/close.svg"/>
|
||||
<FILE id="rl17ZK" name="cog.svg" compile="0" resource="1" file="Resources/svg/cog.svg"/>
|
||||
<FILE id="bMtE0O" name="dash.svg" compile="0" resource="1" file="Resources/svg/dash.svg"/>
|
||||
<FILE id="GmHXfS" name="delay.svg" compile="0" resource="1" file="Resources/svg/delay.svg"/>
|
||||
<FILE id="sDajXu" name="delete.svg" compile="0" resource="1" file="Resources/svg/delete.svg"/>
|
||||
<FILE id="IqXIZW" name="demo.svg" compile="0" resource="1" file="Resources/svg/demo.svg"/>
|
||||
<FILE id="jhZqJb" name="distort.svg" compile="0" resource="1" file="Resources/svg/distort.svg"/>
|
||||
<FILE id="YwkQpy" name="fixed_rotate.svg" compile="0" resource="1"
|
||||
file="Resources/svg/fixed_rotate.svg"/>
|
||||
<FILE id="WIkl6l" name="fullscreen.svg" compile="0" resource="1" file="Resources/svg/fullscreen.svg"/>
|
||||
<FILE id="n1esUp" name="left_arrow.svg" compile="0" resource="1" file="Resources/svg/left_arrow.svg"/>
|
||||
<FILE id="lVwGO7" name="link.svg" compile="0" resource="1" file="Resources/svg/link.svg"/>
|
||||
<FILE id="LujqKC" name="lua.svg" compile="0" resource="1" file="Resources/svg/lua.svg"/>
|
||||
<FILE id="PxYKbt" name="microphone.svg" compile="0" resource="1" file="Resources/svg/microphone.svg"/>
|
||||
<FILE id="apDx43" name="multiplex.svg" compile="0" resource="1" file="Resources/svg/multiplex.svg"/>
|
||||
<FILE id="WoY9r2" name="mute.svg" compile="0" resource="1" file="Resources/svg/mute.svg"/>
|
||||
<FILE id="hJHxFY" name="open_in_new.svg" compile="0" resource="1" file="Resources/svg/open_in_new.svg"/>
|
||||
<FILE id="pSc1mq" name="osci.svg" compile="0" resource="1" file="Resources/svg/osci.svg"/>
|
||||
|
@ -70,11 +79,21 @@
|
|||
<FILE id="n79IAy" name="record.svg" compile="0" resource="1" file="Resources/svg/record.svg"/>
|
||||
<FILE id="WDxwSi" name="repeat.svg" compile="0" resource="1" file="Resources/svg/repeat.svg"/>
|
||||
<FILE id="OaqZb1" name="right_arrow.svg" compile="0" resource="1" file="Resources/svg/right_arrow.svg"/>
|
||||
<FILE id="HF5Lko" name="ripple.svg" compile="0" resource="1" file="Resources/svg/ripple.svg"/>
|
||||
<FILE id="g91LQf" name="rotate.svg" compile="0" resource="1" file="Resources/svg/rotate.svg"/>
|
||||
<FILE id="mplh74" name="scale.svg" compile="0" resource="1" file="Resources/svg/scale.svg"/>
|
||||
<FILE id="vQyuyd" name="smoothing.svg" compile="0" resource="1" file="Resources/svg/smoothing.svg"/>
|
||||
<FILE id="z3A7FT" name="spout.svg" compile="0" resource="1" file="Resources/svg/spout.svg"/>
|
||||
<FILE id="wD6mre" name="stop.svg" compile="0" resource="1" file="Resources/svg/stop.svg"/>
|
||||
<FILE id="OBmfqb" name="swirl.svg" compile="0" resource="1" file="Resources/svg/swirl.svg"/>
|
||||
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
|
||||
<FILE id="rFYmV8" name="timer.svg" compile="0" resource="1" file="Resources/svg/timer.svg"/>
|
||||
<FILE id="E16zAj" name="trace.svg" compile="0" resource="1" file="Resources/svg/trace.svg"/>
|
||||
<FILE id="zQOV5n" name="translate.svg" compile="0" resource="1" file="Resources/svg/translate.svg"/>
|
||||
<FILE id="pZPfFZ" name="vector-cancelling.svg" compile="0" resource="1"
|
||||
file="Resources/svg/vector-cancelling.svg"/>
|
||||
<FILE id="qC6QiP" name="volume.svg" compile="0" resource="1" file="Resources/svg/volume.svg"/>
|
||||
<FILE id="EUejM5" name="wobble.svg" compile="0" resource="1" file="Resources/svg/wobble.svg"/>
|
||||
</GROUP>
|
||||
</GROUP>
|
||||
<GROUP id="{75439074-E50C-362F-1EDF-8B4BE9011259}" name="Source">
|
||||
|
|
|
@ -46,15 +46,26 @@
|
|||
resource="1" file="Resources/oscilloscope/vector_display_reflection.png"/>
|
||||
</GROUP>
|
||||
<GROUP id="{82BCD6F1-A8BF-F30B-5587-81EE70168883}" name="svg">
|
||||
<FILE id="laB1fX" name="bit-crush.svg" compile="0" resource="1" file="Resources/svg/bit-crush.svg"/>
|
||||
<FILE id="Rs9Xr6" name="bounce.svg" compile="0" resource="1" file="Resources/svg/bounce.svg"/>
|
||||
<FILE id="rQlbqP" name="bulge.svg" compile="0" resource="1" file="Resources/svg/bulge.svg"/>
|
||||
<FILE id="jYG5GY" name="close.svg" compile="0" resource="1" file="Resources/svg/close.svg"/>
|
||||
<FILE id="rl17ZK" name="cog.svg" compile="0" resource="1" file="Resources/svg/cog.svg"/>
|
||||
<FILE id="toeDbG" name="dash.svg" compile="0" resource="1" file="Resources/svg/dash.svg"/>
|
||||
<FILE id="bH7fkX" name="delay.svg" compile="0" resource="1" file="Resources/svg/delay.svg"/>
|
||||
<FILE id="sDajXu" name="delete.svg" compile="0" resource="1" file="Resources/svg/delete.svg"/>
|
||||
<FILE id="IqXIZW" name="demo.svg" compile="0" resource="1" file="Resources/svg/demo.svg"/>
|
||||
<FILE id="ATrDxb" name="distort.svg" compile="0" resource="1" file="Resources/svg/distort.svg"/>
|
||||
<FILE id="YwkQpy" name="fixed_rotate.svg" compile="0" resource="1"
|
||||
file="Resources/svg/fixed_rotate.svg"/>
|
||||
<FILE id="WIkl6l" name="fullscreen.svg" compile="0" resource="1" file="Resources/svg/fullscreen.svg"/>
|
||||
<FILE id="AVhjBP" name="kaleidoscope.svg" compile="0" resource="1"
|
||||
file="Resources/svg/kaleidoscope.svg"/>
|
||||
<FILE id="n1esUp" name="left_arrow.svg" compile="0" resource="1" file="Resources/svg/left_arrow.svg"/>
|
||||
<FILE id="WdfE7J" name="link.svg" compile="0" resource="1" file="Resources/svg/link.svg"/>
|
||||
<FILE id="rGSPBN" name="lua.svg" compile="0" resource="1" file="Resources/svg/lua.svg"/>
|
||||
<FILE id="PxYKbt" name="microphone.svg" compile="0" resource="1" file="Resources/svg/microphone.svg"/>
|
||||
<FILE id="YuYfcJ" name="multiplex.svg" compile="0" resource="1" file="Resources/svg/multiplex.svg"/>
|
||||
<FILE id="eGMxwy" name="mute.svg" compile="0" resource="1" file="Resources/svg/mute.svg"/>
|
||||
<FILE id="hJHxFY" name="open_in_new.svg" compile="0" resource="1" file="Resources/svg/open_in_new.svg"/>
|
||||
<FILE id="pSc1mq" name="osci.svg" compile="0" resource="1" file="Resources/svg/osci.svg"/>
|
||||
|
@ -66,11 +77,22 @@
|
|||
<FILE id="n79IAy" name="record.svg" compile="0" resource="1" file="Resources/svg/record.svg"/>
|
||||
<FILE id="vOGcgi" name="repeat.svg" compile="0" resource="1" file="Resources/svg/repeat.svg"/>
|
||||
<FILE id="OaqZb1" name="right_arrow.svg" compile="0" resource="1" file="Resources/svg/right_arrow.svg"/>
|
||||
<FILE id="Knzo1H" name="ripple.svg" compile="0" resource="1" file="Resources/svg/ripple.svg"/>
|
||||
<FILE id="Gxscyd" name="rotate.svg" compile="0" resource="1" file="Resources/svg/rotate.svg"/>
|
||||
<FILE id="cUDYOM" name="scale.svg" compile="0" resource="1" file="Resources/svg/scale.svg"/>
|
||||
<FILE id="ZPUNcg" name="smoothing.svg" compile="0" resource="1" file="Resources/svg/smoothing.svg"/>
|
||||
<FILE id="yiDo4s" name="spout.svg" compile="0" resource="1" file="Resources/svg/spout.svg"/>
|
||||
<FILE id="EOWhGi" name="stop.svg" compile="0" resource="1" file="Resources/svg/stop.svg"/>
|
||||
<FILE id="Z3yYJU" name="swirl.svg" compile="0" resource="1" file="Resources/svg/swirl.svg"/>
|
||||
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
|
||||
<FILE id="rFYmV8" name="timer.svg" compile="0" resource="1" file="Resources/svg/timer.svg"/>
|
||||
<FILE id="ESR7bv" name="trace.svg" compile="0" resource="1" file="Resources/svg/trace.svg"/>
|
||||
<FILE id="ID1vTS" name="translate.svg" compile="0" resource="1" file="Resources/svg/translate.svg"/>
|
||||
<FILE id="Sw4WWb" name="twist.svg" compile="0" resource="1" file="Resources/svg/twist.svg"/>
|
||||
<FILE id="praXUY" name="vector-cancelling.svg" compile="0" resource="1"
|
||||
file="Resources/svg/vector-cancelling.svg"/>
|
||||
<FILE id="qC6QiP" name="volume.svg" compile="0" resource="1" file="Resources/svg/volume.svg"/>
|
||||
<FILE id="SkgUSk" name="wobble.svg" compile="0" resource="1" file="Resources/svg/wobble.svg"/>
|
||||
</GROUP>
|
||||
<GROUP id="{F8A3D32C-4187-9A2F-5D78-040259957E9B}" name="text">
|
||||
<FILE id="N9Q6Zg" name="greek.txt" compile="0" resource="1" file="Resources/text/greek.txt"/>
|
||||
|
@ -92,18 +114,27 @@
|
|||
<FILE id="ux2dO2" name="DistortEffect.h" compile="0" resource="0" file="Source/audio/DistortEffect.h"/>
|
||||
<FILE id="SWC0tN" name="MultiplexEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/MultiplexEffect.h"/>
|
||||
<FILE id="kAlEiD" name="KaleidoscopeEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/KaleidoscopeEffect.h"/>
|
||||
<FILE id="bNcEfx" name="BounceEffect.h" compile="0" resource="0" file="Source/audio/BounceEffect.h"/>
|
||||
<FILE id="h0dMim" name="PerspectiveEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/PerspectiveEffect.h"/>
|
||||
<FILE id="t5g8pf" name="PublicSynthesiser.h" compile="0" resource="0"
|
||||
file="Source/audio/PublicSynthesiser.h"/>
|
||||
<FILE id="L01ZLi" name="RippleEffect.h" compile="0" resource="0" file="Source/audio/RippleEffect.h"/>
|
||||
<FILE id="bXcQHc" name="RotateEffect.h" compile="0" resource="0" file="Source/audio/RotateEffect.h"/>
|
||||
<FILE id="Q5kjpU" name="SampleRateManager.h" compile="0" resource="0"
|
||||
file="Source/audio/SampleRateManager.h"/>
|
||||
<FILE id="Cffx3f" name="ScaleEffect.h" compile="0" resource="0" file="Source/audio/ScaleEffect.h"/>
|
||||
<FILE id="dBaZAV" name="ShapeSound.cpp" compile="1" resource="0" file="Source/audio/ShapeSound.cpp"/>
|
||||
<FILE id="VKBirB" name="ShapeSound.h" compile="0" resource="0" file="Source/audio/ShapeSound.h"/>
|
||||
<FILE id="UcPZ09" name="ShapeVoice.cpp" compile="1" resource="0" file="Source/audio/ShapeVoice.cpp"/>
|
||||
<FILE id="WId4vx" name="ShapeVoice.h" compile="0" resource="0" file="Source/audio/ShapeVoice.h"/>
|
||||
<FILE id="Vwjht7" name="SmoothEffect.h" compile="0" resource="0" file="Source/audio/SmoothEffect.h"/>
|
||||
<FILE id="WACNMe" name="StereoEffect.h" compile="0" resource="0" file="Source/audio/StereoEffect.h"/>
|
||||
<FILE id="yiIupB" name="SwirlEffect.h" compile="0" resource="0" file="Source/audio/SwirlEffect.h"/>
|
||||
<FILE id="qZkeZC" name="TranslateEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/TranslateEffect.h"/>
|
||||
<FILE id="RsHWPN" name="TwistEffect.h" compile="0" resource="0" file="Source/audio/TwistEffect.h"/>
|
||||
<FILE id="Be21D0" name="VectorCancellingEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/VectorCancellingEffect.h"/>
|
||||
|
@ -156,8 +187,20 @@
|
|||
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="SYA32K" name="HoverAnimationMixin.cpp" compile="1" resource="0"
|
||||
file="Source/components/HoverAnimationMixin.cpp"/>
|
||||
<FILE id="JyPgec" name="HoverAnimationMixin.h" compile="0" resource="0"
|
||||
file="Source/components/HoverAnimationMixin.h"/>
|
||||
<FILE id="L9DIT2" name="LabelledTextBox.h" compile="0" resource="0"
|
||||
file="Source/components/LabelledTextBox.h"/>
|
||||
<FILE id="tpNWJ3" name="LuaConsole.cpp" compile="1" resource="0" file="Source/components/LuaConsole.cpp"/>
|
||||
|
@ -660,6 +703,7 @@
|
|||
<MODULEPATH id="chowdsp_data_structures" path="modules/chowdsp_utils/modules/common"/>
|
||||
<MODULEPATH id="juce_sharedtexture" path="modules"/>
|
||||
<MODULEPATH id="osci_render_core" path="modules"/>
|
||||
<MODULEPATH id="chowdsp_gui" path="modules/chowdsp_utils/modules/gui"/>
|
||||
</MODULEPATHS>
|
||||
</LINUX_MAKE>
|
||||
<VS2022 targetFolder="Builds/osci-render/VisualStudio2022" smallIcon="pSc1mq"
|
||||
|
@ -700,6 +744,7 @@
|
|||
<MODULEPATH id="chowdsp_data_structures" path="modules/chowdsp_utils/modules/common"/>
|
||||
<MODULEPATH id="juce_sharedtexture" path="modules"/>
|
||||
<MODULEPATH id="osci_render_core" path="modules"/>
|
||||
<MODULEPATH id="chowdsp_gui" path="modules/chowdsp_utils/modules/gui"/>
|
||||
</MODULEPATHS>
|
||||
</VS2022>
|
||||
<XCODE_MAC targetFolder="Builds/osci-render/MacOSX" extraLinkerFlags="-Wl,-weak_reference_mismatches,weak"
|
||||
|
@ -746,6 +791,7 @@
|
|||
<MODULEPATH id="chowdsp_data_structures" path="modules/chowdsp_utils/modules/common"/>
|
||||
<MODULEPATH id="juce_sharedtexture" path="modules"/>
|
||||
<MODULEPATH id="osci_render_core" path="modules"/>
|
||||
<MODULEPATH id="chowdsp_gui" path="modules/chowdsp_utils/modules/gui"/>
|
||||
</MODULEPATHS>
|
||||
</XCODE_MAC>
|
||||
</EXPORTFORMATS>
|
||||
|
@ -758,6 +804,7 @@
|
|||
useGlobalPath="0"/>
|
||||
<MODULE id="chowdsp_dsp_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
|
||||
<MODULE id="chowdsp_filters" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
|
||||
<MODULE id="chowdsp_gui" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
|
||||
<MODULE id="chowdsp_math" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
|
||||
<MODULE id="chowdsp_simd" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/>
|
||||
<MODULE id="juce_animation" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
|
|
18
sosci.jucer
|
@ -50,15 +50,23 @@
|
|||
file="Resources/sosci/vector_display.sosci"/>
|
||||
</GROUP>
|
||||
<GROUP id="{82BCD6F1-A8BF-F30B-5587-81EE70168883}" name="svg">
|
||||
<FILE id="YzPzNV" name="bit-crush.svg" compile="0" resource="1" file="Resources/svg/bit-crush.svg"/>
|
||||
<FILE id="CHkskO" name="bulge.svg" compile="0" resource="1" file="Resources/svg/bulge.svg"/>
|
||||
<FILE id="k5TKs2" name="close.svg" compile="0" resource="1" file="Resources/svg/close.svg"/>
|
||||
<FILE id="rl17ZK" name="cog.svg" compile="0" resource="1" file="Resources/svg/cog.svg"/>
|
||||
<FILE id="XC04mb" name="dash.svg" compile="0" resource="1" file="Resources/svg/dash.svg"/>
|
||||
<FILE id="qIMww8" name="delay.svg" compile="0" resource="1" file="Resources/svg/delay.svg"/>
|
||||
<FILE id="sDajXu" name="delete.svg" compile="0" resource="1" file="Resources/svg/delete.svg"/>
|
||||
<FILE id="IqXIZW" name="demo.svg" compile="0" resource="1" file="Resources/svg/demo.svg"/>
|
||||
<FILE id="kTJy4S" name="distort.svg" compile="0" resource="1" file="Resources/svg/distort.svg"/>
|
||||
<FILE id="YwkQpy" name="fixed_rotate.svg" compile="0" resource="1"
|
||||
file="Resources/svg/fixed_rotate.svg"/>
|
||||
<FILE id="WIkl6l" name="fullscreen.svg" compile="0" resource="1" file="Resources/svg/fullscreen.svg"/>
|
||||
<FILE id="n1esUp" name="left_arrow.svg" compile="0" resource="1" file="Resources/svg/left_arrow.svg"/>
|
||||
<FILE id="Q2mSgZ" name="link.svg" compile="0" resource="1" file="Resources/svg/link.svg"/>
|
||||
<FILE id="e19TQV" name="lua.svg" compile="0" resource="1" file="Resources/svg/lua.svg"/>
|
||||
<FILE id="PxYKbt" name="microphone.svg" compile="0" resource="1" file="Resources/svg/microphone.svg"/>
|
||||
<FILE id="uEmfHD" name="multiplex.svg" compile="0" resource="1" file="Resources/svg/multiplex.svg"/>
|
||||
<FILE id="WoY9r2" name="mute.svg" compile="0" resource="1" file="Resources/svg/mute.svg"/>
|
||||
<FILE id="hJHxFY" name="open_in_new.svg" compile="0" resource="1" file="Resources/svg/open_in_new.svg"/>
|
||||
<FILE id="pSc1mq" name="osci.svg" compile="0" resource="1" file="Resources/svg/osci.svg"/>
|
||||
|
@ -70,11 +78,21 @@
|
|||
<FILE id="n79IAy" name="record.svg" compile="0" resource="1" file="Resources/svg/record.svg"/>
|
||||
<FILE id="WDxwSi" name="repeat.svg" compile="0" resource="1" file="Resources/svg/repeat.svg"/>
|
||||
<FILE id="OaqZb1" name="right_arrow.svg" compile="0" resource="1" file="Resources/svg/right_arrow.svg"/>
|
||||
<FILE id="gmY7I3" name="ripple.svg" compile="0" resource="1" file="Resources/svg/ripple.svg"/>
|
||||
<FILE id="hmbfOe" name="rotate.svg" compile="0" resource="1" file="Resources/svg/rotate.svg"/>
|
||||
<FILE id="JlQNYG" name="scale.svg" compile="0" resource="1" file="Resources/svg/scale.svg"/>
|
||||
<FILE id="cPSI5m" name="smoothing.svg" compile="0" resource="1" file="Resources/svg/smoothing.svg"/>
|
||||
<FILE id="z3A7FT" name="spout.svg" compile="0" resource="1" file="Resources/svg/spout.svg"/>
|
||||
<FILE id="wD6mre" name="stop.svg" compile="0" resource="1" file="Resources/svg/stop.svg"/>
|
||||
<FILE id="xs9WHW" name="swirl.svg" compile="0" resource="1" file="Resources/svg/swirl.svg"/>
|
||||
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
|
||||
<FILE id="rFYmV8" name="timer.svg" compile="0" resource="1" file="Resources/svg/timer.svg"/>
|
||||
<FILE id="xcg1tN" name="trace.svg" compile="0" resource="1" file="Resources/svg/trace.svg"/>
|
||||
<FILE id="JOyYQQ" name="translate.svg" compile="0" resource="1" file="Resources/svg/translate.svg"/>
|
||||
<FILE id="km6nQQ" name="vector-cancelling.svg" compile="0" resource="1"
|
||||
file="Resources/svg/vector-cancelling.svg"/>
|
||||
<FILE id="qC6QiP" name="volume.svg" compile="0" resource="1" file="Resources/svg/volume.svg"/>
|
||||
<FILE id="Zc5gBX" name="wobble.svg" compile="0" resource="1" file="Resources/svg/wobble.svg"/>
|
||||
</GROUP>
|
||||
</GROUP>
|
||||
<GROUP id="{75439074-E50C-362F-1EDF-8B4BE9011259}" name="Source">
|
||||
|
|