Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion include/element/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,17 @@ class EL_API Node : public Model {
/** Saves the node state from Processor to state property */
void savePluginState();

/** Reads state property and applies to Processor */
/** Reads state property and applies to Processor.
Recurses into child nodes; use restoreOwnPluginState() when children
are restored elsewhere.
*/
void restorePluginState();

/** Reads state property and applies to Processor for this node only,
without recursing into child nodes.
*/
void restoreOwnPluginState();

//=========================================================================
/** Get the number of factory presets */
int getNumPrograms() const;
Expand Down
13 changes: 13 additions & 0 deletions include/element/oversampler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ class Oversampler final {
float getLatencySamples (int index) const;
int getFactor (int index) const;
void prepare (int numChannels, int blockSize);

/** Returns the oversampling chain for the given index, creating and
initializing it on demand with the spec given to prepare().

Chains are expensive to build (IIR filter design + buffers), so they
are only created here, never in prepare(). Must be called on the
message thread, and only after prepare() has set a valid spec.

@param index the processor index; the oversample factor is 2^(index + 1)
@return the chain, or nullptr if the index or spec is invalid
*/
ProcessorType* ensureProcessor (int index);

void reset();

private:
Expand Down
2 changes: 1 addition & 1 deletion include/element/porttype.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#endif

#ifndef EL_INVALID_PORT
#define EL_INVALID_PORT ((uint32_t) - 1)
#define EL_INVALID_PORT ((uint32_t) -1)
#endif

#ifndef EL_INVALID_NODE
Expand Down
4 changes: 1 addition & 3 deletions src/engine/clapprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,7 @@ class CLAPHost final : public CLAPBaseHost
pfds.reserve (fds.size());
for (const auto& f : fds)
{
struct pollfd p
{
};
struct pollfd p {};
p.fd = f.first;
if (f.second & CLAP_POSIX_FD_READ)
p.events |= POLLIN;
Expand Down
159 changes: 111 additions & 48 deletions src/engine/graphmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "nodes/placeholder.hpp"
#include "engine/rootgraph.hpp"

#include "tracer.hpp"
#include "utils.hpp"

namespace element {
Expand Down Expand Up @@ -149,8 +150,7 @@ struct IONodeEnforcer
jassert (ioNodes[t] != nullptr);
}

for (const auto& nodeId : nodesToRemove)
manager.removeNode (nodeId);
manager.removeNodes (nodesToRemove);

model.resetPorts();
}
Expand Down Expand Up @@ -238,7 +238,6 @@ class GraphManager::Binding

manager = std::make_unique<GraphManager> (*sub, owner.pluginManager);
manager->setNodeModel (node);
IONodeEnforcer addIO (*manager);
}
else
{
Expand Down Expand Up @@ -344,6 +343,7 @@ bool GraphManager::contains (const uint32 nodeId) const

Processor* GraphManager::createFilter (const PluginDescription* desc, double x, double y, uint32 nodeId)
{
EL_LOAD_TRACE (String ("createFilter: ") + desc->name);
String errorMessage;
auto node = std::unique_ptr<Processor> (
pluginManager.createGraphNode (*desc, errorMessage));
Expand All @@ -360,7 +360,9 @@ Processor* GraphManager::createFilter (const PluginDescription* desc, double x,
errorMessage = "Could not find node";
}

return node != nullptr ? processor.addNode (node.release(), nodeId) : nullptr;
// Defer the prepare: the caller configures buses and restores plugin
// state first, then prepares once the configuration is final.
return node != nullptr ? processor.addNode (node.release(), nodeId, true) : nullptr;
}

Processor* GraphManager::createPlaceholder (const Node& node)
Expand Down Expand Up @@ -490,17 +492,28 @@ uint32 GraphManager::addNode (const PluginDescription* desc, double rx, double r

if (tryStereo != nullptr && proc->checkBusesLayoutSupported (*tryStereo))
{
proc->suspendProcessing (true);
proc->releaseResources();
if (object->isPrepared)
{
proc->suspendProcessing (true);
proc->releaseResources();

if (! proc->setBusesLayout (*tryStereo))
proc->setBusesLayout (oldLayout);
if (! proc->setBusesLayout (*tryStereo))
proc->setBusesLayout (oldLayout);

proc->prepareToPlay (processor.getSampleRate(), processor.getBlockSize());
proc->suspendProcessing (false);
proc->prepareToPlay (processor.getSampleRate(), processor.getBlockSize());
proc->suspendProcessing (false);
}
else if (! proc->setBusesLayout (*tryStereo))
{
proc->setBusesLayout (oldLayout);
}
}
}

// Deferred by createFilter: prepare once the bus layout is final.
if (processor.prepared() && ! object->isPrepared)
object->prepare (processor.getSampleRate(), processor.getBlockSize(), &processor);

nodes.addChild (data, -1, nullptr);
changed();
}
Expand All @@ -515,34 +528,45 @@ uint32 GraphManager::addNode (const PluginDescription* desc, double rx, double r

void GraphManager::removeNode (const uint32 uid)
{
if (! processor.removeNode (uid))
Array<uint32> uids;
uids.add (uid);
removeNodes (uids);
}

void GraphManager::removeNodes (const juce::Array<uint32>& uids)
{
if (! processor.removeNodes (uids))
return;
for (int i = 0; i < nodes.getNumChildren(); ++i)

for (const auto& uid : uids)
{
const Node node (nodes.getChild (i), false);
if (node.getNodeId() == uid)
for (int i = 0; i < nodes.getNumChildren(); ++i)
{
// the model was probably referencing the node ptr
ProcessorPtr obj = node.getObject();
if (obj)
const Node node (nodes.getChild (i), false);
if (node.getNodeId() == uid)
{
obj->willBeRemoved();
obj->releaseResources();
}
// the model was probably referencing the node ptr
ProcessorPtr obj = node.getObject();
if (obj)
{
obj->willBeRemoved();
obj->releaseResources();
}

for (int i = bindings.size(); --i >= 0;)
{
auto binding = bindings.getUnchecked (i);
if (binding->object == obj)
bindings.remove (i, true);
}
for (int j = bindings.size(); --j >= 0;)
{
auto binding = bindings.getUnchecked (j);
if (binding->object == obj)
bindings.remove (j, true);
}

auto data = node.data();
nodes.removeChild (data, nullptr);
// clear all referecnce counted objects
Node::sanitizeProperties (data, true);
// finally delete the node + plugin instance.
obj = nullptr;
auto data = node.data();
nodes.removeChild (data, nullptr);
// clear all referecnce counted objects
Node::sanitizeProperties (data, true);
// finally delete the node + plugin instance.
obj = nullptr;
}
}
}

Expand Down Expand Up @@ -626,6 +650,7 @@ void GraphManager::removeConnection (uint32 sourceNode, uint32 sourcePort, uint3

void GraphManager::setNodeModel (const Node& node)
{
EL_LOAD_TRACE (String ("setNodeModel: ") + node.getName());
loaded = false;

processor.clear();
Expand Down Expand Up @@ -675,9 +700,9 @@ void GraphManager::setNodeModel (const Node& node)
// If you hit this, then failed nodes didn't get handled properly
jassert (nodes.getNumChildren() == processor.getNumNodes());

// Cheap way to refresh engine-side nodes
// Refresh engine-side nodes. Coalesced: the render sequence would be
// invalidated again by the arc loop below, so don't force a build here.
processor.triggerAsyncUpdate();
processor.handleUpdateNowIfNeeded();

for (int i = 0; i < arcs.getNumChildren(); ++i)
{
Expand Down Expand Up @@ -726,8 +751,11 @@ void GraphManager::setNodeModel (const Node& node)
jassert (arcs.getNumChildren() == processor.getNumConnections());
failed.clearQuick();

IONodeEnforcer enforceIONodes (*this);
processorArcsChanged();
{
EL_LOAD_TRACE ("setNodeModel: enforce IO + sync arcs");
IONodeEnforcer enforceIONodes (*this);
processorArcsChanged();
}
}

void GraphManager::savePluginStates()
Expand Down Expand Up @@ -812,6 +840,7 @@ void GraphManager::setupNode (const ValueTree& data, ProcessorPtr obj)

if (auto* const proc = obj->getAudioProcessor())
{
EL_LOAD_TRACE (String ("setupNode: buses: ") + node.getName());
bool busesConfigured = false;
{
// try to load buses layout.
Expand All @@ -836,11 +865,20 @@ void GraphManager::setupNode (const ValueTree& data, ProcessorPtr obj)

if (proc->checkBusesLayoutSupported (layout))
{
proc->suspendProcessing (true);
proc->releaseResources();
busesConfigured = proc->setBusesLayoutWithoutEnabling (layout);
proc->prepareToPlay (processor.getSampleRate(), processor.getBlockSize());
proc->suspendProcessing (false);
if (obj->isPrepared)
{
proc->suspendProcessing (true);
proc->releaseResources();
busesConfigured = proc->setBusesLayoutWithoutEnabling (layout);
proc->prepareToPlay (processor.getSampleRate(), processor.getBlockSize());
proc->suspendProcessing (false);
}
else
{
// Not prepared yet (deferred by createFilter): the
// layout can be applied directly.
busesConfigured = proc->setBusesLayoutWithoutEnabling (layout);
}
}
}
}
Expand All @@ -855,19 +893,44 @@ void GraphManager::setupNode (const ValueTree& data, ProcessorPtr obj)

if (proc->checkBusesLayoutSupported (layout))
{
proc->suspendProcessing (true);
proc->releaseResources();
proc->setBusesLayoutWithoutEnabling (layout);
proc->prepareToPlay (processor.getSampleRate(), processor.getBlockSize());
proc->suspendProcessing (false);
if (obj->isPrepared)
{
proc->suspendProcessing (true);
proc->releaseResources();
proc->setBusesLayoutWithoutEnabling (layout);
proc->prepareToPlay (processor.getSampleRate(), processor.getBlockSize());
proc->suspendProcessing (false);
}
else
{
proc->setBusesLayoutWithoutEnabling (layout);
}
}

resetPorts = true;
}
}

node.restorePluginState();
node.resetPorts();
{
EL_LOAD_TRACE (String ("setupNode: restore state: ") + node.getName());
// A subgraph's descendants were already restored one-by-one by the
// child GraphManager the Binding created above — restoring them again
// here would call setStateInformation twice per nesting level.
if (obj->isSubGraph())
node.restoreOwnPluginState();
else
node.restorePluginState();
}
{
EL_LOAD_TRACE (String ("setupNode: reset ports: ") + node.getName());
node.resetPorts();
}

// Deferred by createFilter: prepare only now that the bus layout and
// plugin state are final, so the plugin is prepared exactly once.
if (processor.prepared() && ! obj->isPrepared)
obj->prepare (processor.getSampleRate(), processor.getBlockSize(), &processor);

if (node.isA ("Element", EL_NODE_ID_MIDI_INPUT_DEVICE) || node.isA ("Element", EL_NODE_ID_MIDI_OUTPUT_DEVICE))
{
jassert (node.getNumPorts() == 1);
Expand Down
7 changes: 7 additions & 0 deletions src/engine/graphmanager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ class GraphManager : public juce::ChangeBroadcaster
/** Remove a node by ID */
void removeNode (const uint32 nodeId);

/** Removes several nodes by ID, rebuilding the rendering sequence and the
arcs model once for the whole batch.

@param nodeIds the IDs of the nodes to remove
*/
void removeNodes (const juce::Array<uint32>& nodeIds);

/** Disconnect a node from other nodes */
void disconnectNode (const uint32 nodeId, const bool inputs = true, const bool outputs = true, const bool audio = true, const bool midi = true);

Expand Down
Loading
Loading