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