From c205de10b79be9797af3f8831d26ac07f4590d17 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Fri, 4 Apr 2025 11:28:13 +0100 Subject: [PATCH] Flip gionometer and add warning message if opening an obj file that is too large --- Source/MainComponent.cpp | 2 - Source/PluginEditor.cpp | 16 ++++++ Source/PluginEditor.h | 2 + Source/PluginProcessor.cpp | 28 ++++++++++- Source/PluginProcessor.h | 10 ++++ Source/parser/FileParser.cpp | 60 +++++++++++++++++++++-- Source/parser/FileParser.h | 3 +- Source/visualiser/VisualiserComponent.cpp | 2 +- 8 files changed, 112 insertions(+), 11 deletions(-) diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index 2e52e6e..e21f470 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -41,9 +41,7 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess if (index == -1) { return; } - pluginEditor.removeCodeEditor(audioProcessor.getCurrentFileIndex()); audioProcessor.removeFile(audioProcessor.getCurrentFileIndex()); - pluginEditor.fileUpdated(audioProcessor.getCurrentFileName()); }; closeFileButton.setTooltip("Close the currently open file."); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 5cb015d..3c619d3 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -2,7 +2,20 @@ #include "PluginEditor.h" #include "CustomStandaloneFilterWindow.h" +void OscirenderAudioProcessorEditor::registerFileRemovedCallback() { + audioProcessor.setFileRemovedCallback([this](int index) { + removeCodeEditor(index); + fileUpdated(audioProcessor.getCurrentFileName()); + juce::MessageManager::callAsync([this] { + resized(); + }); + }); +} + OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p) : CommonPluginEditor(p, "osci-render", "osci", 1100, 750), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white) { + // Register the file removal callback + registerFileRemovedCallback(); + #if !SOSCI_FEATURES addAndMakeVisible(upgradeButton); upgradeButton.onClick = [this] { @@ -85,6 +98,9 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr } OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() { + // Clear the file removal callback + audioProcessor.setFileRemovedCallback(nullptr); + menuBar.setModel(nullptr); juce::MessageManagerLock lock; audioProcessor.broadcaster.removeChangeListener(this); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 14a47b1..d31542b 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -34,6 +34,8 @@ public: void editCustomFunction(bool enabled); private: + void registerFileRemovedCallback(); + OscirenderAudioProcessor& audioProcessor; public: diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 42cef07..9628c51 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -293,6 +293,11 @@ void OscirenderAudioProcessor::addFile(juce::String fileName, std::shared_ptr callback) { + fileRemovedCallback = std::move(callback); +} + // parsersLock AND effectsLock must be locked before calling this function void OscirenderAudioProcessor::removeFile(int index) { if (index < 0 || index >= fileBlocks.size()) { @@ -303,11 +308,32 @@ void OscirenderAudioProcessor::removeFile(int index) { fileIds.erase(fileIds.begin() + index); parsers.erase(parsers.begin() + index); sounds.erase(sounds.begin() + index); + auto newFileIndex = index; if (newFileIndex >= fileBlocks.size()) { newFileIndex = fileBlocks.size() - 1; } changeCurrentFile(newFileIndex); + + // Notify the editor about the file removal + if (fileRemovedCallback) { + fileRemovedCallback(index); + } +} + +// parsersLock AND effectsLock must be locked before calling this function +void OscirenderAudioProcessor::removeParser(FileParser* parser) { + int parserIndex = -1; + for (int i = 0; i < parsers.size(); i++) { + if (parsers[i].get() == parser) { + parserIndex = i; + break; + } + } + + if (parserIndex >= 0) { + removeFile(parserIndex); + } } int OscirenderAudioProcessor::numFiles() { @@ -321,7 +347,7 @@ void OscirenderAudioProcessor::openFile(int index) { if (index < 0 || index >= fileBlocks.size()) { return; } - parsers[index]->parse(juce::String(fileIds[index]), fileNames[index].fromLastOccurrenceOf(".", true, false).toLowerCase(), std::make_unique(*fileBlocks[index], false), font); + parsers[index]->parse(juce::String(fileIds[index]), fileNames[index], fileNames[index].fromLastOccurrenceOf(".", true, false).toLowerCase(), std::make_unique(*fileBlocks[index], false), font); changeCurrentFile(index); } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a3530c9..e4729fb 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -184,6 +184,9 @@ public: std::function haltRecording; + // Add a callback to notify the editor when a file is removed + std::function fileRemovedCallback; + void addLuaSlider(); void updateEffectPrecedence(); void updateFileBlock(int index, std::shared_ptr block); @@ -205,6 +208,13 @@ public: void addErrorListener(ErrorListener* listener); void removeErrorListener(ErrorListener* listener); void notifyErrorListeners(int lineNumber, juce::String id, juce::String error); + + // Setter for the callback + void setFileRemovedCallback(std::function callback); + + // Added declaration for the new `removeParser` method. + void removeParser(FileParser* parser); + private: std::atomic prevMidiEnabled = !midiEnabled->getBoolValue(); diff --git a/Source/parser/FileParser.cpp b/Source/parser/FileParser.cpp index 85888e7..14f970c 100644 --- a/Source/parser/FileParser.cpp +++ b/Source/parser/FileParser.cpp @@ -4,9 +4,10 @@ #include #include "../PluginProcessor.h" -FileParser::FileParser(OscirenderAudioProcessor &p, std::function errorCallback) : errorCallback(errorCallback), audioProcessor(p) {} +FileParser::FileParser(OscirenderAudioProcessor &p, std::function errorCallback) + : errorCallback(errorCallback), audioProcessor(p) {} -void FileParser::parse(juce::String fileId, juce::String extension, std::unique_ptr stream, juce::Font font) { +void FileParser::parse(juce::String fileId, juce::String fileName, juce::String extension, std::unique_ptr stream, juce::Font font) { juce::SpinLock::ScopedLockType scope(lock); if (extension == ".lua" && lua != nullptr && lua->isFunctionValid()) { @@ -22,7 +23,54 @@ void FileParser::parse(juce::String fileId, juce::String extension, std::unique_ wav = nullptr; if (extension == ".obj") { - object = std::make_shared(stream->readEntireStreamAsString().toStdString()); + // Check file size before parsing + const int64_t fileSize = stream->getTotalLength(); + const int64_t oneMB = 1024 * 1024; // 1MB in bytes + + // Save the file content to avoid losing it after the async operation + juce::String objContent = stream->readEntireStreamAsString(); + + if (fileSize > oneMB) { + // For large files, show an async warning dialog + const double fileSizeMB = fileSize / (1024.0 * 1024.0); + + juce::MessageManager::callAsync([this, objContent, fileSizeMB, fileName]() { + juce::String message = juce::String::formatted( + "The OBJ file '%s' you're trying to open is %.2f MB in size, which may cause performance issues. " + "Would you like to continue loading it?", fileName.toRawUTF8(), fileSizeMB); + + // Show async dialog with callbacks for user response + juce::AlertWindow::showOkCancelBox( + juce::AlertWindow::WarningIcon, + "Large OBJ File", + message, + "Continue", + "Cancel", + nullptr, + juce::ModalCallbackFunction::create([this, objContent](int result) { + if (result == 1) { // 1 = OK button pressed + juce::SpinLock::ScopedLockType scope(lock); + // User chose to continue, load the file + object = std::make_shared(objContent.toStdString()); + } else { + // User canceled, fully close this file parser + juce::SpinLock::ScopedLockType scope(lock); + disable(); // Mark this parser as inactive + + // Notify the processor to remove this parser + juce::MessageManager::callAsync([this] { + juce::SpinLock::ScopedLockType lock1(audioProcessor.parsersLock); + juce::SpinLock::ScopedLockType lock2(audioProcessor.effectsLock); + audioProcessor.removeParser(this); + }); + } + }) + ); + }); + } else { + // For small files, load immediately + object = std::make_shared(objContent.toStdString()); + } } else if (extension == ".svg") { svg = std::make_shared(stream->readEntireStreamAsString()); } else if (extension == ".txt") { @@ -52,8 +100,10 @@ void FileParser::parse(juce::String fileId, juce::String extension, std::unique_ } else if (extension == ".wav" || extension == ".aiff" || extension == ".flac" || extension == ".ogg" || extension == ".mp3") { wav = std::make_shared(audioProcessor); if (!wav->parse(std::move(stream))) { - juce::MessageManager::callAsync([this] { - juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, "Error", "The audio file could not be loaded."); + juce::MessageManager::callAsync([this, fileName] { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error Loading " + fileName, + "The audio file '" + fileName + "' could not be loaded."); }); } } diff --git a/Source/parser/FileParser.h b/Source/parser/FileParser.h index 88a59d8..dcfa03a 100644 --- a/Source/parser/FileParser.h +++ b/Source/parser/FileParser.h @@ -15,7 +15,7 @@ class FileParser { public: FileParser(OscirenderAudioProcessor &p, std::function errorCallback = nullptr); - void parse(juce::String fileName, juce::String extension, std::unique_ptr stream, juce::Font font); + void parse(juce::String fileId, juce::String fileName, juce::String extension, std::unique_ptr stream, juce::Font font); std::vector> nextFrame(); OsciPoint nextSample(lua_State*& L, LuaVariables& vars); void closeLua(lua_State*& L); @@ -39,7 +39,6 @@ private: bool active = true; bool sampleSource = false; - juce::SpinLock lock; std::shared_ptr object; diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp index 05c3b3c..734eed7 100644 --- a/Source/visualiser/VisualiserComponent.cpp +++ b/Source/visualiser/VisualiserComponent.cpp @@ -243,7 +243,7 @@ void VisualiserComponent::runTask(const std::vector& points) { if (settings.isGoniometer()) { // x and y go to a diagonal currently, so we need to scale them down, and rotate them point.scale(1.0 / std::sqrt(2.0), 1.0 / std::sqrt(2.0), 1.0); - point.rotate(0, 0, juce::MathConstants::pi / 4); + point.rotate(0, 0, -juce::MathConstants::pi / 4); } #endif