simple scene/preset handling

pull/295/head
Fabian Gonzalez 2025-04-06 00:54:43 -04:00
rodzic 963a7f28a8
commit ec22c05921
9 zmienionych plików z 441 dodań i 132 usunięć

Wyświetl plik

@ -49,12 +49,6 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP
listBox.setModel(&listBoxModel); listBox.setModel(&listBoxModel);
addAndMakeVisible(listBox); addAndMakeVisible(listBox);
// Add preset buttons and set up callbacks
addAndMakeVisible(loadPresetButton);
addAndMakeVisible(savePresetButton);
loadPresetButton.onClick = [this] { loadPresetClicked(); };
savePresetButton.onClick = [this] { savePresetClicked(); };
setTextLabelPosition(juce::Justification::centred); setTextLabelPosition(juce::Justification::centred);
} }
@ -67,12 +61,6 @@ void EffectsComponent::resized() {
auto area = getLocalBounds(); auto area = getLocalBounds();
auto titleBar = area.removeFromTop(30); auto titleBar = area.removeFromTop(30);
titleBar.removeFromLeft(100); titleBar.removeFromLeft(100);
// Position preset buttons near the top
// auto topArea = area.removeFromTop(25);
// Position the preset buttons below the title bar on the right
savePresetButton.setBounds(titleBar.removeFromRight(50));
loadPresetButton.setBounds(titleBar.removeFromRight(50 + 5)); // Add 5px spacing
randomiseButton.setBounds(titleBar.removeFromLeft(20)); randomiseButton.setBounds(titleBar.removeFromLeft(20));
area = area.reduced(20); area = area.reduced(20);
@ -86,42 +74,3 @@ void EffectsComponent::changeListenerCallback(juce::ChangeBroadcaster* source) {
itemData.resetData(); itemData.resetData();
listBox.updateContent(); listBox.updateContent();
} }
// Add implementations for button handlers
void EffectsComponent::loadPresetClicked()
{
presetChooser = std::make_unique<juce::FileChooser>("Load OsciPreset",
juce::File::getSpecialLocation(juce::File::userDocumentsDirectory),
"*.ospreset");
auto chooserFlags = juce::FileBrowserComponent::openMode |
juce::FileBrowserComponent::canSelectFiles;
presetChooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fc)
{
auto file = fc.getResult();
if (file != juce::File{})
audioProcessor.loadPreset(file);
});
}
void EffectsComponent::savePresetClicked()
{
presetChooser = std::make_unique<juce::FileChooser>("Save OsciPreset",
juce::File::getSpecialLocation(juce::File::userDocumentsDirectory),
"*.ospreset");
auto chooserFlags = juce::FileBrowserComponent::saveMode |
juce::FileBrowserComponent::canSelectFiles;
presetChooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fc)
{
auto file = fc.getResult();
if (file != juce::File{})
{
// Ensure the file has the correct extension
if (!file.hasFileExtension(".ospreset"))
file = file.withFileExtension(".ospreset");
audioProcessor.savePreset(file);
}
});
}

Wyświetl plik

@ -33,12 +33,14 @@ private:
EffectComponent frequency = EffectComponent(*audioProcessor.frequencyEffect, false); EffectComponent frequency = EffectComponent(*audioProcessor.frequencyEffect, false);
juce::TextButton loadPresetButton { "Load" }; // Remove preset buttons and chooser
juce::TextButton savePresetButton { "Save" }; // juce::TextButton loadPresetButton { "Load" };
std::unique_ptr<juce::FileChooser> presetChooser; // juce::TextButton savePresetButton { "Save" };
// std::unique_ptr<juce::FileChooser> presetChooser;
void loadPresetClicked(); // Remove preset click handlers
void savePresetClicked(); // void loadPresetClicked();
// void savePresetClicked();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsComponent) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsComponent)
}; };

Wyświetl plik

