diff --git a/Source/EffectsComponent.cpp b/Source/EffectsComponent.cpp index afd37f7..a775b6f 100644 --- a/Source/EffectsComponent.cpp +++ b/Source/EffectsComponent.cpp @@ -2,7 +2,7 @@ #include "audio/BitCrushEffect.h" #include "PluginEditor.h" -EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p) : audioProcessor(p), itemData(p), listBoxModel(listBox, itemData) { +EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), itemData(p, editor), listBoxModel(listBox, itemData) { setText("Audio Effects"); addAndMakeVisible(frequency); diff --git a/Source/EffectsComponent.h b/Source/EffectsComponent.h index 64522e4..7d653de 100644 --- a/Source/EffectsComponent.h +++ b/Source/EffectsComponent.h @@ -9,7 +9,7 @@ class OscirenderAudioProcessorEditor; class EffectsComponent : public juce::GroupComponent { public: - EffectsComponent(OscirenderAudioProcessor&); + EffectsComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&); ~EffectsComponent() override; void resized() override; diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index 6a56306..a0d39bd 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -15,14 +15,18 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) { juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); + bool fileAdded = false; for (auto& url : chooser.getURLResults()) { if (url.isLocalFile()) { auto file = url.getLocalFile(); audioProcessor.addFile(file); + fileAdded = true; } } - pluginEditor.addCodeEditor(audioProcessor.getCurrentFileIndex()); - pluginEditor.fileUpdated(audioProcessor.getCurrentFileName()); + if (fileAdded) { + pluginEditor.addCodeEditor(audioProcessor.getCurrentFileIndex()); + pluginEditor.fileUpdated(audioProcessor.getCurrentFileName()); + } }); }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index e13a69c..b0e8131 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -13,19 +13,14 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr addAndMakeVisible(collapseButton); collapseButton.onClick = [this] { juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); - int index = audioProcessor.getCurrentFileIndex(); - if (index != -1) { + int originalIndex = audioProcessor.getCurrentFileIndex(); + int index = editingPerspective ? 0 : audioProcessor.getCurrentFileIndex() + 1; + if (originalIndex != -1 || editingPerspective) { if (codeEditors[index]->isVisible()) { codeEditors[index]->setVisible(false); - juce::Path path; - path.addTriangle(0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f); - collapseButton.setShape(path, false, true, true); } else { codeEditors[index]->setVisible(true); updateCodeEditor(); - juce::Path path; - path.addTriangle(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f); - collapseButton.setShape(path, false, true, true); } triggerAsyncUpdate(); } @@ -34,11 +29,14 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr path.addTriangle(0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f); collapseButton.setShape(path, false, true, true); - juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); - for (int i = 0; i < audioProcessor.numFiles(); i++) { - addCodeEditor(i); + { + juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); + addCodeEditor(-1); + for (int i = 0; i < audioProcessor.numFiles(); i++) { + addCodeEditor(i); + } + fileUpdated(audioProcessor.getCurrentFileName()); } - fileUpdated(audioProcessor.getCurrentFileName()); setSize(1100, 750); setResizable(true, true); @@ -62,18 +60,35 @@ void OscirenderAudioProcessorEditor::resized() { volume.setBounds(volumeArea.withSizeKeepingCentre(volumeArea.getWidth(), juce::jmin(volumeArea.getHeight(), 300))); area.removeFromLeft(3); auto sections = 2; - int index = audioProcessor.getCurrentFileIndex(); - if (index != -1) { - if (codeEditors[index]->isVisible()) { - sections++; - codeEditors[index]->setBounds(area.removeFromRight(getWidth() / sections)); - } else { - codeEditors[index]->setBounds(0, 0, 0, 0); - } - collapseButton.setBounds(area.removeFromRight(20)); + bool editorVisible = false; + { + juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); + int originalIndex = audioProcessor.getCurrentFileIndex(); + int index = editingPerspective ? 0 : audioProcessor.getCurrentFileIndex() + 1; + if (originalIndex != -1 || editingPerspective) { + if (codeEditors[index]->isVisible()) { + sections++; + editorVisible = true; + codeEditors[index]->setBounds(area.removeFromRight(getWidth() / sections)); + } else { + codeEditors[index]->setBounds(0, 0, 0, 0); + } + collapseButton.setBounds(area.removeFromRight(20)); + } else { + collapseButton.setBounds(0, 0, 0, 0); + } + } + + if (editorVisible) { + juce::Path path; + path.addTriangle(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f); + collapseButton.setShape(path, false, true, true); } else { - collapseButton.setBounds(0, 0, 0, 0); - } + juce::Path path; + path.addTriangle(0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f); + collapseButton.setShape(path, false, true, true); + } + auto effectsSection = area.removeFromRight(1.2 * getWidth() / sections); main.setBounds(area); if (lua.isVisible() || obj.isVisible()) { @@ -86,25 +101,37 @@ void OscirenderAudioProcessorEditor::resized() { } void OscirenderAudioProcessorEditor::addCodeEditor(int index) { - std::shared_ptr codeDocument = std::make_shared(); + int originalIndex = index; + index++; + std::shared_ptr codeDocument; + std::shared_ptr editor; + + if (index == 0) { + codeDocument = perspectiveCodeDocument; + editor = perspectiveCodeEditor; + } else { + codeDocument = std::make_shared(); + juce::String extension = audioProcessor.getFileName(originalIndex).fromLastOccurrenceOf(".", true, false); + juce::CodeTokeniser* tokeniser = nullptr; + if (extension == ".lua") { + tokeniser = &luaTokeniser; + } else if (extension == ".svg") { + tokeniser = &xmlTokeniser; + } + editor = std::make_shared(*codeDocument, tokeniser); + } + codeDocuments.insert(codeDocuments.begin() + index, codeDocument); - juce::String extension = audioProcessor.getFileName(index).fromLastOccurrenceOf(".", true, false); - juce::CodeTokeniser* tokeniser = nullptr; - if (extension == ".lua") { - tokeniser = &luaTokeniser; - } else if (extension == ".svg") { - tokeniser = &xmlTokeniser; - } - std::shared_ptr editor = std::make_shared(*codeDocument, tokeniser); + codeEditors.insert(codeEditors.begin() + index, editor); + addChildComponent(*editor); // I need to disable accessibility otherwise it doesn't work! Appears to be a JUCE issue, very annoying! editor->setAccessible(false); // listen for changes to the code editor codeDocument->addListener(this); - codeEditors.insert(codeEditors.begin() + index, editor); - addChildComponent(*editor); } void OscirenderAudioProcessorEditor::removeCodeEditor(int index) { + index++; codeEditors.erase(codeEditors.begin() + index); codeDocuments.erase(codeDocuments.begin() + index); } @@ -116,17 +143,22 @@ void OscirenderAudioProcessorEditor::updateCodeEditor() { bool visible = false; for (int i = 0; i < codeEditors.size(); i++) { if (codeEditors[i]->isVisible()) { - visible = true; - break; - } - } - int index = audioProcessor.getCurrentFileIndex(); - if (index != -1 && visible) { + visible = true; + break; + } + } + int originalIndex = audioProcessor.getCurrentFileIndex(); + int index = editingPerspective ? 0 : audioProcessor.getCurrentFileIndex() + 1; + if ((originalIndex != -1 || editingPerspective) && visible) { for (int i = 0; i < codeEditors.size(); i++) { codeEditors[i]->setVisible(false); } codeEditors[index]->setVisible(true); - codeEditors[index]->loadContent(juce::MemoryInputStream(*audioProcessor.getFileBlock(index), false).readEntireStreamAsString()); + if (index == 0) { + codeEditors[index]->loadContent(audioProcessor.perspectiveEffect->getCode()); + } else { + codeEditors[index]->loadContent(juce::MemoryInputStream(*audioProcessor.getFileBlock(originalIndex), false).readEntireStreamAsString()); + } } triggerAsyncUpdate(); } @@ -151,6 +183,14 @@ void OscirenderAudioProcessorEditor::handleAsyncUpdate() { resized(); } +void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) { + editingPerspective = enable; + juce::SpinLock::ScopedLockType lock1(audioProcessor.parsersLock); + juce::SpinLock::ScopedLockType lock2(audioProcessor.effectsLock); + codeEditors[0]->setVisible(enable); + updateCodeEditor(); +} + // parsersLock AND effectsLock must be locked before calling this function void OscirenderAudioProcessorEditor::codeDocumentTextInserted(const juce::String& newText, int insertIndex) { updateCodeDocument(); @@ -161,10 +201,18 @@ void OscirenderAudioProcessorEditor::codeDocumentTextDeleted(int startIndex, int updateCodeDocument(); } +// parsersLock AND effectsLock must be locked before calling this function void OscirenderAudioProcessorEditor::updateCodeDocument() { - int index = audioProcessor.getCurrentFileIndex(); - juce::String file = codeDocuments[index]->getAllContent(); - audioProcessor.updateFileBlock(index, std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); + if (editingPerspective) { + juce::String file = codeDocuments[0]->getAllContent(); + audioProcessor.perspectiveEffect->updateCode(file); + } else { + int originalIndex = audioProcessor.getCurrentFileIndex(); + int index = audioProcessor.getCurrentFileIndex(); + index++; + juce::String file = codeDocuments[index]->getAllContent(); + audioProcessor.updateFileBlock(originalIndex, std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); + } } bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 36913ec..9c9e4cb 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -21,19 +21,25 @@ public: void removeCodeEditor(int index); void fileUpdated(juce::String fileName); void handleAsyncUpdate() override; + + void editPerspectiveFunction(bool enabled); + + std::atomic editingPerspective = false; private: OscirenderAudioProcessor& audioProcessor; MainComponent main{audioProcessor, *this}; LuaComponent lua{audioProcessor, *this}; ObjComponent obj{audioProcessor, *this}; - EffectsComponent effects{audioProcessor}; + EffectsComponent effects{audioProcessor, *this}; VolumeComponent volume{audioProcessor}; std::vector> codeDocuments; std::vector> codeEditors; juce::LuaTokeniser luaTokeniser; juce::XmlTokeniser xmlTokeniser; juce::ShapeButton collapseButton; + std::shared_ptr perspectiveCodeDocument = std::make_shared(); + std::shared_ptr perspectiveCodeEditor = std::make_shared(*perspectiveCodeDocument, &luaTokeniser); void codeDocumentTextInserted(const juce::String& newText, int insertIndex) override; void codeDocumentTextDeleted(int startIndex, int endIndex) override; diff --git a/Source/audio/PerspectiveEffect.cpp b/Source/audio/PerspectiveEffect.cpp index a36cdda..d5f50d2 100644 --- a/Source/audio/PerspectiveEffect.cpp +++ b/Source/audio/PerspectiveEffect.cpp @@ -45,16 +45,19 @@ Vector2 PerspectiveEffect::apply(int index, Vector2 input, const std::vectorsetVariable("x", x); + parser->setVariable("y", y); + parser->setVariable("z", z); - auto result = parser.run(); - if (result.size() >= 3) { - x = result[0]; - y = result[1]; - z = result[2]; + auto result = parser->run(); + if (result.size() >= 3) { + x = result[0]; + y = result[1]; + z = result[2]; + } } } @@ -87,3 +90,15 @@ Vector2 PerspectiveEffect::apply(int index, Vector2 input, const std::vector(code); +} + +juce::String PerspectiveEffect::getCode() { + juce::SpinLock::ScopedLockType lock(codeLock); + return code; +} diff --git a/Source/audio/PerspectiveEffect.h b/Source/audio/PerspectiveEffect.h index 2c16b80..6f8db4b 100644 --- a/Source/audio/PerspectiveEffect.h +++ b/Source/audio/PerspectiveEffect.h @@ -9,14 +9,17 @@ public: PerspectiveEffect(); Vector2 apply(int index, Vector2 input, const std::vector& values, double sampleRate) override; + void updateCode(const juce::String& newCode); + juce::String getCode(); BooleanParameter* fixedRotateX = new BooleanParameter("Perspective Fixed Rotate X", "perspectiveFixedRotateX", false); BooleanParameter* fixedRotateY = new BooleanParameter("Perspective Fixed Rotate Y", "perspectiveFixedRotateY", false); BooleanParameter* fixedRotateZ = new BooleanParameter("Perspective Fixed Rotate Z", "perspectiveFixedRotateZ", false); private: const juce::String DEFAULT_SCRIPT = "return { x, y, z }"; - juce::MemoryBlock code{DEFAULT_SCRIPT.toRawUTF8(), DEFAULT_SCRIPT.getNumBytesAsUTF8() + 1}; - LuaParser parser{DEFAULT_SCRIPT}; + juce::String code = DEFAULT_SCRIPT; + juce::SpinLock codeLock; + std::unique_ptr parser = std::make_unique(code); bool defaultScript = true; float currentRotateX = 0; diff --git a/Source/components/EffectsListComponent.cpp b/Source/components/EffectsListComponent.cpp index 78cd9b2..65f3d59 100644 --- a/Source/components/EffectsListComponent.cpp +++ b/Source/components/EffectsListComponent.cpp @@ -1,7 +1,8 @@ #include "EffectsListComponent.h" #include "SvgButton.h" +#include "../PluginEditor.h" -EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, Effect& effect) : DraggableListBoxItem(lb, data, rn), effect(effect), audioProcessor(data.audioProcessor) { +EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, 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 = std::make_shared(audioProcessor, effect, i, i == 0); @@ -88,10 +89,14 @@ std::shared_ptr EffectsListComponent::createComponent(EffectPar }; return button; } else if (parameter->paramID == "depthScale") { - std::shared_ptr button = std::make_shared(parameter->name, BinaryData::pencil_svg, "white"); + std::shared_ptr button = std::make_shared(parameter->name, BinaryData::pencil_svg, "white", "red"); + std::weak_ptr weakButton = button; button->setEdgeIndent(5); - button->onClick = [this] { - + button->setToggleState(editor.editingPerspective, juce::dontSendNotification); + button->onClick = [this, weakButton] { + if (auto button = weakButton.lock()) { + editor.editPerspectiveFunction(button->getToggleState()); + } }; return button; } diff --git a/Source/components/EffectsListComponent.h b/Source/components/EffectsListComponent.h index 705db95..621871d 100644 --- a/Source/components/EffectsListComponent.h +++ b/Source/components/EffectsListComponent.h @@ -7,12 +7,14 @@ #include "ComponentList.h" // Application-specific data container +class OscirenderAudioProcessorEditor; struct AudioEffectListBoxItemData : public DraggableListBoxItemData { std::vector> data; OscirenderAudioProcessor& audioProcessor; + OscirenderAudioProcessorEditor& editor; - AudioEffectListBoxItemData(OscirenderAudioProcessor& p) : audioProcessor(p) {} + AudioEffectListBoxItemData(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), editor(editor) {} int getNumItems() override { return data.size(); @@ -102,6 +104,7 @@ protected: juce::ListBox list; private: OscirenderAudioProcessor& audioProcessor; + OscirenderAudioProcessorEditor& editor; std::shared_ptr createComponent(EffectParameter* parameter);