kopia lustrzana https://github.com/jameshball/osci-render
simple scene/preset handling
rodzic
963a7f28a8
commit
ec22c05921
|
@ -49,12 +49,6 @@ EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioP
|
|||
listBox.setModel(&listBoxModel);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -67,12 +61,6 @@ void EffectsComponent::resized() {
|
|||
auto area = getLocalBounds();
|
||||
auto titleBar = area.removeFromTop(30);
|
||||
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));
|
||||
area = area.reduced(20);
|
||||
|
@ -86,42 +74,3 @@ void EffectsComponent::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
|||
itemData.resetData();
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,12 +33,14 @@ private:
|
|||
|
||||
EffectComponent frequency = EffectComponent(*audioProcessor.frequencyEffect, false);
|
||||
|
||||
juce::TextButton loadPresetButton { "Load" };
|
||||
juce::TextButton savePresetButton { "Save" };
|
||||
std::unique_ptr<juce::FileChooser> presetChooser;
|
||||
// Remove preset buttons and chooser
|
||||
// juce::TextButton loadPresetButton { "Load" };
|
||||
// juce::TextButton savePresetButton { "Save" };
|
||||
// std::unique_ptr<juce::FileChooser> presetChooser;
|
||||
|
||||
void loadPresetClicked();
|
||||
void savePresetClicked();
|
||||
// Remove preset click handlers
|
||||
// void loadPresetClicked();
|
||||
// void savePresetClicked();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectsComponent)
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
#include "PluginEditor.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
|
||||
addAndMakeVisible(upgradeButton);
|
||||
upgradeButton.onClick = [this] {
|
||||
|
@ -82,6 +82,9 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
|
|||
#endif
|
||||
|
||||
initialiseMenuBar(model);
|
||||
|
||||
addAndMakeVisible(presetComponent);
|
||||
addAndMakeVisible(mainComponent);
|
||||
}
|
||||
|
||||
OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {
|
||||
|
@ -170,7 +173,22 @@ void OscirenderAudioProcessorEditor::resized() {
|
|||
|
||||
if (audioProcessor.visualiserParameters.visualiserFullScreen->getBoolValue()) {
|
||||
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;
|
||||
} else {
|
||||
presetComponent.setVisible(true);
|
||||
settings.setVisible(true);
|
||||
if (!usingNativeMenuBar) menuBar.setVisible(true);
|
||||
volume.setVisible(true);
|
||||
}
|
||||
|
||||
if (!usingNativeMenuBar) {
|
||||
|
@ -186,8 +204,11 @@ void OscirenderAudioProcessorEditor::resized() {
|
|||
auto volumeArea = area.removeFromLeft(30);
|
||||
volume.setBounds(volumeArea.withSizeKeepingCentre(volumeArea.getWidth(), juce::jmin(volumeArea.getHeight(), 300)));
|
||||
area.removeFromLeft(3);
|
||||
bool editorVisible = false;
|
||||
|
||||
auto presetHeight = 160;
|
||||
presetComponent.setBounds(area.removeFromTop(presetHeight));
|
||||
|
||||
bool editorVisible = false;
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||
|
||||
|
@ -208,9 +229,7 @@ void OscirenderAudioProcessorEditor::resized() {
|
|||
|
||||
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
|
||||
// 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);
|
||||
layout.layOutComponents(columns, 3, area.getX(), area.getY() -1 , area.getWidth(), area.getHeight() + 1, false, true);
|
||||
auto dummyBounds = dummy.getBounds();
|
||||
collapseButton.setBounds(dummyBounds.removeFromRight(20));
|
||||
area = dummyBounds;
|
||||
|
@ -245,14 +264,18 @@ void OscirenderAudioProcessorEditor::resized() {
|
|||
|
||||
collapseButton.setVisible(ableToEditFile);
|
||||
|
||||
if (index < codeEditors.size()) {
|
||||
codeEditors[index]->setVisible(fileOpen);
|
||||
if (!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) {
|
||||
|
@ -269,8 +292,6 @@ void OscirenderAudioProcessorEditor::resized() {
|
|||
|
||||
audioProcessor.setProperty("codeEditorLayoutPreferredSize", layout.getItemCurrentRelativeSize(0));
|
||||
audioProcessor.setProperty("luaLayoutPreferredSize", luaLayout.getItemCurrentRelativeSize(0));
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::addCodeEditor(int index) {
|
||||
|
@ -297,9 +318,7 @@ void OscirenderAudioProcessorEditor::addCodeEditor(int index) {
|
|||
codeDocuments.insert(codeDocuments.begin() + index, codeDocument);
|
||||
codeEditors.insert(codeEditors.begin() + index, editor);
|
||||
addChildComponent(*editor);
|
||||
// I need to disable accessibility otherwise it doesn't work! Appears to be a JUCE issue, very annoying!
|
||||
editor->setAccessible(false);
|
||||
// listen for changes to the code editor
|
||||
codeDocument->addListener(this);
|
||||
editor->getEditor().setColourScheme(colourScheme);
|
||||
}
|
||||
|
@ -313,7 +332,6 @@ void OscirenderAudioProcessorEditor::removeCodeEditor(int index) {
|
|||
|
||||
// parsersLock AND effectsLock must be locked before calling this function
|
||||
void OscirenderAudioProcessorEditor::updateCodeEditor(bool binaryFile, bool shouldOpenEditor) {
|
||||
// check if any code editors are visible
|
||||
bool visible = shouldOpenEditor;
|
||||
if (!visible) {
|
||||
for (int i = 0; i < codeEditors.size(); i++) {
|
||||
|
@ -338,10 +356,6 @@ void OscirenderAudioProcessorEditor::updateCodeEditor(bool binaryFile, bool shou
|
|||
codeEditors[i]->setVisible(false);
|
||||
}
|
||||
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;
|
||||
if (index == 0) {
|
||||
codeEditors[index]->getEditor().loadContent(audioProcessor.customEffect->getCode());
|
||||
|
@ -380,8 +394,8 @@ void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcas
|
|||
repaint();
|
||||
} else if (source == &audioProcessor.fileChangeBroadcaster) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||
// triggered when the audioProcessor changes the current file (e.g. to Blender)
|
||||
settings.fileUpdated(audioProcessor.getCurrentFileName());
|
||||
mainComponent.updateFileLabel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "components/LuaConsole.h"
|
||||
#include "visualiser/VisualiserSettings.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 {
|
||||
public:
|
||||
|
@ -84,5 +86,8 @@ public:
|
|||
void mouseDown(const juce::MouseEvent& event) override;
|
||||
void mouseMove(const juce::MouseEvent& event) override;
|
||||
|
||||
PresetComponent presetComponent;
|
||||
MainComponent mainComponent;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessorEditor)
|
||||
};
|
||||
|
|
|
@ -850,30 +850,36 @@ void OscirenderAudioProcessor::envelopeChanged(EnvelopeComponent* changedEnvelop
|
|||
}
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::savePreset(juce::File file) {
|
||||
juce::SpinLock::ScopedLockType lock(effectsLock); // Lock to safely access effects & parameters
|
||||
// Rename and add metadata 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");
|
||||
xml->setAttribute("version", ProjectInfo::versionString); // Use the project version
|
||||
std::unique_ptr<juce::XmlElement> xml = std::make_unique<juce::XmlElement>("OsciScene"); // Root element name changed
|
||||
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");
|
||||
// Save toggleable effects (includes state like enabled, parameter values, LFOs)
|
||||
for (auto& effect : toggleableEffects) {
|
||||
effect->save(effectsXml->createNewChildElement("Effect"));
|
||||
}
|
||||
// Save permanent effects (e.g., frequency parameter)
|
||||
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"));
|
||||
}
|
||||
// Save Lua slider effects
|
||||
for (auto& effect : luaEffects) {
|
||||
effect->save(effectsXml->createNewChildElement("Effect"));
|
||||
}
|
||||
|
||||
// --- Save ADSR Parameters ---
|
||||
// --- Save ADSR Parameters (same as before) ---
|
||||
auto adsrXml = xml->createNewChildElement("ADSR");
|
||||
attackTime->save(adsrXml->createNewChildElement("Parameter"));
|
||||
attackLevel->save(adsrXml->createNewChildElement("Parameter"));
|
||||
|
@ -884,106 +890,149 @@ void OscirenderAudioProcessor::savePreset(juce::File file) {
|
|||
releaseTime->save(adsrXml->createNewChildElement("Parameter"));
|
||||
releaseShape->save(adsrXml->createNewChildElement("Parameter"));
|
||||
|
||||
// --- Save Synth Parameters ---
|
||||
// --- Save Synth Parameters (same as before) ---
|
||||
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");
|
||||
customFunctionXml->addTextElement(juce::Base64::toBase64(customEffect->getCode()));
|
||||
|
||||
// --- Write to File ---
|
||||
if (!xml->writeToFile(file, {})) {
|
||||
// Handle error - maybe log it or show an alert?
|
||||
DBG("Error writing preset file: " + file.getFullPathName());
|
||||
// For now, just log an error message in debug builds.
|
||||
// In a real application, you might want to show a juce::AlertWindow.
|
||||
// --- Save Current Visual File Info ---
|
||||
xml->setAttribute("currentFileIndex", currentFile.load());
|
||||
if (currentFile.load() >= 0 && currentFile.load() < fileNames.size()) {
|
||||
auto currentFileXml = xml->createNewChildElement("CurrentVisualFile");
|
||||
currentFileXml->setAttribute("name", fileNames[currentFile.load()]);
|
||||
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);
|
||||
|
||||
SceneMetadata loadedMetadata; // Create struct instance
|
||||
|
||||
if (xml == nullptr) {
|
||||
DBG("Error parsing preset file: " + file.getFullPathName());
|
||||
// Optionally show an AlertWindow here
|
||||
return;
|
||||
DBG("Error parsing scene file: " + file.getFullPathName());
|
||||
// Return empty metadata on error
|
||||
return loadedMetadata;
|
||||
}
|
||||
|
||||
if (!xml->hasTagName("OsciPreset")) {
|
||||
DBG("Invalid preset file format: " + file.getFullPathName());
|
||||
// Optionally show an AlertWindow here
|
||||
return;
|
||||
// Check for OsciScene or fallback to OsciPreset for backward compatibility?
|
||||
if (!xml->hasTagName("OsciScene") && !xml->hasTagName("OsciPreset")) {
|
||||
DBG("Invalid scene/preset file format: " + file.getFullPathName());
|
||||
// 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
|
||||
juce::SpinLock::ScopedLockType lock(effectsLock);
|
||||
|
||||
// --- Load Effects ---
|
||||
// --- Load Effects ---
|
||||
auto effectsXml = xml->getChildByName("Effects");
|
||||
if (effectsXml != nullptr) {
|
||||
// Iterate through all saved Effect elements
|
||||
for (auto* effectXml : effectsXml->getChildIterator()) {
|
||||
if (effectXml->hasTagName("Effect")) {
|
||||
// Find the matching effect in our processor's lists by 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) {
|
||||
effect->load(effectXml);
|
||||
} 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");
|
||||
if (adsrXml != nullptr) {
|
||||
for (auto* parameterXml : adsrXml->getChildIterator()) {
|
||||
if (parameterXml->hasTagName("Parameter")) {
|
||||
auto paramId = parameterXml->getStringAttribute("id");
|
||||
auto parameter = getFloatParameter(paramId); // Assumes ADSR params are FloatParameters
|
||||
auto parameter = getFloatParameter(paramId);
|
||||
if (parameter != nullptr) {
|
||||
parameter->load(parameterXml);
|
||||
} 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(
|
||||
attackTime->getValueUnnormalised(),
|
||||
decayTime->getValueUnnormalised(),
|
||||
sustainLevel->getValueUnnormalised(),
|
||||
releaseTime->getValueUnnormalised(),
|
||||
attackLevel->getValueUnnormalised(), // Assuming attackLevel corresponds to env's 'peakLevel'
|
||||
attackLevel->getValueUnnormalised(),
|
||||
std::vector<EnvCurve>{ attackShape->getValueUnnormalised(), decayShape->getValueUnnormalised(), releaseShape->getValueUnnormalised() }
|
||||
);
|
||||
}
|
||||
|
||||
// --- Load Synth Parameters ---
|
||||
// --- Load Synth Parameters ---
|
||||
auto synthParamsXml = xml->getChildByName("SynthParameters");
|
||||
if (synthParamsXml != nullptr) {
|
||||
for (auto* parameterXml : synthParamsXml->getChildIterator()) {
|
||||
if (parameterXml->hasTagName("Parameter")) {
|
||||
auto paramId = parameterXml->getStringAttribute("id");
|
||||
// Check if it's the 'voices' parameter
|
||||
if (paramId == voices->getParameterID()) {
|
||||
voices->load(parameterXml);
|
||||
// Trigger parameterValueChanged to update synth voices
|
||||
parameterValueChanged(voices->getParameterIndex(), voices->getValue());
|
||||
}
|
||||
// Add checks for other synth parameters if needed
|
||||
}
|
||||
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");
|
||||
if (customFunctionXml != nullptr) {
|
||||
auto base64Code = customFunctionXml->getAllSubText();
|
||||
|
@ -991,14 +1040,93 @@ void OscirenderAudioProcessor::loadPreset(juce::File file) {
|
|||
if (juce::Base64::convertFromBase64(stream, base64Code)) {
|
||||
customEffect->updateCode(stream.toString());
|
||||
} else {
|
||||
DBG("Preset loading: Failed to decode Base64 Lua code.");
|
||||
DBG("Scene loading: Failed to decode Base64 Lua code.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Notify UI/Listeners ---
|
||||
// Use MessageManagerLock as sendChangeMessage needs to be called from the message thread
|
||||
// --- Load Current Visual File Info (if it's a scene file) ---
|
||||
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;
|
||||
broadcaster.sendChangeMessage();
|
||||
|
||||
// Return the populated metadata struct
|
||||
return loadedMetadata;
|
||||
}
|
||||
|
||||
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() {
|
||||
|
|
|
@ -31,6 +31,14 @@
|
|||
#include "CommonPluginProcessor.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 notifyErrorListeners(int lineNumber, juce::String id, juce::String error);
|
||||
|
||||
void savePreset(juce::File file);
|
||||
void loadPreset(juce::File file);
|
||||
void saveScene(juce::File file, const juce::String& author, const juce::String& collection, const juce::String& presetName, const juce::String& notes);
|
||||
SceneMetadata loadScene(juce::File file);
|
||||
private:
|
||||
|
||||
std::atomic<bool> prevMidiEnabled = isMidiAlwaysEnabled() || !midiEnabled->getBoolValue();
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
};
|
|
@ -206,6 +206,12 @@
|
|||
<FILE id="MWkfTv" name="VolumeComponent.h" compile="0" resource="0"
|
||||
file="Source/components/VolumeComponent.h"/>
|
||||
</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">
|
||||
<FILE id="PcRInQ" name="AudioBackgroundThread.cpp" compile="1" resource="0"
|
||||
file="Source/concurrency/AudioBackgroundThread.cpp"/>
|
||||
|
|
Ładowanie…
Reference in New Issue