@ -2,7 +2,7 @@
#include "PluginEditor.h" #include "PluginEditor.h"
#include "CustomStandaloneFilterWindow.h" #include "CustomStandaloneFilterWindow.h"
OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p) : CommonPluginEditor(p, "osci-render", "osci", 1100, 750), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white) { OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p) : CommonPluginEditor(p, "osci-render", "osci", 1100, 750), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white), presetComponent(p), mainComponent(p, *this) {
#if !SOSCI_FEATURES #if !SOSCI_FEATURES
addAndMakeVisible(upgradeButton); addAndMakeVisible(upgradeButton);
upgradeButton.onClick = [this] { upgradeButton.onClick = [this] {
@ -82,6 +82,9 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
#endif #endif
initialiseMenuBar(model); initialiseMenuBar(model);
addAndMakeVisible(presetComponent);
addAndMakeVisible(mainComponent);
} }
OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() { OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {
@ -170,7 +173,22 @@ void OscirenderAudioProcessorEditor::resized() {
if (audioProcessor.visualiserParameters.visualiserFullScreen->getBoolValue()) { if (audioProcessor.visualiserParameters.visualiserFullScreen->getBoolValue()) {
visualiser.setBounds(area); visualiser.setBounds(area);
presetComponent.setVisible(false);
settings.setVisible(false);
resizerBar.setVisible(false);
luaResizerBar.setVisible(false);
lua.setVisible(false);
console.setVisible(false);
collapseButton.setVisible(false);
setCodeEditorVisible(false);
if (!usingNativeMenuBar) menuBar.setVisible(false);
volume.setVisible(false);
return; return;
} else {
presetComponent.setVisible(true);
settings.setVisible(true);
if (!usingNativeMenuBar) menuBar.setVisible(true);
volume.setVisible(true);
} }
if (!usingNativeMenuBar) { if (!usingNativeMenuBar) {
@ -186,8 +204,11 @@ void OscirenderAudioProcessorEditor::resized() {
auto volumeArea = area.removeFromLeft(30); auto volumeArea = area.removeFromLeft(30);
volume.setBounds(volumeArea.withSizeKeepingCentre(volumeArea.getWidth(), juce::jmin(volumeArea.getHeight(), 300))); volume.setBounds(volumeArea.withSizeKeepingCentre(volumeArea.getWidth(), juce::jmin(volumeArea.getHeight(), 300)));
area.removeFromLeft(3); area.removeFromLeft(3);
bool editorVisible = false;
auto presetHeight = 160;
presetComponent.setBounds(area.removeFromTop(presetHeight));
bool editorVisible = false;
{ {
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
@ -208,9 +229,7 @@ void OscirenderAudioProcessorEditor::resized() {
juce::Component* columns[] = { &dummy, &resizerBar, &dummy2 }; juce::Component* columns[] = { &dummy, &resizerBar, &dummy2 };
// offsetting the y position by -1 and the height by +1 is a hack to fix a bug where the code editor layout.layOutComponents(columns, 3, area.getX(), area.getY() -1 , area.getWidth(), area.getHeight() + 1, false, true);
// doesn't draw up to the edges of the menu bar above.
layout.layOutComponents(columns, 3, area.getX(), area.getY() - 1, area.getWidth(), area.getHeight() + 1, false, true);
auto dummyBounds = dummy.getBounds(); auto dummyBounds = dummy.getBounds();
collapseButton.setBounds(dummyBounds.removeFromRight(20)); collapseButton.setBounds(dummyBounds.removeFromRight(20));
area = dummyBounds; area = dummyBounds;
@ -245,14 +264,18 @@ void OscirenderAudioProcessorEditor::resized() {
collapseButton.setVisible(ableToEditFile); collapseButton.setVisible(ableToEditFile);
if (index < codeEditors.size()) { if (!fileOpen) {
codeEditors[index]->setVisible(fileOpen); if (index < codeEditors.size()) codeEditors[index]->setVisible(false);
resizerBar.setVisible(false);
console.setVisible(false);
luaResizerBar.setVisible(false);
lua.setVisible(false);
} else {
resizerBar.setVisible(true);
console.setVisible(luaFileOpen);
luaResizerBar.setVisible(luaFileOpen);
lua.setVisible(luaFileOpen);
} }
resizerBar.setVisible(fileOpen);
console.setVisible(luaFileOpen);
luaResizerBar.setVisible(luaFileOpen);
lua.setVisible(luaFileOpen);
} }
if (editorVisible) { if (editorVisible) {
@ -269,8 +292,6 @@ void OscirenderAudioProcessorEditor::resized() {
audioProcessor.setProperty("codeEditorLayoutPreferredSize", layout.getItemCurrentRelativeSize(0)); audioProcessor.setProperty("codeEditorLayoutPreferredSize", layout.getItemCurrentRelativeSize(0));
audioProcessor.setProperty("luaLayoutPreferredSize", luaLayout.getItemCurrentRelativeSize(0)); audioProcessor.setProperty("luaLayoutPreferredSize", luaLayout.getItemCurrentRelativeSize(0));
repaint();
} }
void OscirenderAudioProcessorEditor::addCodeEditor(int index) { void OscirenderAudioProcessorEditor::addCodeEditor(int index) {
@ -297,9 +318,7 @@ void OscirenderAudioProcessorEditor::addCodeEditor(int index) {
codeDocuments.insert(codeDocuments.begin() + index, codeDocument); codeDocuments.insert(codeDocuments.begin() + index, codeDocument);
codeEditors.insert(codeEditors.begin() + index, editor); codeEditors.insert(codeEditors.begin() + index, editor);
addChildComponent(*editor); addChildComponent(*editor);
// I need to disable accessibility otherwise it doesn't work! Appears to be a JUCE issue, very annoying!
editor->setAccessible(false); editor->setAccessible(false);
// listen for changes to the code editor
codeDocument->addListener(this); codeDocument->addListener(this);
editor->getEditor().setColourScheme(colourScheme); editor->getEditor().setColourScheme(colourScheme);
} }
@ -313,7 +332,6 @@ void OscirenderAudioProcessorEditor::removeCodeEditor(int index) {
// parsersLock AND effectsLock must be locked before calling this function // parsersLock AND effectsLock must be locked before calling this function
void OscirenderAudioProcessorEditor::updateCodeEditor(bool binaryFile, bool shouldOpenEditor) { void OscirenderAudioProcessorEditor::updateCodeEditor(bool binaryFile, bool shouldOpenEditor) {
// check if any code editors are visible
bool visible = shouldOpenEditor; bool visible = shouldOpenEditor;
if (!visible) { if (!visible) {
for (int i = 0; i < codeEditors.size(); i++) { for (int i = 0; i < codeEditors.size(); i++) {
@ -338,10 +356,6 @@ void OscirenderAudioProcessorEditor::updateCodeEditor(bool binaryFile, bool shou
codeEditors[i]->setVisible(false); codeEditors[i]->setVisible(false);
} }
codeEditors[index]->setVisible(true); codeEditors[index]->setVisible(true);
// used so that codeDocumentTextInserted and codeDocumentTextDeleted know whether the parserLock
// is held by the message thread or not. We hold the lock in this function, but not when the
// code document is updated by the user editing text. Since both functions are called by the
// message thread, this is safe.
updatingDocumentsWithParserLock = true; updatingDocumentsWithParserLock = true;
if (index == 0) { if (index == 0) {
codeEditors[index]->getEditor().loadContent(audioProcessor.customEffect->getCode()); codeEditors[index]->getEditor().loadContent(audioProcessor.customEffect->getCode());
@ -380,8 +394,8 @@ void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcas
repaint(); repaint();
} else if (source == &audioProcessor.fileChangeBroadcaster) { } else if (source == &audioProcessor.fileChangeBroadcaster) {
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
// triggered when the audioProcessor changes the current file (e.g. to Blender)
settings.fileUpdated(audioProcessor.getCurrentFileName()); settings.fileUpdated(audioProcessor.getCurrentFileName());
mainComponent.updateFileLabel();
} }
} }

Wyświetl plik

@ -10,6 +10,8 @@
#include "components/LuaConsole.h" #include "components/LuaConsole.h"
#include "visualiser/VisualiserSettings.h" #include "visualiser/VisualiserSettings.h"
#include "CommonPluginEditor.h" #include "CommonPluginEditor.h"
#include "MainComponent.h"
#include "PresetComponent.h"
class OscirenderAudioProcessorEditor : public CommonPluginEditor, private juce::CodeDocument::Listener, public juce::AsyncUpdater, public juce::ChangeListener, public juce::FileDragAndDropTarget { class OscirenderAudioProcessorEditor : public CommonPluginEditor, private juce::CodeDocument::Listener, public juce::AsyncUpdater, public juce::ChangeListener, public juce::FileDragAndDropTarget {
public: public:
@ -84,5 +86,8 @@ public:
void mouseDown(const juce::MouseEvent& event) override; void mouseDown(const juce::MouseEvent& event) override;
void mouseMove(const juce::MouseEvent& event) override; void mouseMove(const juce::MouseEvent& event) override;
PresetComponent presetComponent;
MainComponent mainComponent;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessorEditor) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessorEditor)
}; };

Wyświetl plik

@ -850,30 +850,36 @@ void OscirenderAudioProcessor::envelopeChanged(EnvelopeComponent* changedEnvelop
} }
} }
void OscirenderAudioProcessor::savePreset(juce::File file) { // Rename and add metadata parameters
juce::SpinLock::ScopedLockType lock(effectsLock); // Lock to safely access effects & parameters void OscirenderAudioProcessor::saveScene(juce::File file, const juce::String& author, const juce::String& collection, const juce::String& presetName, const juce::String& notes) {
// Lock required resources (effects and potentially parsers/files)
juce::SpinLock::ScopedLockType lockEffects(effectsLock);
juce::SpinLock::ScopedLockType lockParsers(parsersLock);
std::unique_ptr<juce::XmlElement> xml = std::make_unique<juce::XmlElement>("OsciPreset"); std::unique_ptr<juce::XmlElement> xml = std::make_unique<juce::XmlElement>("OsciScene"); // Root element name changed
xml->setAttribute("version", ProjectInfo::versionString); // Use the project version xml->setAttribute("version", ProjectInfo::versionString);
// --- Save Effects --- // --- Save Metadata ---
xml->setAttribute("author", author);
xml->setAttribute("collection", collection);
xml->setAttribute("presetName", presetName);
// Use a child element for potentially longer notes
auto* notesXml = xml->createNewChildElement("Notes");
notesXml->addTextElement(notes);
// --- Save Effects (same as before) ---
auto effectsXml = xml->createNewChildElement("Effects"); auto effectsXml = xml->createNewChildElement("Effects");
// Save toggleable effects (includes state like enabled, parameter values, LFOs)
for (auto& effect : toggleableEffects) { for (auto& effect : toggleableEffects) {
effect->save(effectsXml->createNewChildElement("Effect")); effect->save(effectsXml->createNewChildElement("Effect"));
} }
// Save permanent effects (e.g., frequency parameter)
for (auto& effect : permanentEffects) { for (auto& effect : permanentEffects) {
// Only save effects relevant to sound generation/preset? Or save all for consistency?
// Let's save all permanent ones for now, mirroring getStateInformation.
effect->save(effectsXml->createNewChildElement("Effect")); effect->save(effectsXml->createNewChildElement("Effect"));
} }
// Save Lua slider effects
for (auto& effect : luaEffects) { for (auto& effect : luaEffects) {
effect->save(effectsXml->createNewChildElement("Effect")); effect->save(effectsXml->createNewChildElement("Effect"));
} }
// --- Save ADSR Parameters --- // --- Save ADSR Parameters (same as before) ---
auto adsrXml = xml->createNewChildElement("ADSR"); auto adsrXml = xml->createNewChildElement("ADSR");
attackTime->save(adsrXml->createNewChildElement("Parameter")); attackTime->save(adsrXml->createNewChildElement("Parameter"));
attackLevel->save(adsrXml->createNewChildElement("Parameter")); attackLevel->save(adsrXml->createNewChildElement("Parameter"));
@ -884,106 +890,149 @@ void OscirenderAudioProcessor::savePreset(juce::File file) {
releaseTime->save(adsrXml->createNewChildElement("Parameter")); releaseTime->save(adsrXml->createNewChildElement("Parameter"));
releaseShape->save(adsrXml->createNewChildElement("Parameter")); releaseShape->save(adsrXml->createNewChildElement("Parameter"));
// --- Save Synth Parameters --- // --- Save Synth Parameters (same as before) ---
auto synthParamsXml = xml->createNewChildElement("SynthParameters"); auto synthParamsXml = xml->createNewChildElement("SynthParameters");
voices->save(synthParamsXml->createNewChildElement("Parameter")); // Save number of voices voices->save(synthParamsXml->createNewChildElement("Parameter"));
// --- Save Lua Code --- // --- Save Lua Code (same as before) ---
auto customFunctionXml = xml->createNewChildElement("CustomFunction"); auto customFunctionXml = xml->createNewChildElement("CustomFunction");
customFunctionXml->addTextElement(juce::Base64::toBase64(customEffect->getCode())); customFunctionXml->addTextElement(juce::Base64::toBase64(customEffect->getCode()));
// --- Write to File --- // --- Save Current Visual File Info ---
if (!xml->writeToFile(file, {})) { xml->setAttribute("currentFileIndex", currentFile.load());
// Handle error - maybe log it or show an alert? if (currentFile.load() >= 0 && currentFile.load() < fileNames.size()) {
DBG("Error writing preset file: " + file.getFullPathName()); auto currentFileXml = xml->createNewChildElement("CurrentVisualFile");
// For now, just log an error message in debug builds. currentFileXml->setAttribute("name", fileNames[currentFile.load()]);
// In a real application, you might want to show a juce::AlertWindow. auto base64 = fileBlocks[currentFile.load()]->toBase64Encoding();
currentFileXml->addTextElement(base64);
}
// --- Save other relevant parameters (e.g., Main Settings, 3D Settings) ---
// Example: Saving perspective parameters
auto perspectiveParamsXml = xml->createNewChildElement("PerspectiveParameters");
perspective->parameters[0]->save(perspectiveParamsXml->createNewChildElement("Parameter")); // Strength
perspective->parameters[1]->save(perspectiveParamsXml->createNewChildElement("Parameter")); // Focal Length
// Example: Saving animation parameters
auto animParamsXml = xml->createNewChildElement("AnimationParameters");
animateFrames->save(animParamsXml->createNewChildElement("Parameter"));
animationSyncBPM->save(animParamsXml->createNewChildElement("Parameter"));
animationRate->save(animParamsXml->createNewChildElement("Parameter"));
animationOffset->save(animParamsXml->createNewChildElement("Parameter"));
// Add saving for other parameter groups as needed...
// --- Write to File (Ensure .osscene extension) ---
juce::File sceneFile = file;
if (!sceneFile.hasFileExtension(".osscene"))
sceneFile = sceneFile.withFileExtension(".osscene");
if (!xml->writeToFile(sceneFile, {})) {
DBG("Error writing scene file: " + sceneFile.getFullPathName());
} }
} }
void OscirenderAudioProcessor::loadPreset(juce::File file) { // Change return type and populate/return SceneMetadata struct
SceneMetadata OscirenderAudioProcessor::loadScene(juce::File file) {
// Keep existing preset loading logic for now
// TODO: Expand this to load the full scene state (visual file, metadata, other params)
std::unique_ptr<juce::XmlElement> xml = juce::XmlDocument::parse(file); std::unique_ptr<juce::XmlElement> xml = juce::XmlDocument::parse(file);
SceneMetadata loadedMetadata; // Create struct instance
if (xml == nullptr) { if (xml == nullptr) {
DBG("Error parsing preset file: " + file.getFullPathName()); DBG("Error parsing scene file: " + file.getFullPathName());
// Optionally show an AlertWindow here // Return empty metadata on error
return; return loadedMetadata;
} }
if (!xml->hasTagName("OsciPreset")) { // Check for OsciScene or fallback to OsciPreset for backward compatibility?
DBG("Invalid preset file format: " + file.getFullPathName()); if (!xml->hasTagName("OsciScene") && !xml->hasTagName("OsciPreset")) {
// Optionally show an AlertWindow here DBG("Invalid scene/preset file format: " + file.getFullPathName());
return; // Return empty metadata on error
return loadedMetadata;
}
bool isSceneFile = xml->hasTagName("OsciScene");
juce::SpinLock::ScopedLockType lockEffects(effectsLock);
juce::SpinLock::ScopedLockType lockParsers(parsersLock);
// --- Load Metadata (if it's a scene file) and populate struct ---
if (isSceneFile) {
loadedMetadata.author = xml->getStringAttribute("author", "");
loadedMetadata.collection = xml->getStringAttribute("collection", "");
loadedMetadata.presetName = xml->getStringAttribute("presetName", "");
auto* notesXml = xml->getChildByName("Notes");
if (notesXml != nullptr) {
loadedMetadata.notes = notesXml->getAllSubText();
}
// Log statements can be removed if desired
DBG("Loading Scene Metadata:");
DBG(" Author: " + loadedMetadata.author);
DBG(" Collection: " + loadedMetadata.collection);
DBG(" Preset Name: " + loadedMetadata.presetName);
DBG(" Notes: " + loadedMetadata.notes);
} }
// Lock effects/parameters while loading // --- Load Effects ---
juce::SpinLock::ScopedLockType lock(effectsLock);
// --- Load Effects ---
auto effectsXml = xml->getChildByName("Effects"); auto effectsXml = xml->getChildByName("Effects");
if (effectsXml != nullptr) { if (effectsXml != nullptr) {
// Iterate through all saved Effect elements
for (auto* effectXml : effectsXml->getChildIterator()) { for (auto* effectXml : effectsXml->getChildIterator()) {
if (effectXml->hasTagName("Effect")) { if (effectXml->hasTagName("Effect")) {
// Find the matching effect in our processor's lists by ID
auto effectId = effectXml->getStringAttribute("id"); auto effectId = effectXml->getStringAttribute("id");
auto effect = getEffect(effectId); // Assuming getEffect searches all relevant lists (toggleable, permanent, lua) auto effect = getEffect(effectId);
if (effect != nullptr) { if (effect != nullptr) {
effect->load(effectXml); effect->load(effectXml);
} else { } else {
DBG("Preset loading: Could not find effect with ID: " + effectId); DBG("Scene loading: Could not find effect with ID: " + effectId);
} }
} }
} }
updateEffectPrecedence(); // Re-sort toggleable effects based on loaded precedence updateEffectPrecedence();
} }
// --- Load ADSR Parameters --- // --- Load ADSR Parameters ---
auto adsrXml = xml->getChildByName("ADSR"); auto adsrXml = xml->getChildByName("ADSR");
if (adsrXml != nullptr) { if (adsrXml != nullptr) {
for (auto* parameterXml : adsrXml->getChildIterator()) { for (auto* parameterXml : adsrXml->getChildIterator()) {
if (parameterXml->hasTagName("Parameter")) { if (parameterXml->hasTagName("Parameter")) {
auto paramId = parameterXml->getStringAttribute("id"); auto paramId = parameterXml->getStringAttribute("id");
auto parameter = getFloatParameter(paramId); // Assumes ADSR params are FloatParameters auto parameter = getFloatParameter(paramId);
if (parameter != nullptr) { if (parameter != nullptr) {
parameter->load(parameterXml); parameter->load(parameterXml);
} else { } else {
DBG("Preset loading: Could not find ADSR parameter with ID: " + paramId); DBG("Scene loading: Could not find ADSR parameter with ID: " + paramId);
} }
} }
} }
// Update the internal adsrEnv object after loading parameters
adsrEnv = Env::adsr( adsrEnv = Env::adsr(
attackTime->getValueUnnormalised(), attackTime->getValueUnnormalised(),
decayTime->getValueUnnormalised(), decayTime->getValueUnnormalised(),
sustainLevel->getValueUnnormalised(), sustainLevel->getValueUnnormalised(),
releaseTime->getValueUnnormalised(), releaseTime->getValueUnnormalised(),
attackLevel->getValueUnnormalised(), // Assuming attackLevel corresponds to env's 'peakLevel' attackLevel->getValueUnnormalised(),
std::vector<EnvCurve>{ attackShape->getValueUnnormalised(), decayShape->getValueUnnormalised(), releaseShape->getValueUnnormalised() } std::vector<EnvCurve>{ attackShape->getValueUnnormalised(), decayShape->getValueUnnormalised(), releaseShape->getValueUnnormalised() }
); );
} }
// --- Load Synth Parameters --- // --- Load Synth Parameters ---
auto synthParamsXml = xml->getChildByName("SynthParameters"); auto synthParamsXml = xml->getChildByName("SynthParameters");
if (synthParamsXml != nullptr) { if (synthParamsXml != nullptr) {
for (auto* parameterXml : synthParamsXml->getChildIterator()) { for (auto* parameterXml : synthParamsXml->getChildIterator()) {
if (parameterXml->hasTagName("Parameter")) { if (parameterXml->hasTagName("Parameter")) {
auto paramId = parameterXml->getStringAttribute("id"); auto paramId = parameterXml->getStringAttribute("id");
// Check if it's the 'voices' parameter
if (paramId == voices->getParameterID()) { if (paramId == voices->getParameterID()) {
voices->load(parameterXml); voices->load(parameterXml);
// Trigger parameterValueChanged to update synth voices
parameterValueChanged(voices->getParameterIndex(), voices->getValue()); parameterValueChanged(voices->getParameterIndex(), voices->getValue());
} }
// Add checks for other synth parameters if needed
else { else {
DBG("Preset loading: Could not find Synth parameter with ID: " + paramId); DBG("Scene loading: Could not find Synth parameter with ID: " + paramId);
} }
} }
} }
} }
// --- Load Lua Code --- // --- Load Lua Code ---
auto customFunctionXml = xml->getChildByName("CustomFunction"); auto customFunctionXml = xml->getChildByName("CustomFunction");
if (customFunctionXml != nullptr) { if (customFunctionXml != nullptr) {
auto base64Code = customFunctionXml->getAllSubText(); auto base64Code = customFunctionXml->getAllSubText();
@ -991,14 +1040,93 @@ void OscirenderAudioProcessor::loadPreset(juce::File file) {
if (juce::Base64::convertFromBase64(stream, base64Code)) { if (juce::Base64::convertFromBase64(stream, base64Code)) {
customEffect->updateCode(stream.toString()); customEffect->updateCode(stream.toString());
} else { } else {
DBG("Preset loading: Failed to decode Base64 Lua code."); DBG("Scene loading: Failed to decode Base64 Lua code.");
} }
} }
// --- Notify UI/Listeners --- // --- Load Current Visual File Info (if it's a scene file) ---
// Use MessageManagerLock as sendChangeMessage needs to be called from the message thread if (isSceneFile) {
int loadedFileIndex = xml->getIntAttribute("currentFileIndex", -1);
auto* currentFileXml = xml->getChildByName("CurrentVisualFile");
if (currentFileXml != nullptr && loadedFileIndex != -1) {
auto fileName = currentFileXml->getStringAttribute("name");
auto base64Data = currentFileXml->getAllSubText();
std::shared_ptr<juce::MemoryBlock> fileBlock = std::make_shared<juce::MemoryBlock>();
if (fileBlock->fromBase64Encoding(base64Data)) {
// Need to find if this file already exists or add it
// This requires more careful handling to avoid duplicates
// and potentially clearing existing files first.
// For now, let's just try adding it. If it needs replacing,
// the logic gets more complex.
// A simpler approach might be to clear all existing files first?
// Clear existing files (Careful! This deletes user's loaded files)
// int numFiles = fileBlocks.size();
// for (int i = numFiles - 1; i >= 0; --i) {
// removeFile(i);
// }
// Then add the saved file:
// addFile(fileName, fileBlock);
// changeCurrentFile(0); // Assuming it becomes the first file
DBG("Scene loading: Visual file found - Name: " + fileName + ", Index: " + juce::String(loadedFileIndex));
DBG("Scene loading: TODO - Implement proper visual file loading logic (clearing/adding/selecting)");
} else {
DBG("Scene loading: Failed to decode Base64 visual file data.");
}
} else if (loadedFileIndex == -1) {
// If scene saved with no visual file selected, clear current file?
// changeCurrentFile(-1);
DBG("Scene loading: TODO - Implement logic for loading scene with no visual file selected.");
}
}
// --- Load other parameters (if it's a scene file) ---
if (isSceneFile) {
// Example: Loading perspective parameters
auto perspectiveParamsXml = xml->getChildByName("PerspectiveParameters");
if (perspectiveParamsXml != nullptr) {
for (auto* parameterXml : perspectiveParamsXml->getChildIterator()) {
if (parameterXml->hasTagName("Parameter")) {
auto paramId = parameterXml->getStringAttribute("id");
auto parameter = getFloatParameter(paramId); // Assuming perspective params are float
if (parameter != nullptr) {
parameter->load(parameterXml);
} else {
DBG("Scene loading: Could not find Perspective parameter with ID: " + paramId);
}
}
}
}
// Example: Loading animation parameters
auto animParamsXml = xml->getChildByName("AnimationParameters");
if (animParamsXml != nullptr) {
for (auto* parameterXml : animParamsXml->getChildIterator()) {
if (parameterXml->hasTagName("Parameter")) {
auto paramId = parameterXml->getStringAttribute("id");
// Need to check boolean and float params separately
auto boolParam = getBooleanParameter(paramId);
auto floatParam = getFloatParameter(paramId);
if (boolParam != nullptr) {
boolParam->load(parameterXml);
} else if (floatParam != nullptr) {
floatParam->load(parameterXml);
} else {
DBG("Scene loading: Could not find Animation parameter with ID: " + paramId);
}
}
}
}
// Add loading for other parameter groups as needed...
}
// --- Notify UI/Listeners ---
juce::MessageManagerLock mmlock; juce::MessageManagerLock mmlock;
broadcaster.sendChangeMessage(); broadcaster.sendChangeMessage();
// Return the populated metadata struct
return loadedMetadata;
} }
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() {

Wyświetl plik

@ -31,6 +31,14 @@
#include "CommonPluginProcessor.h" #include "CommonPluginProcessor.h"
#include "MidiAlwaysEnabled.h" #include "MidiAlwaysEnabled.h"
// Define a struct to hold scene metadata
struct SceneMetadata {
juce::String author;
juce::String collection;
juce::String presetName;
juce::String notes;
};
//============================================================================== //==============================================================================
/** /**
*/ */
@ -207,8 +215,8 @@ public:
void removeErrorListener(ErrorListener* listener); void removeErrorListener(ErrorListener* listener);
void notifyErrorListeners(int lineNumber, juce::String id, juce::String error); void notifyErrorListeners(int lineNumber, juce::String id, juce::String error);
void savePreset(juce::File file); void saveScene(juce::File file, const juce::String& author, const juce::String& collection, const juce::String& presetName, const juce::String& notes);
void loadPreset(juce::File file); SceneMetadata loadScene(juce::File file);
private: private:
std::atomic<bool> prevMidiEnabled = isMidiAlwaysEnabled() || !midiEnabled->getBoolValue(); std::atomic<bool> prevMidiEnabled = isMidiAlwaysEnabled() || !midiEnabled->getBoolValue();

Wyświetl plik

@ -0,0 +1,150 @@
#include "PresetComponent.h"
#include "PluginProcessor.h" // Include processor again if needed (usually just in .h)
//==============================================================================
PresetComponent::PresetComponent(OscirenderAudioProcessor& p) : processor(p)
{
// Setup Labels
authorLabel.setText("Author:", juce::dontSendNotification);
collectionLabel.setText("Collection:", juce::dontSendNotification);
presetNameLabel.setText("Scene Name:", juce::dontSendNotification);
notesLabel.setText("Notes:", juce::dontSendNotification);
// Setup TextEditors
presetNameEditor.setJustification(juce::Justification::centredLeft);
authorEditor.setJustification(juce::Justification::centredLeft);
collectionEditor.setJustification(juce::Justification::centredLeft);
notesEditor.setMultiLine(true);
notesEditor.setReturnKeyStartsNewLine(true);
// Make components visible
addAndMakeVisible(authorLabel);
addAndMakeVisible(authorEditor);
addAndMakeVisible(collectionLabel);
addAndMakeVisible(collectionEditor);
addAndMakeVisible(presetNameLabel);
addAndMakeVisible(presetNameEditor);
addAndMakeVisible(notesLabel);
addAndMakeVisible(notesEditor);
addAndMakeVisible(loadSceneButton);
addAndMakeVisible(saveSceneButton);
// Setup button callbacks
loadSceneButton.onClick = [this] { loadSceneClicked(); };
saveSceneButton.onClick = [this] { saveSceneClicked(); };
// TODO: Need a way to get initial/loaded metadata to populate fields.
// Maybe processor needs getter methods or PresetComponent becomes a ChangeListener?
}
PresetComponent::~PresetComponent()
{
// Destructor logic if needed
}
void PresetComponent::paint (juce::Graphics& g)
{
// Optional: Add background painting or borders
// g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
// g.setColour (juce::Colours::grey);
// g.drawRect (getLocalBounds(), 1);
}
void PresetComponent::resized()
{
auto bounds = getLocalBounds().reduced(10); // Add some padding
auto topRow = bounds.removeFromTop(25);
loadSceneButton.setBounds(topRow.removeFromLeft(100));
saveSceneButton.setBounds(topRow.removeFromLeft(100).translated(105, 0));
bounds.removeFromTop(10); // Spacing
auto metadataRowHeight = 25;
auto labelWidth = 80;
auto editorWidth = bounds.getWidth() - labelWidth - 5; // 5px spacing
auto nameRow = bounds.removeFromTop(metadataRowHeight);
presetNameLabel.setBounds(nameRow.removeFromLeft(labelWidth));
presetNameEditor.setBounds(nameRow.withX(nameRow.getX() + 5).withWidth(editorWidth));
bounds.removeFromTop(5); // Spacing
auto authorRow = bounds.removeFromTop(metadataRowHeight);
authorLabel.setBounds(authorRow.removeFromLeft(labelWidth));
authorEditor.setBounds(authorRow.withX(authorRow.getX() + 5).withWidth(editorWidth));
bounds.removeFromTop(5); // Spacing
auto collectionRow = bounds.removeFromTop(metadataRowHeight);
collectionLabel.setBounds(collectionRow.removeFromLeft(labelWidth));
collectionEditor.setBounds(collectionRow.withX(collectionRow.getX() + 5).withWidth(editorWidth));
bounds.removeFromTop(5); // Spacing
notesLabel.setBounds(bounds.removeFromTop(metadataRowHeight)); // Label takes full width temporarily
notesLabel.setJustificationType(juce::Justification::topLeft); // Align label top-left
bounds.removeFromTop(5); // Spacing
notesEditor.setBounds(bounds); // Notes editor takes remaining space
}
void PresetComponent::loadSceneClicked()
{
sceneChooser = std::make_unique<juce::FileChooser>("Load OsciScene File",
juce::File::getSpecialLocation(juce::File::userDocumentsDirectory),
"*.osscene"); // Use new extension
auto chooserFlags = juce::FileBrowserComponent::openMode |
juce::FileBrowserComponent::canSelectFiles;
sceneChooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fc)
{
auto file = fc.getResult();
if (file != juce::File{})
{
// Call loadScene and capture the returned metadata
SceneMetadata loadedData = processor.loadScene(file);
// Update the text fields using the returned metadata
updateMetadataFields(loadedData.author, loadedData.collection, loadedData.presetName, loadedData.notes);
// The DBG message is no longer a TODO
DBG("PresetComponent: Updated metadata fields after loading scene.");
}
});
}
void PresetComponent::saveSceneClicked()
{
sceneChooser = std::make_unique<juce::FileChooser>("Save OsciScene File",
juce::File::getSpecialLocation(juce::File::userDocumentsDirectory),
"*.osscene", // Use new extension
true); // useNativeBox = true
auto chooserFlags = juce::FileBrowserComponent::saveMode |
juce::FileBrowserComponent::canSelectFiles;
sceneChooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fc)
{
auto file = fc.getResult();
if (file != juce::File{})
{
// Get metadata from TextEditors
auto author = authorEditor.getText();
auto collection = collectionEditor.getText();
auto presetName = presetNameEditor.getText();
auto notes = notesEditor.getText();
// Call the processor's save function
processor.saveScene(file, author, collection, presetName, notes);
}
});
}
// Method implementation to update text fields
void PresetComponent::updateMetadataFields(const juce::String& author, const juce::String& collection, const juce::String& presetName, const juce::String& notes)
{
authorEditor.setText(author, juce::dontSendNotification);
collectionEditor.setText(collection, juce::dontSendNotification);
presetNameEditor.setText(presetName, juce::dontSendNotification);
notesEditor.setText(notes, juce::dontSendNotification);
}

Wyświetl plik

@ -0,0 +1,47 @@
#pragma once
#include <JuceHeader.h>
#include "PluginProcessor.h" // Include processor header
/*
Component for handling scene loading, saving, and metadata.
*/
class PresetComponent : public juce::Component
{
public:
PresetComponent(OscirenderAudioProcessor& processor);
~PresetComponent() override;
void paint (juce::Graphics&) override;
void resized() override;
// Method to update text fields after loading a scene
void updateMetadataFields(const juce::String& author, const juce::String& collection, const juce::String& presetName, const juce::String& notes);
private:
OscirenderAudioProcessor& processor; // Reference to the processor
// Metadata Labels and TextEditors
juce::Label authorLabel;
juce::TextEditor authorEditor;
juce::Label collectionLabel;
juce::TextEditor collectionEditor;
juce::Label presetNameLabel;
juce::TextEditor presetNameEditor;
juce::Label notesLabel;
juce::TextEditor notesEditor; // Multi-line for notes
// Load/Save Buttons
juce::TextButton loadSceneButton { "Load Scene" };
juce::TextButton saveSceneButton { "Save Scene" };
// File Chooser
std::unique_ptr<juce::FileChooser> sceneChooser;
// Button Handlers
void loadSceneClicked();
void saveSceneClicked();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PresetComponent)
};

Wyświetl plik

@ -206,6 +206,12 @@
<FILE id="MWkfTv" name="VolumeComponent.h" compile="0" resource="0" <FILE id="MWkfTv" name="VolumeComponent.h" compile="0" resource="0"
file="Source/components/VolumeComponent.h"/> file="Source/components/VolumeComponent.h"/>
</GROUP> </GROUP>
<GROUP id="{PRESET_COMPONENT_PLACEHOLDER}" name="PresetComponent">
<FILE id="PrStCpH" name="PresetComponent.h" compile="0" resource="0"
file="Source/PresetComponent.h"/>
<FILE id="PrStCpC" name="PresetComponent.cpp" compile="1" resource="0"
file="Source/PresetComponent.cpp"/>
</GROUP>
<GROUP id="{9F5970A9-8094-E7F3-7AC1-812AE5589B9F}" name="concurrency"> <GROUP id="{9F5970A9-8094-E7F3-7AC1-812AE5589B9F}" name="concurrency">
<FILE id="PcRInQ" name="AudioBackgroundThread.cpp" compile="1" resource="0" <FILE id="PcRInQ" name="AudioBackgroundThread.cpp" compile="1" resource="0"
file="Source/concurrency/AudioBackgroundThread.cpp"/> file="Source/concurrency/AudioBackgroundThread.cpp"/>