Buffer copying on processBlock not fast enough?

Hey there. I am building a Sidechain compression plugin which allows you to get a Sidechain input from another track where the DAW doesn’t support sidechaining. For this, I made use of the Singleton structure in JUCE to be able to share data between the two instances (one on the source, one on the target track), where the sending instance puts the data it receives in processBlock in a AudioBuffer object in my Singleton struct and the receiving instance is reading that and copies it into the actual buffer by doing

AudioBuffer<float>& remoteBuffer = singleton->remoteBuffer;
if (remoteBuffer.getNumSamples() > 0)
    buffer.makeCopyOf(remoteBuffer);

This works, but when I’m listening I can hear that the track with the receiving end has some stuttering in the audio signal. Is my attempt too slow? Thanks!

makeCopyOf doesn’t only copy the samples, it can/will also reallocate the buffer. It has the argument that makes it attempt to not reallocate, though. You should not ever allocate memory in the audio processing thread.

However, I suspect there are other, much worse flaws, in your design at the moment. Passing audio data between plugin instances is a very complicated thing to attempt and there are no guarantees it can even be made to work.

Yea I recognized that it’s complicated but generally speaking when I’m sharing a buffer via a shared memory and only pass references instead of copying, I should be able to have no stuttering in the buffers as there’s nothing copied. I just hope it doesn’t involve any thread difficulties and/or memory lock stuff. Is there maybe any other attempt?

As already pointed out, makeCopyOf should not be used in the audio thread, although it has a flag to avoid reallocation it will not guarantee you to not allocate if I get it right. copyFrom is the function to use if you are copying on the audio thread.

However, I doubt your design works stable. Just consider those totally possible scenarios:

  • The host processes all plugins in one thread. However it choses to sometimes process the source plugin before the receiver plugin and sometimes does it the other way round
  • The host renders plugins on multiple threads. The source plugin accesses the shared buffer at the very same time as the destination plugin does
  • The host renders plugins in separate processes. Both plugin instances create an own instance of your singleton in their own process
  • The host decides to call the source plugin with a different block size than the destination plugin

Judging from the code snippet I see, I would expect all those cases to lead to a fail or massive drop of buffers / discontinuity in the audio signal.

4 Likes

Very true. The only reliable way to do side chain is to use the host mechanism for that, which guarantees order of processing for source and target.

And if the host doesn’t have sidechaining features, too bad…(Which host would that be, by the way…?)

Some of the smaller ones I think, can Premiere Pro or Final Cut do side chain?
Some hosts aren’t meant for complicated routings…

1 Like

Haha, FCPX doesn’t even do aux tracks, so where would it send the side chain to? FCPX is unsuitable for anything audio beyond copying a clip in. Granted, you can add effects on a clip and automate, but that’s as good as it gets.

Cubase Elements (the cheapest version) has no sidechain support and upgrading is pretty expensive so I thought my idea might be nice! But if it won’t work, it won’t :slight_smile: There are some plugins though which have exactly this sidechain support I wanted to do so I wonder how they got it working

1 Like

Maybe those plugins are using a shared thread-safe queue (ring buffer) and add some latency…? That way there could be at least some chance the receiving plugins will have enough audio to work with. Or maybe the developers of those plugins have asked Steinberg how it could be done inside Cubase Elements…

Well, what would be the point for Steinberg to have different tiers, if they add a workaround to get the functionality regardless?

Simple demands -> cheap software
Complicated demands -> expensive software

Ableton offers sidechain support in their Intro version for 79€. They probably have other limits but for a small indie musician like me it’s hard to cover

Are those plugins you mentioned that do sidechain made by Steinberg? If so, it makes sense they would expose ‘secret’ parts of their API to their own plugins.

If those are 3rd party, could you please name those so I could test it myself and maybe figure out what black magic they pulled off there? :slight_smile:

Sure, found this one here: https://beatrig.com/product/sidekickex6/

Haven’t installed it, but could it be, that they just share the envelope, which is not as sensible, since it is low-passed anyway, and in most cases not as critical?

You could do nasty tests, like setting the send plugin on a normal track and the other on a track that does a lot of latency compensation?

Just a thought

I’m guessing you meant sensitive here?

iirc http://www.db-audioware.com/sidechain-compressor does what OP is trying to achieve: comes with 2 plugins, a “sender” which you put as insert on your kick for instance, and the actual compressor which can receive from the sender that you would put on your bass.

Yea exactly. Even though there are solutions I’m very interested in seeing how they achieved it. I’m eager to learn as JUCE is an extremely interesting framework for me (I’m coming from game development)

It basically allows you to do quite simple “black box” plugins, similar to simple guitar pedals or MIDI controlled hardware synths. Anything more complicated will be a world of pain. Might be possible to do or not. (And that isn’t even really Juce’s fault, the plugin formats and APIs are just quite limited in what they expect you to do.)

Good word, and clearing up the difference between CopyOf and copyFrom.

Do you think passing write pointers into functions and iterating from the first reference is faster than copying an audioblock?

void function(juce::AudioBuffer){
// loop by channel or sample.
input[sample]

}

versus

void function(float* left, float*){
//loop by increment
input++
}

or would there be no difference at all?

There’s no guarantee that your plugins will be called in the correct order for sidechaining to work. Some hosts might be using multithreaded audio pipelines (this is especially common when rendering).

You probably want to use a FIFO (see juce::AbstractFifo) for passing audio between the plugins.

You might be able to achieve some hacks; your plugins might always
be called in the incorrect order, for example. You could skip a frame and have both plugins running a frame behind.