kopia lustrzana https://github.com/jameshball/osci-render
Merge pull request #51 from jameshball/project-files
Add support for project files and saving state in a DAWpull/170/head
commit
9293214943
|
|
@ -16,15 +16,6 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP
|
|||
audioProcessor.frequencyEffect->setValue(frequency.slider.getValue());
|
||||
};
|
||||
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
for (int i = 0; i < audioProcessor.toggleableEffects.size(); i++) {
|
||||
auto effect = audioProcessor.toggleableEffects[i];
|
||||
effect->setValue(effect->getValue());
|
||||
itemData.data.push_back(effect);
|
||||
}
|
||||
}
|
||||
|
||||
/*addBtn.setButtonText("Add Item...");
|
||||
addBtn.onClick = [this]()
|
||||
{
|
||||
|
|
@ -33,12 +24,18 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP
|
|||
};
|
||||
addAndMakeVisible(addBtn);*/
|
||||
|
||||
{
|
||||
juce::MessageManagerLock lock;
|
||||
audioProcessor.broadcaster.addChangeListener(this);
|
||||
}
|
||||
|
||||
listBox.setModel(&listBoxModel);
|
||||
addAndMakeVisible(listBox);
|
||||
}
|
||||
|
||||
EffectsComponent::~EffectsComponent() {
|
||||
|
||||
juce::MessageManagerLock lock;
|
||||
audioProcessor.broadcaster.removeChangeListener(this);
|
||||
}
|
||||
|
||||
void EffectsComponent::resized() {
|
||||
|
|
@ -48,3 +45,8 @@ void EffectsComponent::resized() {
|
|||
area.removeFromTop(6);
|
||||
listBox.setBounds(area);
|
||||
}
|
||||
|
||||
void EffectsComponent::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
||||
itemData.resetData();
|
||||
listBox.updateContent();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@
|
|||
#include "components/EffectsListComponent.h"
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class EffectsComponent : public juce::GroupComponent {
|
||||
class EffectsComponent : public juce::GroupComponent, public juce::ChangeListener {
|
||||
public:
|
||||
EffectsComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&);
|
||||
~EffectsComponent() override;
|
||||
|
||||
void resized() override;
|
||||
void changeListenerCallback(juce::ChangeBroadcaster* source) override;
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,275 @@
|
|||
#include "PluginProcessor.h"
|
||||
|
||||
void OscirenderAudioProcessor::openLegacyProject(const juce::XmlElement* xml) {
|
||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
|
||||
if (xml != nullptr && xml->hasTagName("project")) {
|
||||
auto slidersXml = xml->getChildByName("sliders");
|
||||
if (slidersXml != nullptr) {
|
||||
for (auto sliderXml : slidersXml->getChildIterator()) {
|
||||
auto id = sliderXml->getTagName();
|
||||
auto valueXml = sliderXml->getChildByName("value");
|
||||
auto minXml = sliderXml->getChildByName("min");
|
||||
auto maxXml = sliderXml->getChildByName("max");
|
||||
|
||||
double value = valueXml != nullptr ? valueXml->getAllSubText().getDoubleValue() : 0.0;
|
||||
double min = minXml != nullptr ? minXml->getAllSubText().getDoubleValue() : 0.0;
|
||||
double max = maxXml != nullptr ? maxXml->getAllSubText().getDoubleValue() : 0.0;
|
||||
|
||||
value = valueFromLegacy(value, id);
|
||||
min = valueFromLegacy(min, id);
|
||||
max = valueFromLegacy(max, id);
|
||||
|
||||
auto pair = effectFromLegacyId(id, true);
|
||||
auto effect = pair.first;
|
||||
auto parameter = pair.second;
|
||||
|
||||
if (effect != nullptr && parameter != nullptr) {
|
||||
if (id != "volume" && id != "threshold") {
|
||||
parameter->min = min;
|
||||
parameter->max = max;
|
||||
}
|
||||
parameter->setUnnormalisedValueNotifyingHost(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto translationXml = xml->getChildByName("translation");
|
||||
if (translationXml != nullptr) {
|
||||
auto xXml = translationXml->getChildByName("x");
|
||||
auto yXml = translationXml->getChildByName("y");
|
||||
auto translateEffect = getEffect("translateX");
|
||||
if (translateEffect != nullptr) {
|
||||
auto x = translateEffect->getParameter("translateX");
|
||||
if (x != nullptr && xXml != nullptr) {
|
||||
x->setUnnormalisedValueNotifyingHost(xXml->getAllSubText().getDoubleValue());
|
||||
}
|
||||
auto y = translateEffect->getParameter("translateY");
|
||||
if (y != nullptr && yXml != nullptr) {
|
||||
y->setUnnormalisedValueNotifyingHost(yXml->getAllSubText().getDoubleValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto perspectiveFixedRotateXml = xml->getChildByName("perspectiveFixedRotate");
|
||||
if (perspectiveFixedRotateXml != nullptr) {
|
||||
auto xXml = perspectiveFixedRotateXml->getChildByName("x");
|
||||
auto yXml = perspectiveFixedRotateXml->getChildByName("y");
|
||||
auto zXml = perspectiveFixedRotateXml->getChildByName("z");
|
||||
auto x = getBooleanParameter("perspectiveFixedRotateX");
|
||||
auto y = getBooleanParameter("perspectiveFixedRotateY");
|
||||
auto z = getBooleanParameter("perspectiveFixedRotateZ");
|
||||
if (x != nullptr && xXml != nullptr) {
|
||||
x->setBoolValueNotifyingHost(xXml->getAllSubText() == "true");
|
||||
}
|
||||
if (y != nullptr && yXml != nullptr) {
|
||||
y->setBoolValueNotifyingHost(yXml->getAllSubText() == "true");
|
||||
}
|
||||
if (z != nullptr && zXml != nullptr) {
|
||||
z->setBoolValueNotifyingHost(zXml->getAllSubText() == "true");
|
||||
}
|
||||
}
|
||||
|
||||
auto objectFixedRotate = xml->getChildByName("objectFixedRotate");
|
||||
if (objectFixedRotate != nullptr) {
|
||||
auto xXml = objectFixedRotate->getChildByName("x");
|
||||
auto yXml = objectFixedRotate->getChildByName("y");
|
||||
auto zXml = objectFixedRotate->getChildByName("z");
|
||||
auto x = getBooleanParameter("objFixedRotateX");
|
||||
auto y = getBooleanParameter("objFixedRotateY");
|
||||
auto z = getBooleanParameter("objFixedRotateZ");
|
||||
if (x != nullptr && xXml != nullptr) {
|
||||
x->setBoolValueNotifyingHost(xXml->getAllSubText() == "true");
|
||||
}
|
||||
if (y != nullptr && yXml != nullptr) {
|
||||
y->setBoolValueNotifyingHost(yXml->getAllSubText() == "true");
|
||||
}
|
||||
if (z != nullptr && zXml != nullptr) {
|
||||
z->setBoolValueNotifyingHost(zXml->getAllSubText() == "true");
|
||||
}
|
||||
}
|
||||
|
||||
auto checkBoxesXml = xml->getChildByName("checkBoxes");
|
||||
if (checkBoxesXml != nullptr) {
|
||||
for (auto checkBoxXml : checkBoxesXml->getChildIterator()) {
|
||||
auto id = checkBoxXml->getTagName();
|
||||
auto selectedXml = checkBoxXml->getChildByName("selected");
|
||||
auto animationXml = checkBoxXml->getChildByName("animation");
|
||||
auto animationSpeedXml = checkBoxXml->getChildByName("animationSpeed");
|
||||
|
||||
bool selected = selectedXml != nullptr ? selectedXml->getAllSubText() == "true" : false;
|
||||
LfoType lfoType = animationXml != nullptr ? lfoTypeFromLegacyAnimationType(animationXml->getAllSubText()) : LfoType::Static;
|
||||
double lfoRate = animationSpeedXml != nullptr ? animationSpeedXml->getAllSubText().getDoubleValue() : 1.0;
|
||||
|
||||
auto pair = effectFromLegacyId(id, true);
|
||||
auto effect = pair.first;
|
||||
auto parameter = pair.second;
|
||||
|
||||
if (effect != nullptr && parameter != nullptr && parameter->lfo != nullptr && parameter->lfoRate != nullptr) {
|
||||
if (effect->enabled != nullptr && effect->getId() == parameter->paramID) {
|
||||
effect->enabled->setBoolValueNotifyingHost(selected);
|
||||
}
|
||||
parameter->lfo->setUnnormalisedValueNotifyingHost((int) lfoType);
|
||||
parameter->lfoRate->setUnnormalisedValueNotifyingHost(lfoRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateEffectPrecedence();
|
||||
|
||||
auto perspectiveFunction = xml->getChildByName("depthFunction");
|
||||
if (perspectiveFunction != nullptr) {
|
||||
auto stream = juce::MemoryOutputStream();
|
||||
juce::Base64::convertFromBase64(stream, perspectiveFunction->getAllSubText());
|
||||
perspectiveEffect->updateCode(stream.toString());
|
||||
}
|
||||
// close all files
|
||||
auto numFiles = fileBlocks.size();
|
||||
for (int i = 0; i < numFiles; i++) {
|
||||
removeFile(0);
|
||||
}
|
||||
|
||||
auto filesXml = xml->getChildByName("files");
|
||||
if (filesXml != nullptr) {
|
||||
for (auto fileXml : filesXml->getChildIterator()) {
|
||||
auto nameXml = fileXml->getChildByName("name");
|
||||
auto dataXml = fileXml->getChildByName("data");
|
||||
auto fileName = nameXml != nullptr ? nameXml->getAllSubText() : "";
|
||||
auto data = dataXml != nullptr ? dataXml->getAllSubText() : "";
|
||||
|
||||
auto stream = juce::MemoryOutputStream();
|
||||
juce::Base64::convertFromBase64(stream, data);
|
||||
auto fileBlock = std::make_shared<juce::MemoryBlock>(stream.getData(), stream.getDataSize());
|
||||
addFile(fileName, fileBlock);
|
||||
}
|
||||
}
|
||||
|
||||
auto frameSourceXml = xml->getChildByName("frameSource");
|
||||
if (frameSourceXml != nullptr) {
|
||||
changeCurrentFile(frameSourceXml->getAllSubText().getIntValue());
|
||||
}
|
||||
broadcaster.sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// gets the effect from the legacy id and optionally updates the precedence
|
||||
std::pair<std::shared_ptr<Effect>, EffectParameter*> OscirenderAudioProcessor::effectFromLegacyId(const juce::String& id, bool updatePrecedence) {
|
||||
auto effectId = id;
|
||||
juce::String paramId = "";
|
||||
int precedence = -1;
|
||||
|
||||
if (id == "vectorCancelling") {
|
||||
precedence = 0;
|
||||
} else if (id == "bitCrush") {
|
||||
precedence = 1;
|
||||
} else if (id == "verticalDistort") {
|
||||
precedence = 2;
|
||||
effectId = "distortY";
|
||||
} else if (id == "horizontalDistort") {
|
||||
precedence = 3;
|
||||
effectId = "distortX";
|
||||
} else if (id == "wobble") {
|
||||
precedence = 4;
|
||||
} else if (id == "smoothing") {
|
||||
precedence = 100;
|
||||
} else if (id == "rotateSpeed3d") {
|
||||
precedence = 52;
|
||||
effectId = "perspectiveStrength";
|
||||
paramId = "perspectiveRotateSpeed";
|
||||
} else if (id == "zPos") {
|
||||
precedence = 52;
|
||||
effectId = "perspectiveStrength";
|
||||
paramId = "perspectiveZPos";
|
||||
} else if (id == "imageRotateX") {
|
||||
precedence = 52;
|
||||
effectId = "perspectiveStrength";
|
||||
paramId = "perspectiveRotateX";
|
||||
} else if (id == "imageRotateY") {
|
||||
precedence = 52;
|
||||
effectId = "perspectiveStrength";
|
||||
paramId = "perspectiveRotateY";
|
||||
} else if (id == "imageRotateZ") {
|
||||
precedence = 52;
|
||||
effectId = "perspectiveStrength";
|
||||
paramId = "perspectiveRotateZ";
|
||||
} else if (id == "depthScale") {
|
||||
precedence = 52;
|
||||
effectId = "perspectiveStrength";
|
||||
} else if (id == "translationScale") {
|
||||
// this doesn't exist in the new version
|
||||
} else if (id == "translationSpeed") {
|
||||
// this doesn't exist in the new version
|
||||
} else if (id == "rotateSpeed") {
|
||||
precedence = 50;
|
||||
effectId = "2DRotateSpeed";
|
||||
} else if (id == "visibility") {
|
||||
// this doesn't exist in the new version
|
||||
} else if (id == "delayDecay") {
|
||||
precedence = 101;
|
||||
} else if (id == "delayEchoLength") {
|
||||
precedence = 101;
|
||||
effectId = "delayDecay";
|
||||
paramId = "delayLength";
|
||||
} else if (id == "bulge") {
|
||||
precedence = 53;
|
||||
} else if (id == "focalLength") {
|
||||
effectId = "objFocalLength";
|
||||
} else if (id == "objectXRotate") {
|
||||
effectId = "objRotateX";
|
||||
} else if (id == "objectYRotate") {
|
||||
effectId = "objRotateY";
|
||||
} else if (id == "objectZRotate") {
|
||||
effectId = "objRotateZ";
|
||||
} else if (id == "objectRotateSpeed") {
|
||||
effectId = "objRotateSpeed";
|
||||
} else if (id == "octave") {
|
||||
// this doesn't exist in the new version
|
||||
} else if (id == "micVolume") {
|
||||
// this doesn't exist in the new version
|
||||
} else if (id == "brightness") {
|
||||
// this doesn't exist in the new version
|
||||
}
|
||||
|
||||
paramId = paramId == "" ? effectId : paramId;
|
||||
auto effect = getEffect(effectId);
|
||||
|
||||
if (effect == nullptr) {
|
||||
return std::make_pair(nullptr, nullptr);
|
||||
} else {
|
||||
if (updatePrecedence) {
|
||||
effect->setPrecedence(precedence);
|
||||
}
|
||||
auto parameter = effect->getParameter(paramId);
|
||||
return std::make_pair(effect, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
LfoType OscirenderAudioProcessor::lfoTypeFromLegacyAnimationType(const juce::String& type) {
|
||||
if (type == "Static") {
|
||||
return LfoType::Static;
|
||||
} else if (type == "Sine") {
|
||||
return LfoType::Sine;
|
||||
} else if (type == "Square") {
|
||||
return LfoType::Square;
|
||||
} else if (type == "Seesaw") {
|
||||
return LfoType::Seesaw;
|
||||
} else if (type == "Linear") {
|
||||
return LfoType::Triangle;
|
||||
} else if (type == "Forward") {
|
||||
return LfoType::Sawtooth;
|
||||
} else if (type == "Reverse") {
|
||||
return LfoType::ReverseSawtooth;
|
||||
} else {
|
||||
return LfoType::Static;
|
||||
}
|
||||
}
|
||||
|
||||
double OscirenderAudioProcessor::valueFromLegacy(double value, const juce::String& id) {
|
||||
if (id == "volume") {
|
||||
return value / 3.0;
|
||||
} else if (id == "frequency") {
|
||||
return std::pow(12000.0, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
@ -10,6 +10,9 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
|
|||
addChildComponent(obj);
|
||||
addAndMakeVisible(volume);
|
||||
|
||||
menuBar.setModel(&menuBarModel);
|
||||
addAndMakeVisible(menuBar);
|
||||
|
||||
addAndMakeVisible(collapseButton);
|
||||
collapseButton.onClick = [this] {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||
|
|
@ -31,11 +34,12 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
|
|||
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||
addCodeEditor(-1);
|
||||
for (int i = 0; i < audioProcessor.numFiles(); i++) {
|
||||
addCodeEditor(i);
|
||||
}
|
||||
fileUpdated(audioProcessor.getCurrentFileName());
|
||||
initialiseCodeEditors();
|
||||
}
|
||||
|
||||
{
|
||||
juce::MessageManagerLock lock;
|
||||
audioProcessor.broadcaster.addChangeListener(this);
|
||||
}
|
||||
|
||||
setSize(1100, 750);
|
||||
|
|
@ -43,10 +47,24 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
|
|||
setResizeLimits(500, 400, 999999, 999999);
|
||||
}
|
||||
|
||||
OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {}
|
||||
OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {
|
||||
juce::MessageManagerLock lock;
|
||||
audioProcessor.broadcaster.removeChangeListener(this);
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::paint(juce::Graphics& g)
|
||||
{
|
||||
// parsersLock must be held
|
||||
void OscirenderAudioProcessorEditor::initialiseCodeEditors() {
|
||||
codeDocuments.clear();
|
||||
codeEditors.clear();
|
||||
// -1 is the perspective function
|
||||
addCodeEditor(-1);
|
||||
for (int i = 0; i < audioProcessor.numFiles(); i++) {
|
||||
addCodeEditor(i);
|
||||
}
|
||||
fileUpdated(audioProcessor.getCurrentFileName());
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::paint(juce::Graphics& g) {
|
||||
g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
|
||||
|
||||
g.setColour(juce::Colours::white);
|
||||
|
|
@ -55,6 +73,8 @@ void OscirenderAudioProcessorEditor::paint(juce::Graphics& g)
|
|||
|
||||
void OscirenderAudioProcessorEditor::resized() {
|
||||
auto area = getLocalBounds();
|
||||
menuBar.setBounds(area.removeFromTop(25));
|
||||
area.removeFromTop(2);
|
||||
area.removeFromLeft(3);
|
||||
auto volumeArea = area.removeFromLeft(30);
|
||||
volume.setBounds(volumeArea.withSizeKeepingCentre(volumeArea.getWidth(), juce::jmin(volumeArea.getHeight(), 300)));
|
||||
|
|
@ -97,7 +117,6 @@ void OscirenderAudioProcessorEditor::resized() {
|
|||
obj.setBounds(altEffectsSection);
|
||||
}
|
||||
effects.setBounds(effectsSection);
|
||||
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::addCodeEditor(int index) {
|
||||
|
|
@ -183,6 +202,11 @@ void OscirenderAudioProcessorEditor::handleAsyncUpdate() {
|
|||
resized();
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||
initialiseCodeEditors();
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) {
|
||||
editingPerspective = enable;
|
||||
juce::SpinLock::ScopedLockType lock1(audioProcessor.parsersLock);
|
||||
|
|
@ -216,39 +240,108 @@ void OscirenderAudioProcessorEditor::updateCodeDocument() {
|
|||
}
|
||||
|
||||
bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) {
|
||||
juce::SpinLock::ScopedLockType parserLock(audioProcessor.parsersLock);
|
||||
juce::SpinLock::ScopedLockType effectsLock(audioProcessor.effectsLock);
|
||||
bool consumeKey1 = true;
|
||||
{
|
||||
juce::SpinLock::ScopedLockType parserLock(audioProcessor.parsersLock);
|
||||
juce::SpinLock::ScopedLockType effectsLock(audioProcessor.effectsLock);
|
||||
|
||||
int numFiles = audioProcessor.numFiles();
|
||||
int currentFile = audioProcessor.getCurrentFileIndex();
|
||||
bool changedFile = false;
|
||||
bool consumeKey = true;
|
||||
if (key.getTextCharacter() == 'j') {
|
||||
if (numFiles > 1) {
|
||||
currentFile++;
|
||||
if (currentFile == numFiles) {
|
||||
currentFile = 0;
|
||||
int numFiles = audioProcessor.numFiles();
|
||||
int currentFile = audioProcessor.getCurrentFileIndex();
|
||||
bool changedFile = false;
|
||||
|
||||
if (key.getTextCharacter() == 'j') {
|
||||
if (numFiles > 1) {
|
||||
currentFile++;
|
||||
if (currentFile == numFiles) {
|
||||
currentFile = 0;
|
||||
}
|
||||
changedFile = true;
|
||||
}
|
||||
changedFile = true;
|
||||
} else if (key.getTextCharacter() == 'k') {
|
||||
if (numFiles > 1) {
|
||||
currentFile--;
|
||||
if (currentFile < 0) {
|
||||
currentFile = numFiles - 1;
|
||||
}
|
||||
changedFile = true;
|
||||
}
|
||||
} else {
|
||||
consumeKey1 = false;
|
||||
}
|
||||
} else if (key.getTextCharacter() == 'k') {
|
||||
if (numFiles > 1) {
|
||||
currentFile--;
|
||||
if (currentFile < 0) {
|
||||
currentFile = numFiles - 1;
|
||||
}
|
||||
changedFile = true;
|
||||
}
|
||||
} else if (key.isKeyCode(juce::KeyPress::escapeKey)) {
|
||||
obj.disableMouseRotation();
|
||||
} else {
|
||||
consumeKey = false;
|
||||
}
|
||||
|
||||
if (changedFile) {
|
||||
audioProcessor.changeCurrentFile(currentFile);
|
||||
fileUpdated(audioProcessor.getCurrentFileName());
|
||||
if (changedFile) {
|
||||
audioProcessor.changeCurrentFile(currentFile);
|
||||
fileUpdated(audioProcessor.getCurrentFileName());
|
||||
}
|
||||
}
|
||||
|
||||
return consumeKey;
|
||||
bool consumeKey2 = true;
|
||||
if (key.isKeyCode(juce::KeyPress::escapeKey)) {
|
||||
obj.disableMouseRotation();
|
||||
} else if (key.getModifiers().isCommandDown() && key.getModifiers().isShiftDown() && key.getKeyCode() == 'S') {
|
||||
saveProjectAs();
|
||||
} else if (key.getModifiers().isCommandDown() && key.getKeyCode() == 'S') {
|
||||
saveProject();
|
||||
} else if (key.getModifiers().isCommandDown() && key.getKeyCode() == 'O') {
|
||||
openProject();
|
||||
} else {
|
||||
consumeKey2 = false;
|
||||
}
|
||||
|
||||
return consumeKey1 || consumeKey2;
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::newProject() {
|
||||
// TODO: open a default project
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::openProject() {
|
||||
chooser = std::make_unique<juce::FileChooser>("Load osci-render Project", juce::File::getSpecialLocation(juce::File::userHomeDirectory), "*.osci");
|
||||
auto flags = juce::FileBrowserComponent::openMode;
|
||||
|
||||
chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) {
|
||||
auto file = chooser.getResult();
|
||||
if (file != juce::File()) {
|
||||
auto data = juce::MemoryBlock();
|
||||
if (file.loadFileAsData(data)) {
|
||||
audioProcessor.setStateInformation(data.getData(), data.getSize());
|
||||
}
|
||||
audioProcessor.currentProjectFile = file.getFullPathName();
|
||||
updateTitle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::saveProject() {
|
||||
if (audioProcessor.currentProjectFile.isEmpty()) {
|
||||
saveProjectAs();
|
||||
} else {
|
||||
auto data = juce::MemoryBlock();
|
||||
audioProcessor.getStateInformation(data);
|
||||
auto file = juce::File(audioProcessor.currentProjectFile);
|
||||
file.create();
|
||||
file.replaceWithData(data.getData(), data.getSize());
|
||||
updateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::saveProjectAs() {
|
||||
chooser = std::make_unique<juce::FileChooser>("Save osci-render Project", juce::File::getSpecialLocation(juce::File::userHomeDirectory), "*.osci");
|
||||
auto flags = juce::FileBrowserComponent::saveMode;
|
||||
|
||||
chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) {
|
||||
auto file = chooser.getResult();
|
||||
if (file != juce::File()) {
|
||||
audioProcessor.currentProjectFile = file.getFullPathName();
|
||||
saveProject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::updateTitle() {
|
||||
juce::String title = "osci-render";
|
||||
if (!audioProcessor.currentProjectFile.isEmpty()) {
|
||||
title += " - " + audioProcessor.currentProjectFile;
|
||||
}
|
||||
getTopLevelComponent()->setName(title);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@
|
|||
#include "LuaComponent.h"
|
||||
#include "ObjComponent.h"
|
||||
#include "components/VolumeComponent.h"
|
||||
#include "components/MainMenuBarModel.h"
|
||||
|
||||
|
||||
class OscirenderAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::CodeDocument::Listener, public juce::AsyncUpdater {
|
||||
class OscirenderAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::CodeDocument::Listener, public juce::AsyncUpdater, public juce::ChangeListener {
|
||||
public:
|
||||
OscirenderAudioProcessorEditor(OscirenderAudioProcessor&);
|
||||
~OscirenderAudioProcessorEditor() override;
|
||||
|
|
@ -17,13 +18,21 @@ public:
|
|||
void paint(juce::Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
void initialiseCodeEditors();
|
||||
void addCodeEditor(int index);
|
||||
void removeCodeEditor(int index);
|
||||
void fileUpdated(juce::String fileName);
|
||||
void handleAsyncUpdate() override;
|
||||
void changeListenerCallback(juce::ChangeBroadcaster* source) override;
|
||||
|
||||
void editPerspectiveFunction(bool enabled);
|
||||
|
||||
void newProject();
|
||||
void openProject();
|
||||
void saveProject();
|
||||
void saveProjectAs();
|
||||
void updateTitle();
|
||||
|
||||
std::atomic<bool> editingPerspective = false;
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
|
|
@ -41,6 +50,10 @@ private:
|
|||
std::shared_ptr<juce::CodeDocument> perspectiveCodeDocument = std::make_shared<juce::CodeDocument>();
|
||||
std::shared_ptr<juce::CodeEditorComponent> perspectiveCodeEditor = std::make_shared<juce::CodeEditorComponent>(*perspectiveCodeDocument, &luaTokeniser);
|
||||
|
||||
std::unique_ptr<juce::FileChooser> chooser;
|
||||
MainMenuBarModel menuBarModel{*this};
|
||||
juce::MenuBarComponent menuBar;
|
||||
|
||||
void codeDocumentTextInserted(const juce::String& newText, int insertIndex) override;
|
||||
void codeDocumentTextDeleted(int startIndex, int endIndex) override;
|
||||
void updateCodeDocument();
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
{
|
||||
producer.startThread();
|
||||
|
||||
juce::SpinLock::ScopedLockType lock(effectsLock);
|
||||
// locking isn't necessary here because we are in the constructor
|
||||
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<BitCrushEffect>(),
|
||||
|
|
@ -46,19 +46,19 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<RotateEffect>(),
|
||||
new EffectParameter("2D Rotate", "rotateSpeed", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("2D Rotate", "2DRotateSpeed", 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<VectorCancellingEffect>(),
|
||||
new EffectParameter("Vector Cancel", "vectorCancelling", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Vector Cancelling", "vectorCancelling", 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<DistortEffect>(false),
|
||||
new EffectParameter("X Distort", "horizontalDistort", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Distort X", "distortX", 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<DistortEffect>(true),
|
||||
new EffectParameter("Y Distort", "verticalDistort", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Distort Y", "distortY", 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
|
|
@ -77,26 +77,28 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
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)}
|
||||
std::vector<EffectParameter*>{new EffectParameter("Delay Decay", "delayDecay", 0.0, 0.0, 1.0), new EffectParameter("Delay Length", "delayLength", 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),
|
||||
new EffectParameter("3D Perspective", "perspectiveStrength", 0.0, 0.0, 1.0),
|
||||
new EffectParameter("Depth (z)", "perspectiveZPos", 0.1, 0.0, 1.0),
|
||||
new EffectParameter("Rotate Speed", "perspectiveRotateSpeed", 0.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate X", "perspectiveRotateX", 1.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate Y", "perspectiveRotateY", 1.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate Z", "perspectiveRotateZ", 0.0, -1.0, 1.0),
|
||||
}
|
||||
));
|
||||
toggleableEffects.push_back(traceMax);
|
||||
toggleableEffects.push_back(traceMin);
|
||||
|
||||
for (auto& effect : toggleableEffects) {
|
||||
for (int i = 0; i < toggleableEffects.size(); i++) {
|
||||
auto effect = toggleableEffects[i];
|
||||
effect->markEnableable(false);
|
||||
addParameter(effect->enabled);
|
||||
effect->enabled->setValueNotifyingHost(false);
|
||||
effect->setPrecedence(i);
|
||||
}
|
||||
|
||||
permanentEffects.push_back(frequencyEffect);
|
||||
|
|
@ -112,11 +114,11 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
addLuaSlider();
|
||||
}
|
||||
|
||||
auto effects = toggleableEffects;
|
||||
effects.insert(effects.end(), permanentEffects.begin(), permanentEffects.end());
|
||||
effects.insert(effects.end(), luaEffects.begin(), luaEffects.end());
|
||||
allEffects = toggleableEffects;
|
||||
allEffects.insert(allEffects.end(), permanentEffects.begin(), permanentEffects.end());
|
||||
allEffects.insert(allEffects.end(), luaEffects.begin(), luaEffects.end());
|
||||
|
||||
for (auto effect : effects) {
|
||||
for (auto effect : allEffects) {
|
||||
for (auto effectParameter : effect->parameters) {
|
||||
auto parameters = effectParameter->getParameters();
|
||||
for (auto parameter : parameters) {
|
||||
|
|
@ -125,12 +127,16 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
}
|
||||
}
|
||||
|
||||
addParameter(fixedRotateX);
|
||||
addParameter(fixedRotateY);
|
||||
addParameter(fixedRotateZ);
|
||||
addParameter(perspectiveEffect->fixedRotateX);
|
||||
addParameter(perspectiveEffect->fixedRotateY);
|
||||
addParameter(perspectiveEffect->fixedRotateZ);
|
||||
booleanParameters.push_back(fixedRotateX);
|
||||
booleanParameters.push_back(fixedRotateY);
|
||||
booleanParameters.push_back(fixedRotateZ);
|
||||
booleanParameters.push_back(perspectiveEffect->fixedRotateX);
|
||||
booleanParameters.push_back(perspectiveEffect->fixedRotateY);
|
||||
booleanParameters.push_back(perspectiveEffect->fixedRotateZ);
|
||||
|
||||
for (auto parameter : booleanParameters) {
|
||||
addParameter(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
OscirenderAudioProcessor::~OscirenderAudioProcessor() {}
|
||||
|
|
@ -257,6 +263,26 @@ void OscirenderAudioProcessor::updateObjValues() {
|
|||
rotateSpeed->apply();
|
||||
}
|
||||
|
||||
// effectsLock should be held when calling this
|
||||
std::shared_ptr<Effect> OscirenderAudioProcessor::getEffect(juce::String id) {
|
||||
for (auto& effect : allEffects) {
|
||||
if (effect->getId() == id) {
|
||||
return effect;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// effectsLock should be held when calling this
|
||||
BooleanParameter* OscirenderAudioProcessor::getBooleanParameter(juce::String id) {
|
||||
for (auto& parameter : booleanParameters) {
|
||||
if (parameter->paramID == id) {
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// effectsLock MUST be held when calling this
|
||||
void OscirenderAudioProcessor::updateEffectPrecedence() {
|
||||
auto sortFunc = [](std::shared_ptr<Effect> a, std::shared_ptr<Effect> b) {
|
||||
|
|
@ -294,15 +320,28 @@ void OscirenderAudioProcessor::addFile(juce::String fileName, const char* data,
|
|||
openFile(fileBlocks.size() - 1);
|
||||
}
|
||||
|
||||
// parsersLock AND effectsLock must be locked before calling this function
|
||||
void OscirenderAudioProcessor::addFile(juce::String fileName, std::shared_ptr<juce::MemoryBlock> data) {
|
||||
fileBlocks.push_back(data);
|
||||
fileNames.push_back(fileName);
|
||||
parsers.push_back(std::make_unique<FileParser>());
|
||||
|
||||
openFile(fileBlocks.size() - 1);
|
||||
}
|
||||
|
||||
// parsersLock AND effectsLock must be locked before calling this function
|
||||
void OscirenderAudioProcessor::removeFile(int index) {
|
||||
if (index < 0 || index >= fileBlocks.size()) {
|
||||
return;
|
||||
}
|
||||
changeCurrentFile(index - 1);
|
||||
fileBlocks.erase(fileBlocks.begin() + index);
|
||||
fileNames.erase(fileNames.begin() + index);
|
||||
parsers.erase(parsers.begin() + index);
|
||||
parsers.erase(parsers.begin() + index);
|
||||
auto newFileIndex = index;
|
||||
if (newFileIndex >= fileBlocks.size()) {
|
||||
newFileIndex = fileBlocks.size() - 1;
|
||||
}
|
||||
changeCurrentFile(newFileIndex);
|
||||
}
|
||||
|
||||
int OscirenderAudioProcessor::numFiles() {
|
||||
|
|
@ -553,28 +592,114 @@ void OscirenderAudioProcessor::incrementShapeDrawing() {
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
bool OscirenderAudioProcessor::hasEditor() const
|
||||
{
|
||||
bool OscirenderAudioProcessor::hasEditor() const {
|
||||
return true; // (change this to false if you choose to not supply an editor)
|
||||
}
|
||||
|
||||
juce::AudioProcessorEditor* OscirenderAudioProcessor::createEditor()
|
||||
{
|
||||
juce::AudioProcessorEditor* OscirenderAudioProcessor::createEditor() {
|
||||
return new OscirenderAudioProcessorEditor (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void OscirenderAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
|
||||
{
|
||||
// You should use this method to store your parameters in the memory block.
|
||||
// You could do that either as raw data, or use the XML or ValueTree classes
|
||||
// as intermediaries to make it easy to save and load complex data.
|
||||
void OscirenderAudioProcessor::getStateInformation(juce::MemoryBlock& destData) {
|
||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
|
||||
std::unique_ptr<juce::XmlElement> xml = std::make_unique<juce::XmlElement>("project");
|
||||
xml->setAttribute("version", ProjectInfo::versionString);
|
||||
auto effectsXml = xml->createNewChildElement("effects");
|
||||
for (auto effect : allEffects) {
|
||||
effect->save(effectsXml->createNewChildElement("effect"));
|
||||
}
|
||||
|
||||
auto booleanParametersXml = xml->createNewChildElement("booleanParameters");
|
||||
for (auto parameter : booleanParameters) {
|
||||
auto parameterXml = booleanParametersXml->createNewChildElement("parameter");
|
||||
parameter->save(parameterXml);
|
||||
}
|
||||
|
||||
auto perspectiveFunction = xml->createNewChildElement("perspectiveFunction");
|
||||
perspectiveFunction->addTextElement(juce::Base64::toBase64(perspectiveEffect->getCode()));
|
||||
auto filesXml = xml->createNewChildElement("files");
|
||||
|
||||
for (int i = 0; i < fileBlocks.size(); i++) {
|
||||
auto fileXml = filesXml->createNewChildElement("file");
|
||||
fileXml->setAttribute("name", fileNames[i]);
|
||||
auto fileString = juce::MemoryInputStream(*fileBlocks[i], false).readEntireStreamAsString();
|
||||
fileXml->addTextElement(juce::Base64::toBase64(fileString));
|
||||
}
|
||||
xml->setAttribute("currentFile", currentFile);
|
||||
copyXmlToBinary(*xml, destData);
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
||||
{
|
||||
// You should use this method to restore your parameters from this memory block,
|
||||
// whose contents will have been created by the getStateInformation() call.
|
||||
void OscirenderAudioProcessor::setStateInformation(const void* data, int sizeInBytes) {
|
||||
std::unique_ptr<juce::XmlElement> xml;
|
||||
|
||||
const uint32_t magicXmlNumber = 0x21324356;
|
||||
if (sizeInBytes > 8 && juce::ByteOrder::littleEndianInt(data) == magicXmlNumber) {
|
||||
// this is a binary xml format
|
||||
xml = getXmlFromBinary(data, sizeInBytes);
|
||||
} else {
|
||||
// this is a text xml format
|
||||
xml = juce::XmlDocument::parse(juce::String((const char*)data, sizeInBytes));
|
||||
}
|
||||
|
||||
if (xml.get() != nullptr && xml->hasTagName("project")) {
|
||||
auto versionXml = xml->getChildByName("version");
|
||||
if (versionXml != nullptr && versionXml->getAllSubText().startsWith("v1.")) {
|
||||
openLegacyProject(xml.get());
|
||||
return;
|
||||
}
|
||||
|
||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
|
||||
auto effectsXml = xml->getChildByName("effects");
|
||||
if (effectsXml != nullptr) {
|
||||
for (auto effectXml : effectsXml->getChildIterator()) {
|
||||
auto effect = getEffect(effectXml->getStringAttribute("id"));
|
||||
if (effect != nullptr) {
|
||||
effect->load(effectXml);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateEffectPrecedence();
|
||||
|
||||
auto booleanParametersXml = xml->getChildByName("booleanParameters");
|
||||
if (booleanParametersXml != nullptr) {
|
||||
for (auto parameterXml : booleanParametersXml->getChildIterator()) {
|
||||
auto parameter = getBooleanParameter(parameterXml->getStringAttribute("id"));
|
||||
if (parameter != nullptr) {
|
||||
parameter->load(parameterXml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto perspectiveFunction = xml->getChildByName("perspectiveFunction");
|
||||
if (perspectiveFunction != nullptr) {
|
||||
auto stream = juce::MemoryOutputStream();
|
||||
juce::Base64::convertFromBase64(stream, perspectiveFunction->getAllSubText());
|
||||
perspectiveEffect->updateCode(stream.toString());
|
||||
}
|
||||
// close all files
|
||||
auto numFiles = fileBlocks.size();
|
||||
for (int i = 0; i < numFiles; i++) {
|
||||
removeFile(0);
|
||||
}
|
||||
|
||||
auto filesXml = xml->getChildByName("files");
|
||||
if (filesXml != nullptr) {
|
||||
for (auto fileXml : filesXml->getChildIterator()) {
|
||||
auto fileName = fileXml->getStringAttribute("name");
|
||||
auto stream = juce::MemoryOutputStream();
|
||||
juce::Base64::convertFromBase64(stream, fileXml->getAllSubText());
|
||||
auto fileBlock = std::make_shared<juce::MemoryBlock>(stream.getData(), stream.getDataSize());
|
||||
addFile(fileName, fileBlock);
|
||||
}
|
||||
}
|
||||
changeCurrentFile(xml->getIntAttribute("currentFile", -1));
|
||||
broadcaster.sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -32,11 +32,9 @@ class OscirenderAudioProcessor : public juce::AudioProcessor
|
|||
, public FrameConsumer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
OscirenderAudioProcessor();
|
||||
~OscirenderAudioProcessor() override;
|
||||
|
||||
//==============================================================================
|
||||
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
|
||||
void releaseResources() override;
|
||||
|
||||
|
|
@ -46,28 +44,22 @@ public:
|
|||
|
||||
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
|
||||
|
||||
//==============================================================================
|
||||
juce::AudioProcessorEditor* createEditor() override;
|
||||
bool hasEditor() const override;
|
||||
|
||||
//==============================================================================
|
||||
const juce::String getName() const override;
|
||||
|
||||
bool acceptsMidi() const override;
|
||||
bool producesMidi() const override;
|
||||
bool isMidiEffect() const override;
|
||||
double getTailLengthSeconds() const override;
|
||||
|
||||
//==============================================================================
|
||||
int getNumPrograms() override;
|
||||
int getCurrentProgram() override;
|
||||
void setCurrentProgram (int index) override;
|
||||
const juce::String getProgramName (int index) override;
|
||||
void changeProgramName (int index, const juce::String& newName) override;
|
||||
|
||||
//==============================================================================
|
||||
void getStateInformation (juce::MemoryBlock& destData) override;
|
||||
void setStateInformation (const void* data, int sizeInBytes) override;
|
||||
void setCurrentProgram(int index) override;
|
||||
const juce::String getProgramName(int index) override;
|
||||
void changeProgramName(int index, const juce::String& newName) override;
|
||||
void getStateInformation(juce::MemoryBlock& destData) override;
|
||||
void setStateInformation(const void* data, int sizeInBytes) override;
|
||||
|
||||
std::atomic<double> currentSampleRate = 0.0;
|
||||
|
||||
|
|
@ -104,7 +96,7 @@ public:
|
|||
camera->setFocalLength(values[0]);
|
||||
}
|
||||
return input;
|
||||
}, new EffectParameter("Focal length", "focalLength", 1.0, 0.0, 2.0)
|
||||
}, new EffectParameter("Focal length", "objFocalLength", 1.0, 0.0, 2.0)
|
||||
);
|
||||
|
||||
BooleanParameter* fixedRotateX = new BooleanParameter("Object Fixed Rotate X", "objFixedRotateX", false);
|
||||
|
|
@ -175,6 +167,8 @@ public:
|
|||
std::vector<juce::String> fileNames;
|
||||
std::atomic<int> currentFile = -1;
|
||||
|
||||
juce::ChangeBroadcaster broadcaster;
|
||||
|
||||
FrameProducer producer = FrameProducer(*this, std::make_shared<FileParser>());
|
||||
|
||||
BufferProducer audioProducer;
|
||||
|
|
@ -182,12 +176,17 @@ public:
|
|||
PitchDetector pitchDetector{audioProducer};
|
||||
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(pitchDetector);
|
||||
|
||||
// shouldn't be accessed by audio thread, but needs to persist when GUI is closed
|
||||
// so should only be accessed by message thread
|
||||
juce::String currentProjectFile;
|
||||
|
||||
void addLuaSlider();
|
||||
void addFrame(std::vector<std::unique_ptr<Shape>> frame, int fileIndex) override;
|
||||
void updateEffectPrecedence();
|
||||
void updateFileBlock(int index, std::shared_ptr<juce::MemoryBlock> block);
|
||||
void addFile(juce::File file);
|
||||
void addFile(juce::String fileName, const char* data, const int size);
|
||||
void addFile(juce::String fileName, std::shared_ptr<juce::MemoryBlock> data);
|
||||
void removeFile(int index);
|
||||
int numFiles();
|
||||
void changeCurrentFile(int index);
|
||||
|
|
@ -214,6 +213,8 @@ private:
|
|||
double lengthIncrement = 0.0;
|
||||
bool invalidateFrameBuffer = false;
|
||||
|
||||
std::vector<BooleanParameter*> booleanParameters;
|
||||
std::vector<std::shared_ptr<Effect>> allEffects;
|
||||
std::vector<std::shared_ptr<Effect>> permanentEffects;
|
||||
|
||||
std::shared_ptr<Effect> traceMax = std::make_shared<Effect>(
|
||||
|
|
@ -246,6 +247,12 @@ private:
|
|||
void openFile(int index);
|
||||
void updateLuaValues();
|
||||
void updateObjValues();
|
||||
std::shared_ptr<Effect> getEffect(juce::String id);
|
||||
BooleanParameter* getBooleanParameter(juce::String id);
|
||||
void openLegacyProject(const juce::XmlElement* xml);
|
||||
std::pair<std::shared_ptr<Effect>, EffectParameter*> effectFromLegacyId(const juce::String& id, bool updatePrecedence = false);
|
||||
LfoType lfoTypeFromLegacyAnimationType(const juce::String& type);
|
||||
double valueFromLegacy(double value, const juce::String& id);
|
||||
|
||||
const double MIN_LENGTH_INCREMENT = 0.000001;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,20 +2,9 @@
|
|||
#include "../shape/Vector2.h"
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class BooleanParameter : public juce::AudioProcessorParameter {
|
||||
class BooleanParameter : public juce::AudioProcessorParameterWithID {
|
||||
public:
|
||||
juce::String name;
|
||||
juce::String id;
|
||||
|
||||
BooleanParameter(juce::String name, juce::String id, bool value) : name(name), id(id), value(value) {}
|
||||
|
||||
// COPY CONSTRUCTOR SHOULD ONLY BE USED BEFORE
|
||||
// THE OBJECT IS USED IN MULTIPLE THREADS
|
||||
BooleanParameter(const BooleanParameter& other) {
|
||||
name = other.name;
|
||||
id = other.id;
|
||||
value.store(other.value.load());
|
||||
}
|
||||
BooleanParameter(juce::String name, juce::String id, bool value) : AudioProcessorParameterWithID(id, name), value(value) {}
|
||||
|
||||
juce::String getName(int maximumStringLength) const override {
|
||||
return name.substring(0, maximumStringLength);
|
||||
|
|
@ -86,6 +75,15 @@ public:
|
|||
return juce::AudioProcessorParameter::genericParameter;
|
||||
}
|
||||
|
||||
void save(juce::XmlElement* xml) {
|
||||
xml->setAttribute("id", paramID);
|
||||
xml->setAttribute("value", value.load());
|
||||
}
|
||||
|
||||
void load(juce::XmlElement* xml) {
|
||||
setBoolValueNotifyingHost(xml->getBoolAttribute("value", getDefaultValue()));
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> value = false;
|
||||
};
|
||||
|
|
@ -148,3 +148,42 @@ juce::String Effect::getId() {
|
|||
juce::String Effect::getName() {
|
||||
return parameters[0]->name;
|
||||
}
|
||||
|
||||
void Effect::save(juce::XmlElement* xml) {
|
||||
if (enabled != nullptr) {
|
||||
auto enabledXml = xml->createNewChildElement("enabled");
|
||||
enabled->save(enabledXml);
|
||||
}
|
||||
xml->setAttribute("id", getId());
|
||||
xml->setAttribute("precedence", precedence);
|
||||
for (auto parameter : parameters) {
|
||||
parameter->save(xml->createNewChildElement("parameter"));
|
||||
}
|
||||
}
|
||||
|
||||
void Effect::load(juce::XmlElement* xml) {
|
||||
if (enabled != nullptr) {
|
||||
auto enabledXml = xml->getChildByName("enabled");
|
||||
if (enabledXml != nullptr) {
|
||||
enabled->load(enabledXml);
|
||||
}
|
||||
}
|
||||
if (xml->hasAttribute("precedence")) {
|
||||
setPrecedence(xml->getIntAttribute("precedence"));
|
||||
}
|
||||
for (auto parameterXml : xml->getChildIterator()) {
|
||||
auto parameter = getParameter(parameterXml->getStringAttribute("id"));
|
||||
if (parameter != nullptr) {
|
||||
parameter->load(parameterXml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EffectParameter* Effect::getParameter(juce::String id) {
|
||||
for (auto parameter : parameters) {
|
||||
if (parameter->paramID == id) {
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ public:
|
|||
void markEnableable(bool enabled);
|
||||
juce::String getId();
|
||||
juce::String getName();
|
||||
void save(juce::XmlElement* xml);
|
||||
void load(juce::XmlElement* xml);
|
||||
EffectParameter* getParameter(juce::String id);
|
||||
|
||||
std::vector<EffectParameter*> parameters;
|
||||
BooleanParameter* enabled;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,30 @@ public:
|
|||
return juce::AudioProcessorParameter::genericParameter;
|
||||
}
|
||||
|
||||
void save(juce::XmlElement* xml) {
|
||||
xml->setAttribute("id", paramID);
|
||||
xml->setAttribute("value", value.load());
|
||||
xml->setAttribute("min", min.load());
|
||||
xml->setAttribute("max", max.load());
|
||||
xml->setAttribute("step", step.load());
|
||||
}
|
||||
|
||||
// opt to not change any values if not found
|
||||
void load(juce::XmlElement* xml) {
|
||||
if (xml->hasAttribute("value")) {
|
||||
value = xml->getDoubleAttribute("value");
|
||||
}
|
||||
if (xml->hasAttribute("min")) {
|
||||
min = xml->getDoubleAttribute("min");
|
||||
}
|
||||
if (xml->hasAttribute("max")) {
|
||||
max = xml->getDoubleAttribute("max");
|
||||
}
|
||||
if (xml->hasAttribute("step")) {
|
||||
step = xml->getDoubleAttribute("step");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// value is not necessarily in the range [min, max] so effect applications may need to clip to a valid range
|
||||
std::atomic<float> value = 0.0;
|
||||
|
|
@ -239,26 +263,36 @@ public:
|
|||
}
|
||||
|
||||
float getValueForText(const juce::String& text) const override {
|
||||
int unnormalisedValue;
|
||||
if (text == "Static") {
|
||||
return (int)LfoType::Static;
|
||||
unnormalisedValue = (int)LfoType::Static;
|
||||
} else if (text == "Sine") {
|
||||
return (int)LfoType::Sine;
|
||||
unnormalisedValue = (int)LfoType::Sine;
|
||||
} else if (text == "Square") {
|
||||
return (int)LfoType::Square;
|
||||
unnormalisedValue = (int)LfoType::Square;
|
||||
} else if (text == "Seesaw") {
|
||||
return (int)LfoType::Seesaw;
|
||||
unnormalisedValue = (int)LfoType::Seesaw;
|
||||
} else if (text == "Triangle") {
|
||||
return (int)LfoType::Triangle;
|
||||
unnormalisedValue = (int)LfoType::Triangle;
|
||||
} else if (text == "Sawtooth") {
|
||||
return (int)LfoType::Sawtooth;
|
||||
unnormalisedValue = (int)LfoType::Sawtooth;
|
||||
} else if (text == "Reverse Sawtooth") {
|
||||
return (int)LfoType::ReverseSawtooth;
|
||||
unnormalisedValue = (int)LfoType::ReverseSawtooth;
|
||||
} else if (text == "Noise") {
|
||||
return (int)LfoType::Noise;
|
||||
unnormalisedValue = (int)LfoType::Noise;
|
||||
} else {
|
||||
return (int)LfoType::Static;
|
||||
unnormalisedValue = (int)LfoType::Static;
|
||||
}
|
||||
return getNormalisedValue(unnormalisedValue);
|
||||
}
|
||||
|
||||
void save(juce::XmlElement* xml) {
|
||||
xml->setAttribute("lfo", getText(getValue(), 100));
|
||||
}
|
||||
|
||||
void load(juce::XmlElement* xml) {
|
||||
setValueNotifyingHost(getValueForText(xml->getStringAttribute("lfo")));
|
||||
}
|
||||
};
|
||||
|
||||
class EffectParameter : public FloatParameter {
|
||||
|
|
@ -287,5 +321,25 @@ public:
|
|||
lfoRate = nullptr;
|
||||
}
|
||||
|
||||
void save(juce::XmlElement* xml) {
|
||||
FloatParameter::save(xml);
|
||||
|
||||
if (lfo != nullptr && lfoRate != nullptr) {
|
||||
auto lfoXml = xml->createNewChildElement("lfo");
|
||||
lfo->save(lfoXml);
|
||||
lfoRate->save(lfoXml);
|
||||
}
|
||||
}
|
||||
|
||||
void load(juce::XmlElement* xml) {
|
||||
FloatParameter::load(xml);
|
||||
|
||||
auto lfoXml = xml->getChildByName("lfo");
|
||||
if (lfoXml != nullptr) {
|
||||
lfo->load(lfoXml);
|
||||
lfoRate->load(lfoXml);
|
||||
}
|
||||
}
|
||||
|
||||
EffectParameter(juce::String name, juce::String id, float value, float min, float max, float step = 0.001, bool smoothValueChange = true) : FloatParameter(name, id, value, min, max, step), smoothValueChange(smoothValueChange) {}
|
||||
};
|
||||
|
|
@ -46,9 +46,6 @@ Vector2 PerspectiveEffect::apply(int index, Vector2 input, const std::vector<dou
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@ void EffectsListComponent::resized() {
|
|||
}
|
||||
|
||||
std::shared_ptr<juce::Component> EffectsListComponent::createComponent(EffectParameter* parameter) {
|
||||
if (parameter->paramID == "rotateX" || parameter->paramID == "rotateY" || parameter->paramID == "rotateZ") {
|
||||
if (parameter->paramID == "perspectiveRotateX" || parameter->paramID == "perspectiveRotateY" || parameter->paramID == "perspectiveRotateZ") {
|
||||
BooleanParameter* toggle;
|
||||
if (parameter->paramID == "rotateX") {
|
||||
if (parameter->paramID == "perspectiveRotateX") {
|
||||
toggle = audioProcessor.perspectiveEffect->fixedRotateX;
|
||||
} else if (parameter->paramID == "rotateY") {
|
||||
} else if (parameter->paramID == "perspectiveRotateY") {
|
||||
toggle = audioProcessor.perspectiveEffect->fixedRotateY;
|
||||
} else if (parameter->paramID == "rotateZ") {
|
||||
} else if (parameter->paramID == "perspectiveRotateZ") {
|
||||
toggle = audioProcessor.perspectiveEffect->fixedRotateZ;
|
||||
}
|
||||
std::shared_ptr<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::fixed_rotate_svg, "white", "red", toggle);
|
||||
|
|
@ -88,7 +88,7 @@ std::shared_ptr<juce::Component> EffectsListComponent::createComponent(EffectPar
|
|||
toggle->setBoolValueNotifyingHost(!toggle->getBoolValue());
|
||||
};
|
||||
return button;
|
||||
} else if (parameter->paramID == "depthScale") {
|
||||
} else if (parameter->paramID == "perspectiveStrength") {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,19 @@ struct AudioEffectListBoxItemData : public DraggableListBoxItemData
|
|||
OscirenderAudioProcessor& audioProcessor;
|
||||
OscirenderAudioProcessorEditor& editor;
|
||||
|
||||
AudioEffectListBoxItemData(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), editor(editor) {}
|
||||
AudioEffectListBoxItemData(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), editor(editor) {
|
||||
resetData();
|
||||
}
|
||||
|
||||
void resetData() {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
data.clear();
|
||||
for (int i = 0; i < audioProcessor.toggleableEffects.size(); i++) {
|
||||
auto effect = audioProcessor.toggleableEffects[i];
|
||||
effect->setValue(effect->getValue());
|
||||
data.push_back(effect);
|
||||
}
|
||||
}
|
||||
|
||||
int getNumItems() override {
|
||||
return data.size();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
#include "MainMenuBarModel.h"
|
||||
#include "../PluginEditor.h"
|
||||
|
||||
juce::StringArray MainMenuBarModel::getMenuBarNames() {
|
||||
return juce::StringArray("File");
|
||||
}
|
||||
|
||||
juce::PopupMenu MainMenuBarModel::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
|
||||
juce::PopupMenu menu;
|
||||
menu.addItem(1, "Open");
|
||||
menu.addItem(2, "Save");
|
||||
menu.addItem(3, "Save As");
|
||||
return menu;
|
||||
}
|
||||
|
||||
void MainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
|
||||
switch (menuItemID) {
|
||||
case 1:
|
||||
editor.openProject();
|
||||
break;
|
||||
case 2:
|
||||
editor.saveProject();
|
||||
break;
|
||||
case 3:
|
||||
editor.saveProjectAs();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MainMenuBarModel::menuBarActivated(bool isActive) {}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class MainMenuBarModel : public juce::MenuBarModel {
|
||||
public:
|
||||
MainMenuBarModel(OscirenderAudioProcessorEditor& editor) : editor(editor) {}
|
||||
~MainMenuBarModel() override {}
|
||||
|
||||
juce::StringArray getMenuBarNames() override;
|
||||
juce::PopupMenu getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) override;
|
||||
void menuItemSelected(int menuItemID, int topLevelMenuIndex) override;
|
||||
void menuBarActivated(bool isActive);
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessorEditor& editor;
|
||||
};
|
||||
|
|
@ -4,7 +4,8 @@
|
|||
addUsingNamespaceToJuceHeader="0" displaySplashScreen="0" jucerFormatVersion="1"
|
||||
pluginCharacteristicsValue="pluginProducesMidiOut,pluginWantsMidiIn"
|
||||
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
|
||||
cppLanguageStandard="20" projectLineFeed=" " headerPath="./include">
|
||||
cppLanguageStandard="20" projectLineFeed=" " headerPath="./include"
|
||||
version="2.0.0">
|
||||
<MAINGROUP id="j5Ge2T" name="osci-render">
|
||||
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
|
||||
<GROUP id="{C2609827-4F4A-1ADA-8BA1-A40C1D92649C}" name="lua">
|
||||
|
|
@ -112,6 +113,10 @@
|
|||
file="Source/components/LuaListComponent.cpp"/>
|
||||
<FILE id="x0Syav" name="LuaListComponent.h" compile="0" resource="0"
|
||||
file="Source/components/LuaListComponent.h"/>
|
||||
<FILE id="CfBFAE" name="MainMenuBarModel.cpp" compile="1" resource="0"
|
||||
file="Source/components/MainMenuBarModel.cpp"/>
|
||||
<FILE id="ubDcnO" name="MainMenuBarModel.h" compile="0" resource="0"
|
||||
file="Source/components/MainMenuBarModel.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"
|
||||
|
|
@ -294,6 +299,8 @@
|
|||
<FILE id="eAqAle" name="IXWebSocketVersion.h" compile="0" resource="0"
|
||||
file="Source/ixwebsocket/IXWebSocketVersion.h"/>
|
||||
</GROUP>
|
||||
<FILE id="uyOdTl" name="LegacyProject.cpp" compile="1" resource="0"
|
||||
file="Source/LegacyProject.cpp"/>
|
||||
<GROUP id="{75F6236A-68A5-85DA-EDAE-23D1621601DB}" name="lua">
|
||||
<FILE id="X5i9iw" name="lapi.c" compile="1" resource="0" file="Source/lua/lapi.c"/>
|
||||
<FILE id="J62WSE" name="lapi.h" compile="0" resource="0" file="Source/lua/lapi.h"/>
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue