diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index a46bfe7..a357d28 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -7,30 +7,56 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess setText("Main Settings"); addAndMakeVisible(fileButton); - - fileButton.setButtonText("Choose File"); + fileButton.setButtonText("Choose File(s)"); fileButton.onClick = [this] { chooser = std::make_unique("Open", juce::File::getSpecialLocation(juce::File::userHomeDirectory), "*.obj;*.svg;*.lua;*.txt"); - auto flags = juce::FileBrowserComponent::openMode; + auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectMultipleItems; chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) { - if (chooser.getURLResult().isLocalFile()) { - auto file = chooser.getResult(); - audioProcessor.addFile(file); - pluginEditor.updateCodeEditor(); + for (auto& url : chooser.getURLResults()) { + if (url.isLocalFile()) { + auto file = url.getLocalFile(); + audioProcessor.addFile(file); + } } + pluginEditor.updateCodeEditor(); + updateFileLabel(); }); }; + + addAndMakeVisible(closeFileButton); + closeFileButton.setButtonText("Close File"); + + closeFileButton.onClick = [this] { + audioProcessor.removeFile(audioProcessor.getCurrentFileIndex()); + pluginEditor.updateCodeEditor(); + updateFileLabel(); + }; + + addAndMakeVisible(fileLabel); + updateFileLabel(); } MainComponent::~MainComponent() { } -void MainComponent::resized() { - auto xPadding = 10; - auto yPadding = 20; - auto buttonWidth = 100; - auto buttonHeight = 40; - fileButton.setBounds(xPadding, yPadding, buttonWidth, buttonHeight); +void MainComponent::updateFileLabel() { + if (audioProcessor.getCurrentFileIndex() == -1) { + fileLabel.setText("No file open", juce::dontSendNotification); + return; + } + + fileLabel.setText(audioProcessor.getCurrentFile().getFileName(), juce::dontSendNotification); +} + +void MainComponent::resized() { + auto baseYPadding = 10; + auto xPadding = 10; + auto yPadding = 10; + auto buttonWidth = 120; + auto buttonHeight = 40; + fileButton.setBounds(xPadding, baseYPadding + yPadding, buttonWidth, buttonHeight); + closeFileButton.setBounds(xPadding, baseYPadding + yPadding + buttonHeight + yPadding, buttonWidth, buttonHeight); + fileLabel.setBounds(xPadding + buttonWidth + xPadding, baseYPadding + yPadding, getWidth() - xPadding - buttonWidth - xPadding, buttonHeight); } diff --git a/Source/MainComponent.h b/Source/MainComponent.h index 323e572..1a483ce 100644 --- a/Source/MainComponent.h +++ b/Source/MainComponent.h @@ -13,13 +13,16 @@ public: ~MainComponent() override; void resized() override; - private: OscirenderAudioProcessor& audioProcessor; OscirenderAudioProcessorEditor& pluginEditor; std::unique_ptr chooser; juce::TextButton fileButton; + juce::TextButton closeFileButton; + juce::Label fileLabel; + + void updateFileLabel(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent) }; \ No newline at end of file diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index d5717f0..2656fed 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -54,6 +54,7 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr collapseButton.setShape(path, false, true, true); } else { codeEditor->setVisible(true); + updateCodeEditor(); juce::Path path; path.addTriangle(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f); collapseButton.setShape(path, false, true, true); @@ -93,15 +94,40 @@ void OscirenderAudioProcessorEditor::resized() { } void OscirenderAudioProcessorEditor::updateCodeEditor() { - codeEditor->loadContent(juce::MemoryInputStream(*audioProcessor.getFileBlock(audioProcessor.getCurrentFile()), false).readEntireStreamAsString()); + if (codeEditor->isVisible() && audioProcessor.getCurrentFileIndex() != -1) { + codeEditor->loadContent(juce::MemoryInputStream(*audioProcessor.getFileBlock(audioProcessor.getCurrentFileIndex()), false).readEntireStreamAsString()); + } } void OscirenderAudioProcessorEditor::codeDocumentTextInserted(const juce::String& newText, int insertIndex) { juce::String file = codeDocument.getAllContent(); - audioProcessor.updateFileBlock(audioProcessor.getCurrentFile(), std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); + audioProcessor.updateFileBlock(audioProcessor.getCurrentFileIndex(), std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); } void OscirenderAudioProcessorEditor::codeDocumentTextDeleted(int startIndex, int endIndex) { juce::String file = codeDocument.getAllContent(); - audioProcessor.updateFileBlock(audioProcessor.getCurrentFile(), std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); + audioProcessor.updateFileBlock(audioProcessor.getCurrentFileIndex(), std::make_shared(file.toRawUTF8(), file.getNumBytesAsUTF8() + 1)); +} + +bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) { + int numFiles = audioProcessor.numFiles(); + int currentFile = audioProcessor.getCurrentFileIndex(); + if (key.getTextCharacter() == 'j') { + currentFile++; + if (currentFile == numFiles) { + currentFile = 0; + } + audioProcessor.changeCurrentFile(currentFile); + updateCodeEditor(); + return true; + } else if (key.getTextCharacter() == 'k') { + currentFile--; + if (currentFile < 0) { + currentFile = numFiles - 1; + } + audioProcessor.changeCurrentFile(currentFile); + updateCodeEditor(); + return true; + } + return false; } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 8dbdf24..8035de7 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -41,5 +41,7 @@ private: void codeDocumentTextInserted(const juce::String& newText, int insertIndex) override; void codeDocumentTextDeleted(int startIndex, int endIndex) override; + bool keyPressed(const juce::KeyPress& key) override; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 28dee27..2b83b58 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -29,10 +29,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() ) #endif { - parsers.push_back(std::make_unique()); - files.emplace_back(); - fileBlocks.emplace_back(); - producer = std::make_unique(*this, parsers[0]); + producer = std::make_unique(*this, std::make_shared()); producer->startThread(); allEffects.push_back(std::make_shared(std::make_unique(), "Bit Crush", "bitCrush")); @@ -197,24 +194,6 @@ void OscirenderAudioProcessor::updateAngleDelta() { thetaDelta = cyclesPerSample * 2.0 * juce::MathConstants::pi; } -void OscirenderAudioProcessor::addFrame(std::vector> frame) { - const auto scope = frameFifo.write(1); - - if (scope.blockSize1 > 0) { - frameBuffer[scope.startIndex1].clear(); - for (auto& shape : frame) { - frameBuffer[scope.startIndex1].push_back(std::move(shape)); - } - } - - if (scope.blockSize2 > 0) { - frameBuffer[scope.startIndex2].clear(); - for (auto& shape : frame) { - frameBuffer[scope.startIndex2].push_back(std::move(shape)); - } - } -} - void OscirenderAudioProcessor::enableEffect(std::shared_ptr effect) { // need to make a new vector because the old one is being iterated over in another thread std::shared_ptr>> newEffects = std::make_shared>>(); @@ -282,8 +261,10 @@ void OscirenderAudioProcessor::addFile(juce::File file) { } void OscirenderAudioProcessor::removeFile(int index) { - openFile(index - 1); - parsers[index]->disable(); + if (index < 0 || index >= fileBlocks.size()) { + return; + } + changeCurrentFile(index - 1); fileBlocks.erase(fileBlocks.begin() + index); files.erase(files.begin() + index); parsers.erase(parsers.begin() + index); @@ -294,19 +275,56 @@ int OscirenderAudioProcessor::numFiles() { } void OscirenderAudioProcessor::openFile(int index) { - currentFile = index; + if (index < 0 || index >= fileBlocks.size()) { + return; + } parsers[index]->parse(files[index].getFileExtension(), std::make_unique(*fileBlocks[index], false)); - producer->setSource(parsers[index]); + producer->setSource(parsers[index], index); + currentFile = index; + invalidateFrameBuffer = true; } -int OscirenderAudioProcessor::getCurrentFile() { +void OscirenderAudioProcessor::changeCurrentFile(int index) { + if (index < 0 || index >= fileBlocks.size()) { + return; + } + producer->setSource(parsers[index], index); + currentFile = index; + invalidateFrameBuffer = true; +} + +int OscirenderAudioProcessor::getCurrentFileIndex() { return currentFile; } +juce::File OscirenderAudioProcessor::getCurrentFile() { + return files[currentFile]; +} + std::shared_ptr OscirenderAudioProcessor::getFileBlock(int index) { return fileBlocks[index]; } +void OscirenderAudioProcessor::addFrame(std::vector> frame, int fileIndex) { + const auto scope = frameFifo.write(1); + + if (scope.blockSize1 > 0) { + frameBuffer[scope.startIndex1].clear(); + for (auto& shape : frame) { + frameBuffer[scope.startIndex1].push_back(std::move(shape)); + } + frameBufferIndices[scope.startIndex1] = fileIndex; + } + + if (scope.blockSize2 > 0) { + frameBuffer[scope.startIndex2].clear(); + for (auto& shape : frame) { + frameBuffer[scope.startIndex2].push_back(std::move(shape)); + } + frameBufferIndices[scope.startIndex2] = fileIndex; + } +} + void OscirenderAudioProcessor::updateFrame() { currentShape = 0; shapeDrawn = 0.0; @@ -318,8 +336,10 @@ void OscirenderAudioProcessor::updateFrame() { if (scope.blockSize1 > 0) { frame.swap(frameBuffer[scope.startIndex1]); + currentBufferIndex = frameBufferIndices[scope.startIndex1]; } else if (scope.blockSize2 > 0) { frame.swap(frameBuffer[scope.startIndex2]); + currentBufferIndex = frameBufferIndices[scope.startIndex2]; } frameLength = Shape::totalLength(frame); @@ -349,6 +369,17 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer& buffer, j auto* channelData = buffer.getArrayOfWritePointers(); auto numSamples = buffer.getNumSamples(); + + if (invalidateFrameBuffer) { + frameFifo.reset(); + // keeps getting the next frame until the frame comes from the file that we want to render. + // this MIGHT be hacky and cause issues later down the line, but for now it works as a + // solution to get instant changing of current file when pressing j and k. + while (currentBufferIndex != currentFile) { + updateFrame(); + } + invalidateFrameBuffer = false; + } for (auto sample = 0; sample < numSamples; ++sample) { updateLengthIncrement(); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index e3497bf..d98e0b6 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -74,7 +74,7 @@ public: BitCrushEffect bitCrushEffect = BitCrushEffect(); BulgeEffect bulgeEffect = BulgeEffect(); - + std::vector> parsers; std::vector> fileBlocks; std::vector files; @@ -83,7 +83,7 @@ public: std::unique_ptr producer; void updateAngleDelta(); - void addFrame(std::vector> frame) override; + void addFrame(std::vector> frame, int fileIndex) override; void enableEffect(std::shared_ptr effect); void disableEffect(std::shared_ptr effect); void updateEffectPrecedence(); @@ -92,7 +92,9 @@ public: void removeFile(int index); int numFiles(); void openFile(int index); - int getCurrentFile(); + void changeCurrentFile(int index); + int getCurrentFileIndex(); + juce::File getCurrentFile(); std::shared_ptr getFileBlock(int index); private: double theta = 0.0; @@ -100,13 +102,16 @@ private: juce::AbstractFifo frameFifo{ 10 }; std::vector> frameBuffer[10]; + int frameBufferIndices[10]; int currentShape = 0; std::vector> frame; + int currentBufferIndex = -1; double frameLength; double shapeDrawn = 0.0; double frameDrawn = 0.0; double lengthIncrement = 0.0; + bool invalidateFrameBuffer = false; void updateFrame(); void updateLengthIncrement(); diff --git a/Source/parser/FrameConsumer.h b/Source/parser/FrameConsumer.h index 2436353..5ba4907 100644 --- a/Source/parser/FrameConsumer.h +++ b/Source/parser/FrameConsumer.h @@ -6,5 +6,5 @@ class FrameConsumer { public: - virtual void addFrame(std::vector> frame) = 0; + virtual void addFrame(std::vector> frame, int fileIndex) = 0; }; \ No newline at end of file diff --git a/Source/parser/FrameProducer.cpp b/Source/parser/FrameProducer.cpp index 3dddd5f..8244b82 100644 --- a/Source/parser/FrameProducer.cpp +++ b/Source/parser/FrameProducer.cpp @@ -9,11 +9,12 @@ FrameProducer::~FrameProducer() { void FrameProducer::run() { while (!threadShouldExit() && frameSource->isActive()) { - frameConsumer.addFrame(frameSource->next()); + frameConsumer.addFrame(frameSource->next(), sourceFileIndex); } } -void FrameProducer::setSource(std::shared_ptr source) { +void FrameProducer::setSource(std::shared_ptr source, int fileIndex) { // TODO: should make this atomic frameSource = source; + sourceFileIndex = fileIndex; } diff --git a/Source/parser/FrameProducer.h b/Source/parser/FrameProducer.h index 6cfa025..65b3991 100644 --- a/Source/parser/FrameProducer.h +++ b/Source/parser/FrameProducer.h @@ -10,8 +10,9 @@ public: ~FrameProducer() override; void run() override; - void setSource(std::shared_ptr); + void setSource(std::shared_ptr, int fileIndex); private: FrameConsumer& frameConsumer; std::shared_ptr frameSource; + int sourceFileIndex = 0; }; \ No newline at end of file