kopia lustrzana https://github.com/jameshball/osci-render
Merge pull request #49 from jameshball/perspective-effect
Implement 3D perspective effectpull/170/head
commit
08c8239d39
|
@ -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))
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" />
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 243 B |
|
@ -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);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
class OscirenderAudioProcessorEditor;
|
||||
class EffectsComponent : public juce::GroupComponent {
|
||||
public:
|
||||
EffectsComponent(OscirenderAudioProcessor&);
|
||||
EffectsComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&);
|
||||
~EffectsComponent() override;
|
||||
|
||||
void resized() override;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "ObjComponent.h"
|
||||
#include "PluginEditor.h"
|
||||
#include <numbers>
|
||||
#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() {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <JuceHeader.h>
|
||||
#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<juce::Drawable> fixedRotateWhite;
|
||||
std::unique_ptr<juce::Drawable> fixedRotateRed;
|
||||
std::shared_ptr<juce::DrawableButton> fixedRotateX = std::make_shared<juce::DrawableButton>("fixedRotateX", juce::DrawableButton::ButtonStyle::ImageFitted);
|
||||
std::shared_ptr<juce::DrawableButton> fixedRotateY = std::make_shared<juce::DrawableButton>("fixedRotateY", juce::DrawableButton::ButtonStyle::ImageFitted);
|
||||
std::shared_ptr<juce::DrawableButton> fixedRotateZ = std::make_shared<juce::DrawableButton>("fixedRotateZ", juce::DrawableButton::ButtonStyle::ImageFitted);
|
||||
std::shared_ptr<SvgButton> fixedRotateX = std::make_shared<SvgButton>("fixedRotateX", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateX);
|
||||
std::shared_ptr<SvgButton> fixedRotateY = std::make_shared<SvgButton>("fixedRotateY", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateY);
|
||||
std::shared_ptr<SvgButton> fixedRotateZ = std::make_shared<SvgButton>("fixedRotateZ", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateZ);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ObjComponent)
|
||||
};
|
|
@ -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<juce::CodeDocument> codeDocument = std::make_shared<juce::CodeDocument>();
|
||||
int originalIndex = index;
|
||||
index++;
|
||||
std::shared_ptr<juce::CodeDocument> codeDocument;
|
||||
std::shared_ptr<juce::CodeEditorComponent> editor;
|
||||
|
||||
if (index == 0) {
|
||||
codeDocument = perspectiveCodeDocument;
|
||||
editor = perspectiveCodeEditor;
|
||||
} else {
|
||||
codeDocument = std::make_shared<juce::CodeDocument>();
|
||||
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<juce::CodeEditorComponent>(*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<juce::CodeEditorComponent> editor = std::make_shared<juce::CodeEditorComponent>(*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<juce::MemoryBlock>(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<juce::MemoryBlock>(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1));
|
||||
}
|
||||
}
|
||||
|
||||
bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) {
|
||||
|
|
|
@ -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<bool> 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<std::shared_ptr<juce::CodeDocument>> codeDocuments;
|
||||
std::vector<std::shared_ptr<juce::CodeEditorComponent>> codeEditors;
|
||||
juce::LuaTokeniser luaTokeniser;
|
||||
juce::XmlTokeniser xmlTokeniser;
|
||||
juce::ShapeButton collapseButton;
|
||||
std::shared_ptr<juce::CodeDocument> perspectiveCodeDocument = std::make_shared<juce::CodeDocument>();
|
||||
std::shared_ptr<juce::CodeEditorComponent> perspectiveCodeEditor = std::make_shared<juce::CodeEditorComponent>(*perspectiveCodeDocument, &luaTokeniser);
|
||||
|
||||
void codeDocumentTextInserted(const juce::String& newText, int insertIndex) override;
|
||||
void codeDocumentTextDeleted(int startIndex, int endIndex) override;
|
||||
|
|
|
@ -79,6 +79,17 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
delayEffect,
|
||||
std::vector<EffectParameter*>{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<Effect>(
|
||||
perspectiveEffect,
|
||||
std::vector<EffectParameter*>{
|
||||
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<float>& 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<float>& 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<float>& 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
|
||||
|
|
|
@ -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<bool> fixedRotateX = false;
|
||||
std::atomic<bool> fixedRotateY = false;
|
||||
std::atomic<bool> 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<Effect> rotateX = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& 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<Effect> rotateY = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& 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<Effect> rotateZ = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& 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<Effect> rotateSpeed = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& 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> delayEffect = std::make_shared<DelayEffect>();
|
||||
std::shared_ptr<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>();
|
||||
|
||||
juce::SpinLock parsersLock;
|
||||
std::vector<std::shared_ptr<FileParser>> parsers;
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
namespace Util {
|
||||
void changeSvgColour(juce::XmlElement* xml, juce::String colour) {
|
||||
forEachXmlChildElement(*xml, xmlnode) {
|
||||
xmlnode->setAttribute("fill", colour);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
#include "PerspectiveEffect.h"
|
||||
#include <numbers>
|
||||
|
||||
PerspectiveEffect::PerspectiveEffect() {}
|
||||
|
||||
Vector2 PerspectiveEffect::apply(int index, Vector2 input, const std::vector<double>& 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<LuaParser>(code);
|
||||
}
|
||||
|
||||
juce::String PerspectiveEffect::getCode() {
|
||||
juce::SpinLock::ScopedLockType lock(codeLock);
|
||||
return code;
|
||||
}
|
|
@ -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<double>& 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<LuaParser> parser = std::make_unique<LuaParser>(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;
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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> 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> effectComponent = std::make_shared<EffectComponent>(data.audioProcessor, *effect, i, i == 0);
|
||||
std::shared_ptr<EffectComponent> effectComponent = std::make_shared<EffectComponent>(audioProcessor, effect, i, i == 0);
|
||||
// using weak_ptr to avoid circular reference and memory leak
|
||||
std::weak_ptr<EffectComponent> 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<juce::Component> 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<SvgButton> button = std::make_shared<SvgButton>(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<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::pencil_svg, "white", "red");
|
||||
std::weak_ptr<SvgButton> 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<EffectsListComponent> item(dynamic_cast<EffectsListComponent*>(existingComponentToUpdate));
|
||||
if (juce::isPositiveAndBelow(rowNumber, modelData.getNumItems())) {
|
||||
auto data = (AudioEffectListBoxItemData&)modelData;
|
||||
item = std::make_unique<EffectsListComponent>(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, data.getEffect(rowNumber));
|
||||
item = std::make_unique<EffectsListComponent>(listBox, (AudioEffectListBoxItemData&)modelData, rowNumber, *data.getEffect(rowNumber));
|
||||
}
|
||||
return item.release();
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
#include "ComponentList.h"
|
||||
|
||||
// Application-specific data container
|
||||
class OscirenderAudioProcessorEditor;
|
||||
struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
||||
{
|
||||
std::vector<std::shared_ptr<Effect>> 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> 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& effect;
|
||||
ComponentListModel listModel;
|
||||
juce::ListBox list;
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
OscirenderAudioProcessorEditor& editor;
|
||||
|
||||
std::shared_ptr<juce::Component> createComponent(EffectParameter* parameter);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsListComponent)
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
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<juce::Drawable> normalImage;
|
||||
std::unique_ptr<juce::Drawable> normalImageOn;
|
||||
BooleanParameter* toggle;
|
||||
|
||||
void changeSvgColour(juce::XmlElement* xml, juce::String colour) {
|
||||
forEachXmlChildElement(*xml, xmlnode) {
|
||||
xmlnode->setAttribute("fill", colour);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -12,7 +12,7 @@ VisualiserComponent::~VisualiserComponent() {
|
|||
}
|
||||
|
||||
void VisualiserComponent::setBuffer(std::vector<float>& 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<float> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ public:
|
|||
void run() override;
|
||||
|
||||
private:
|
||||
juce::SpinLock lock;
|
||||
juce::CriticalSection lock;
|
||||
std::vector<float> buffer;
|
||||
int numChannels = 2;
|
||||
juce::Colour backgroundColour, waveformColour;
|
||||
|
|
|
@ -89,8 +89,8 @@ public:
|
|||
|
||||
std::shared_ptr<std::vector<float>> firstBuffer = std::make_shared<std::vector<float>>();
|
||||
std::shared_ptr<std::vector<float>> secondBuffer = std::make_shared<std::vector<float>>();
|
||||
std::shared_ptr<juce::SpinLock> firstBufferLock = std::make_shared<juce::SpinLock>();
|
||||
std::shared_ptr<juce::SpinLock> secondBufferLock = std::make_shared<juce::SpinLock>();
|
||||
std::shared_ptr<juce::CriticalSection> firstBufferLock = std::make_shared<juce::CriticalSection>();
|
||||
std::shared_ptr<juce::CriticalSection> secondBufferLock = std::make_shared<juce::CriticalSection>();
|
||||
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
|
||||
|
|
|
@ -3,35 +3,6 @@
|
|||
#include <JuceHeader.h>
|
||||
#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<bool> 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<BufferConsumer> 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<BufferConsumer> 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<std::vector<float>> 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<std::shared_ptr<BufferConsumer>> consumers;
|
||||
std::vector<int> bufferPositions;
|
||||
};
|
|
@ -28,11 +28,11 @@ void LuaParser::parse() {
|
|||
}
|
||||
|
||||
// only the audio thread runs this fuction
|
||||
Vector2 LuaParser::draw() {
|
||||
Vector2 sample;
|
||||
std::vector<float> LuaParser::run() {
|
||||
std::vector<float> 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);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include "../shape/Vector2.h"
|
||||
#include <JuceHeader.h>
|
||||
#include "../shape/Shape.h"
|
||||
|
||||
|
@ -9,7 +8,7 @@ public:
|
|||
LuaParser(juce::String script);
|
||||
~LuaParser();
|
||||
|
||||
Vector2 draw();
|
||||
std::vector<float> run();
|
||||
void setVariable(juce::String variableName, double value);
|
||||
|
||||
private:
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<FILE id="IqXIZW" name="demo.svg" compile="0" resource="1" file="Resources/svg/demo.svg"/>
|
||||
<FILE id="YwkQpy" name="fixed_rotate.svg" compile="0" resource="1"
|
||||
file="Resources/svg/fixed_rotate.svg"/>
|
||||
<FILE id="D2AI1b" name="pencil.svg" compile="0" resource="1" file="Resources/svg/pencil.svg"/>
|
||||
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
|
||||
<FILE id="qC6QiP" name="volume.svg" compile="0" resource="1" file="Resources/svg/volume.svg"/>
|
||||
</GROUP>
|
||||
|
@ -54,6 +55,10 @@
|
|||
file="Source/audio/EffectParameter.h"/>
|
||||
<FILE id="uhyh7T" name="LuaEffect.cpp" compile="1" resource="0" file="Source/audio/LuaEffect.cpp"/>
|
||||
<FILE id="jqDcZq" name="LuaEffect.h" compile="0" resource="0" file="Source/audio/LuaEffect.h"/>
|
||||
<FILE id="QBWW9w" name="PerspectiveEffect.cpp" compile="1" resource="0"
|
||||
file="Source/audio/PerspectiveEffect.cpp"/>
|
||||
<FILE id="h0dMim" name="PerspectiveEffect.h" compile="0" resource="0"
|
||||
file="Source/audio/PerspectiveEffect.h"/>
|
||||
<FILE id="t2bsR8" name="PitchDetector.cpp" compile="1" resource="0"
|
||||
file="Source/audio/PitchDetector.cpp"/>
|
||||
<FILE id="rQC2gX" name="PitchDetector.h" compile="0" resource="0" file="Source/audio/PitchDetector.h"/>
|
||||
|
@ -108,6 +113,7 @@
|
|||
<FILE id="x0Syav" name="LuaListComponent.h" compile="0" resource="0"
|
||||
file="Source/components/LuaListComponent.h"/>
|
||||
<FILE id="QQzSwh" name="SliderTextBox.h" compile="0" resource="0" file="Source/components/SliderTextBox.h"/>
|
||||
<FILE id="QrDKRZ" name="SvgButton.h" compile="0" resource="0" file="Source/components/SvgButton.h"/>
|
||||
<FILE id="y3UiR0" name="VisualiserComponent.cpp" compile="1" resource="0"
|
||||
file="Source/components/VisualiserComponent.cpp"/>
|
||||
<FILE id="ZueyNl" name="VisualiserComponent.h" compile="0" resource="0"
|
||||
|
@ -434,7 +440,6 @@
|
|||
<FILE id="vIYWRG" name="TextParser.cpp" compile="1" resource="0" file="Source/txt/TextParser.cpp"/>
|
||||
<FILE id="LlefOK" name="TextParser.h" compile="0" resource="0" file="Source/txt/TextParser.h"/>
|
||||
</GROUP>
|
||||
<FILE id="JceyXh" name="Util.h" compile="0" resource="0" file="Source/Util.h"/>
|
||||
<GROUP id="{022CB910-9A16-C4AE-4C3B-9CB57BE87FC2}" name="xml">
|
||||
<FILE id="pW7WRh" name="pugiconfig.hpp" compile="0" resource="0" file="Source/xml/pugiconfig.hpp"/>
|
||||
<FILE id="CnkgyF" name="pugixml.cpp" compile="1" resource="0" file="Source/xml/pugixml.cpp"/>
|
||||
|
|
Ładowanie…
Reference in New Issue