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;
|
||||
showRightArrow = audioProcessor.getCurrentFileIndex() < audioProcessor.numFiles() - 1;
|
||||
|
||||
if (audioProcessor.objectServerRendering) {
|
||||
fileLabel.setText("Rendering from Blender", juce::dontSendNotification);
|
||||
} else if (audioProcessor.getCurrentFileIndex() == -1) {
|
||||
fileLabel.setText("No file open", juce::dontSendNotification);
|
||||
} else {
|
||||
fileLabel.setText(audioProcessor.getCurrentFileName(), juce::dontSendNotification);
|
||||
}
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.syphonLock);
|
||||
if (audioProcessor.objectServerRendering) {
|
||||
fileLabel.setText("Rendering from Blender", juce::dontSendNotification);
|
||||
} else if (audioProcessor.isSyphonInputActive()) {
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
#include "CustomStandaloneFilterWindow.h"
|
||||
#include "components/SyphonInputSelectorComponent.h"
|
||||
#include "../modules/juce_sharedtexture/SharedTexture.h"
|
||||
#include <memory>
|
||||
|
||||
void OscirenderAudioProcessorEditor::registerFileRemovedCallback() {
|
||||
audioProcessor.setFileRemovedCallback([this](int index) {
|
||||
|
@ -522,3 +525,38 @@ void OscirenderAudioProcessorEditor::openVisualiserSettings() {
|
|||
visualiserSettingsWindow.setVisible(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 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)
|
||||
};
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
#include "audio/BitCrushEffect.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)) {
|
||||
// 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());
|
||||
outputBuffer3d.clear();
|
||||
|
||||
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());
|
||||
{
|
||||
juce::SpinLock::ScopedLockType sLock(syphonLock);
|
||||
if (isSyphonInputActive()) {
|
||||
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
|
||||
auto midiIterator = midiMessages.cbegin();
|
||||
std::for_each(midiIterator,
|
||||
midiMessages.cend(),
|
||||
[&] (const juce::MidiMessageMetadata& meta) { synth.publicHandleMidiEvent(meta.getMessage()); }
|
||||
);
|
||||
} else {
|
||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
synth.renderNextBlock(outputBuffer3d, midiMessages, 0, buffer.getNumSamples());
|
||||
for (int i = 0; i < synth.getNumVoices(); i++) {
|
||||
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
|
||||
if (voice->isVoiceActive()) {
|
||||
customEffect->frequency = voice->getFrequency();
|
||||
break;
|
||||
// handle all midi messages
|
||||
auto midiIterator = midiMessages.cbegin();
|
||||
std::for_each(midiIterator,
|
||||
midiMessages.cend(),
|
||||
[&] (const juce::MidiMessageMetadata& meta) { synth.publicHandleMidiEvent(meta.getMessage()); }
|
||||
);
|
||||
} else {
|
||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
synth.renderNextBlock(outputBuffer3d, midiMessages, 0, buffer.getNumSamples());
|
||||
for (int i = 0; i < synth.getNumVoices(); i++) {
|
||||
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
|
||||
if (voice->isVoiceActive()) {
|
||||
customEffect->frequency = voice->getFrequency();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -542,7 +557,6 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
|||
auto* channelData = buffer.getArrayOfWritePointers();
|
||||
|
||||
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) {
|
||||
// Update frame animation
|
||||
if (animateFrames->getBoolValue()) {
|
||||
if (juce::JUCEApplicationBase::isStandaloneApp()) {
|
||||
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() {
|
||||
return new OscirenderAudioProcessor();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
#include "audio/CustomEffect.h"
|
||||
#include "audio/DashedLineEffect.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;
|
||||
|
||||
|
||||
#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)
|
||||
};
|
||||
|
|
|
@ -79,8 +79,8 @@ void SettingsComponent::fileUpdated(juce::String fileName) {
|
|||
juce::String extension = fileName.fromLastOccurrenceOf(".", true, false).toLowerCase();
|
||||
txt.setVisible(false);
|
||||
frame.setVisible(false);
|
||||
bool isImage = extension == ".gif" || extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".mov" || extension == ".mp4";
|
||||
if (fileName.isEmpty() || audioProcessor.objectServerRendering) {
|
||||
bool isImage = extension == ".gif" || extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".mov" || extension == ".mp4" || audioProcessor.isSyphonInputStarted();
|
||||
if ((fileName.isEmpty() && !audioProcessor.isSyphonInputStarted()) || audioProcessor.objectServerRendering) {
|
||||
// do nothing
|
||||
} else if (extension == ".txt") {
|
||||
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();
|
||||
});
|
||||
|
||||
// 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) {
|
||||
addMenuItem(3, "Settings...", [this] {
|
||||
editor.openAudioSettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void OsciMainMenuBarModel::openSyphonInputDialog() {
|
||||
editor.openSyphonInputDialog();
|
||||
}
|
||||
|
||||
void OsciMainMenuBarModel::disconnectSyphonInput() {
|
||||
audioProcessor.disconnectSyphonInput();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ class OscirenderAudioProcessor;
|
|||
class OsciMainMenuBarModel : public MainMenuBarModel {
|
||||
public:
|
||||
OsciMainMenuBarModel(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor);
|
||||
void openSyphonInputDialog();
|
||||
void disconnectSyphonInput();
|
||||
|
||||
private:
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
juce::String fileName = file.getFullPathName();
|
||||
gd_GIF *gif = gd_open_gif(fileName.toRawUTF8());
|
||||
|
@ -301,12 +320,22 @@ void ImageParser::resetPosition() {
|
|||
}
|
||||
|
||||
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;
|
||||
if (index < 0 || frames.size() <= 0 || index >= frames[frameIndex].size()) {
|
||||
return 0;
|
||||
}
|
||||
float pixel = frames[frameIndex][index] / (float) std::numeric_limits<uint8_t>::max();
|
||||
// never traverse transparent pixels
|
||||
if (invert && pixel > 0) {
|
||||
pixel = 1 - pixel;
|
||||
}
|
||||
|
@ -360,6 +389,8 @@ void ImageParser::findNearestNeighbour(int searchRadius, float thresholdPow, int
|
|||
}
|
||||
|
||||
osci::Point ImageParser::getSample() {
|
||||
juce::SpinLock::ScopedLockType lock(liveImageLock);
|
||||
|
||||
if (ALGORITHM == "HILLIGOSS") {
|
||||
if (count % jumpFrequency() == 0) {
|
||||
resetPosition();
|
||||
|
|
|
@ -7,8 +7,13 @@ class CommonPluginEditor;
|
|||
|
||||
class ImageParser {
|
||||
public:
|
||||
ImageParser(OscirenderAudioProcessor& p, juce::String fileName, juce::MemoryBlock image);
|
||||
~ImageParser();
|
||||
ImageParser(OscirenderAudioProcessor& p, juce::String fileName, juce::MemoryBlock image);
|
||||
// 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);
|
||||
osci::Point getSample();
|
||||
|
@ -58,4 +63,9 @@ private:
|
|||
double scanX = -1;
|
||||
double scanY = 1;
|
||||
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="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="rA7fII" name="SyphonInputSelectorComponent.h" compile="0"
|
||||
resource="0" file="Source/components/SyphonInputSelectorComponent.h"/>
|
||||
<FILE id="WfUlLH" name="TimelineComponent.cpp" compile="1" resource="0"
|
||||
file="Source/components/TimelineComponent.cpp"/>
|
||||
<FILE id="fsckjw" name="TimelineComponent.h" compile="0" resource="0"
|
||||
|
@ -642,6 +644,8 @@
|
|||
file="Source/FrameSettingsComponent.cpp"/>
|
||||
<FILE id="lzBNS1" name="FrameSettingsComponent.h" compile="0" resource="0"
|
||||
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="TJDqWs" name="LookAndFeel.h" compile="0" resource="0" file="Source/LookAndFeel.h"/>
|
||||
<FILE id="X26RjJ" name="LuaComponent.cpp" compile="1" resource="0"
|
||||
|
@ -669,6 +673,8 @@
|
|||
file="Source/SettingsComponent.cpp"/>
|
||||
<FILE id="Vlmozi" name="SettingsComponent.h" compile="0" resource="0"
|
||||
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="Source/TxtComponent.cpp"/>
|
||||
<FILE id="kxPbsL" name="TxtComponent.h" compile="0" resource="0" file="Source/TxtComponent.h"/>
|
||||
|
|
Ładowanie…
Reference in New Issue