Refactor how syphon/spout input works to be cleaner and less buggy

visualiser-refactor
James H Ball 2025-05-03 11:57:14 +01:00
rodzic 9e93ffc260
commit 67650b682e
9 zmienionych plików z 71 dodań i 119 usunięć

Wyświetl plik

@ -22,9 +22,6 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
juce::FileBrowserComponent::canSelectFiles;
chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) {
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType syphonLock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parsersLock(audioProcessor.parsersLock);
bool fileAdded = false;
for (auto& file : chooser.getResults()) {
@ -66,9 +63,6 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
addAndMakeVisible(leftArrow);
leftArrow.onClick = [this] {
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parserLock(audioProcessor.parsersLock);
juce::SpinLock::ScopedLockType effectsLock(audioProcessor.effectsLock);
@ -83,9 +77,6 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
addAndMakeVisible(rightArrow);
rightArrow.onClick = [this] {
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parserLock(audioProcessor.parsersLock);
juce::SpinLock::ScopedLockType effectsLock(audioProcessor.effectsLock);
@ -108,9 +99,6 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
addAndMakeVisible(createFile);
createFile.onClick = [this] {
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType syphonLock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parsersLock(audioProcessor.parsersLock);
auto fileNameText = fileName.getText();
auto fileTypeText = fileType.getText();
@ -173,8 +161,8 @@ void MainComponent::updateFileLabel() {
{
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
if (audioProcessor.isSyphonInputActive()) {
fileLabel.setText(audioProcessor.getSyphonSourceName(), juce::dontSendNotification);
if (audioProcessor.syphonInputActive) {
fileLabel.setText(pluginEditor.getSyphonSourceName(), juce::dontSendNotification);
} else
#endif
if (audioProcessor.objectServerRendering) {

Wyświetl plik

@ -54,9 +54,6 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
colourScheme = lookAndFeel.getDefaultColourScheme();
{
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType syphonLock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
initialiseCodeEditors();
}
@ -143,9 +140,6 @@ void OscirenderAudioProcessorEditor::filesDropped(const juce::StringArray& files
if (file.hasFileExtension("osci")) {
openProject(file);
} else {
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType syphonLock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parsersLock(audioProcessor.parsersLock);
juce::SpinLock::ScopedLockType effectsLock(audioProcessor.effectsLock);
@ -379,9 +373,6 @@ void OscirenderAudioProcessorEditor::handleAsyncUpdate() {
void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) {
if (source == &audioProcessor.broadcaster) {
{
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType syphonLock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parsersLock(audioProcessor.parsersLock);
initialiseCodeEditors();
settings.update();
@ -389,9 +380,6 @@ void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcas
resized();
repaint();
} else if (source == &audioProcessor.fileChangeBroadcaster) {
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType syphonLock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parsersLock(audioProcessor.parsersLock);
// triggered when the audioProcessor changes the current file (e.g. to Blender)
settings.fileUpdated(audioProcessor.getCurrentFileName());
@ -462,9 +450,6 @@ void OscirenderAudioProcessorEditor::updateCodeDocument() {
bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) {
bool consumeKey = false;
{
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
#endif
juce::SpinLock::ScopedLockType parserLock(audioProcessor.parsersLock);
juce::SpinLock::ScopedLockType effectsLock(audioProcessor.effectsLock);
@ -527,13 +512,12 @@ void OscirenderAudioProcessorEditor::openVisualiserSettings() {
void OscirenderAudioProcessorEditor::openSyphonInputDialog() {
SyphonInputSelectorComponent* selector = nullptr;
{
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
selector = new SyphonInputSelectorComponent(
sharedTextureManager,
[this](const juce::String& server, const juce::String& app) { onSyphonInputSelected(server, app); },
[this]() { onSyphonInputDisconnected(); },
audioProcessor.isSyphonInputActive(),
audioProcessor.getSyphonSourceName());
[this](const juce::String& server, const juce::String& app) { connectSyphonInput(server, app); },
[this]() { disconnectSyphonInput(); },
syphonFrameGrabber && syphonFrameGrabber->isActive(),
getSyphonSourceName());
}
juce::DialogWindow::LaunchOptions options;
options.content.setOwned(selector);
@ -546,13 +530,36 @@ void OscirenderAudioProcessorEditor::openSyphonInputDialog() {
options.launchAsync();
}
void OscirenderAudioProcessorEditor::onSyphonInputSelected(const juce::String& server, const juce::String& app) {
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
audioProcessor.connectSyphonInput(server, app);
void OscirenderAudioProcessorEditor::connectSyphonInput(const juce::String& server, const juce::String& app) {
juce::SpinLock::ScopedLockType lock(syphonLock);
if (!syphonFrameGrabber) {
syphonFrameGrabber = std::make_unique<SyphonFrameGrabber>(sharedTextureManager, server, app, audioProcessor.syphonImageParser);
audioProcessor.syphonInputActive = true;
{
juce::MessageManagerLock lock;
audioProcessor.fileChangeBroadcaster.sendChangeMessage();
}
}
}
void OscirenderAudioProcessorEditor::onSyphonInputDisconnected() {
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
audioProcessor.disconnectSyphonInput();
void OscirenderAudioProcessorEditor::disconnectSyphonInput() {
juce::SpinLock::ScopedLockType lock(syphonLock);
if (!syphonFrameGrabber) {
return;
}
audioProcessor.syphonInputActive = false;
syphonFrameGrabber.reset();
{
juce::MessageManagerLock lock;
audioProcessor.fileChangeBroadcaster.sendChangeMessage();
}
}
juce::String OscirenderAudioProcessorEditor::getSyphonSourceName() const {
juce::SpinLock::ScopedLockType lock(syphonLock);
if (syphonFrameGrabber) {
return syphonFrameGrabber->getSourceName();
}
return "";
}
#endif

Wyświetl plik

@ -1,15 +1,16 @@
#pragma once
#include <JuceHeader.h>
#include "CommonPluginEditor.h"
#include "LookAndFeel.h"
#include "MidiComponent.h"
#include "PluginProcessor.h"
#include "SettingsComponent.h"
#include "MidiComponent.h"
#include "components/OsciMainMenuBarModel.h"
#include "LookAndFeel.h"
#include "components/ErrorCodeEditorComponent.h"
#include "components/LuaConsole.h"
#include "components/OsciMainMenuBarModel.h"
#include "visualiser/VisualiserSettings.h"
#include "CommonPluginEditor.h"
class OscirenderAudioProcessorEditor : public CommonPluginEditor, private juce::CodeDocument::Listener, public juce::AsyncUpdater, public juce::ChangeListener, public juce::FileDragAndDropTarget {
public:
@ -18,7 +19,7 @@ public:
void paint(juce::Graphics&) override;
void resized() override;
bool isBinaryFile(juce::String name);
void initialiseCodeEditors();
void addCodeEditor(int index);
@ -37,15 +38,15 @@ private:
void registerFileRemovedCallback();
OscirenderAudioProcessor& audioProcessor;
public:
public:
const double CLOSED_PREF_SIZE = 30.0;
const double RESIZER_BAR_SIZE = 7.0;
std::atomic<bool> editingCustomFunction = false;
SettingsComponent settings{audioProcessor, *this};
#if !OSCI_PREMIUM
juce::TextButton upgradeButton{"Upgrade to premium!"};
#endif
@ -62,7 +63,7 @@ public:
juce::CodeEditorComponent::ColourScheme colourScheme;
juce::LuaTokeniser luaTokeniser;
juce::XmlTokeniser xmlTokeniser;
juce::ShapeButton collapseButton;
juce::ShapeButton collapseButton;
std::shared_ptr<juce::CodeDocument> customFunctionCodeDocument = std::make_shared<juce::CodeDocument>();
std::shared_ptr<OscirenderCodeEditorComponent> customFunctionCodeEditor = std::make_shared<OscirenderCodeEditorComponent>(*customFunctionCodeDocument, &luaTokeniser, audioProcessor, CustomEffect::UNIQUE_ID, CustomEffect::FILE_NAME);
@ -76,8 +77,8 @@ public:
std::atomic<bool> updatingDocumentsWithParserLock = false;
void codeDocumentTextInserted(const juce::String& newText, int insertIndex) override;
void codeDocumentTextDeleted(int startIndex, int endIndex) override;
void codeDocumentTextInserted(const juce::String& newText, int insertIndex) override;
void codeDocumentTextDeleted(int startIndex, int endIndex) override;
void updateCodeDocument();
void updateCodeEditor(bool binaryFile, bool shouldOpenEditor = false);
void setCodeEditorVisible(std::optional<bool> visible);
@ -86,10 +87,16 @@ public:
void mouseDown(const juce::MouseEvent& event) override;
void mouseMove(const juce::MouseEvent& event) override;
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
// Syphon/Spout input dialog
void openSyphonInputDialog();
void onSyphonInputSelected(const juce::String& server, const juce::String& app);
void onSyphonInputDisconnected();
void connectSyphonInput(const juce::String& server, const juce::String& app);
void disconnectSyphonInput();
juce::String getSyphonSourceName() const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessorEditor)
juce::SpinLock syphonLock;
std::unique_ptr<SyphonFrameGrabber> syphonFrameGrabber;
#endif
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscirenderAudioProcessorEditor)
};

Wyświetl plik

@ -505,8 +505,7 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
{
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
juce::SpinLock::ScopedLockType sLock(syphonLock);
if (isSyphonInputActive()) {
if (syphonInputActive) {
for (int sample = 0; sample < outputBuffer3d.getNumSamples(); sample++) {
osci::Point point = syphonImageParser.getSample();
outputBuffer3d.setSample(0, sample, point.x);
@ -514,7 +513,7 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
}
} else
#endif
if (usingInput && totalNumInputChannels >= 1) {
if (usingInput && totalNumInputChannels >= 1) {
if (totalNumInputChannels >= 2) {
for (auto channel = 0; channel < juce::jmin(2, totalNumInputChannels); channel++) {
outputBuffer3d.copyFrom(channel, 0, inputBuffer, channel, 0, buffer.getNumSamples());
@ -898,49 +897,6 @@ void OscirenderAudioProcessor::envelopeChanged(EnvelopeComponent* changedEnvelop
}
}
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
// Syphon/Spout input management
// syphonLock must be held when calling this function
bool OscirenderAudioProcessor::isSyphonInputActive() const {
return syphonFrameGrabber != nullptr && syphonFrameGrabber->isActive();
}
// syphonLock must be held when calling this function
bool OscirenderAudioProcessor::isSyphonInputStarted() const {
return syphonFrameGrabber != nullptr;
}
// syphonLock must be held when calling this function
void OscirenderAudioProcessor::connectSyphonInput(const juce::String& server, const juce::String& app) {
auto editor = dynamic_cast<OscirenderAudioProcessorEditor*>(getActiveEditor());
if (!syphonFrameGrabber && editor) {
syphonFrameGrabber = std::make_unique<SyphonFrameGrabber>(editor->sharedTextureManager, server, app, syphonImageParser);
{
juce::MessageManagerLock lock;
fileChangeBroadcaster.sendChangeMessage();
}
}
}
// syphonLock must be held when calling this function
void OscirenderAudioProcessor::disconnectSyphonInput() {
syphonFrameGrabber.reset();
{
juce::MessageManagerLock lock;
fileChangeBroadcaster.sendChangeMessage();
}
}
// syphonLock must be held when calling this function
juce::String OscirenderAudioProcessor::getSyphonSourceName() const {
if (syphonFrameGrabber) {
return syphonFrameGrabber->getSourceName();
}
return "";
}
#endif
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() {
return new OscirenderAudioProcessor();
}

Wyświetl plik

@ -282,19 +282,12 @@ private:
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
public:
bool isSyphonInputActive() const;
bool isSyphonInputStarted() const;
void connectSyphonInput(const juce::String& server, const juce::String& app);
void disconnectSyphonInput();
juce::String getSyphonSourceName() const;
std::atomic<bool> syphonInputActive = false;
juce::SpinLock syphonLock;
private:
ImageParser syphonImageParser = ImageParser(*this);
std::unique_ptr<SyphonFrameGrabber> syphonFrameGrabber;
#endif
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscirenderAudioProcessor)
};

Wyświetl plik

@ -84,7 +84,7 @@ void SettingsComponent::fileUpdated(juce::String fileName) {
// Check if the file is an image based on extension or Syphon/Spout input
bool isSyphonActive = false;
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
isSyphonActive = audioProcessor.isSyphonInputStarted();
isSyphonActive = audioProcessor.syphonInputActive;
#endif
bool isImage = isSyphonActive ||

Wyświetl plik

@ -64,11 +64,12 @@ OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, Oscirend
#if (JUCE_MAC || JUCE_WINDOWS) && OSCI_PREMIUM
// Add Syphon/Spout input menu item under Recording
addMenuItem(2, audioProcessor.isSyphonInputActive() ? "Disconnect Syphon/Spout Input" : "Select Syphon/Spout Input...", [this] {
if (audioProcessor.isSyphonInputActive())
disconnectSyphonInput();
else
addMenuItem(2, audioProcessor.syphonInputActive ? "Disconnect Syphon/Spout Input" : "Select Syphon/Spout Input...", [this] {
if (audioProcessor.syphonInputActive) {
editor.disconnectSyphonInput();
} else {
openSyphonInputDialog();
}
});
#endif
@ -83,8 +84,4 @@ OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, Oscirend
void OsciMainMenuBarModel::openSyphonInputDialog() {
editor.openSyphonInputDialog();
}
void OsciMainMenuBarModel::disconnectSyphonInput() {
audioProcessor.disconnectSyphonInput();
}
#endif

Wyświetl plik

@ -5,7 +5,7 @@
class SyphonFrameGrabber : private juce::Thread, public juce::Component {
public:
SyphonFrameGrabber(SharedTextureManager& manager, juce::String server, juce::String app, ImageParser& parser, int pollMs = 16)
SyphonFrameGrabber(SharedTextureManager& manager, juce::String server, juce::String app, ImageParser& parser, int pollMs = 8)
: juce::Thread("SyphonFrameGrabber"), pollIntervalMs(pollMs), manager(manager), parser(parser) {
// Create the invisible OpenGL context component
glContextComponent = std::make_unique<InvisibleOpenGLContextComponent>();
@ -30,6 +30,8 @@ public:
}
~SyphonFrameGrabber() override {
juce::CriticalSection::ScopedLockType lock(openGLLock);
stopThread(500);
if (receiver) {
manager.removeReceiver(receiver);
@ -41,6 +43,8 @@ public:
void run() override {
while (!threadShouldExit()) {
{
juce::CriticalSection::ScopedLockType lock(openGLLock);
bool activated = false;
if (glContextComponent) {
activated = glContextComponent->getContext().makeActive();

Wyświetl plik

@ -4,7 +4,7 @@
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" pluginCharacteristicsValue="pluginWantsMidiIn"
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
cppLanguageStandard="20" projectLineFeed="&#10;" headerPath="./include"
version="2.5.0.0" companyName="James H Ball" companyWebsite="https://osci-render.com"
version="2.5.0.1" companyName="James H Ball" companyWebsite="https://osci-render.com"
companyEmail="james@ball.sh" defines="NOMINMAX=1&#10;INTERNET_FLAG_NO_AUTO_REDIRECT=0&#10;OSCI_PREMIUM=1&#10;JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1&#10;JUCE_MODAL_LOOPS_PERMITTED=1"
pluginAUMainType="'aumf'">
<MAINGROUP id="j5Ge2T" name="osci-render">