Working further on this issue, I think I am getting closer to a solution, but I need some more help to actually resolve this.
Following @frankfilipanits advice, given in one of the linked posts, I have checked that:
-
NotificationReceived()
gets called on the main thread for the events I’m interested to. (AAX_eNotificationEvent_SideChainBeingConnected
and AAX_eNotificationEvent_SideChainBeingDisconnected
)
-
I also have found that it is correctly called with the “connected” notification upon load of a session that was saved with the connected sidechain.
The two considerations above lead me to believe that NotificationReceived()
would be a perfect place where to put an invocation to preparePlugin(), as shown below:
AAX_Result NotificationReceived (AAX_CTypeID type, const void* data, uint32_t size) override
{
if (type == AAX_eNotificationEvent_EnteringOfflineMode) pluginInstance->setNonRealtime (true);
if (type == AAX_eNotificationEvent_ExitingOfflineMode) pluginInstance->setNonRealtime (false);
// added by me
if (type == AAX_eNotificationEvent_SideChainBeingConnected) preparePlugin ();
if (type == AAX_eNotificationEvent_SideChainBeingDisconnected) preparePlugin ();
return AAX_CEffectParameters::NotificationReceived (type, data, size);
}
The nice thing about preparePlugin()
is that it takes care of calling prepareToPlay()
when it detects that the buses layout has changed.
The not-so-nice think about preparePlugin()
is that its current implementation does not check whether the sidechain channel is actually connected.
Instead, if a sidechain bus has been declared to be present upon construction of the AudioProcessor
, then the corresponding channel set is always mono()
and never disabled()
.
Luckily, the possibility to check whether the sidechain is actually connected exists, and traces of it can be found in algorithmCallback()
:
static void algorithmCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd)
{
for (JUCEAlgorithmContext* const* iter = instancesBegin; iter < instancesEnd; ++iter)
{
const JUCEAlgorithmContext& i = **iter;
int sideChainBufferIdx = i.pluginInstance->parameters.hasSidechain && i.sideChainBuffers != nullptr
? static_cast<int> (*i.sideChainBuffers) : -1;
// sidechain index of zero is an invalid index
if (sideChainBufferIdx <= 0)
sideChainBufferIdx = -1;
// yfede: I'm talking about this IF above
i.pluginInstance->parameters.process (i.inputChannels, i.outputChannels, sideChainBufferIdx,
*(i.bufferSize), *(i.bypass) != 0,
getMidiNodeIn(i), getMidiNodeOut(i));
}
}
I have checked and can confirm that in the code above, sideChainBufferIdx
is -1 when sidechain is disconnected, and it assumes a > 0 value when sidechain is connected.
That information would be perfect for our goal, but unfortunately it lives in the algorithm context and I found no easy way to access it from within preparePlugin()
.
As a second option, we can pass that information to preparePlugin()
depending on the notification that is received. For that, I added a bool sidechainConnected
parameter to preparePlugin()
, and there I pass the value that corresponds to the notification, as shown below:
AAX_Result NotificationReceived (AAX_CTypeID type, const void* data, uint32_t size) override
{
if (type == AAX_eNotificationEvent_EnteringOfflineMode) pluginInstance->setNonRealtime (true);
if (type == AAX_eNotificationEvent_ExitingOfflineMode) pluginInstance->setNonRealtime (false);
// added by me
if (type == AAX_eNotificationEvent_SideChainBeingConnected) preparePlugin (true);
if (type == AAX_eNotificationEvent_SideChainBeingDisconnected) preparePlugin (false);
return AAX_CEffectParameters::NotificationReceived (type, data, size);
}
That seems to work well, but now I need to actually “do something” inside preparePlugin()
to enable/disable the sidechain input. What I attempted is this:
... snippet from preparePlugin() ....
AudioProcessor::BusesLayout newLayout;
if (! fullBusesLayoutFromMainLayout (audioProcessor, inputSet, outputSet, newLayout))
{
if (isPrepared)
{
isPrepared = false;
audioProcessor.releaseResources();
}
return AAX_ERROR_UNIMPLEMENTED;
}
/* yfede: I added this IF in an attempt to disable the otherwise always mono sidechain
when no sidechain is actually connected */
if (newLayout.inputBuses.size () > 1 && sidechainConnected == false)
newLayout.inputBuses.getReference (1) = AudioChannelSet::disabled ();
const bool layoutChanged = (oldLayout != newLayout);
The problem with the above code is that I hit an assertion at line 1048. My understanding is that this needs to be supported in more places throughout the AAX wrapper, but I’m not expert enough of the implementation of the multibus API to really know where to make changes.
What I noticed, however, is that all throughout the AAX code there are hasSidechain
flags which sometimes are used with meaning “the AAX supports connection of a sidechain channel”, while some other times they mean “the sidechain bus is not disabled”.
Albeit similar, the two concepts are not the same: the plug-in can either support sidechain or not (isSidechainSupported
would be a better name for this concept), and if it supports sidechain, then an actual sidechain input can be connected to it or not (isSidechainConnected
would be a better name for this other concept).
With that straightened out, then it is easy to see that these equivalences should hold:
isSidechainSupported == (layout.inputBuses.size() > 1);
and
isSidechainConnected == (layout.getNumChannels (true, 1) > 0);
Perhaps if the AAX code is clarified in those terms, then the correct way to implement sidechain connection detection will pop out naturally.
@fabian, we know you are the expert here, any chance you can look into this?
EDIT: it goes without saying that, if the plug-in supports sidechain, but a channel is not currently connected to it, then layout.inputBuses.size()
should be 2 but layout.inputBuses.getReference (1)
should equal to AudioChannelSet::disabled()
(which is the same to say that the number of channels in that bus is 0).