Merge pull request #9 from jameshball/develop

Sync LuaJIT to Develop
pull/316/head
DJLevel3 2025-08-17 07:33:42 -06:00 zatwierdzone przez GitHub
commit ab49a8caf4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
79 zmienionych plików z 2750 dodań i 1239 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

File diff suppressed because one or more lines are too long

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 5.3 KiB

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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();

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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();

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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[]) {

Wyświetl plik

@ -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

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;
};

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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);

Wyświetl plik

@ -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;
};

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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();
}
}
}
}
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
};

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;
}
};

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;

Wyświetl plik

@ -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));

Wyświetl plik

@ -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;

Wyświetl plik

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

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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)
};

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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)
};

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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)
};

Wyświetl plik

@ -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) {}

Wyświetl plik

@ -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;
};

Wyświetl plik

@ -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

Wyświetl plik

@ -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 };
};

Wyświetl plik

@ -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(); });
}

Wyświetl plik

@ -9,6 +9,7 @@ class SosciAudioProcessor;
class SosciMainMenuBarModel : public MainMenuBarModel {
public:
SosciMainMenuBarModel(SosciPluginEditor& editor, SosciAudioProcessor& processor);
void resetMenuItems();
SosciPluginEditor& editor;

Wyświetl plik

@ -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());
}
};

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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">

Wyświetl plik

@ -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"/>

Wyświetl plik

@ -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">