kopia lustrzana https://github.com/jameshball/osci-render
Get initial working version of syphon input
rodzic
fb1db3c886
commit
590eace784
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
|
||||||
|
// An invisible component that owns a persistent OpenGL context, always attached to the desktop.
|
||||||
|
class InvisibleOpenGLContextComponent : public juce::Component {
|
||||||
|
public:
|
||||||
|
InvisibleOpenGLContextComponent() {
|
||||||
|
setSize(1, 1); // Minimal size
|
||||||
|
setBounds(0, 0, 1, 1); // Minimal bounds
|
||||||
|
setVisible(true);
|
||||||
|
setOpaque(false);
|
||||||
|
context.setComponentPaintingEnabled(false);
|
||||||
|
context.attachTo(*this);
|
||||||
|
addToDesktop(
|
||||||
|
juce::ComponentPeer::windowIsTemporary |
|
||||||
|
juce::ComponentPeer::windowIgnoresKeyPresses |
|
||||||
|
juce::ComponentPeer::windowIgnoresMouseClicks
|
||||||
|
);
|
||||||
|
}
|
||||||
|
~InvisibleOpenGLContextComponent() override {
|
||||||
|
context.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::OpenGLContext& getContext() { return context; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
juce::OpenGLContext context;
|
||||||
|
};
|
|
@ -157,13 +157,18 @@ void MainComponent::updateFileLabel() {
|
||||||
showLeftArrow = audioProcessor.getCurrentFileIndex() > 0;
|
showLeftArrow = audioProcessor.getCurrentFileIndex() > 0;
|
||||||
showRightArrow = audioProcessor.getCurrentFileIndex() < audioProcessor.numFiles() - 1;
|
showRightArrow = audioProcessor.getCurrentFileIndex() < audioProcessor.numFiles() - 1;
|
||||||
|
|
||||||
if (audioProcessor.objectServerRendering) {
|
{
|
||||||
fileLabel.setText("Rendering from Blender", juce::dontSendNotification);
|
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
|
||||||
} else if (audioProcessor.getCurrentFileIndex() == -1) {
|
if (audioProcessor.objectServerRendering) {
|
||||||
fileLabel.setText("No file open", juce::dontSendNotification);
|
fileLabel.setText("Rendering from Blender", juce::dontSendNotification);
|
||||||
} else {
|
} else if (audioProcessor.isSyphonInputActive()) {
|
||||||
fileLabel.setText(audioProcessor.getCurrentFileName(), juce::dontSendNotification);
|
fileLabel.setText(audioProcessor.getSyphonSourceName(), juce::dontSendNotification);
|
||||||
}
|
} else if (audioProcessor.getCurrentFileIndex() == -1) {
|
||||||
|
fileLabel.setText("No file open", juce::dontSendNotification);
|
||||||
|
} else {
|
||||||
|
fileLabel.setText(audioProcessor.getCurrentFileName(), juce::dontSendNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resized();
|
resized();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#include "PluginProcessor.h"
|
#include "PluginProcessor.h"
|
||||||
#include "PluginEditor.h"
|
#include "PluginEditor.h"
|
||||||
#include "CustomStandaloneFilterWindow.h"
|
#include "CustomStandaloneFilterWindow.h"
|
||||||
|
#include "components/SyphonInputSelectorComponent.h"
|
||||||
|
#include "../modules/juce_sharedtexture/SharedTexture.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
void OscirenderAudioProcessorEditor::registerFileRemovedCallback() {
|
void OscirenderAudioProcessorEditor::registerFileRemovedCallback() {
|
||||||
audioProcessor.setFileRemovedCallback([this](int index) {
|
audioProcessor.setFileRemovedCallback([this](int index) {
|
||||||
|
@ -522,3 +525,38 @@ void OscirenderAudioProcessorEditor::openVisualiserSettings() {
|
||||||
visualiserSettingsWindow.setVisible(true);
|
visualiserSettingsWindow.setVisible(true);
|
||||||
visualiserSettingsWindow.toFront(true);
|
visualiserSettingsWindow.toFront(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OscirenderAudioProcessorEditor::openSyphonInputDialog() {
|
||||||
|
#if JUCE_MAC || JUCE_WINDOWS
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
juce::DialogWindow::LaunchOptions options;
|
||||||
|
options.content.setOwned(selector);
|
||||||
|
options.content->setSize(350, 120);
|
||||||
|
options.dialogTitle = "Select Syphon/Spout Input";
|
||||||
|
options.dialogBackgroundColour = juce::Colours::darkgrey;
|
||||||
|
options.escapeKeyTriggersCloseButton = true;
|
||||||
|
options.useNativeTitleBar = true;
|
||||||
|
options.resizable = false;
|
||||||
|
options.launchAsync();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscirenderAudioProcessorEditor::onSyphonInputSelected(const juce::String& server, const juce::String& app) {
|
||||||
|
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
|
||||||
|
audioProcessor.connectSyphonInput(server, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscirenderAudioProcessorEditor::onSyphonInputDisconnected() {
|
||||||
|
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
|
||||||
|
audioProcessor.disconnectSyphonInput();
|
||||||
|
}
|
||||||
|
|
|
@ -86,5 +86,10 @@ 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;
|
||||||
|
|
||||||
|
// Syphon/Spout input dialog
|
||||||
|
void openSyphonInputDialog();
|
||||||
|
void onSyphonInputSelected(const juce::String& server, const juce::String& app);
|
||||||
|
void onSyphonInputDisconnected();
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessorEditor)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessorEditor)
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,12 @@
|
||||||
#include "audio/BitCrushEffect.h"
|
#include "audio/BitCrushEffect.h"
|
||||||
#include "audio/BulgeEffect.h"
|
#include "audio/BulgeEffect.h"
|
||||||
|
|
||||||
|
#if JUCE_MAC || JUCE_WINDOWS
|
||||||
|
#include "SyphonFrameGrabber.h"
|
||||||
|
#include "img/ImageParser.h"
|
||||||
|
#include "../modules/juce_sharedtexture/SharedTexture.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(BusesProperties().withInput("Input", juce::AudioChannelSet::namedChannelSet(2), true).withOutput("Output", juce::AudioChannelSet::stereo(), true)) {
|
OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(BusesProperties().withInput("Input", juce::AudioChannelSet::namedChannelSet(2), true).withOutput("Output", juce::AudioChannelSet::stereo(), true)) {
|
||||||
// locking isn't necessary here because we are in the constructor
|
// locking isn't necessary here because we are in the constructor
|
||||||
|
@ -507,32 +513,41 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
||||||
juce::AudioBuffer<float> outputBuffer3d = juce::AudioBuffer<float>(3, buffer.getNumSamples());
|
juce::AudioBuffer<float> outputBuffer3d = juce::AudioBuffer<float>(3, buffer.getNumSamples());
|
||||||
outputBuffer3d.clear();
|
outputBuffer3d.clear();
|
||||||
|
|
||||||
if (usingInput && totalNumInputChannels >= 1) {
|
{
|
||||||
if (totalNumInputChannels >= 2) {
|
juce::SpinLock::ScopedLockType sLock(syphonLock);
|
||||||
for (auto channel = 0; channel < juce::jmin(2, totalNumInputChannels); channel++) {
|
if (isSyphonInputActive()) {
|
||||||
outputBuffer3d.copyFrom(channel, 0, inputBuffer, channel, 0, buffer.getNumSamples());
|
for (int sample = 0; sample < outputBuffer3d.getNumSamples(); sample++) {
|
||||||
|
osci::Point point = syphonImageParser.getSample();
|
||||||
|
outputBuffer3d.setSample(0, sample, point.x);
|
||||||
|
outputBuffer3d.setSample(1, sample, point.y);
|
||||||
|
}
|
||||||
|
} else 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());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For mono input, copy the single channel to both left and right
|
||||||
|
outputBuffer3d.copyFrom(0, 0, inputBuffer, 0, 0, buffer.getNumSamples());
|
||||||
|
outputBuffer3d.copyFrom(1, 0, inputBuffer, 0, 0, buffer.getNumSamples());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// For mono input, copy the single channel to both left and right
|
|
||||||
outputBuffer3d.copyFrom(0, 0, inputBuffer, 0, 0, buffer.getNumSamples());
|
|
||||||
outputBuffer3d.copyFrom(1, 0, inputBuffer, 0, 0, buffer.getNumSamples());
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle all midi messages
|
// handle all midi messages
|
||||||
auto midiIterator = midiMessages.cbegin();
|
auto midiIterator = midiMessages.cbegin();
|
||||||
std::for_each(midiIterator,
|
std::for_each(midiIterator,
|
||||||
midiMessages.cend(),
|
midiMessages.cend(),
|
||||||
[&] (const juce::MidiMessageMetadata& meta) { synth.publicHandleMidiEvent(meta.getMessage()); }
|
[&] (const juce::MidiMessageMetadata& meta) { synth.publicHandleMidiEvent(meta.getMessage()); }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||||
synth.renderNextBlock(outputBuffer3d, midiMessages, 0, buffer.getNumSamples());
|
synth.renderNextBlock(outputBuffer3d, midiMessages, 0, buffer.getNumSamples());
|
||||||
for (int i = 0; i < synth.getNumVoices(); i++) {
|
for (int i = 0; i < synth.getNumVoices(); i++) {
|
||||||
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
|
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
|
||||||
if (voice->isVoiceActive()) {
|
if (voice->isVoiceActive()) {
|
||||||
customEffect->frequency = voice->getFrequency();
|
customEffect->frequency = voice->getFrequency();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -542,7 +557,6 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
||||||
auto* channelData = buffer.getArrayOfWritePointers();
|
auto* channelData = buffer.getArrayOfWritePointers();
|
||||||
|
|
||||||
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) {
|
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) {
|
||||||
// Update frame animation
|
|
||||||
if (animateFrames->getBoolValue()) {
|
if (animateFrames->getBoolValue()) {
|
||||||
if (juce::JUCEApplicationBase::isStandaloneApp()) {
|
if (juce::JUCEApplicationBase::isStandaloneApp()) {
|
||||||
animationFrame = animationFrame + sTimeSec * animationRate->getValueUnnormalised();
|
animationFrame = animationFrame + sTimeSec * animationRate->getValueUnnormalised();
|
||||||
|
@ -890,6 +904,49 @@ void OscirenderAudioProcessor::envelopeChanged(EnvelopeComponent* changedEnvelop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JUCE_MAC || JUCE_WINDOWS
|
||||||
|
// 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() {
|
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() {
|
||||||
return new OscirenderAudioProcessor();
|
return new OscirenderAudioProcessor();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,11 @@
|
||||||
#include "audio/CustomEffect.h"
|
#include "audio/CustomEffect.h"
|
||||||
#include "audio/DashedLineEffect.h"
|
#include "audio/DashedLineEffect.h"
|
||||||
#include "CommonPluginProcessor.h"
|
#include "CommonPluginProcessor.h"
|
||||||
|
#include "SyphonFrameGrabber.h"
|
||||||
|
|
||||||
|
#if JUCE_MAC || JUCE_WINDOWS
|
||||||
|
#include "../modules/juce_sharedtexture/SharedTexture.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/**
|
/**
|
||||||
|
@ -282,6 +287,21 @@ private:
|
||||||
|
|
||||||
juce::AudioPlayHead* playHead;
|
juce::AudioPlayHead* playHead;
|
||||||
|
|
||||||
|
|
||||||
|
#if JUCE_MAC || JUCE_WINDOWS
|
||||||
|
public:
|
||||||
|
bool isSyphonInputActive() const;
|
||||||
|
bool isSyphonInputStarted() const;
|
||||||
|
void connectSyphonInput(const juce::String& server, const juce::String& app);
|
||||||
|
void disconnectSyphonInput();
|
||||||
|
juce::String getSyphonSourceName() const;
|
||||||
|
|
||||||
|
juce::SpinLock syphonLock;
|
||||||
|
private:
|
||||||
|
ImageParser syphonImageParser = ImageParser(*this);
|
||||||
|
std::unique_ptr<SyphonFrameGrabber> syphonFrameGrabber;
|
||||||
|
#endif
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessor)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscirenderAudioProcessor)
|
||||||
};
|
};
|
||||||
|
|
|
@ -79,8 +79,8 @@ void SettingsComponent::fileUpdated(juce::String fileName) {
|
||||||
juce::String extension = fileName.fromLastOccurrenceOf(".", true, false).toLowerCase();
|
juce::String extension = fileName.fromLastOccurrenceOf(".", true, false).toLowerCase();
|
||||||
txt.setVisible(false);
|
txt.setVisible(false);
|
||||||
frame.setVisible(false);
|
frame.setVisible(false);
|
||||||
bool isImage = extension == ".gif" || extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".mov" || extension == ".mp4";
|
bool isImage = extension == ".gif" || extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".mov" || extension == ".mp4" || audioProcessor.isSyphonInputStarted();
|
||||||
if (fileName.isEmpty() || audioProcessor.objectServerRendering) {
|
if ((fileName.isEmpty() && !audioProcessor.isSyphonInputStarted()) || audioProcessor.objectServerRendering) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else if (extension == ".txt") {
|
} else if (extension == ".txt") {
|
||||||
txt.setVisible(true);
|
txt.setVisible(true);
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
#pragma once
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
#include "InvisibleOpenGLContextComponent.h"
|
||||||
|
|
||||||
|
class SyphonFrameGrabber : private juce::Thread, public juce::Component
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SyphonFrameGrabber(SharedTextureManager& manager, juce::String server, juce::String app, ImageParser& parser, int pollMs = 16)
|
||||||
|
: juce::Thread("SyphonFrameGrabber"), pollIntervalMs(pollMs), manager(manager), parser(parser)
|
||||||
|
{
|
||||||
|
// Create the invisible OpenGL context component
|
||||||
|
glContextComponent = std::make_unique<InvisibleOpenGLContextComponent>();
|
||||||
|
receiver = manager.addReceiver(server, app);
|
||||||
|
if (receiver) {
|
||||||
|
receiver->setUseCPUImage(true); // for pixel access
|
||||||
|
}
|
||||||
|
startThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
~SyphonFrameGrabber() override {
|
||||||
|
stopThread(500);
|
||||||
|
if (receiver) {
|
||||||
|
manager.removeReceiver(receiver);
|
||||||
|
receiver = nullptr;
|
||||||
|
}
|
||||||
|
glContextComponent.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() override {
|
||||||
|
while (!threadShouldExit()) {
|
||||||
|
{
|
||||||
|
if (glContextComponent) {
|
||||||
|
glContextComponent->getContext().makeActive();
|
||||||
|
}
|
||||||
|
receiver->renderGL();
|
||||||
|
if (glContextComponent) {
|
||||||
|
glContextComponent->getContext().deactivateCurrentContext();
|
||||||
|
}
|
||||||
|
if (isActive() && receiver->isConnected) {
|
||||||
|
juce::Image image = receiver->getImage();
|
||||||
|
parser.updateLiveFrame(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wait(pollIntervalMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActive() const
|
||||||
|
{
|
||||||
|
return receiver != nullptr && receiver->isInit && receiver->enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::String getSourceName() const
|
||||||
|
{
|
||||||
|
if (receiver) {
|
||||||
|
return receiver->sharingName + " (" + receiver->sharingAppName + ")";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int pollIntervalMs;
|
||||||
|
SharedTextureManager& manager;
|
||||||
|
SharedTextureReceiver* receiver = nullptr;
|
||||||
|
ImageParser& parser;
|
||||||
|
std::unique_ptr<InvisibleOpenGLContextComponent> glContextComponent;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SyphonFrameGrabber)
|
||||||
|
};
|
|
@ -62,9 +62,25 @@ OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, Oscirend
|
||||||
editor.openRecordingSettings();
|
editor.openRecordingSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
openSyphonInputDialog();
|
||||||
|
});
|
||||||
|
|
||||||
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
|
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
|
||||||
addMenuItem(3, "Settings...", [this] {
|
addMenuItem(3, "Settings...", [this] {
|
||||||
editor.openAudioSettings();
|
editor.openAudioSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OsciMainMenuBarModel::openSyphonInputDialog() {
|
||||||
|
editor.openSyphonInputDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OsciMainMenuBarModel::disconnectSyphonInput() {
|
||||||
|
audioProcessor.disconnectSyphonInput();
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ class OscirenderAudioProcessor;
|
||||||
class OsciMainMenuBarModel : public MainMenuBarModel {
|
class OsciMainMenuBarModel : public MainMenuBarModel {
|
||||||
public:
|
public:
|
||||||
OsciMainMenuBarModel(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor);
|
OsciMainMenuBarModel(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor);
|
||||||
|
void openSyphonInputDialog();
|
||||||
|
void disconnectSyphonInput();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OscirenderAudioProcessor& audioProcessor;
|
OscirenderAudioProcessor& audioProcessor;
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
#include "../modules/juce_sharedtexture/SharedTexture.h"
|
||||||
|
|
||||||
|
class SyphonInputSelectorComponent : public juce::Component, private juce::Button::Listener {
|
||||||
|
public:
|
||||||
|
SyphonInputSelectorComponent(SharedTextureManager& manager, std::function<void(const juce::String&, const juce::String&)> onConnect, std::function<void()> onDisconnect, bool isConnected, juce::String currentSource)
|
||||||
|
: sharedTextureManager(manager), onConnectCallback(onConnect), onDisconnectCallback(onDisconnect), connected(isConnected), currentSourceName(currentSource) {
|
||||||
|
addAndMakeVisible(sourceLabel);
|
||||||
|
sourceLabel.setText("Syphon/Spout Source:", juce::dontSendNotification);
|
||||||
|
|
||||||
|
addAndMakeVisible(sourceDropdown);
|
||||||
|
sourceDropdown.onChange = [this] {
|
||||||
|
selectedSource = sourceDropdown.getText();
|
||||||
|
};
|
||||||
|
|
||||||
|
addAndMakeVisible(connectButton);
|
||||||
|
connectButton.setButtonText(connected ? "Disconnect" : "Connect");
|
||||||
|
connectButton.addListener(this);
|
||||||
|
|
||||||
|
refreshSources();
|
||||||
|
if (!currentSourceName.isEmpty()) {
|
||||||
|
sourceDropdown.setText(currentSourceName, juce::dontSendNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshSources() {
|
||||||
|
sourceDropdown.clear();
|
||||||
|
auto sources = sharedTextureManager.getAvailableSenders();
|
||||||
|
for (const auto& s : sources)
|
||||||
|
sourceDropdown.addItem(s, sourceDropdown.getNumItems() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resized() override {
|
||||||
|
auto area = getLocalBounds().reduced(10);
|
||||||
|
sourceLabel.setBounds(area.removeFromTop(24));
|
||||||
|
sourceDropdown.setBounds(area.removeFromTop(28));
|
||||||
|
connectButton.setBounds(area.removeFromTop(28).reduced(0, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonClicked(juce::Button* b) override {
|
||||||
|
if (connected) {
|
||||||
|
onDisconnectCallback();
|
||||||
|
} else {
|
||||||
|
auto selected = sourceDropdown.getText();
|
||||||
|
if (selected.isNotEmpty()) {
|
||||||
|
// Syphon: "ServerName - AppName"
|
||||||
|
auto parts = juce::StringArray::fromTokens(selected, "-", "");
|
||||||
|
juce::String server = parts[0].trim();
|
||||||
|
juce::String app = parts.size() > 1 ? parts[1].trim() : juce::String();
|
||||||
|
onConnectCallback(server, app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SharedTextureManager& sharedTextureManager;
|
||||||
|
std::function<void(const juce::String&, const juce::String&)> onConnectCallback;
|
||||||
|
std::function<void()> onDisconnectCallback;
|
||||||
|
bool connected;
|
||||||
|
juce::String currentSourceName;
|
||||||
|
juce::String selectedSource;
|
||||||
|
|
||||||
|
juce::Label sourceLabel;
|
||||||
|
juce::ComboBox sourceDropdown;
|
||||||
|
juce::TextButton connectButton;
|
||||||
|
};
|
|
@ -48,6 +48,25 @@ ImageParser::ImageParser(OscirenderAudioProcessor& p, juce::String extension, ju
|
||||||
setFrame(0);
|
setFrame(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructor for live Syphon/Spout input
|
||||||
|
ImageParser::ImageParser(OscirenderAudioProcessor& p) : audioProcessor(p), usingLiveImage(true) {
|
||||||
|
width = 1;
|
||||||
|
height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageParser::updateLiveFrame(const juce::Image& newImage)
|
||||||
|
{
|
||||||
|
if (newImage.isValid()) {
|
||||||
|
juce::SpinLock::ScopedLockType lock(liveImageLock);
|
||||||
|
liveImage = newImage;
|
||||||
|
liveImage.duplicateIfShared();
|
||||||
|
width = liveImage.getWidth();
|
||||||
|
height = liveImage.getHeight();
|
||||||
|
visited.resize(width * height);
|
||||||
|
visited.assign(width * height, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ImageParser::processGifFile(juce::File& file) {
|
void ImageParser::processGifFile(juce::File& file) {
|
||||||
juce::String fileName = file.getFullPathName();
|
juce::String fileName = file.getFullPathName();
|
||||||
gd_GIF *gif = gd_open_gif(fileName.toRawUTF8());
|
gd_GIF *gif = gd_open_gif(fileName.toRawUTF8());
|
||||||
|
@ -301,12 +320,22 @@ void ImageParser::resetPosition() {
|
||||||
}
|
}
|
||||||
|
|
||||||
float ImageParser::getPixelValue(int x, int y, bool invert) {
|
float ImageParser::getPixelValue(int x, int y, bool invert) {
|
||||||
|
if (usingLiveImage) {
|
||||||
|
if (liveImage.isValid()) {
|
||||||
|
if (x < 0 || x >= width || y < 0 || y >= height) return 0;
|
||||||
|
juce::Colour pixel = liveImage.getPixelAt(x, height - y - 1);
|
||||||
|
float value = pixel.getBrightness();
|
||||||
|
if (invert && value > 0) value = 1.0f - value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int index = (height - y - 1) * width + x;
|
int index = (height - y - 1) * width + x;
|
||||||
if (index < 0 || frames.size() <= 0 || index >= frames[frameIndex].size()) {
|
if (index < 0 || frames.size() <= 0 || index >= frames[frameIndex].size()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
float pixel = frames[frameIndex][index] / (float) std::numeric_limits<uint8_t>::max();
|
float pixel = frames[frameIndex][index] / (float) std::numeric_limits<uint8_t>::max();
|
||||||
// never traverse transparent pixels
|
|
||||||
if (invert && pixel > 0) {
|
if (invert && pixel > 0) {
|
||||||
pixel = 1 - pixel;
|
pixel = 1 - pixel;
|
||||||
}
|
}
|
||||||
|
@ -360,6 +389,8 @@ void ImageParser::findNearestNeighbour(int searchRadius, float thresholdPow, int
|
||||||
}
|
}
|
||||||
|
|
||||||
osci::Point ImageParser::getSample() {
|
osci::Point ImageParser::getSample() {
|
||||||
|
juce::SpinLock::ScopedLockType lock(liveImageLock);
|
||||||
|
|
||||||
if (ALGORITHM == "HILLIGOSS") {
|
if (ALGORITHM == "HILLIGOSS") {
|
||||||
if (count % jumpFrequency() == 0) {
|
if (count % jumpFrequency() == 0) {
|
||||||
resetPosition();
|
resetPosition();
|
||||||
|
|
|
@ -7,8 +7,13 @@ class CommonPluginEditor;
|
||||||
|
|
||||||
class ImageParser {
|
class ImageParser {
|
||||||
public:
|
public:
|
||||||
ImageParser(OscirenderAudioProcessor& p, juce::String fileName, juce::MemoryBlock image);
|
ImageParser(OscirenderAudioProcessor& p, juce::String fileName, juce::MemoryBlock image);
|
||||||
~ImageParser();
|
// Constructor for live Syphon/Spout input
|
||||||
|
ImageParser(OscirenderAudioProcessor& p);
|
||||||
|
~ImageParser();
|
||||||
|
|
||||||
|
// Update the live frame (for Syphon/Spout)
|
||||||
|
void updateLiveFrame(const juce::Image& newImage);
|
||||||
|
|
||||||
void setFrame(int index);
|
void setFrame(int index);
|
||||||
osci::Point getSample();
|
osci::Point getSample();
|
||||||
|
@ -58,4 +63,9 @@ private:
|
||||||
double scanX = -1;
|
double scanX = -1;
|
||||||
double scanY = 1;
|
double scanY = 1;
|
||||||
int scanCount = 0;
|
int scanCount = 0;
|
||||||
|
|
||||||
|
// Live image support
|
||||||
|
juce::SpinLock liveImageLock;
|
||||||
|
bool usingLiveImage = false;
|
||||||
|
juce::Image liveImage;
|
||||||
};
|
};
|
||||||
|
|
|
@ -120,7 +120,6 @@ void ObjectServer::run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f2faeeb981b5b1676c3aa349e60335922c966ae4
|
Subproject commit 8c77ff43e2d22ae927bfb3a268d0724e4174b53f
|
|
@ -179,6 +179,8 @@
|
||||||
<FILE id="QQzSwh" name="SliderTextBox.h" compile="0" resource="0" file="Source/components/SliderTextBox.h"/>
|
<FILE id="QQzSwh" name="SliderTextBox.h" compile="0" resource="0" file="Source/components/SliderTextBox.h"/>
|
||||||
<FILE id="QrDKRZ" name="SvgButton.h" compile="0" resource="0" file="Source/components/SvgButton.h"/>
|
<FILE id="QrDKRZ" name="SvgButton.h" compile="0" resource="0" file="Source/components/SvgButton.h"/>
|
||||||
<FILE id="qzfstC" name="SwitchButton.h" compile="0" resource="0" file="Source/components/SwitchButton.h"/>
|
<FILE id="qzfstC" name="SwitchButton.h" compile="0" resource="0" file="Source/components/SwitchButton.h"/>
|
||||||
|
<FILE id="rA7fII" name="SyphonInputSelectorComponent.h" compile="0"
|
||||||
|
resource="0" file="Source/components/SyphonInputSelectorComponent.h"/>
|
||||||
<FILE id="WfUlLH" name="TimelineComponent.cpp" compile="1" resource="0"
|
<FILE id="WfUlLH" name="TimelineComponent.cpp" compile="1" resource="0"
|
||||||
file="Source/components/TimelineComponent.cpp"/>
|
file="Source/components/TimelineComponent.cpp"/>
|
||||||
<FILE id="fsckjw" name="TimelineComponent.h" compile="0" resource="0"
|
<FILE id="fsckjw" name="TimelineComponent.h" compile="0" resource="0"
|
||||||
|
@ -642,6 +644,8 @@
|
||||||
file="Source/FrameSettingsComponent.cpp"/>
|
file="Source/FrameSettingsComponent.cpp"/>
|
||||||
<FILE id="lzBNS1" name="FrameSettingsComponent.h" compile="0" resource="0"
|
<FILE id="lzBNS1" name="FrameSettingsComponent.h" compile="0" resource="0"
|
||||||
file="Source/FrameSettingsComponent.h"/>
|
file="Source/FrameSettingsComponent.h"/>
|
||||||
|
<FILE id="nfoWJk" name="InvisibleOpenGLContextComponent.h" compile="0"
|
||||||
|
resource="0" file="Source/InvisibleOpenGLContextComponent.h"/>
|
||||||
<FILE id="d2zFqF" name="LookAndFeel.cpp" compile="1" resource="0" file="Source/LookAndFeel.cpp"/>
|
<FILE id="d2zFqF" name="LookAndFeel.cpp" compile="1" resource="0" file="Source/LookAndFeel.cpp"/>
|
||||||
<FILE id="TJDqWs" name="LookAndFeel.h" compile="0" resource="0" file="Source/LookAndFeel.h"/>
|
<FILE id="TJDqWs" name="LookAndFeel.h" compile="0" resource="0" file="Source/LookAndFeel.h"/>
|
||||||
<FILE id="X26RjJ" name="LuaComponent.cpp" compile="1" resource="0"
|
<FILE id="X26RjJ" name="LuaComponent.cpp" compile="1" resource="0"
|
||||||
|
@ -669,6 +673,8 @@
|
||||||
file="Source/SettingsComponent.cpp"/>
|
file="Source/SettingsComponent.cpp"/>
|
||||||
<FILE id="Vlmozi" name="SettingsComponent.h" compile="0" resource="0"
|
<FILE id="Vlmozi" name="SettingsComponent.h" compile="0" resource="0"
|
||||||
file="Source/SettingsComponent.h"/>
|
file="Source/SettingsComponent.h"/>
|
||||||
|
<FILE id="jyHVpz" name="SyphonFrameGrabber.h" compile="0" resource="0"
|
||||||
|
file="Source/SyphonFrameGrabber.h"/>
|
||||||
<FILE id="UxZu4n" name="TxtComponent.cpp" compile="1" resource="0"
|
<FILE id="UxZu4n" name="TxtComponent.cpp" compile="1" resource="0"
|
||||||
file="Source/TxtComponent.cpp"/>
|
file="Source/TxtComponent.cpp"/>
|
||||||
<FILE id="kxPbsL" name="TxtComponent.h" compile="0" resource="0" file="Source/TxtComponent.h"/>
|
<FILE id="kxPbsL" name="TxtComponent.h" compile="0" resource="0" file="Source/TxtComponent.h"/>
|
||||||
|
|
Ładowanie…
Reference in New Issue