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() { | ||||
|  | @ -47,4 +44,9 @@ 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)) { | ||||
| 
 | ||||
|         if (changedFile) { | ||||
|             audioProcessor.changeCurrentFile(currentFile); | ||||
|             fileUpdated(audioProcessor.getCurrentFileName()); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     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 { | ||||
| 		consumeKey = false; | ||||
|         consumeKey2 = false; | ||||
| 	} | ||||
| 
 | ||||
|     if (changedFile) { | ||||
|         audioProcessor.changeCurrentFile(currentFile); | ||||
|         fileUpdated(audioProcessor.getCurrentFileName()); | ||||
|     } | ||||
| 
 | ||||
|     return consumeKey; | ||||
|     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(); | ||||
|  |  | |||
|  | @ -33,8 +33,8 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() | |||
| #endif | ||||
|     { | ||||
|     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); | ||||
|  | @ -174,6 +166,8 @@ public: | |||
|     std::vector<std::shared_ptr<juce::MemoryBlock>> fileBlocks; | ||||
|     std::vector<juce::String> fileNames; | ||||
|     std::atomic<int> currentFile = -1; | ||||
| 
 | ||||
|     juce::ChangeBroadcaster broadcaster; | ||||
|      | ||||
|     FrameProducer producer = FrameProducer(*this, std::make_shared<FileParser>()); | ||||
| 
 | ||||
|  | @ -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
	
	 James H Ball
						James H Ball