From 1bf3ba6646c827961f49ea0d937066215aa49edc Mon Sep 17 00:00:00 2001 From: James Ball Date: Sun, 10 Sep 2023 17:43:37 +0100 Subject: [PATCH] Add Blender support --- Source/MainComponent.cpp | 9 +- Source/PluginEditor.cpp | 11 +- Source/PluginProcessor.cpp | 32 ++++-- Source/PluginProcessor.h | 7 ++ Source/SettingsComponent.cpp | 2 +- Source/audio/ShapeSound.cpp | 6 +- Source/audio/ShapeSound.h | 1 + Source/audio/ShapeVoice.cpp | 5 +- Source/obj/ObjectServer.cpp | 188 +++++++++++++++++++++++++++++++++++ Source/obj/ObjectServer.h | 17 ++++ osci-render.jucer | 3 + 11 files changed, 265 insertions(+), 16 deletions(-) create mode 100644 Source/obj/ObjectServer.cpp create mode 100644 Source/obj/ObjectServer.h diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index db62e13..03ce1d9 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -107,12 +107,13 @@ MainComponent::~MainComponent() { } void MainComponent::updateFileLabel() { - if (audioProcessor.getCurrentFileIndex() == -1) { + if (audioProcessor.objectServerRendering) { + fileLabel.setText("Rendering from Blender", juce::dontSendNotification); + } else if (audioProcessor.getCurrentFileIndex() == -1) { fileLabel.setText("No file open", juce::dontSendNotification); - return; + } else { + fileLabel.setText(audioProcessor.getCurrentFileName(), juce::dontSendNotification); } - - fileLabel.setText(audioProcessor.getCurrentFileName(), juce::dontSendNotification); } void MainComponent::resized() { diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 0f54ba3..df319f5 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -42,6 +42,7 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr { juce::MessageManagerLock lock; + audioProcessor.fileChangeBroadcaster.addChangeListener(this); audioProcessor.broadcaster.addChangeListener(this); } @@ -65,6 +66,7 @@ OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() { juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr); juce::MessageManagerLock lock; audioProcessor.broadcaster.removeChangeListener(this); + audioProcessor.fileChangeBroadcaster.removeChangeListener(this); } // parsersLock must be held @@ -206,8 +208,13 @@ void OscirenderAudioProcessorEditor::handleAsyncUpdate() { void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) { juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock); - initialiseCodeEditors(); - settings.update(); + if (source == &audioProcessor.broadcaster) { + initialiseCodeEditors(); + settings.update(); + } else if (source == &audioProcessor.fileChangeBroadcaster) { + // triggered when the audioProcessor changes the current file (e.g. to Blender) + settings.fileUpdated(audioProcessor.getCurrentFileName()); + } } void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) { diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 271a651..c2db5af 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -388,11 +388,13 @@ void OscirenderAudioProcessor::changeCurrentFile(int index) { } void OscirenderAudioProcessor::changeSound(ShapeSound::Ptr sound) { - synth.clearSounds(); - synth.addSound(sound); - for (int i = 0; i < synth.getNumVoices(); i++) { - auto voice = dynamic_cast(synth.getVoice(i)); - voice->updateSound(sound.get()); + if (!objectServerRendering || sound == objectServerSound) { + synth.clearSounds(); + synth.addSound(sound); + for (int i = 0; i < synth.getNumVoices(); i++) { + auto voice = dynamic_cast(synth.getVoice(i)); + voice->updateSound(sound.get()); + } } } @@ -405,7 +407,7 @@ std::shared_ptr OscirenderAudioProcessor::getCurrentFileParser() { } juce::String OscirenderAudioProcessor::getCurrentFileName() { - if (currentFile == -1) { + if (objectServerRendering || currentFile == -1) { return ""; } else { return fileNames[currentFile]; @@ -420,6 +422,24 @@ std::shared_ptr OscirenderAudioProcessor::getFileBlock(int in return fileBlocks[index]; } +void OscirenderAudioProcessor::setObjectServerRendering(bool enabled) { + { + juce::SpinLock::ScopedLockType lock1(parsersLock); + + objectServerRendering = enabled; + if (enabled) { + changeSound(objectServerSound); + } else { + changeCurrentFile(currentFile); + } + } + + { + juce::MessageManagerLock lock; + fileChangeBroadcaster.sendChangeMessage(); + } +} + void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels(); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 9966960..622d827 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -20,6 +20,7 @@ #include "audio/PitchDetector.h" #include "audio/WobbleEffect.h" #include "audio/PerspectiveEffect.h" +#include "obj/ObjectServer.h" //============================================================================== /** @@ -187,6 +188,8 @@ public: std::atomic currentFile = -1; juce::ChangeBroadcaster broadcaster; + std::atomic objectServerRendering = false; + juce::ChangeBroadcaster fileChangeBroadcaster; private: juce::SpinLock consumerLock; @@ -203,6 +206,8 @@ public: juce::SpinLock fontLock; juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain); + ShapeSound::Ptr objectServerSound = new ShapeSound(); + void addLuaSlider(); void updateEffectPrecedence(); void updateFileBlock(int index, std::shared_ptr block); @@ -218,6 +223,7 @@ public: juce::String getCurrentFileName(); juce::String getFileName(int index); std::shared_ptr getFileBlock(int index); + void setObjectServerRendering(bool enabled); private: std::atomic volume = 1.0; std::atomic threshold = 1.0; @@ -232,6 +238,7 @@ private: juce::Synthesiser synth; AudioWebSocketServer softwareOscilloscopeServer{*this}; + ObjectServer objectServer{*this}; void updateLuaValues(); void updateObjValues(); diff --git a/Source/SettingsComponent.cpp b/Source/SettingsComponent.cpp index ff4ca9a..ef293fe 100644 --- a/Source/SettingsComponent.cpp +++ b/Source/SettingsComponent.cpp @@ -47,7 +47,7 @@ void SettingsComponent::fileUpdated(juce::String fileName) { lua.setVisible(false); obj.setVisible(false); txt.setVisible(false); - if (fileName.isEmpty()) { + if (fileName.isEmpty() || audioProcessor.objectServerRendering) { // do nothing } else if (extension == ".lua") { lua.setVisible(true); diff --git a/Source/audio/ShapeSound.cpp b/Source/audio/ShapeSound.cpp index 152f9ba..9ef27cc 100644 --- a/Source/audio/ShapeSound.cpp +++ b/Source/audio/ShapeSound.cpp @@ -9,9 +9,13 @@ ShapeSound::ShapeSound(std::shared_ptr parser) : parser(parser) { producer->startThread(); } +ShapeSound::ShapeSound() {} + ShapeSound::~ShapeSound() { frames.kill(); - producer->stopThread(1000); + if (producer != nullptr) { + producer->stopThread(1000); + } } bool ShapeSound::appliesToNote(int note) { diff --git a/Source/audio/ShapeSound.h b/Source/audio/ShapeSound.h index 9757573..0a84a5f 100644 --- a/Source/audio/ShapeSound.h +++ b/Source/audio/ShapeSound.h @@ -8,6 +8,7 @@ class ShapeSound : public juce::SynthesiserSound, public FrameConsumer { public: ShapeSound(std::shared_ptr parser); + ShapeSound(); ~ShapeSound() override; bool appliesToNote(int note) override; diff --git a/Source/audio/ShapeVoice.cpp b/Source/audio/ShapeVoice.cpp index b884ed0..829c979 100644 --- a/Source/audio/ShapeVoice.cpp +++ b/Source/audio/ShapeVoice.cpp @@ -88,10 +88,11 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star bool renderingSample = true; if (sound.load() != nullptr) { - renderingSample = sound.load()->parser->isSample(); + auto parser = sound.load()->parser; + renderingSample = parser != nullptr && parser->isSample(); if (renderingSample) { - channels = sound.load()->parser->nextSample(); + channels = parser->nextSample(); } else if (currentShape < frame.size()) { auto& shape = frame[currentShape]; double length = shape->length(); diff --git a/Source/obj/ObjectServer.cpp b/Source/obj/ObjectServer.cpp new file mode 100644 index 0000000..f1ca510 --- /dev/null +++ b/Source/obj/ObjectServer.cpp @@ -0,0 +1,188 @@ +#include "ObjectServer.h" +#include "../PluginProcessor.h" +#include "../shape/Line.h" + +ObjectServer::ObjectServer(OscirenderAudioProcessor& p) : audioProcessor(p), juce::Thread("Object Server") { + startThread(); +} + +ObjectServer::~ObjectServer() { + stopThread(1000); +} + +void ObjectServer::run() { + if (socket.createListener(51677, "127.0.0.1")) { + // preallocating a large buffer to avoid allocations in the loop + std::unique_ptr message{ new char[10 * 1024 * 1024] }; + + while (!threadShouldExit()) { + if (socket.waitUntilReady(true, 200)) { + std::unique_ptr connection(socket.waitForNextConnection()); + + if (connection != nullptr) { + audioProcessor.setObjectServerRendering(true); + + while (!threadShouldExit()) { + if (connection->waitUntilReady(true, 200) == 1) { + int i = 0; + + // read until we get a newline + while (!threadShouldExit()) { + char buffer[1024]; + int bytesRead = connection->read(buffer, sizeof(buffer), false); + + if (bytesRead <= 0) { + break; + } + + std::memcpy(message.get() + i, buffer, bytesRead); + i += bytesRead; + + if (message[i] == '\n') { + message[i] = '\0'; + break; + } + } + + if (strncmp(message.get(), "CLOSE", 5) == 0) { + connection->close(); + audioProcessor.setObjectServerRendering(false); + break; + } + + // format of json is: + // { + // "objects": [ + // { + // "name": "Line Art", + // "vertices": [ + // [ + // { + // "x": double value, + // "y": double value, + // "z": double value + // }, + // ... + // ], + // ... + // ], + // "matrix": [ + // 16 double values + // ] + // } + // ], + // "focalLength": double value + // } + + auto json = juce::JSON::parse(message.get()); + + auto objects = *json.getProperty("objects", juce::Array()).getArray(); + std::vector> allMatrices; + std::vector>> allVertices; + + double focalLength = json.getProperty("focalLength", 1); + + for (int i = 0; i < objects.size(); i++) { + auto verticesArray = *objects[i].getProperty("vertices", juce::Array()).getArray(); + std::vector> vertices; + + for (auto& vertexArrayVar : verticesArray) { + vertices.push_back(std::vector()); + auto& vertexArray = *vertexArrayVar.getArray(); + for (auto& vertex : vertexArray) { + double x = vertex.getProperty("x", 0); + double y = vertex.getProperty("y", 0); + double z = vertex.getProperty("z", 0); + vertices[vertices.size() - 1].push_back(Vector3D(x, y, z)); + } + } + auto matrix = *objects[i].getProperty("matrix", juce::Array()).getArray(); + + allMatrices.push_back(std::vector()); + for (auto& value : matrix) { + allMatrices[i].push_back(value); + } + + std::vector> reorderedVertices; + + if (vertices.size() > 0 && matrix.size() == 16) { + std::vector visited = std::vector(vertices.size(), false); + std::vector order = std::vector(vertices.size(), 0); + visited[0] = true; + + auto endPoint = vertices[0].back(); + + for (int i = 1; i < vertices.size(); i++) { + int minPath = 0; + double minDistance = 9999999; + for (int j = 0; j < vertices.size(); j++) { + if (!visited[j]) { + auto startPoint = vertices[j][0]; + + double diffX = endPoint.x - startPoint.x; + double diffY = endPoint.y - startPoint.y; + double diffZ = endPoint.z - startPoint.z; + + double distance = std::sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ); + if (distance < minDistance) { + minPath = j; + minDistance = distance; + } + } + } + visited[minPath] = true; + order[i] = minPath; + endPoint = vertices[minPath].back(); + } + + for (int i = 0; i < vertices.size(); i++) { + std::vector reorderedVertex; + int index = order[i]; + for (int j = 0; j < vertices[index].size(); j++) { + reorderedVertex.push_back(vertices[index][j]); + } + reorderedVertices.push_back(reorderedVertex); + } + } + + allVertices.push_back(reorderedVertices); + } + + // generate a frame from the vertices and matrix + std::vector> frame; + + for (int i = 0; i < objects.size(); i++) { + for (int j = 0; j < allVertices[i].size(); j++) { + for (int k = 0; k < allVertices[i][j].size() - 1; k++) { + auto start = allVertices[i][j][k]; + auto end = allVertices[i][j][k + 1]; + + // multiply the start and end points by the matrix + double rotatedX = start.x * allMatrices[i][0] + start.y * allMatrices[i][1] + start.z * allMatrices[i][2] + allMatrices[i][3]; + double rotatedY = start.x * allMatrices[i][4] + start.y * allMatrices[i][5] + start.z * allMatrices[i][6] + allMatrices[i][7]; + double rotatedZ = start.x * allMatrices[i][8] + start.y * allMatrices[i][9] + start.z * allMatrices[i][10] + allMatrices[i][11]; + + double rotatedX2 = end.x * allMatrices[i][0] + end.y * allMatrices[i][1] + end.z * allMatrices[i][2] + allMatrices[i][3]; + double rotatedY2 = end.x * allMatrices[i][4] + end.y * allMatrices[i][5] + end.z * allMatrices[i][6] + allMatrices[i][7]; + double rotatedZ2 = end.x * allMatrices[i][8] + end.y * allMatrices[i][9] + end.z * allMatrices[i][10] + allMatrices[i][11]; + + double x = rotatedX * focalLength / rotatedZ; + double y = rotatedY * focalLength / rotatedZ; + + double x2 = rotatedX2 * focalLength / rotatedZ2; + double y2 = rotatedY2 * focalLength / rotatedZ2; + + frame.push_back(std::make_unique(x, y, x2, y2)); + } + } + } + + audioProcessor.objectServerSound->addFrame(frame); + } + } + } + + } + } + } +} diff --git a/Source/obj/ObjectServer.h b/Source/obj/ObjectServer.h new file mode 100644 index 0000000..576faa8 --- /dev/null +++ b/Source/obj/ObjectServer.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class OscirenderAudioProcessor; +class ObjectServer : public juce::Thread { +public: + ObjectServer(OscirenderAudioProcessor& p); + ~ObjectServer(); + + void run() override; + +private: + OscirenderAudioProcessor& audioProcessor; + + juce::StreamingSocket socket; +}; \ No newline at end of file diff --git a/osci-render.jucer b/osci-render.jucer index 5005673..bdad703 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -390,6 +390,9 @@ + +