Send buffer between plugin

Hi, I created two plugins (sender, receiver) to share buffers,

If I create an instance of sender and send the buffer to an instance of receiver in the same graph all works fine, but my purpose is to create an instance of sender in a cubase track an instance of receiver in another cubase track.
In this scenario (among two different cubase tracks), I can connect receiver instance to sender instance, but audioBuffer is never received and randomly some midi messages of midiBuffer comes to receiver.

What’s wrong?

.h:

[…]

Type getType() const                 { return type; } 
 juce::Uuid getUuid()                { return uuid; } 

 juce::String getLabel() const        { return label; } 
 void setLabel(juce::String newLabel) { if (type == Type::sender) { label = newLabel; } } 
     
 Connector* getConnectedSender() const; 
 Uuid   getConnectedSenderUuid() const; 

 void connectToSender(juce::Uuid byUuid); 
 void connectToSender(Connector* sender) { connectToSender(sender->uuid); } 
  
 void disconnectFromSender();  

 static Connector* getSender(juce::Uuid byUuid); 
 static juce::Array<Connector*> getSenders() { return getInstancesOfType(Type::sender); } 
 static juce::Array<Connector*> getReceivers() { return getInstancesOfType(Type::receiver); } 
  
 private: 
 static juce::Array<Connector*> getInstancesOfType(Type type); 

 Type type; 

 juce::Uuid uuid; 
 juce::String label; 

 juce::AudioBuffer<float> audioBuffer; 
 juce::MidiBuffer midiBuffer;  
  
 juce::Uuid desiredSenderUuid{ nullptr }; 
 Connector* connectedSender{ nullptr };  
  
 static juce::CriticalSection instancesLock; 
  
 static juce::Array<Connector*> instances; 
 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connector) 

};

.cpp:

void Connector::processBlock (AudioBuffer<float>& audioBuff, MidiBuffer& midiBuff) 
{ 
 juce::ScopedNoDenormals noDenormals; 
 auto totalNumInputChannels = getTotalNumInputChannels(); 
 auto totalNumOutputChannels = getTotalNumOutputChannels(); 

 for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) 
     audioBuff.clear(i, 0, audioBuff.getNumSamples()); 
  
 if (type == Type::sender) 
 { 
     audioBuffer.makeCopyOf(audioBuff, false); 
     midiBuffer.data = midiBuff.data; 
 } else { 
     if (connectedSender) 
     { 
         audioBuff.makeCopyOf(connectedSender->audioBuffer); 
         midiBuff.data = connectedSender->midiBuffer.data; 
     } 
 } 
}

Unfortunately it’ll not be as simple as copying a buffer between instances I guess.

Things you need to deal with:

  • There is no guarantee of the order in which the host will process sender and receiver
  • There is no guarantee that both will be processed with the same block size
  • There is no guarantee that the buffers that sender and receiver get are time-aligned (like when host does latency compensation)

To pull that kind of thing off, you’ll need a ring buffer and some logic to make sure it never runs over or under. Likely you need to add some latency between sender and receiver, and deal with timestamps to find out where on the timeline each instance is running. And for that you have to hope that the host reports these timestamps correctly when there’s latency compensation (IIRC Logic reports weird stuff for example).

1 Like

I doubt that it’s possible to make this work reliably because the host (Cubase in this case) has lots of freedom to treat the plugin instances that you have no control over. Some challenges to consider:

  • Processing of both instances can happen on different threads/cores
  • Both instances can have different latencies due to the chain of processors before them in the DAWs mixing graph
  • Buffer sizes can be different due to DAW optimizations
  • What happens if both instances are in a chain, forcing sequential processing order due to dependencies in the DAWs routing graph?
  • A plugin instance could be in “offline processing” mode.
  • What about non-realtime export?

I know there are cases where plugin instances exchange data (e.g. izotopes “meter taps”), but I’ve only ever seen this being used for measurement data that is not realtime critical, in analzyers. As far as I know there also are some plugins exchanging configuration data among instances.

1 Like

The “correct” way to do these things is to use the hosts routing facilities (sends) to connect instances within the hosts processing graph. Cubase and Reaper (and certainly others) are able to route audio and MIDI from anything to anything if you set it up correctly and as long as you do not create cycles/feedback loops.

Sadly, the current plugin formats do not allow to integrate with the host deeply enough to do this automagically.

2 Likes

really thank you @hugoderwolf and @jcomusic , it’s really more difficult of what I could imagine, I k now daw facilities are better, but for a lot of reason I need to send audio in my app

I haven’t seen a plugin send audio across instances before but I have seen it done with metadata - for example Blue Cat’s gain linking. Is there a way for your design to work if you ask the user to connect the plugin instances’ audio via a sidechain and then you just send metadata from instance to instance? It would probably be possible to verify the sidechain is set up via this metadata side channel and then you could activate the processing you want to do.

1 Like