Any way to retrieve a bus from BusesLayout by its string reference?

  • I think the buses layout can be checked from the message thread, so the UI Timer can do it independently.
  • You don’t need to check for suspension. The caller of processBlock should take the callback lock and make the check. This is done by all the plugin wrappers.
  • You shouldn’t write on an input buffer, especially in this case -it could be the sidechain. Use your own buffer for the sum to mono.
  • Someone would disagree, but I think the enum is overkill. I’d use -1 for mixToMono, and 0… for single channels.
void MyAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    if ((host.isLogic() || host.isGarageBand()) &&
        getBusesLayout().getChannelSet (true, 1) == AudioChannelSet::disabled())
        return;

    AudioBuffer<float> input,
                       output{ AudioProcessor::getBusBuffer (buffer, false, 0) },
                       inBus { AudioProcessor::getBusBuffer (buffer, true, host.isLogic() || host.isGarageBand()) };
    int numChannels{ inBus.getNumChannels() },
        numSamples { inBus.getNumSamples() };

    if (numChannels > 1 && modulatorInput < 0)
    {
        input = AudioBuffer<float> (1, numSamples);
        input.copyFrom (0, 0, inBus, 0, 0, numSamples);

        for (int chan{ 1 }; chan < numChannels; ++chan)
            input.addFrom (0, 0, inBus, chan, 0, numSamples);

        input.applyGain (1.0f / numChannels); // int / int makes an int division
    }
    else
    {
        input = AudioBuffer<float> (inBus.getArrayOfWritePointers() + std::clamp (modulatorInput, 0, numChannels - 1), 1, numSamples);
    }
    // etc
}

I don’t like this usage of creating a read buffer with getArrayOfWritePointers, but maybe you need it. If you’re going to write on input later, this should change to make a copy from the start.

1 Like

Thanks for the thorough answer!

ok cool

OK I was wondering if that was more of a host thing, I’ll just take that out :slight_smile:

okay - I was just trying to be too clever I guess :tipping_hand_woman:

I would normally agree, for pretty much every other state thing in my plugin I’m just using primitive types for representation, but since the input source is like the most performance-critical thing of all, I wanted to be super-explicit with which one is selected and exactly what happens in each case.

this code example looks good and makes sense to me! My only thought is, isn’t this line:

allocating memory on the audio thread?

I can pretty easily just make another member AudioBuffer specifically for summing to mono, and now my switch statement looks like this:

switch(modulatorInput)
    {
        case ModulatorInputSource::left:
            input = AudioBuffer<float> (inBus.getArrayOfWritePointers(), 1, totalNumSamples);
            break;
        
        case ModulatorInputSource::right:
            input = AudioBuffer<float> (inBus.getArrayOfWritePointers() + (inBus.getNumChannels() > 1), 1, totalNumSamples);
            break;
        
        case ModulatorInputSource::mixToMono:
        {
            monoSummingBuffer.copyFrom(0, 0, inBus, 0, 0, totalNumSamples);
            
            for(int channel = 1; channel < inBus.getNumChannels(); ++channel)
                monoSummingBuffer.addFrom(0, 0, inBus, channel, 0, totalNumSamples);
            
            monoSummingBuffer.applyGain(0, totalNumSamples, 1.0f / inBus.getNumChannels());
            
            input = AudioBuffer<float> (monoSummingBuffer.getArrayOfWritePointers(), 1, totalNumSamples);
            
            break;
        }
    }

and if I’m correct in my logic, there should be no memory allocation anywhere and I should end up with a mono buffer named input that can be passed to my processing functions, regardless of which case of the switch statement is executed… hopefully? ¯_(ツ)_/¯

Yup, it is. Don’t do that :sweat_smile:

Good :+1:

Your switch isn’t accounting for the case when modulatorInput is mixToMono but the input is mono. You shouldn’t add channels if there’s only one channel. Without the enum, the else was covering that. It looks strange to me to turn numbers into names and then back into the same numbers: 0 → left → 0, 1 → right → 1. I thoroughly avoid explicit references to channels -it harms genericity. (Unless they have to be processed differently, of course.)

1 Like

If this happens, then wouldn’t my switch just never execute the for loop

so it will copy the mono input into monoSummingBuffer and then stop, and make input an alias buffer for the mono input it just copied…

It would make the copy and apply a gain of 1 -both costly, unnecessary things.

1 Like

You’re right that the enum doesn’t allow for selecting the input as an individual channel higher than 0 or 1, so I suppose it could be technically more flexible your way…

But I do know that the output will always be stereo, so the input bus will pretty much always have to match that, right? meaning input bus will pretty much always be 2 channels

true. I could add a check for this

It’s rather a formal quibble, of course. If you take care of the corner case, the enum doesn’t do any real harm.

1 Like

Like this?

switch(modulatorInput)
    {
        case ModulatorInputSource::left:
            input = AudioBuffer<float> (inBus.getArrayOfWritePointers(), 1, totalNumSamples);
            break;
        
        case ModulatorInputSource::right:
            input = AudioBuffer<float> (inBus.getArrayOfWritePointers() + (inBus.getNumChannels() > 1), 1, totalNumSamples);
            break;
        
        case ModulatorInputSource::mixToMono:
        {
            if (inBus.getNumChannels() == 1)
            {
                input = AudioBuffer<float> (inBus.getArrayOfWritePointers(), 1, totalNumSamples);
                break;
            }
            
            monoSummingBuffer.copyFrom(0, 0, inBus, 0, 0, totalNumSamples);
            
            const int totalNumChannels = inBus.getNumChannels();
            
            for(int channel = 1; channel < totalNumChannels; ++channel)
                monoSummingBuffer.addFrom(0, 0, inBus, channel, 0, totalNumSamples);
            
            monoSummingBuffer.applyGain(0, totalNumSamples, 1.0f / totalNumChannels);
            
            input = AudioBuffer<float> (monoSummingBuffer.getArrayOfWritePointers(), 1, totalNumSamples);
            break;
        }
    }

since this is so fundamental to the signal flow, I prefer to be super explicit with exactly how the construction of the input buffer is handled ¯_(ツ)_/¯

it makes me slightly less anxious lol

:grin:
The formalist in me wants an else and no breaks in that last case #quibblingneverends

1 Like

haha that one’s certainly debatable…

I seem to recall the Juce coding standards saying not to ever write an else after a return or break ¯_(ツ)_/¯

but I could just write it entirely without breaks, you’re right

Oh, I’m not very religious about Juce coding standards, but I feel wrong (?) when I open a scope and then slap a return at the end of it :smile: OTOH, if the only content of a branch is the return, I’d rather do that on the first line than have a whole function into double braces. I don’t think Jules would like that, but it makes me less anxious too #lol

1 Like