diff --git a/Resources/lua/demo.lua b/Resources/lua/demo.lua index 8d1cf95f..6a8cedc0 100644 --- a/Resources/lua/demo.lua +++ b/Resources/lua/demo.lua @@ -61,25 +61,6 @@ function rotate(point, rotate_x, rotate_y, rotate_z) end num_points = 16 --- 3D cube draw path -points = { - {x=-1.0, y=-1.0, z=1.0}, - {x=1.0, y=-1.0, z=1.0}, - {x=1.0, y=-1.0, z=-1.0}, - {x=-1.0, y=-1.0, z=-1.0}, - {x=1.0, y=-1.0, z=-1.0}, - {x=1.0, y=1.0, z=-1.0}, - {x=-1.0, y=1.0, z=-1.0}, - {x=-1.0, y=-1.0, z=-1.0}, - {x=-1.0, y=-1.0, z=1.0}, - {x=1.0, y=-1.0, z=1.0}, - {x=1.0, y=1.0, z=1.0}, - {x=-1.0, y=1.0, z=1.0}, - {x=1.0, y=1.0, z=1.0}, - {x=1.0, y=1.0, z=-1.0}, - {x=-1.0, y=1.0, z=-1.0}, - {x=-1.0, y=1.0, z=1.0} -} -- Percentage of the image that has currently been drawn. -- The 'or' syntax sets 'drawing_progress' to 0 initially, or the @@ -104,6 +85,25 @@ end -- prev_start ~= start_index == true whenever a new line has started if prev_start ~= start_index then rotate_speed = slider_d * step / 50000 + -- 3D cube draw path + points = { + {x=-1.0, y=-1.0, z=1.0}, + {x=1.0, y=-1.0, z=1.0}, + {x=1.0, y=-1.0, z=-1.0}, + {x=-1.0, y=-1.0, z=-1.0}, + {x=1.0, y=-1.0, z=-1.0}, + {x=1.0, y=1.0, z=-1.0}, + {x=-1.0, y=1.0, z=-1.0}, + {x=-1.0, y=-1.0, z=-1.0}, + {x=-1.0, y=-1.0, z=1.0}, + {x=1.0, y=-1.0, z=1.0}, + {x=1.0, y=1.0, z=1.0}, + {x=-1.0, y=1.0, z=1.0}, + {x=1.0, y=1.0, z=1.0}, + {x=1.0, y=1.0, z=-1.0}, + {x=-1.0, y=1.0, z=-1.0}, + {x=-1.0, y=1.0, z=1.0} + } -- rotate and project the start and end points proj_start = project(rotate(points[start_index], rotate_speed, rotate_speed, 0)) proj_end = project(rotate(points[end_index], rotate_speed, rotate_speed, 0)) diff --git a/Resources/svg/pencil.svg b/Resources/svg/pencil.svg new file mode 100644 index 00000000..a53aa941 --- /dev/null +++ b/Resources/svg/pencil.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/Source/EffectsComponent.cpp b/Source/EffectsComponent.cpp index afd37f70..a775b6f1 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 64522e44..7d653dea 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 6a563064..a0d39bde 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/ObjComponent.cpp b/Source/ObjComponent.cpp index 162e4039..92dc9f8a 100644 --- a/Source/ObjComponent.cpp +++ b/Source/ObjComponent.cpp @@ -1,7 +1,6 @@ #include "ObjComponent.h" #include "PluginEditor.h" #include -#include "Util.h" ObjComponent::ObjComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor) { setText("3D .obj File Settings"); @@ -26,9 +25,9 @@ ObjComponent::ObjComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessor audioProcessor.rotateY->setValue(rotateY.slider.getValue()); audioProcessor.rotateZ->setValue(rotateZ.slider.getValue()); - audioProcessor.fixedRotateX = fixedRotateX->getToggleState(); - audioProcessor.fixedRotateY = fixedRotateY->getToggleState(); - audioProcessor.fixedRotateZ = fixedRotateZ->getToggleState(); + audioProcessor.fixedRotateX->setBoolValueNotifyingHost(fixedRotateX->getToggleState()); + audioProcessor.fixedRotateY->setBoolValueNotifyingHost(fixedRotateY->getToggleState()); + audioProcessor.fixedRotateZ->setBoolValueNotifyingHost(fixedRotateZ->getToggleState()); }; rotateX.slider.onValueChange = onRotationChange; @@ -67,21 +66,6 @@ ObjComponent::ObjComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessor } }; - auto doc = juce::XmlDocument::parse(BinaryData::fixed_rotate_svg); - Util::changeSvgColour(doc.get(), "white"); - fixedRotateWhite = juce::Drawable::createFromSVG(*doc); - Util::changeSvgColour(doc.get(), "red"); - fixedRotateRed = juce::Drawable::createFromSVG(*doc); - - // TODO: any way of removing this duplication? - getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite); - fixedRotateX->setClickingTogglesState(true); - fixedRotateY->setClickingTogglesState(true); - fixedRotateZ->setClickingTogglesState(true); - fixedRotateX->setImages(fixedRotateWhite.get(), nullptr, nullptr, nullptr, fixedRotateRed.get()); - fixedRotateY->setImages(fixedRotateWhite.get(), nullptr, nullptr, nullptr, fixedRotateRed.get()); - fixedRotateZ->setImages(fixedRotateWhite.get(), nullptr, nullptr, nullptr, fixedRotateRed.get()); - fixedRotateX->onClick = onRotationChange; fixedRotateY->onClick = onRotationChange; fixedRotateZ->onClick = onRotationChange; @@ -89,10 +73,6 @@ ObjComponent::ObjComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessor rotateX.setComponent(fixedRotateX); rotateY.setComponent(fixedRotateY); rotateZ.setComponent(fixedRotateZ); - - fixedRotateX->setToggleState(audioProcessor.fixedRotateX, juce::NotificationType::dontSendNotification); - fixedRotateY->setToggleState(audioProcessor.fixedRotateY, juce::NotificationType::dontSendNotification); - fixedRotateZ->setToggleState(audioProcessor.fixedRotateZ, juce::NotificationType::dontSendNotification); } ObjComponent::~ObjComponent() { diff --git a/Source/ObjComponent.h b/Source/ObjComponent.h index f25418a5..a1783814 100644 --- a/Source/ObjComponent.h +++ b/Source/ObjComponent.h @@ -3,6 +3,7 @@ #include #include "PluginProcessor.h" #include "components/EffectComponent.h" +#include "components/SvgButton.h" class OscirenderAudioProcessorEditor; class ObjComponent : public juce::GroupComponent, public juce::MouseListener { @@ -26,11 +27,9 @@ private: juce::TextButton resetRotation{"Reset Rotation"}; juce::ToggleButton mouseRotate{"Rotate with Mouse (Esc to disable)"}; - std::unique_ptr fixedRotateWhite; - std::unique_ptr fixedRotateRed; - std::shared_ptr fixedRotateX = std::make_shared("fixedRotateX", juce::DrawableButton::ButtonStyle::ImageFitted); - std::shared_ptr fixedRotateY = std::make_shared("fixedRotateY", juce::DrawableButton::ButtonStyle::ImageFitted); - std::shared_ptr fixedRotateZ = std::make_shared("fixedRotateZ", juce::DrawableButton::ButtonStyle::ImageFitted); + std::shared_ptr fixedRotateX = std::make_shared("fixedRotateX", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateX); + std::shared_ptr fixedRotateY = std::make_shared("fixedRotateY", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateY); + std::shared_ptr fixedRotateZ = std::make_shared("fixedRotateZ", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateZ); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ObjComponent) }; \ No newline at end of file diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 2fed7ebd..b0e81318 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -13,32 +13,30 @@ 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); } - resized(); + triggerAsyncUpdate(); } }; juce::Path path; 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,19 +143,24 @@ 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()); + } } - resized(); + triggerAsyncUpdate(); } // parsersLock MUST be locked before calling this function @@ -147,6 +179,18 @@ void OscirenderAudioProcessorEditor::fileUpdated(juce::String fileName) { updateCodeEditor(); } +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(); @@ -157,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 cf9b9735..9c9e4cb1 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -9,7 +9,7 @@ #include "components/VolumeComponent.h" -class OscirenderAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::CodeDocument::Listener { +class OscirenderAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::CodeDocument::Listener, public juce::AsyncUpdater { public: OscirenderAudioProcessorEditor(OscirenderAudioProcessor&); ~OscirenderAudioProcessorEditor() override; @@ -20,19 +20,26 @@ public: void addCodeEditor(int index); 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/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 5ea915c0..41f421f4 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -79,6 +79,17 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() delayEffect, std::vector{new EffectParameter("Delay Decay", "delayDecay", 0.0, 0.0, 1.0), new EffectParameter("Delay Length", "delayEchoLength", 0.5, 0.0, 1.0)} )); + toggleableEffects.push_back(std::make_shared( + perspectiveEffect, + std::vector{ + new EffectParameter("3D Perspective", "depthScale", 0.0, 0.0, 1.0), + new EffectParameter("3D Depth (z)", "zPos", 0.1, 0.0, 1.0), + new EffectParameter("3D Rotate Speed", "rotateSpeed3D", 0.0, -1.0, 1.0), + new EffectParameter("Rotate X", "rotateX", 1.0, -1.0, 1.0), + new EffectParameter("Rotate Y", "rotateY", 1.0, -1.0, 1.0), + new EffectParameter("Rotate Z", "rotateZ", 0.0, -1.0, 1.0), + } + )); toggleableEffects.push_back(traceMax); toggleableEffects.push_back(traceMin); @@ -113,6 +124,13 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() } } } + + addParameter(fixedRotateX); + addParameter(fixedRotateY); + addParameter(fixedRotateZ); + addParameter(perspectiveEffect->fixedRotateX); + addParameter(perspectiveEffect->fixedRotateY); + addParameter(perspectiveEffect->fixedRotateZ); } OscirenderAudioProcessor::~OscirenderAudioProcessor() {} @@ -453,7 +471,8 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, ju } { - juce::SpinLock::ScopedLockType lock(effectsLock); + juce::SpinLock::ScopedLockType lock1(parsersLock); + juce::SpinLock::ScopedLockType lock2(effectsLock); for (auto& effect : toggleableEffects) { if (effect->enabled->getValue()) { channels = effect->apply(sample, channels); @@ -494,6 +513,11 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, ju if (!renderingSample && frameDrawn >= drawnFrameLength) { updateFrame(); + // TODO: updateFrame already iterates over all the shapes, + // so we can improve performance by calculating frameDrawn + // and shapeDrawn directly. frameDrawn is simply actualTraceMin * 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 (traceMinEnabled) { while (frameDrawn < actualTraceMin * frameLength) { incrementShapeDrawing(); @@ -503,12 +527,14 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, ju } } +// TODO this is the slowest part of the program - any way to improve this would help! void OscirenderAudioProcessor::incrementShapeDrawing() { double length = currentShape < frame.size() ? frame[currentShape]->len : 0.0; // hard cap on how many times it can be over the length to // prevent audio stuttering - frameDrawn += juce::jmin(lengthIncrement, 20 * length); - shapeDrawn += juce::jmin(lengthIncrement, 20 * length); + auto increment = juce::jmin(lengthIncrement, 20 * length); + frameDrawn += increment; + shapeDrawn += increment; // Need to skip all shapes that the lengthIncrement draws over. // This is especially an issue when there are lots of small lines being diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index e96312b0..a4ed4039 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -20,6 +20,7 @@ #include "audio/DelayEffect.h" #include "audio/PitchDetector.h" #include "audio/WobbleEffect.h" +#include "audio/PerspectiveEffect.h" //============================================================================== /** @@ -106,23 +107,23 @@ public: }, new EffectParameter("Focal length", "focalLength", 1.0, 0.0, 2.0) ); - std::atomic fixedRotateX = false; - std::atomic fixedRotateY = false; - std::atomic fixedRotateZ = false; + BooleanParameter* fixedRotateX = new BooleanParameter("Object Fixed Rotate X", "objFixedRotateX", false); + BooleanParameter* fixedRotateY = new BooleanParameter("Object Fixed Rotate Y", "objFixedRotateY", false); + BooleanParameter* fixedRotateZ = new BooleanParameter("Object Fixed Rotate Z", "objFixedRotateZ", false); std::shared_ptr rotateX = std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { if (getCurrentFileIndex() != -1) { auto obj = getCurrentFileParser()->getObject(); if (obj == nullptr) return input; auto rotation = values[0] * std::numbers::pi; - if (fixedRotateX) { + if (fixedRotateX->getBoolValue()) { obj->setCurrentRotationX(rotation); } else { obj->setBaseRotationX(rotation); } } return input; - }, new EffectParameter("Rotate X", "rotateX", 1.0, -1.0, 1.0) + }, new EffectParameter("Rotate X", "objRotateX", 1.0, -1.0, 1.0) ); std::shared_ptr rotateY = std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { @@ -130,14 +131,14 @@ public: auto obj = getCurrentFileParser()->getObject(); if (obj == nullptr) return input; auto rotation = values[0] * std::numbers::pi; - if (fixedRotateY) { + if (fixedRotateY->getBoolValue()) { obj->setCurrentRotationY(rotation); } else { obj->setBaseRotationY(rotation); } } return input; - }, new EffectParameter("Rotate Y", "rotateY", 1.0, -1.0, 1.0) + }, new EffectParameter("Rotate Y", "objRotateY", 1.0, -1.0, 1.0) ); std::shared_ptr rotateZ = std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { @@ -145,14 +146,14 @@ public: auto obj = getCurrentFileParser()->getObject(); if (obj == nullptr) return input; auto rotation = values[0] * std::numbers::pi; - if (fixedRotateZ) { + if (fixedRotateZ->getBoolValue()) { obj->setCurrentRotationZ(rotation); } else { obj->setBaseRotationZ(rotation); } } return input; - }, new EffectParameter("Rotate Z", "rotateZ", 0.0, -1.0, 1.0) + }, new EffectParameter("Rotate Z", "objRotateZ", 0.0, -1.0, 1.0) ); std::shared_ptr rotateSpeed = std::make_shared( [this](int index, Vector2 input, const std::vector& values, double sampleRate) { @@ -162,10 +163,11 @@ public: obj->setRotationSpeed(values[0]); } return input; - }, new EffectParameter("Rotate Speed", "rotateSpeed3D", 0.0, -1.0, 1.0) + }, new EffectParameter("Rotate Speed", "objRotateSpeed", 0.0, -1.0, 1.0) ); std::shared_ptr delayEffect = std::make_shared(); + std::shared_ptr perspectiveEffect = std::make_shared(); juce::SpinLock parsersLock; std::vector> parsers; diff --git a/Source/Util.h b/Source/Util.h deleted file mode 100644 index 0965058a..00000000 --- a/Source/Util.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include - -namespace Util { - void changeSvgColour(juce::XmlElement* xml, juce::String colour) { - forEachXmlChildElement(*xml, xmlnode) { - xmlnode->setAttribute("fill", colour); - } - } -} \ No newline at end of file diff --git a/Source/audio/BooleanParameter.h b/Source/audio/BooleanParameter.h index 108d0b76..abe07ee1 100644 --- a/Source/audio/BooleanParameter.h +++ b/Source/audio/BooleanParameter.h @@ -29,10 +29,22 @@ public: return value.load(); } + bool getBoolValue() const { + return value.load(); + } + void setValue(float newValue) override { value.store(newValue >= 0.5f); } + void setBoolValue(bool newValue) { + value.store(newValue); + } + + void setBoolValueNotifyingHost(bool newValue) { + setValueNotifyingHost(newValue ? 1.0f : 0.0f); + } + float getDefaultValue() const override { return false; } diff --git a/Source/audio/Effect.cpp b/Source/audio/Effect.cpp index 7475c78d..3a43295f 100644 --- a/Source/audio/Effect.cpp +++ b/Source/audio/Effect.cpp @@ -28,9 +28,10 @@ void Effect::animateValues() { auto parameter = parameters[i]; float minValue = parameter->min; float maxValue = parameter->max; - float phase = parameter->lfo != nullptr ? nextPhase(parameter) : 0.0; + bool lfoEnabled = parameter->lfo != nullptr && parameter->lfo->getValueUnnormalised() != (int)LfoType::Static; + float phase = lfoEnabled ? nextPhase(parameter) : 0.0; float percentage = phase / (2 * std::numbers::pi); - LfoType type = parameter->lfo != nullptr ? (LfoType)(int)parameter->lfo->getValueUnnormalised() : LfoType::Static; + LfoType type = lfoEnabled ? (LfoType)(int)parameter->lfo->getValueUnnormalised() : LfoType::Static; switch (type) { case LfoType::Sine: diff --git a/Source/audio/PerspectiveEffect.cpp b/Source/audio/PerspectiveEffect.cpp new file mode 100644 index 00000000..ebcc5b0a --- /dev/null +++ b/Source/audio/PerspectiveEffect.cpp @@ -0,0 +1,107 @@ +#include "PerspectiveEffect.h" +#include + +PerspectiveEffect::PerspectiveEffect() {} + +Vector2 PerspectiveEffect::apply(int index, Vector2 input, const std::vector& values, double sampleRate) { + auto effectScale = values[0]; + auto depth = 1.0 + (values[1] - 0.1) * 3; + auto rotateSpeed = linearSpeedToActualSpeed(values[2]); + double baseRotateX, baseRotateY, baseRotateZ; + if (fixedRotateX->getBoolValue()) { + baseRotateX = 0; + currentRotateX = values[3] * std::numbers::pi; + } else { + baseRotateX = values[3] * std::numbers::pi; + } + if (fixedRotateY->getBoolValue()) { + baseRotateY = 0; + currentRotateY = values[4] * std::numbers::pi; + } else { + baseRotateY = values[4] * std::numbers::pi; + } + if (fixedRotateZ->getBoolValue()) { + baseRotateZ = 0; + currentRotateZ = values[5] * std::numbers::pi; + } else { + baseRotateZ = values[5] * std::numbers::pi; + } + + currentRotateX += baseRotateX * rotateSpeed; + currentRotateY += baseRotateY * rotateSpeed; + currentRotateZ += baseRotateZ * rotateSpeed; + + if (currentRotateX > std::numbers::pi * 8) { + currentRotateX -= std::numbers::pi * 8; + } + if (currentRotateY > std::numbers::pi * 8) { + currentRotateY -= std::numbers::pi * 8; + } + if (currentRotateZ > std::numbers::pi * 8) { + currentRotateZ -= std::numbers::pi * 8; + } + + auto x = input.x; + auto y = input.y; + auto z = 0.0; + + { + // TODO: Instead of evaluating the script every time, we could evaluate it + // once at the start for all values of x and y and then interpolate between + // the results. + juce::SpinLock::ScopedLockType lock(codeLock); + if (!defaultScript) { + parser->setVariable("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 rotateX = baseRotateX + currentRotateX; + auto rotateY = baseRotateY + currentRotateY; + auto rotateZ = baseRotateZ + currentRotateZ; + + // rotate around x-axis + double cosValue = std::cos(rotateX); + double sinValue = std::sin(rotateX); + double y2 = cosValue * y - sinValue * z; + double z2 = sinValue * y + cosValue * z; + + // rotate around y-axis + cosValue = std::cos(rotateY); + sinValue = std::sin(rotateY); + double x2 = cosValue * x + sinValue * z2; + double z3 = -sinValue * x + cosValue * z2; + + // rotate around z-axis + cosValue = cos(rotateZ); + sinValue = sin(rotateZ); + double x3 = cosValue * x2 - sinValue * y2; + double y3 = sinValue * x2 + cosValue * y2; + + // perspective projection + auto focalLength = 1.0; + return Vector2( + (1 - effectScale) * input.x + effectScale * (x3 * focalLength / (z3 - depth)), + (1 - effectScale) * input.y + effectScale * (y3 * focalLength / (z3 - depth)) + ); +} + +void PerspectiveEffect::updateCode(const juce::String& newCode) { + juce::SpinLock::ScopedLockType lock(codeLock); + defaultScript = newCode == DEFAULT_SCRIPT; + code = newCode; + parser = std::make_unique(code); +} + +juce::String PerspectiveEffect::getCode() { + juce::SpinLock::ScopedLockType lock(codeLock); + return code; +} diff --git a/Source/audio/PerspectiveEffect.h b/Source/audio/PerspectiveEffect.h new file mode 100644 index 00000000..6f8db4b9 --- /dev/null +++ b/Source/audio/PerspectiveEffect.h @@ -0,0 +1,32 @@ +#pragma once +#include "EffectApplication.h" +#include "../shape/Vector2.h" +#include "../audio/Effect.h" +#include "../lua/LuaParser.h" + +class PerspectiveEffect : public EffectApplication { +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::String code = DEFAULT_SCRIPT; + juce::SpinLock codeLock; + std::unique_ptr parser = std::make_unique(code); + bool defaultScript = true; + + float currentRotateX = 0; + float currentRotateY = 0; + float currentRotateZ = 0; + + float linearSpeedToActualSpeed(float rotateSpeed) { + return (std::exp(3 * juce::jmin(10.0f, std::abs(rotateSpeed))) - 1) / 50000.0; + } +}; \ No newline at end of file diff --git a/Source/components/EffectComponent.cpp b/Source/components/EffectComponent.cpp index 586fd901..a37cdc75 100644 --- a/Source/components/EffectComponent.cpp +++ b/Source/components/EffectComponent.cpp @@ -2,7 +2,7 @@ EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect, int index) : effect(effect), index(index), audioProcessor(p) { addAndMakeVisible(slider); - addAndMakeVisible(lfoSlider); + addChildComponent(lfoSlider); addAndMakeVisible(selected); addAndMakeVisible(lfo); @@ -128,7 +128,7 @@ void EffectComponent::resized() { lfo.setBounds(bounds.removeFromRight(100).reduced(5)); } - auto checkboxLabel = bounds.removeFromLeft(110); + auto checkboxLabel = bounds.removeFromLeft(120); if (checkboxVisible) { checkboxLabel.removeFromLeft(2); diff --git a/Source/components/EffectsListComponent.cpp b/Source/components/EffectsListComponent.cpp index df69b414..65f3d59a 100644 --- a/Source/components/EffectsListComponent.cpp +++ b/Source/components/EffectsListComponent.cpp @@ -1,15 +1,17 @@ #include "EffectsListComponent.h" +#include "SvgButton.h" +#include "../PluginEditor.h" -EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr effect) : DraggableListBoxItem(lb, data, rn), effect(effect) { - auto parameters = effect->parameters; +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(data.audioProcessor, *effect, i, i == 0); + std::shared_ptr effectComponent = std::make_shared(audioProcessor, effect, i, i == 0); // using weak_ptr to avoid circular reference and memory leak std::weak_ptr weakEffectComponent = effectComponent; effectComponent->slider.setValue(parameters[i]->getValueUnnormalised(), juce::dontSendNotification); effectComponent->slider.onValueChange = [this, i, weakEffectComponent] { if (auto effectComponent = weakEffectComponent.lock()) { - this->effect->setValue(i, effectComponent->slider.getValue()); + this->effect.setValue(i, effectComponent->slider.getValue()); } }; @@ -17,12 +19,17 @@ EffectsListComponent::EffectsListComponent(DraggableListBox& lb, AudioEffectList effectComponent->selected.onClick = [this, weakEffectComponent] { if (auto effectComponent = weakEffectComponent.lock()) { auto data = (AudioEffectListBoxItemData&)modelData; - juce::SpinLock::ScopedLockType lock(data.audioProcessor.effectsLock); + juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); data.setSelected(rowNum, effectComponent->selected.getToggleState()); } }; } + auto component = createComponent(parameters[i]); + if (component != nullptr) { + effectComponent->setComponent(component); + } + listModel.addComponent(effectComponent); } @@ -66,6 +73,36 @@ void EffectsListComponent::resized() { list.setBounds(area); } +std::shared_ptr EffectsListComponent::createComponent(EffectParameter* parameter) { + if (parameter->paramID == "rotateX" || parameter->paramID == "rotateY" || parameter->paramID == "rotateZ") { + BooleanParameter* toggle; + if (parameter->paramID == "rotateX") { + toggle = audioProcessor.perspectiveEffect->fixedRotateX; + } else if (parameter->paramID == "rotateY") { + toggle = audioProcessor.perspectiveEffect->fixedRotateY; + } else if (parameter->paramID == "rotateZ") { + toggle = audioProcessor.perspectiveEffect->fixedRotateZ; + } + std::shared_ptr button = std::make_shared(parameter->name, BinaryData::fixed_rotate_svg, "white", "red", toggle); + button->onClick = [this, toggle] { + toggle->setBoolValueNotifyingHost(!toggle->getBoolValue()); + }; + return button; + } else if (parameter->paramID == "depthScale") { + std::shared_ptr button = std::make_shared(parameter->name, BinaryData::pencil_svg, "white", "red"); + std::weak_ptr weakButton = button; + button->setEdgeIndent(5); + button->setToggleState(editor.editingPerspective, juce::dontSendNotification); + button->onClick = [this, weakButton] { + if (auto button = weakButton.lock()) { + editor.editPerspectiveFunction(button->getToggleState()); + } + }; + return button; + } + return nullptr; +} + int EffectsListBoxModel::getRowHeight(int row) { auto data = (AudioEffectListBoxItemData&)modelData; return data.getEffect(row)->parameters.size() * 30; @@ -79,7 +116,7 @@ juce::Component* EffectsListBoxModel::refreshComponentForRow(int rowNumber, bool std::unique_ptr item(dynamic_cast(existingComponentToUpdate)); if (juce::isPositiveAndBelow(rowNumber, modelData.getNumItems())) { auto data = (AudioEffectListBoxItemData&)modelData; - item = std::make_unique(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, data.getEffect(rowNumber)); + item = std::make_unique(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, *data.getEffect(rowNumber)); } return item.release(); } diff --git a/Source/components/EffectsListComponent.h b/Source/components/EffectsListComponent.h index 67cd5567..621871d4 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(); @@ -89,7 +91,7 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData class EffectsListComponent : public DraggableListBoxItem { public: - EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, std::shared_ptr effect); + EffectsListComponent(DraggableListBox& lb, AudioEffectListBoxItemData& data, int rn, Effect& effect); ~EffectsListComponent(); void paint(juce::Graphics& g) override; @@ -97,10 +99,15 @@ public: void resized() override; protected: - std::shared_ptr effect; + Effect& effect; ComponentListModel listModel; juce::ListBox list; private: + OscirenderAudioProcessor& audioProcessor; + OscirenderAudioProcessorEditor& editor; + + std::shared_ptr createComponent(EffectParameter* parameter); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsListComponent) }; diff --git a/Source/components/SvgButton.h b/Source/components/SvgButton.h new file mode 100644 index 00000000..9a654c15 --- /dev/null +++ b/Source/components/SvgButton.h @@ -0,0 +1,53 @@ +#pragma once +#include + +class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParameter::Listener, public juce::AsyncUpdater { + public: + SvgButton(juce::String name, juce::String svg, juce::String colour, juce::String colourOn, BooleanParameter* toggle = nullptr) : juce::DrawableButton(name, juce::DrawableButton::ButtonStyle::ImageFitted), toggle(toggle) { + auto doc = juce::XmlDocument::parse(svg); + changeSvgColour(doc.get(), colour); + normalImage = juce::Drawable::createFromSVG(*doc); + changeSvgColour(doc.get(), colourOn); + normalImageOn = juce::Drawable::createFromSVG(*doc); + + getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite); + + if (colour != colourOn) { + setClickingTogglesState(true); + } + setImages(normalImage.get(), nullptr, nullptr, nullptr, normalImageOn.get()); + + if (toggle != nullptr) { + toggle->addListener(this); + setToggleState(toggle->getBoolValue(), juce::NotificationType::dontSendNotification); + } + } + + SvgButton(juce::String name, juce::String svg, juce::String colour) : SvgButton(name, svg, colour, colour) {} + + ~SvgButton() override { + if (toggle != nullptr) { + toggle->removeListener(this); + } + } + + void parameterValueChanged(int parameterIndex, float newValue) override { + triggerAsyncUpdate(); + } + + void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override {} + + void handleAsyncUpdate() override { + setToggleState(toggle->getBoolValue(), juce::NotificationType::dontSendNotification); + } +private: + std::unique_ptr normalImage; + std::unique_ptr normalImageOn; + BooleanParameter* toggle; + + void changeSvgColour(juce::XmlElement* xml, juce::String colour) { + forEachXmlChildElement(*xml, xmlnode) { + xmlnode->setAttribute("fill", colour); + } + } +}; diff --git a/Source/components/VisualiserComponent.cpp b/Source/components/VisualiserComponent.cpp index 33853065..230e817b 100644 --- a/Source/components/VisualiserComponent.cpp +++ b/Source/components/VisualiserComponent.cpp @@ -12,7 +12,7 @@ VisualiserComponent::~VisualiserComponent() { } void VisualiserComponent::setBuffer(std::vector& newBuffer) { - juce::SpinLock::ScopedLockType scope(lock); + juce::CriticalSection::ScopedLockType scope(lock); buffer.clear(); for (int i = 0; i < newBuffer.size(); i += precision * numChannels) { buffer.push_back(newBuffer[i]); @@ -31,7 +31,7 @@ void VisualiserComponent::paint(juce::Graphics& g) { auto r = getLocalBounds().toFloat(); auto minDim = juce::jmin(r.getWidth(), r.getHeight()); - juce::SpinLock::ScopedLockType scope(lock); + juce::CriticalSection::ScopedLockType scope(lock); if (buffer.size() > 0) { g.setColour(waveformColour); paintXY(g, r.withSizeKeepingCentre(minDim, minDim)); @@ -86,6 +86,6 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle area double strength = 10; lengthScale = std::log(strength * lengthScale + 1) / std::log(strength + 1); g.setColour(waveformColour.withAlpha(lengthScale)); - g.drawLine(line, 2.0f); + g.drawLine(line, area.getWidth() / 150.0f); } } diff --git a/Source/components/VisualiserComponent.h b/Source/components/VisualiserComponent.h index eca34697..e6533ff9 100644 --- a/Source/components/VisualiserComponent.h +++ b/Source/components/VisualiserComponent.h @@ -18,7 +18,7 @@ public: void run() override; private: - juce::SpinLock lock; + juce::CriticalSection lock; std::vector buffer; int numChannels = 2; juce::Colour backgroundColour, waveformColour; diff --git a/Source/concurrency/BufferConsumer.h b/Source/concurrency/BufferConsumer.h index 889cf169..3988be1d 100644 --- a/Source/concurrency/BufferConsumer.h +++ b/Source/concurrency/BufferConsumer.h @@ -89,8 +89,8 @@ public: std::shared_ptr> firstBuffer = std::make_shared>(); std::shared_ptr> secondBuffer = std::make_shared>(); - std::shared_ptr firstBufferLock = std::make_shared(); - std::shared_ptr secondBufferLock = std::make_shared(); + std::shared_ptr firstBufferLock = std::make_shared(); + std::shared_ptr secondBufferLock = std::make_shared(); private: // Indirectly used by the producer to signal whether it holds the lock on the buffer. // This is accurate if the global producer lock is held as the buffer lock is acquired diff --git a/Source/concurrency/BufferProducer.h b/Source/concurrency/BufferProducer.h index 4ea60c20..a8f17192 100644 --- a/Source/concurrency/BufferProducer.h +++ b/Source/concurrency/BufferProducer.h @@ -3,35 +3,6 @@ #include #include "BufferConsumer.h" -// This is needed over juce::SpinLock because juce::SpinLock yeilds, which -// leads to some consumers never holding the lock. -// TODO: verify that this is a legitimate solution. -struct crude_spinlock { - std::atomic lock_ = {0}; - - void lock() noexcept { - for (;;) { - // Optimistically assume the lock is free on the first try - if (!lock_.exchange(true, std::memory_order_acquire)) { - return; - } - // Wait for lock to be released without generating cache misses - while (lock_.load(std::memory_order_relaxed)) {} - } - } - - bool try_lock() noexcept { - // First do a relaxed load to check if lock is free in order to prevent - // unnecessary cache misses if someone does while(!try_lock()) - return !lock_.load(std::memory_order_relaxed) && - !lock_.exchange(true, std::memory_order_acquire); - } - - void unlock() noexcept { - lock_.store(false, std::memory_order_release); - } -}; - class BufferProducer { public: BufferProducer() {} @@ -42,17 +13,16 @@ public: // being written to. // This is only called by the thread that owns the consumer thread. void registerConsumer(std::shared_ptr consumer) { - lock.lock(); + juce::CriticalSection::ScopedLockType l(lock); consumers.push_back(consumer); bufferPositions.push_back(0); consumer->getBuffer(true); - lock.unlock(); } // This is only called by the thread that owns the consumer thread. // This can't happen at the same time as write() it locks the producer lock. void unregisterConsumer(std::shared_ptr consumer) { - lock.lock(); + juce::CriticalSection::ScopedLockType l(lock); for (int i = 0; i < consumers.size(); i++) { if (consumers[i] == consumer) { consumer->releaseLock(); @@ -61,12 +31,11 @@ public: break; } } - lock.unlock(); } // Writes a sample to the current buffer for all consumers. void write(float left, float right) { - lock.lock(); + juce::CriticalSection::ScopedLockType l(lock); for (int i = 0; i < consumers.size(); i++) { std::shared_ptr> buffer = consumers[i]->getBuffer(false); if (buffer == nullptr) { @@ -85,11 +54,10 @@ public: consumers[i]->finishedWriting(); } } - lock.unlock(); } private: - crude_spinlock lock; + juce::CriticalSection lock; std::vector> consumers; std::vector bufferPositions; }; \ No newline at end of file diff --git a/Source/lua/LuaParser.cpp b/Source/lua/LuaParser.cpp index 9f66dde6..052a698b 100644 --- a/Source/lua/LuaParser.cpp +++ b/Source/lua/LuaParser.cpp @@ -28,11 +28,11 @@ void LuaParser::parse() { } // only the audio thread runs this fuction -Vector2 LuaParser::draw() { - Vector2 sample; +std::vector LuaParser::run() { + std::vector values; if (functionRef == -1) { - return sample; + return values; } lua_pushnumber(L, step); @@ -60,29 +60,25 @@ Vector2 LuaParser::draw() { 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); + auto length = lua_rawlen(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); + for (int i = 1; i <= length; i++) { + lua_pushinteger(L, i); + lua_gettable(L, -2); + float value = lua_tonumber(L, -1); + lua_pop(L, 1); + values.push_back(value); + } } lua_pop(L, 1); step++; - return sample; + return values; } -// this CANNOT run at the same time as draw() +// this CANNOT run at the same time as run() // many threads can run this function void LuaParser::setVariable(juce::String variableName, double value) { juce::SpinLock::ScopedLockType lock(variableLock); diff --git a/Source/lua/LuaParser.h b/Source/lua/LuaParser.h index 0b52750b..8159fae2 100644 --- a/Source/lua/LuaParser.h +++ b/Source/lua/LuaParser.h @@ -1,5 +1,4 @@ #pragma once -#include "../shape/Vector2.h" #include #include "../shape/Shape.h" @@ -9,7 +8,7 @@ public: LuaParser(juce::String script); ~LuaParser(); - Vector2 draw(); + std::vector run(); void setVariable(juce::String variableName, double value); private: diff --git a/Source/parser/FileParser.cpp b/Source/parser/FileParser.cpp index d5e52e81..7683c74d 100644 --- a/Source/parser/FileParser.cpp +++ b/Source/parser/FileParser.cpp @@ -48,7 +48,11 @@ Vector2 FileParser::nextSample() { juce::SpinLock::ScopedLockType scope(lock); if (lua != nullptr) { - return lua->draw(); + auto values = lua->run(); + if (values.size() < 2) { + return Vector2(); + } + return Vector2(values[0], values[1]); } } diff --git a/osci-render.jucer b/osci-render.jucer index 46ec3ef5..9560fa82 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -17,6 +17,7 @@ + @@ -54,6 +55,10 @@ file="Source/audio/EffectParameter.h"/> + + @@ -108,6 +113,7 @@ + -