diff --git a/Source/EffectComponentGroup.cpp b/Source/EffectComponentGroup.cpp deleted file mode 100644 index 0e41322..0000000 --- a/Source/EffectComponentGroup.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "EffectComponentGroup.h" - -EffectComponentGroup::EffectComponentGroup(const juce::String& id, const juce::String& name) : id(id) { - addAndMakeVisible(slider); - addAndMakeVisible(label); - - label.setText(name, juce::dontSendNotification); - label.attachToComponent(&slider, true); - - slider.setSliderStyle(juce::Slider::LinearHorizontal); - slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 90, slider.getTextBoxHeight()); -} - -EffectComponentGroup::~EffectComponentGroup() { -} - -void EffectComponentGroup::resized() { - auto sliderLeft = 100; - slider.setBounds(sliderLeft, 0, getWidth() - 200, 20); -} diff --git a/Source/EffectComponentGroup.h b/Source/EffectComponentGroup.h deleted file mode 100644 index 634c42c..0000000 --- a/Source/EffectComponentGroup.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -class EffectComponentGroup : public juce::Component { -public: - EffectComponentGroup(const juce::String& id, const juce::String& name); - ~EffectComponentGroup() override; - - void resized() override; - - juce::Slider slider; - juce::Label label; - juce::String id; - -private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectComponentGroup) -}; - diff --git a/Source/EffectsComponent.cpp b/Source/EffectsComponent.cpp index 1415913..5d73c49 100644 --- a/Source/EffectsComponent.cpp +++ b/Source/EffectsComponent.cpp @@ -5,8 +5,8 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p) : audioProcessor setText("Audio Effects"); addAndMakeVisible(frequency); + frequency.setHideCheckbox(true); - frequency.slider.setRange(0.0, 12000.0); frequency.slider.setSkewFactorFromMidPoint(500.0); frequency.slider.setTextValueSuffix("Hz"); frequency.slider.setValue(audioProcessor.frequency, juce::dontSendNotification); @@ -43,13 +43,8 @@ EffectsComponent::~EffectsComponent() { } void EffectsComponent::resized() { - auto xPadding = 10; - auto yPadding = 20; - frequency.setBounds(xPadding, yPadding, getWidth() - xPadding, 40); - auto area = getLocalBounds().reduced(20); - auto row = area.removeFromTop(24); - addBtn.setBounds(row.removeFromRight(100)); + frequency.setBounds(area.removeFromTop(30)); area.removeFromTop(6); listBox.setBounds(area); diff --git a/Source/EffectsComponent.h b/Source/EffectsComponent.h index dfabc9d..76917d9 100644 --- a/Source/EffectsComponent.h +++ b/Source/EffectsComponent.h @@ -2,10 +2,9 @@ #include #include "audio/BitCrushEffect.h" -#include "EffectComponentGroup.h" #include "PluginProcessor.h" #include "components/DraggableListBox.h" -#include "components/MyListComponent.h" +#include "components/EffectsListComponent.h" class EffectsComponent : public juce::GroupComponent { public: @@ -17,13 +16,13 @@ public: private: OscirenderAudioProcessor& audioProcessor; - juce::TextButton addBtn; + // juce::TextButton addBtn; - MyListBoxItemData itemData; - MyListBoxModel listBoxModel; + AudioEffectListBoxItemData itemData; + EffectsListBoxModel listBoxModel; DraggableListBox listBox; - EffectComponentGroup frequency = EffectComponentGroup("frequency", "Frequency"); + EffectComponent frequency = EffectComponent(0.0, 12000.0, 0.1, 400, "Frequency", "frequency"); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsComponent) }; \ No newline at end of file diff --git a/Source/LuaComponent.cpp b/Source/LuaComponent.cpp new file mode 100644 index 0000000..a42fe2a --- /dev/null +++ b/Source/LuaComponent.cpp @@ -0,0 +1,18 @@ +#include "LuaComponent.h" +#include "PluginEditor.h" + +LuaComponent::LuaComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor), slidersModel(sliders, p) { + setText(".lua File Settings"); + + sliders.setModel(&slidersModel); + sliders.setRowHeight(30); + addAndMakeVisible(sliders); +} + +LuaComponent::~LuaComponent() { +} + +void LuaComponent::resized() { + auto area = getLocalBounds().reduced(20); + sliders.setBounds(area); +} diff --git a/Source/LuaComponent.h b/Source/LuaComponent.h new file mode 100644 index 0000000..7f5b4ad --- /dev/null +++ b/Source/LuaComponent.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "PluginProcessor.h" +#include "components/LuaListComponent.h" + +class OscirenderAudioProcessorEditor; +class LuaComponent : public juce::GroupComponent { +public: + LuaComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&); + ~LuaComponent() override; + + void resized() override; +private: + OscirenderAudioProcessor& audioProcessor; + OscirenderAudioProcessorEditor& pluginEditor; + + LuaListBoxModel slidersModel; + juce::ListBox sliders; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LuaComponent) +}; \ No newline at end of file diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index a357d28..cd7ceb2 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -20,6 +20,7 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess audioProcessor.addFile(file); } } + pluginEditor.addCodeEditor(audioProcessor.getCurrentFileIndex()); pluginEditor.updateCodeEditor(); updateFileLabel(); }); @@ -29,6 +30,11 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess closeFileButton.setButtonText("Close File"); closeFileButton.onClick = [this] { + int index = audioProcessor.getCurrentFileIndex(); + if (index == -1) { + return; + } + pluginEditor.removeCodeEditor(audioProcessor.getCurrentFileIndex()); audioProcessor.removeFile(audioProcessor.getCurrentFileIndex()); pluginEditor.updateCodeEditor(); updateFileLabel(); diff --git a/Source/MainComponent.h b/Source/MainComponent.h index 1a483ce..6d35db2 100644 --- a/Source/MainComponent.h +++ b/Source/MainComponent.h @@ -1,7 +1,6 @@ #pragma once #include -#include "EffectComponentGroup.h" #include "PluginProcessor.h" #include "parser/FileParser.h" #include "parser/FrameProducer.h" @@ -13,6 +12,7 @@ public: ~MainComponent() override; void resized() override; + void updateFileLabel(); private: OscirenderAudioProcessor& audioProcessor; OscirenderAudioProcessorEditor& pluginEditor; @@ -22,7 +22,5 @@ private: juce::TextButton closeFileButton; juce::Label fileLabel; - void updateFileLabel(); - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent) }; \ No newline at end of file diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 2656fed..558b363 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -11,7 +11,7 @@ //============================================================================== OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p) - : AudioProcessorEditor(&p), audioProcessor(p), effects(p), main(p, *this), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white) + : AudioProcessorEditor(&p), audioProcessor(p), effects(p), main(p, *this), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white), lua(p, *this) { // Make sure that before the constructor has finished, you've set the // editor's size to whatever you need it to be. @@ -20,50 +20,31 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr addAndMakeVisible(effects); addAndMakeVisible(main); - - codeEditor = std::make_unique(codeDocument, &luaTokeniser); - addAndMakeVisible(*codeEditor); - - codeEditor->loadContent (R"LUA( - -- defines a factorial function - function fact (n) - if n == 0 then - return 1 - else - return n * fact(n-1) - end - end - - print("enter a number:") - a = io.read("*number") -- read a number - print(fact(a)) -)LUA"); - - // I need to disable accessibility otherwise it doesn't work! Appears to be a JUCE issue, very annoying! - codeEditor->setAccessible(false); - - // listen for changes to the code editor - codeDocument.addListener(this); + addAndMakeVisible(lua); addAndMakeVisible(collapseButton); collapseButton.onClick = [this] { - if (codeEditor->isVisible()) { - codeEditor->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 { - codeEditor->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); - } - resized(); + int index = audioProcessor.getCurrentFileIndex(); + if (index != -1) { + 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); + } + resized(); + } }; juce::Path path; - path.addTriangle(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f); + path.addTriangle(0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f); collapseButton.setShape(path, false, true, true); + resized(); } OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {} @@ -80,54 +61,102 @@ void OscirenderAudioProcessorEditor::paint (juce::Graphics& g) void OscirenderAudioProcessorEditor::resized() { auto area = getLocalBounds(); auto sections = 2; - if (codeEditor != nullptr) { - if (codeEditor->isVisible()) { + int index = audioProcessor.getCurrentFileIndex(); + if (index != -1) { + if (codeEditors[index]->isVisible()) { sections++; - codeEditor->setBounds(area.removeFromRight(getWidth() / sections)); + codeEditors[index]->setBounds(area.removeFromRight(getWidth() / sections)); } else { - codeEditor->setBounds(0, 0, 0, 0); + codeEditors[index]->setBounds(0, 0, 0, 0); } collapseButton.setBounds(area.removeFromRight(20)); - } + } else { + collapseButton.setBounds(0, 0, 0, 0); + } effects.setBounds(area.removeFromRight(getWidth() / sections)); main.setBounds(area.removeFromTop(getHeight() / 2)); + lua.setBounds(area); +} + +void OscirenderAudioProcessorEditor::addCodeEditor(int index) { + std::shared_ptr codeDocument = std::make_shared(); + codeDocuments.insert(codeDocuments.begin() + index, codeDocument); + juce::String extension = audioProcessor.getFile(index).getFileExtension(); + juce::CodeTokeniser* tokeniser = nullptr; + if (extension == ".lua") { + tokeniser = &luaTokeniser; + } else if (extension == ".svg") { + tokeniser = &xmlTokeniser; + } + std::shared_ptr editor = std::make_shared(*codeDocument, tokeniser); + // 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) { + codeEditors.erase(codeEditors.begin() + index); + codeDocuments.erase(codeDocuments.begin() + index); } void OscirenderAudioProcessorEditor::updateCodeEditor() { - if (codeEditor->isVisible() && audioProcessor.getCurrentFileIndex() != -1) { - codeEditor->loadContent(juce::MemoryInputStream(*audioProcessor.getFileBlock(audioProcessor.getCurrentFileIndex()), false).readEntireStreamAsString()); + // check if any code editors are visible + 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) { + 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()); } + resized(); } void OscirenderAudioProcessorEditor::codeDocumentTextInserted(const juce::String& newText, int insertIndex) { - juce::String file = codeDocument.getAllContent(); - audioProcessor.updateFileBlock(audioProcessor.getCurrentFileIndex(), std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); + int index = audioProcessor.getCurrentFileIndex(); + juce::String file = codeDocuments[index]->getAllContent(); + audioProcessor.updateFileBlock(index, std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); } void OscirenderAudioProcessorEditor::codeDocumentTextDeleted(int startIndex, int endIndex) { - juce::String file = codeDocument.getAllContent(); - audioProcessor.updateFileBlock(audioProcessor.getCurrentFileIndex(), std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); + int index = audioProcessor.getCurrentFileIndex(); + juce::String file = codeDocuments[index]->getAllContent(); + audioProcessor.updateFileBlock(index, std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); } bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) { int numFiles = audioProcessor.numFiles(); int currentFile = audioProcessor.getCurrentFileIndex(); + bool updated = false; if (key.getTextCharacter() == 'j') { currentFile++; if (currentFile == numFiles) { currentFile = 0; } - audioProcessor.changeCurrentFile(currentFile); - updateCodeEditor(); - return true; + updated = true; } else if (key.getTextCharacter() == 'k') { currentFile--; if (currentFile < 0) { currentFile = numFiles - 1; } + updated = true; + } + + if (updated) { audioProcessor.changeCurrentFile(currentFile); updateCodeEditor(); - return true; + main.updateFileLabel(); } - return false; + + return updated; } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 8035de7..d92818c 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -10,9 +10,9 @@ #include #include "PluginProcessor.h" -#include "EffectComponentGroup.h" #include "EffectsComponent.h" #include "MainComponent.h" +#include "LuaComponent.h" //============================================================================== /** @@ -26,16 +26,20 @@ public: //============================================================================== void paint (juce::Graphics&) override; void resized() override; - + + void addCodeEditor(int index); + void removeCodeEditor(int index); void updateCodeEditor(); private: OscirenderAudioProcessor& audioProcessor; MainComponent main; + LuaComponent lua; EffectsComponent effects; - juce::CodeDocument codeDocument; + std::vector> codeDocuments; + std::vector> codeEditors; juce::LuaTokeniser luaTokeniser; - std::unique_ptr codeEditor; + juce::XmlTokeniser xmlTokeniser; juce::ShapeButton collapseButton; void codeDocumentTextInserted(const juce::String& newText, int insertIndex) override; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 0b583b0..896b52d 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -14,6 +14,9 @@ #include "audio/VectorCancellingEffect.h" #include "audio/DistortEffect.h" #include "audio/SmoothEffect.h" +#include "audio/BitCrushEffect.h" +#include "audio/BulgeEffect.h" +#include "audio/LuaEffect.h" //============================================================================== OscirenderAudioProcessor::OscirenderAudioProcessor() @@ -38,6 +41,10 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() allEffects.push_back(std::make_shared(std::make_unique(true), "Vertical shift", "verticalDistort")); allEffects.push_back(std::make_shared(std::make_unique(false), "Horizontal shift", "horizontalDistort")); allEffects.push_back(std::make_shared(std::make_unique(), "Smoothing", "smoothing")); + + for (int i = 0; i < 5; i++) { + addLuaSlider(); + } } OscirenderAudioProcessor::~OscirenderAudioProcessor() @@ -145,6 +152,26 @@ bool OscirenderAudioProcessor::isBusesLayoutSupported (const BusesLayout& layout } #endif +void OscirenderAudioProcessor::addLuaSlider() { + juce::String sliderName = ""; + + int sliderNum = luaEffects.size() + 1; + while (sliderNum > 0) { + int mod = (sliderNum - 1) % 26; + sliderName = (char)(mod + 'A') + sliderName; + sliderNum = (sliderNum - mod) / 26; + } + + luaEffects.push_back(std::make_shared(std::make_unique(sliderName, *this), "Lua " + sliderName, "lua" + sliderName)); +} + +void OscirenderAudioProcessor::updateLuaValues() { + Vector2 vector; + for (auto& effect : luaEffects) { + effect->apply(0, vector); + } +} + void OscirenderAudioProcessor::updateAngleDelta() { auto cyclesPerSample = frequency / currentSampleRate; thetaDelta = cyclesPerSample * 2.0 * juce::MathConstants::pi; @@ -238,9 +265,14 @@ void OscirenderAudioProcessor::openFile(int index) { producer->setSource(parsers[index], index); currentFile = index; invalidateFrameBuffer = true; + updateLuaValues(); } void OscirenderAudioProcessor::changeCurrentFile(int index) { + if (index == -1) { + currentFile = -1; + producer->setSource(std::make_shared(), -1); + } if (index < 0 || index >= fileBlocks.size()) { return; } @@ -253,10 +285,18 @@ int OscirenderAudioProcessor::getCurrentFileIndex() { return currentFile; } +std::shared_ptr OscirenderAudioProcessor::getCurrentFileParser() { + return parsers[currentFile]; +} + juce::File OscirenderAudioProcessor::getCurrentFile() { return files[currentFile]; } +juce::File OscirenderAudioProcessor::getFile(int index) { + return files[index]; +} + std::shared_ptr OscirenderAudioProcessor::getFileBlock(int index) { return fileBlocks[index]; } @@ -300,7 +340,9 @@ void OscirenderAudioProcessor::updateFrame() { frameLength = Shape::totalLength(frame); } - } + } else { + DBG("frame not ready!"); + } } void OscirenderAudioProcessor::updateLengthIncrement() { @@ -345,7 +387,11 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer& buffer, j double y = 0.0; double length = 0.0; - if (currentShape < frame.size()) { + bool renderingSample = currentFile >= 0 && parsers[currentFile]->isSample(); + + if (renderingSample) { + channels = parsers[currentFile]->nextSample(); + } else if (currentShape < frame.size()) { auto& shape = frame[currentShape]; length = shape->length(); double drawingProgress = length == 0.0 ? 1 : shapeDrawn / length; @@ -360,6 +406,7 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer& buffer, j x = channels.x; y = channels.y; + if (totalNumOutputChannels >= 2) { channelData[0][sample] = x; @@ -368,29 +415,31 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer& buffer, j channelData[0][sample] = x; } - // hard cap on how many times it can be over the length to - // prevent audio stuttering - frameDrawn += std::min(lengthIncrement, 20 * length); - shapeDrawn += std::min(lengthIncrement, 20 * length); + if (!renderingSample) { + // hard cap on how many times it can be over the length to + // prevent audio stuttering + frameDrawn += std::min(lengthIncrement, 20 * length); + shapeDrawn += std::min(lengthIncrement, 20 * length); - // Need to skip all shapes that the lengthIncrement draws over. - // This is especially an issue when there are lots of small lines being - // drawn. - while (shapeDrawn > length) { - shapeDrawn -= length; - currentShape++; - if (currentShape >= frame.size()) { - currentShape = 0; - break; - } - // POTENTIAL TODO: Think of a way to make this more efficient when iterating - // this loop many times - length = frame[currentShape]->len; - } + // Need to skip all shapes that the lengthIncrement draws over. + // This is especially an issue when there are lots of small lines being + // drawn. + while (shapeDrawn > length) { + shapeDrawn -= length; + currentShape++; + if (currentShape >= frame.size()) { + currentShape = 0; + break; + } + // POTENTIAL TODO: Think of a way to make this more efficient when iterating + // this loop many times + length = frame[currentShape]->len; + } + } - if (frameDrawn >= frameLength) { - updateFrame(); - } + if (!renderingSample && frameDrawn >= frameLength) { + updateFrame(); + } } juce::MidiBuffer processedMidi; diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index d98e0b6..d2feea0 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -14,8 +14,6 @@ #include "parser/FrameProducer.h" #include "parser/FrameConsumer.h" #include "audio/Effect.h" -#include "audio/BitCrushEffect.h" -#include "audio/BulgeEffect.h" //============================================================================== /** @@ -72,8 +70,7 @@ public: std::vector> allEffects; std::shared_ptr>> enabledEffects = std::make_shared>>(); - BitCrushEffect bitCrushEffect = BitCrushEffect(); - BulgeEffect bulgeEffect = BulgeEffect(); + std::vector> luaEffects; std::vector> parsers; std::vector> fileBlocks; @@ -82,6 +79,8 @@ public: std::unique_ptr producer; + void addLuaSlider(); + void updateLuaValues(); void updateAngleDelta(); void addFrame(std::vector> frame, int fileIndex) override; void enableEffect(std::shared_ptr effect); @@ -94,7 +93,9 @@ public: void openFile(int index); void changeCurrentFile(int index); int getCurrentFileIndex(); + std::shared_ptr getCurrentFileParser(); juce::File getCurrentFile(); + juce::File getFile(int index); std::shared_ptr getFileBlock(int index); private: double theta = 0.0; @@ -115,6 +116,7 @@ private: void updateFrame(); void updateLengthIncrement(); + void syncLuaSliders(); const double MIN_LENGTH_INCREMENT = 0.000001; diff --git a/Source/audio/LuaEffect.cpp b/Source/audio/LuaEffect.cpp new file mode 100644 index 0000000..eeeb721 --- /dev/null +++ b/Source/audio/LuaEffect.cpp @@ -0,0 +1,14 @@ +#include "LuaEffect.h" +#include "../lua/LuaParser.h" + +Vector2 LuaEffect::apply(int index, Vector2 input, double value, double frequency, double sampleRate) { + int fileIndex = audioProcessor.getCurrentFileIndex(); + if (fileIndex == -1) { + return input; + } + std::shared_ptr parser = audioProcessor.getCurrentFileParser()->getLua(); + if (parser != nullptr) { + parser->setVariable("slider_" + name.toLowerCase(), value); + } + return input; +} diff --git a/Source/audio/LuaEffect.h b/Source/audio/LuaEffect.h new file mode 100644 index 0000000..cf97666 --- /dev/null +++ b/Source/audio/LuaEffect.h @@ -0,0 +1,15 @@ +#pragma once +#include "EffectApplication.h" +#include "../shape/Vector2.h" +#include "../audio/Effect.h" +#include "../PluginProcessor.h" + +class LuaEffect : public EffectApplication { +public: + LuaEffect(juce::String name, OscirenderAudioProcessor& p) : audioProcessor(p), name(name) {}; + + Vector2 apply(int index, Vector2 input, double value, double frequency, double sampleRate) override; +private: + OscirenderAudioProcessor& audioProcessor; + juce::String name; +}; \ No newline at end of file diff --git a/Source/components/DraggableListBox.cpp b/Source/components/DraggableListBox.cpp index 51eaa0d..c904759 100644 --- a/Source/components/DraggableListBox.cpp +++ b/Source/components/DraggableListBox.cpp @@ -4,8 +4,6 @@ DraggableListBoxItemData::~DraggableListBoxItemData() {}; void DraggableListBoxItem::paint(juce::Graphics& g) { - modelData.paintContents(rowNum, g, getLocalBounds()); - if (insertAfter) { g.setColour(juce::Colours::red); diff --git a/Source/components/DraggableListBox.h b/Source/components/DraggableListBox.h index b00e708..17f907f 100644 --- a/Source/components/DraggableListBox.h +++ b/Source/components/DraggableListBox.h @@ -8,7 +8,6 @@ struct DraggableListBoxItemData virtual ~DraggableListBoxItemData() = 0; virtual int getNumItems() = 0; - virtual void paintContents(int, juce::Graphics&, juce::Rectangle) = 0; virtual void moveBefore(int indexOfItemToMove, int indexOfItemToPlaceBefore) = 0; virtual void moveAfter(int indexOfItemToMove, int indexOfItemToPlaceAfter) = 0; diff --git a/Source/components/EffectComponent.cpp b/Source/components/EffectComponent.cpp new file mode 100644 index 0000000..e52c156 --- /dev/null +++ b/Source/components/EffectComponent.cpp @@ -0,0 +1,52 @@ +#include "EffectComponent.h" + +EffectComponent::EffectComponent(double min, double max, double step, double value, juce::String name, juce::String id) : name(name), id(id) { + componentSetup(); + slider.setRange(min, max, step); + slider.setValue(value, juce::dontSendNotification); +} + +EffectComponent::EffectComponent(double min, double max, double step, Effect& effect) : name(effect.getName()), id(effect.getId()) { + componentSetup(); + slider.setRange(min, max, step); + slider.setValue(effect.getValue(), juce::dontSendNotification); +} + +void EffectComponent::componentSetup() { + addAndMakeVisible(slider); + addAndMakeVisible(selected); + + slider.setSliderStyle(juce::Slider::LinearHorizontal); + slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 90, slider.getTextBoxHeight()); + + selected.setToggleState(false, juce::dontSendNotification); +} + + +EffectComponent::~EffectComponent() {} + +void EffectComponent::resized() { + auto sliderRight = getWidth() - 140; + auto bounds = getLocalBounds(); + bounds.removeFromRight(10); + slider.setBounds(bounds.removeFromRight(sliderRight)); + if (hideCheckbox) { + bounds.removeFromLeft(5); + } else { + bounds.removeFromLeft(2); + selected.setBounds(bounds.removeFromLeft(25)); + } + textBounds = bounds; +} + +void EffectComponent::paint(juce::Graphics& g) { + g.fillAll(juce::Colours::lightgrey); + g.setColour(juce::Colours::black); + auto bounds = getLocalBounds(); + g.drawRect(bounds); + g.drawText(name, textBounds, juce::Justification::left); +} + +void EffectComponent::setHideCheckbox(bool hide) { + hideCheckbox = hide; +} diff --git a/Source/components/EffectComponent.h b/Source/components/EffectComponent.h new file mode 100644 index 0000000..89bdc2f --- /dev/null +++ b/Source/components/EffectComponent.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include "../PluginProcessor.h" +#include "../audio/Effect.h" + + +class EffectComponent : public juce::Component { +public: + EffectComponent(double min, double max, double step, double value, juce::String name, juce::String id); + EffectComponent(double min, double max, double step, Effect& effect); + ~EffectComponent(); + + void resized() override; + void paint(juce::Graphics& g) override; + + void setHideCheckbox(bool hide); + + juce::Slider slider; + juce::String id; + juce::String name; + juce::ToggleButton selected; + +private: + void componentSetup(); + bool hideCheckbox = false; + juce::Rectangle textBounds; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectComponent) +}; diff --git a/Source/components/EffectsListComponent.cpp b/Source/components/EffectsListComponent.cpp new file mode 100644 index 0000000..06b369c --- /dev/null +++ b/Source/components/EffectsListComponent.cpp @@ -0,0 +1,59 @@ +#include "EffectsListComponent.h" + +EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr effectComponent) : DraggableListBoxItem(lb, data, rn), effectComponent(effectComponent) { + addAndMakeVisible(*effectComponent); + + effectComponent->slider.setValue(data.getValue(rn), juce::dontSendNotification); + effectComponent->slider.onValueChange = [this] { + ((AudioEffectListBoxItemData&)modelData).setValue(rowNum, this->effectComponent->slider.getValue()); + }; + + // check if effect is in audioProcessor enabled effects + bool isSelected = false; + for (auto effect : *data.audioProcessor.enabledEffects) { + if (effect->getId() == data.getId(rn)) { + isSelected = true; + break; + } + } + effectComponent->selected.setToggleState(isSelected, juce::dontSendNotification); + effectComponent->selected.onClick = [this] { + ((AudioEffectListBoxItemData&)modelData).setSelected(rowNum, this->effectComponent->selected.getToggleState()); + }; +} + +EffectsListComponent::~EffectsListComponent() {} + +void EffectsListComponent::paint(juce::Graphics& g) { + DraggableListBoxItem::paint(g); + auto bounds = getLocalBounds(); + bounds.removeFromLeft(20); + // draw drag and drop handle using circles + g.setColour(juce::Colours::white); + double size = 4; + double leftPad = 4; + double spacing = 7; + double topPad = 7; + g.fillEllipse(leftPad, topPad, size, size); + g.fillEllipse(leftPad, topPad + spacing, size, size); + g.fillEllipse(leftPad, topPad + 2 * spacing, size, size); + g.fillEllipse(leftPad + spacing, topPad, size, size); + g.fillEllipse(leftPad + spacing, topPad + spacing, size, size); + g.fillEllipse(leftPad + spacing, topPad + 2 * spacing, size, size); +} + +void EffectsListComponent::resized() { + auto area = getLocalBounds(); + area.removeFromLeft(20); + effectComponent->setBounds(area); +} + +juce::Component* EffectsListBoxModel::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component *existingComponentToUpdate) { + std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); + if (juce::isPositiveAndBelow(rowNumber, modelData.getNumItems())) { + auto data = (AudioEffectListBoxItemData&)modelData; + std::shared_ptr effectComponent = std::make_shared(0, 1, 0.01, 0, data.getText(rowNumber), data.getId(rowNumber)); + item = std::make_unique(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, effectComponent); + } + return item.release(); +} diff --git a/Source/components/MyListComponent.h b/Source/components/EffectsListComponent.h similarity index 73% rename from Source/components/MyListComponent.h rename to Source/components/EffectsListComponent.h index 141203d..9f6a746 100644 --- a/Source/components/MyListComponent.h +++ b/Source/components/EffectsListComponent.h @@ -3,14 +3,15 @@ #include #include "../PluginProcessor.h" #include "../audio/Effect.h" +#include "EffectComponent.h" // Application-specific data container -struct MyListBoxItemData : public DraggableListBoxItemData +struct AudioEffectListBoxItemData : public DraggableListBoxItemData { std::vector> data; OscirenderAudioProcessor& audioProcessor; - MyListBoxItemData(OscirenderAudioProcessor& p) : audioProcessor(p) {} + AudioEffectListBoxItemData(OscirenderAudioProcessor& p) : audioProcessor(p) {} int getNumItems() override { return data.size(); @@ -24,14 +25,6 @@ struct MyListBoxItemData : public DraggableListBoxItemData // data.push_back(juce::String("Yahoo")); } - void paintContents(int rowNum, juce::Graphics& g, juce::Rectangle bounds) override { - g.fillAll(juce::Colours::lightgrey); - g.setColour(juce::Colours::black); - g.drawRect(bounds); - bounds.removeFromLeft(30); - g.drawText(data[rowNum]->getName(), bounds, juce::Justification::left); - } - void moveBefore(int indexOfItemToMove, int indexOfItemToPlaceBefore) override { auto effect = data[indexOfItemToMove]; @@ -98,32 +91,27 @@ struct MyListBoxItemData : public DraggableListBoxItemData }; // Custom list-item Component (which includes item-delete button) -class MyListComponent : public DraggableListBoxItem +class EffectsListComponent : public DraggableListBoxItem { public: - MyListComponent(DraggableListBox& lb, MyListBoxItemData& data, int rn); - ~MyListComponent(); + EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr effectComponent); + ~EffectsListComponent(); - void paint(juce::Graphics&) override; + void paint(juce::Graphics& g) override; void resized() override; protected: - juce::Rectangle dataArea; - - juce::Slider slider; - juce::String id; - juce::ToggleButton selected; - + std::shared_ptr effectComponent; private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MyListComponent) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsListComponent) }; // Customized DraggableListBoxModel overrides refreshComponentForRow() to ensure that every -// list-item Component is a MyListComponent. -class MyListBoxModel : public DraggableListBoxModel +// list-item Component is a EffectsListComponent. +class EffectsListBoxModel : public DraggableListBoxModel { public: - MyListBoxModel(DraggableListBox& lb, DraggableListBoxItemData& md) + EffectsListBoxModel(DraggableListBox& lb, DraggableListBoxItemData& md) : DraggableListBoxModel(lb, md) {} juce::Component* refreshComponentForRow(int, bool, juce::Component*) override; diff --git a/Source/components/LuaListComponent.cpp b/Source/components/LuaListComponent.cpp new file mode 100644 index 0000000..a048c71 --- /dev/null +++ b/Source/components/LuaListComponent.cpp @@ -0,0 +1,45 @@ +#include "LuaListComponent.h" + +LuaListComponent::LuaListComponent(OscirenderAudioProcessor& p, Effect& effect) { + effectComponent = std::make_shared(0.0, 1.0, 0.01, effect); + effectComponent->setHideCheckbox(true); + + effectComponent->slider.onValueChange = [this, &effect, &p] { + effect.setValue(effectComponent->slider.getValue()); + effect.apply(0, Vector2()); + }; + + addAndMakeVisible(*effectComponent); +} + +LuaListComponent::~LuaListComponent() {} + +void LuaListComponent::resized() { + effectComponent->setBounds(getLocalBounds()); +} + +void paintListBoxItem(int sliderNum, juce::Graphics& g, int width, int height, bool rowIsSelected) {} + +int LuaListBoxModel::getNumRows() { + return audioProcessor.luaEffects.size() + 1; +} + +void LuaListBoxModel::paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) {} + +juce::Component* LuaListBoxModel::refreshComponentForRow(int rowNum, bool isRowSelected, juce::Component *existingComponentToUpdate) { + if (rowNum < getNumRows() - 1) { + std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); + if (juce::isPositiveAndBelow(rowNum, getNumRows())) { + item = std::make_unique(audioProcessor, *audioProcessor.luaEffects[rowNum]); + } + return item.release(); + } else { + std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); + item = std::make_unique("+"); + item->onClick = [this]() { + audioProcessor.addLuaSlider(); + listBox.updateContent(); + }; + return item.release(); + } +} diff --git a/Source/components/LuaListComponent.h b/Source/components/LuaListComponent.h new file mode 100644 index 0000000..8a1a7e2 --- /dev/null +++ b/Source/components/LuaListComponent.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include "../PluginProcessor.h" +#include "../audio/Effect.h" +#include "EffectComponent.h" + +class LuaListComponent : public juce::Component +{ +public: + LuaListComponent(OscirenderAudioProcessor& p, Effect& effect); + ~LuaListComponent(); + + void resized() override; + +protected: + std::shared_ptr effectComponent; +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LuaListComponent) +}; + +class LuaListBoxModel : public juce::ListBoxModel +{ +public: + LuaListBoxModel(juce::ListBox& lb, OscirenderAudioProcessor& p) : listBox(lb), audioProcessor(p) {} + + int getNumRows() override; + void paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) override; + juce::Component* refreshComponentForRow(int sliderNum, bool isRowSelected, juce::Component *existingComponentToUpdate) override; + +private: + int numSliders = 5; + juce::ListBox& listBox; + OscirenderAudioProcessor& audioProcessor; +}; diff --git a/Source/components/MyListComponent.cpp b/Source/components/MyListComponent.cpp deleted file mode 100644 index 7ca398a..0000000 --- a/Source/components/MyListComponent.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "MyListComponent.h" - -MyListComponent::MyListComponent(DraggableListBox& lb, MyListBoxItemData& data, int rn) : DraggableListBoxItem(lb, data, rn) { - addAndMakeVisible(slider); - addAndMakeVisible(selected); - - slider.setSliderStyle(juce::Slider::LinearHorizontal); - slider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 90, slider.getTextBoxHeight()); - slider.setRange(0.0, 1.0, 0.01); - slider.setValue(data.getValue(rn), juce::dontSendNotification); - slider.onValueChange = [this] { - ((MyListBoxItemData&)modelData).setValue(rowNum, slider.getValue()); - }; - - // check if effect is in audioProcessor enabled effects - bool isSelected = false; - for (auto effect : *data.audioProcessor.enabledEffects) { - if (effect->getId() == data.getId(rn)) { - isSelected = true; - break; - } - } - selected.setToggleState(isSelected, juce::dontSendNotification); - selected.onClick = [this] { - ((MyListBoxItemData&)modelData).setSelected(rowNum, selected.getToggleState()); - }; -} - -MyListComponent::~MyListComponent() {} - -void MyListComponent::paint (juce::Graphics& g) { - modelData.paintContents(rowNum, g, dataArea); - DraggableListBoxItem::paint(g); -} - -void MyListComponent::resized() { - auto sliderLeft = 150; - slider.setBounds(sliderLeft, 0, getWidth() - sliderLeft - 10, getHeight()); - selected.setBounds(2, 0, 25, getHeight()); -} - - -juce::Component* MyListBoxModel::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component *existingComponentToUpdate) { - std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); - if (juce::isPositiveAndBelow(rowNumber, modelData.getNumItems())) - { - item = std::make_unique(listBox, (MyListBoxItemData&)modelData, rowNumber); - } - return item.release(); -} diff --git a/Source/lua/LuaParser.cpp b/Source/lua/LuaParser.cpp index f697b25..c03c16c 100644 --- a/Source/lua/LuaParser.cpp +++ b/Source/lua/LuaParser.cpp @@ -8,45 +8,93 @@ LuaParser::LuaParser(juce::String script) { luaL_openlibs(L); this->script = script; + parse(); } LuaParser::~LuaParser() { lua_close(L); } -std::vector> LuaParser::draw() { - std::vector> shapes; - - for (int i = 0; i < 100; i++) { - lua_pushnumber(L, step); - lua_setglobal(L, "step"); - - const int ret = luaL_dostring(L, script.toUTF8()); - if (ret != 0) { - const char* error = lua_tostring(L, -1); - DBG(error); - lua_pop(L, 1); - } else if (lua_istable(L, -1)) { - // get the first element of the table - lua_pushinteger(L, 1); - lua_gettable(L, -2); - float x = (int)lua_tonumber(L, -1); - lua_pop(L, 1); - - // get the second element of the table - lua_pushinteger(L, 2); - lua_gettable(L, -2); - float y = (int)lua_tonumber(L, -1); - lua_pop(L, 1); - - shapes.push_back(std::make_unique(x, y)); - } - - // pop the table - lua_pop(L, 1); - - step++; +void LuaParser::parse() { + const int ret = luaL_loadstring(L, script.toUTF8()); + if (ret != 0) { + const char* error = lua_tostring(L, -1); + DBG(error); + lua_pop(L, 1); + functionRef = -1; + } else { + functionRef = luaL_ref(L, LUA_REGISTRYINDEX); } - - return shapes; +} + +// only the audio thread runs this fuction +Vector2 LuaParser::draw() { + Vector2 sample; + + if (functionRef == -1) { + return sample; + } + + lua_pushnumber(L, step); + lua_setglobal(L, "step"); + + // this CANNOT run at the same time as setVariable + if (updateVariables) { + bool expected = false; + if (accessingVariables.compare_exchange_strong(expected, true)) { + for (int i = 0; i < variableNames.size(); i++) { + lua_pushnumber(L, variables[i]); + lua_setglobal(L, variableNames[i].toUTF8()); + DBG("set " + variableNames[i] + " to " + juce::String(variables[i])); + } + variableNames.clear(); + variables.clear(); + accessingVariables = false; + updateVariables = false; + } + } + + lua_geti(L, LUA_REGISTRYINDEX, functionRef); + + const int ret = lua_pcall(L, 0, LUA_MULTRET, 0); + if (ret != 0) { + const char* error = lua_tostring(L, -1); + DBG(error); + functionRef = -1; + } else if (lua_istable(L, -1)) { + // get the first element of the table + lua_pushinteger(L, 1); + lua_gettable(L, -2); + float x = lua_tonumber(L, -1); + lua_pop(L, 1); + + // get the second element of the table + lua_pushinteger(L, 2); + lua_gettable(L, -2); + float y = lua_tonumber(L, -1); + lua_pop(L, 1); + + sample = Vector2(x, y); + } + + lua_pop(L, 1); + + step++; + + return sample; +} + +// this CANNOT run at the same time as draw() +// many threads can run this function +bool LuaParser::setVariable(juce::String variableName, double value) { + bool expected = false; + // this is very unlikely to fail, and if it does, it's not a big deal + if (accessingVariables.compare_exchange_strong(expected, true)) { + variableNames.push_back(variableName); + variables.push_back(value); + accessingVariables = false; + updateVariables = true; + return true; + } + return false; } diff --git a/Source/lua/LuaParser.h b/Source/lua/LuaParser.h index 1596168..1840009 100644 --- a/Source/lua/LuaParser.h +++ b/Source/lua/LuaParser.h @@ -9,9 +9,18 @@ public: LuaParser(juce::String script); ~LuaParser(); - std::vector> draw(); + Vector2 draw(); + bool setVariable(juce::String variableName, double value); + private: + void parse(); + + int functionRef = -1; long step = 1; lua_State* L; juce::String script; + std::atomic updateVariables = false; + std::atomic accessingVariables = false; + std::vector variableNames; + std::vector variables; }; \ No newline at end of file diff --git a/Source/parser/FileParser.cpp b/Source/parser/FileParser.cpp index 85a51cf..386346b 100644 --- a/Source/parser/FileParser.cpp +++ b/Source/parser/FileParser.cpp @@ -23,6 +23,8 @@ void FileParser::parse(juce::String extension, std::unique_ptr(stream->readEntireStreamAsString()); } + + sampleSource = lua != nullptr; } std::vector> FileParser::nextFrame() { @@ -30,7 +32,6 @@ std::vector> FileParser::nextFrame() { auto tempCamera = camera; auto tempSvg = svg; auto tempText = text; - auto tempLua = lua; if (tempObject != nullptr && tempCamera != nullptr) { return tempCamera->draw(*tempObject); @@ -38,14 +39,23 @@ std::vector> FileParser::nextFrame() { return tempSvg->draw(); } else if (tempText != nullptr) { return tempText->draw(); - } else if (tempLua != nullptr) { - return tempLua->draw(); } auto tempShapes = std::vector>(); tempShapes.push_back(std::make_unique(0, 0, 0.5, 0.5, std::numbers::pi / 4.0, 2 * std::numbers::pi)); return tempShapes; } +Vector2 FileParser::nextSample() { + auto tempLua = lua; + if (tempLua != nullptr) { + return tempLua->draw(); + } +} + +bool FileParser::isSample() { + return sampleSource; +} + bool FileParser::isActive() { return active; } @@ -57,3 +67,23 @@ void FileParser::disable() { void FileParser::enable() { active = true; } + +std::shared_ptr FileParser::getObject() { + return object; +} + +std::shared_ptr FileParser::getCamera() { + return camera; +} + +std::shared_ptr FileParser::getSvg() { + return svg; +} + +std::shared_ptr FileParser::getText() { + return text; +} + +std::shared_ptr FileParser::getLua() { + return lua; +} diff --git a/Source/parser/FileParser.h b/Source/parser/FileParser.h index 6214358..1764786 100644 --- a/Source/parser/FileParser.h +++ b/Source/parser/FileParser.h @@ -14,12 +14,21 @@ public: void parse(juce::String extension, std::unique_ptr) override; std::vector> nextFrame() override; + Vector2 nextSample() override; + bool isSample() override; bool isActive() override; void disable() override; void enable() override; + std::shared_ptr getObject(); + std::shared_ptr getCamera(); + std::shared_ptr getSvg(); + std::shared_ptr getText(); + std::shared_ptr getLua(); + private: bool active = true; + bool sampleSource = false; std::shared_ptr object; std::shared_ptr camera; diff --git a/Source/parser/FrameProducer.h b/Source/parser/FrameProducer.h index 65b3991..4ae980f 100644 --- a/Source/parser/FrameProducer.h +++ b/Source/parser/FrameProducer.h @@ -14,5 +14,5 @@ public: private: FrameConsumer& frameConsumer; std::shared_ptr frameSource; - int sourceFileIndex = 0; + int sourceFileIndex = -1; }; \ No newline at end of file diff --git a/Source/parser/FrameSource.h b/Source/parser/FrameSource.h index efb4927..a15ff04 100644 --- a/Source/parser/FrameSource.h +++ b/Source/parser/FrameSource.h @@ -9,6 +9,8 @@ class FrameSource { public: virtual void parse(juce::String extension, std::unique_ptr) = 0; virtual std::vector> nextFrame() = 0; + virtual Vector2 nextSample() = 0; + virtual bool isSample() = 0; virtual bool isActive() = 0; virtual void disable() = 0; virtual void enable() = 0; diff --git a/osci-render.jucer b/osci-render.jucer index 7dc6732..0968341 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -7,6 +7,9 @@ cppLanguageStandard="20" projectLineFeed=" " headerPath="./include"> + + @@ -81,10 +84,18 @@ file="Source/components/DraggableListBox.cpp"/> - - + + + + + + + + @@ -196,10 +209,6 @@ - -