1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-30 02:50:05 +00:00

AU Wrapper: Use correct sample rate for MIDI FX with no buses

MIDI FX AudioUnits can retrieve their current sample rate from an output
bus. However, it's reasonable for a JUCE MIDI FX plugin to require 0
output buses, in which case the correct sample rate cannot be retrieved.

This patch modifies the AU wrapper to ensure that MIDI FX plugins will
always have at least one output bus. This bus does not necessarily
correspond to a matching bus in the AudioProcessor, and it will not be
passed to AudioProcessor::processBlock. With the output bus in place,
the correct sample rate is reported in `prepareToPlay`.
This commit is contained in:
reuk 2021-04-15 19:10:47 +01:00
parent a3e421a36b
commit cafb437762
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
3 changed files with 126 additions and 77 deletions

View file

@ -131,8 +131,8 @@ public:
JuceAU (AudioUnit component)
: AudioProcessorHolder (activePlugins.size() + activeUIs.size() == 0),
MusicDeviceBase (component,
(UInt32) AudioUnitHelpers::getBusCount (juceFilter.get(), true),
(UInt32) AudioUnitHelpers::getBusCount (juceFilter.get(), false)),
(UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true),
(UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)),
mapper (*juceFilter)
{
inParameterChangedCallback = false;
@ -210,7 +210,7 @@ public:
return err;
mapper.alloc();
pulledSucceeded.calloc (static_cast<size_t> (AudioUnitHelpers::getBusCount (juceFilter.get(), true)));
pulledSucceeded.calloc (static_cast<size_t> (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true)));
prepareToPlay();
@ -300,7 +300,7 @@ public:
if (isInput) return false;
#endif
const int busCount = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput);
const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput);
return (juceFilter->canAddBus (isInput) || (busCount > 0 && juceFilter->canRemoveBus (isInput)));
#endif
}
@ -313,12 +313,12 @@ public:
if ((err = scopeToDirection (scope, isInput)) != noErr)
return err;
if (count != (UInt32) AudioUnitHelpers::getBusCount (juceFilter.get(), isInput))
if (count != (UInt32) AudioUnitHelpers::getBusCount (*juceFilter, isInput))
{
#ifdef JucePlugin_PreferredChannelConfigurations
return kAudioUnitErr_PropertyNotWritable;
#else
const int busCount = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput);
const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput);
if ((! juceFilter->canAddBus (isInput)) && ((busCount == 0) || (! juceFilter->canRemoveBus (isInput))))
return kAudioUnitErr_PropertyNotWritable;
@ -362,7 +362,7 @@ public:
if (err != noErr)
{
// restore bus state
const int newBusCount = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput);
const int newBusCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput);
for (int i = newBusCount; i != busCount; i += (busCount > newBusCount ? 1 : -1))
{
if (busCount > newBusCount)
@ -798,15 +798,14 @@ public:
UInt32 GetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element,
AudioChannelLayout* outLayoutPtr, Boolean& outWritable) override
{
bool isInput;
int busNr;
outWritable = false;
if (elementToBusIdx (scope, element, isInput, busNr) != noErr)
const auto info = getElementInfo (scope, element);
if (info.error != noErr)
return 0;
if (busIgnoresLayout (isInput, busNr))
if (busIgnoresLayout (info.isInput, info.busNr))
return 0;
outWritable = true;
@ -816,7 +815,7 @@ public:
if (outLayoutPtr != nullptr)
{
zeromem (outLayoutPtr, sizeInBytes);
outLayoutPtr->mChannelLayoutTag = getCurrentLayout (isInput, busNr);
outLayoutPtr->mChannelLayoutTag = getCurrentLayout (info.isInput, info.busNr);
}
return sizeInBytes;
@ -824,16 +823,15 @@ public:
UInt32 GetChannelLayoutTags (AudioUnitScope scope, AudioUnitElement element, AudioChannelLayoutTag* outLayoutTags) override
{
bool isInput;
int busNr;
const auto info = getElementInfo (scope, element);
if (elementToBusIdx (scope, element, isInput, busNr) != noErr)
if (info.error != noErr)
return 0;
if (busIgnoresLayout (isInput, busNr))
if (busIgnoresLayout (info.isInput, info.busNr))
return 0;
const Array<AudioChannelLayoutTag>& layouts = getSupportedBusLayouts (isInput, busNr);
const Array<AudioChannelLayoutTag>& layouts = getSupportedBusLayouts (info.isInput, info.busNr);
if (outLayoutTags != nullptr)
std::copy (layouts.begin(), layouts.end(), outLayoutTags);
@ -843,20 +841,18 @@ public:
OSStatus SetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, const AudioChannelLayout* inLayout) override
{
bool isInput;
int busNr;
OSStatus err;
const auto info = getElementInfo (scope, element);
if ((err = elementToBusIdx (scope, element, isInput, busNr)) != noErr)
return err;
if (info.error != noErr)
return info.error;
if (busIgnoresLayout (isInput, busNr))
if (busIgnoresLayout (info.isInput, info.busNr))
return kAudioUnitErr_PropertyNotWritable;
if (inLayout == nullptr)
return kAudioUnitErr_InvalidPropertyValue;
if (const AUIOElement* ioElement = GetIOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element))
if (const AUIOElement* ioElement = GetIOElement (info.isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element))
{
const AudioChannelSet newChannelSet = CoreAudioLayouts::fromCoreAudio (*inLayout);
const int currentNumChannels = static_cast<int> (ioElement->GetStreamFormat().NumberChannels());
@ -872,11 +868,11 @@ public:
if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, isInput, busNr, newChannelNum, configs))
return kAudioUnitErr_FormatNotSupported;
#else
if (! juceFilter->getBus (isInput, busNr)->isLayoutSupported (newChannelSet))
if (! juceFilter->getBus (info.isInput, info.busNr)->isLayoutSupported (newChannelSet))
return kAudioUnitErr_FormatNotSupported;
#endif
getCurrentLayout (isInput, busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet);
getCurrentLayout (info.isInput, info.busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet);
return noErr;
}
@ -1051,7 +1047,14 @@ public:
ComponentResult Version() override { return JucePlugin_VersionCode; }
bool SupportsTail() override { return true; }
Float64 GetTailTime() override { return juceFilter->getTailLengthSeconds(); }
double getSampleRate() { return AudioUnitHelpers::getBusCount (juceFilter.get(), false) > 0 ? GetOutput (0)->GetStreamFormat().mSampleRate : 44100.0; }
double getSampleRate()
{
if (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false) > 0)
return GetOutput (0)->GetStreamFormat().mSampleRate;
return 44100.0;
}
Float64 GetLatency() override
{
@ -1206,32 +1209,33 @@ public:
//==============================================================================
bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override
{
bool ignore;
int busIdx;
const auto info = getElementInfo (scope, element);
return ((! IsInitialized()) && (elementToBusIdx (scope, element, ignore, busIdx) == noErr));
return ((! IsInitialized()) && (info.error == noErr));
}
bool ValidFormat (AudioUnitScope scope, AudioUnitElement element, const CAStreamBasicDescription& format) override
{
bool isInput;
int busNr;
// DSP Quattro incorrectly uses global scope for the ValidFormat call
if (scope == kAudioUnitScope_Global)
return ValidFormat (kAudioUnitScope_Input, element, format)
|| ValidFormat (kAudioUnitScope_Output, element, format);
if (elementToBusIdx (scope, element, isInput, busNr) != noErr)
const auto info = getElementInfo (scope, element);
if (info.error != noErr)
return false;
if (info.kind == BusKind::wrapperOnly)
return true;
const int newNumChannels = static_cast<int> (format.NumberChannels());
const int oldNumChannels = juceFilter->getChannelCountOfBus (isInput, busNr);
const int oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr);
if (newNumChannels == oldNumChannels)
return true;
if (AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNr))
if (AudioProcessor::Bus* bus = juceFilter->getBus (info.isInput, info.busNr))
{
if (! MusicDeviceBase::ValidFormat (scope, element, format))
return false;
@ -1252,17 +1256,15 @@ public:
// AU requires us to override this for the sole reason that we need to find a default layout tag if the number of channels have changed
OSStatus ChangeStreamFormat (AudioUnitScope scope, AudioUnitElement element, const CAStreamBasicDescription& old, const CAStreamBasicDescription& format) override
{
bool isInput;
int busNr;
OSStatus err = elementToBusIdx (scope, element, isInput, busNr);
const auto info = getElementInfo (scope, element);
if (err != noErr)
return err;
if (info.error != noErr)
return info.error;
AudioChannelLayoutTag& currentTag = getCurrentLayout (isInput, busNr);
AudioChannelLayoutTag& currentTag = getCurrentLayout (info.isInput, info.busNr);
const int newNumChannels = static_cast<int> (format.NumberChannels());
const int oldNumChannels = juceFilter->getChannelCountOfBus (isInput, busNr);
const int oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr);
#ifdef JucePlugin_PreferredChannelConfigurations
short configs[][2] = {JucePlugin_PreferredChannelConfigurations};
@ -1272,13 +1274,21 @@ public:
#endif
// predict channel layout
AudioChannelSet set = (newNumChannels != oldNumChannels) ? juceFilter->getBus (isInput, busNr)->supportedLayoutWithChannels (newNumChannels)
: juceFilter->getChannelLayoutOfBus (isInput, busNr);
const auto set = [&]
{
if (info.kind == BusKind::wrapperOnly)
return AudioChannelSet::discreteChannels (newNumChannels);
if (newNumChannels != oldNumChannels)
return juceFilter->getBus (info.isInput, info.busNr)->supportedLayoutWithChannels (newNumChannels);
return juceFilter->getChannelLayoutOfBus (info.isInput, info.busNr);
}();
if (set == AudioChannelSet())
return kAudioUnitErr_FormatNotSupported;
err = MusicDeviceBase::ChangeStreamFormat (scope, element, old, format);
const auto err = MusicDeviceBase::ChangeStreamFormat (scope, element, old, format);
if (err == noErr)
currentTag = CoreAudioLayouts::toCoreAudio (set);
@ -1302,8 +1312,8 @@ public:
ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence;
const int numInputBuses = static_cast<int> (GetScope (kAudioUnitScope_Input) .GetNumberOfElements());
const int numOutputBuses = static_cast<int> (GetScope (kAudioUnitScope_Output).GetNumberOfElements());
const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true);
const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false);
// set buffer pointers to minimize copying
{
@ -1839,14 +1849,18 @@ private:
void prepareOutputBuffers (const UInt32 nFrames) noexcept
{
const unsigned int numOutputBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements();
const auto numProcessorBuses = AudioUnitHelpers::getBusCount (*juceFilter, false);
const auto numWrapperBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements();
for (unsigned int busIdx = 0; busIdx < numOutputBuses; ++busIdx)
for (UInt32 busIdx = 0; busIdx < numWrapperBuses; ++busIdx)
{
AUOutputElement* output = GetOutput (busIdx);
if (output->WillAllocateBuffer())
output->PrepareBuffer (nFrames);
if (busIdx >= (UInt32) numProcessorBuses)
AudioUnitHelpers::clearAudioBuffer (output->GetBufferList());
}
}
@ -1944,16 +1958,37 @@ private:
? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr;
}
OSStatus elementToBusIdx (AudioUnitScope scope, AudioUnitElement element, bool& isInput, int& busIdx) noexcept
enum class BusKind
{
processor,
wrapperOnly,
};
struct ElementInfo
{
int busNr;
BusKind kind;
bool isInput;
OSStatus error;
};
ElementInfo getElementInfo (AudioUnitScope scope, AudioUnitElement element) noexcept
{
bool isInput = false;
OSStatus err;
busIdx = static_cast<int> (element);
if ((err = scopeToDirection (scope, isInput)) != noErr)
return { {}, {}, {}, err };
if ((err = scopeToDirection (scope, isInput)) != noErr) return err;
if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (juceFilter.get(), isInput))) return noErr;
const auto busIdx = static_cast<int> (element);
return kAudioUnitErr_InvalidElement;
if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (*juceFilter, isInput)))
return { busIdx, BusKind::processor, isInput, noErr };
if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCountForWrapper (*juceFilter, isInput)))
return { busIdx, BusKind::wrapperOnly, isInput, noErr };
return { {}, {}, {}, kAudioUnitErr_InvalidElement };
}
//==============================================================================
@ -2069,22 +2104,25 @@ private:
OSStatus syncAudioUnitWithProcessor()
{
OSStatus err = noErr;
const int enabledInputs = AudioUnitHelpers::getBusCount (juceFilter.get(), true);
const int enabledOutputs = AudioUnitHelpers::getBusCount (juceFilter.get(), false);
const auto numWrapperInputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true);
const auto numWrapperOutputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false);
if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast<UInt32> (enabledInputs))) != noErr)
if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast<UInt32> (numWrapperInputs))) != noErr)
return err;
if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast<UInt32> (enabledOutputs))) != noErr)
if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast<UInt32> (numWrapperOutputs))) != noErr)
return err;
addSupportedLayoutTags();
for (int i = 0; i < enabledInputs; ++i)
const auto numProcessorInputs = AudioUnitHelpers::getBusCount (*juceFilter, true);
const auto numProcessorOutputs = AudioUnitHelpers::getBusCount (*juceFilter, false);
for (int i = 0; i < numProcessorInputs; ++i)
if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr)
return err;
for (int i = 0; i < enabledOutputs; ++i)
for (int i = 0; i < numProcessorOutputs; ++i)
if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr)
return err;
@ -2093,8 +2131,8 @@ private:
OSStatus syncProcessorWithAudioUnit()
{
const int numInputBuses = AudioUnitHelpers::getBusCount (juceFilter.get(), true);
const int numOutputBuses = AudioUnitHelpers::getBusCount (juceFilter.get(), false);
const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true);
const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false);
const int numInputElements = static_cast<int> (GetScope(kAudioUnitScope_Input). GetNumberOfElements());
const int numOutputElements = static_cast<int> (GetScope(kAudioUnitScope_Output).GetNumberOfElements());
@ -2229,7 +2267,7 @@ private:
{
auto& layouts = isInput ? supportedInputLayouts : supportedOutputLayouts;
layouts.clear();
auto numBuses = AudioUnitHelpers::getBusCount (juceFilter.get(), isInput);
auto numBuses = AudioUnitHelpers::getBusCount (*juceFilter, isInput);
for (int busNr = 0; busNr < numBuses; ++busNr)
{
@ -2244,8 +2282,8 @@ private:
{
currentInputLayout.clear(); currentOutputLayout.clear();
currentInputLayout. resize (AudioUnitHelpers::getBusCount (juceFilter.get(), true));
currentOutputLayout.resize (AudioUnitHelpers::getBusCount (juceFilter.get(), false));
currentInputLayout. resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true));
currentOutputLayout.resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false));
addSupportedLayoutTagsForDirection (true);
addSupportedLayoutTagsForDirection (false);

View file

@ -782,7 +782,7 @@ public:
for (int dir = 0; dir < 2; ++dir)
{
const bool isInput = (dir == 0);
const int n = AudioUnitHelpers::getBusCount (&processor, isInput);
const int n = AudioUnitHelpers::getBusCount (processor, isInput);
Array<AudioChannelSet>& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses);
AUAudioUnitBusArray* auBuses = (isInput ? [getAudioUnit() inputBusses] : [getAudioUnit() outputBusses]);
@ -839,7 +839,7 @@ public:
audioBuffer.prepare (totalInChannels, totalOutChannels, static_cast<int> (maxFrames));
double sampleRate = (jmax (AudioUnitHelpers::getBusCount (&processor, true), AudioUnitHelpers::getBusCount (&processor, false)) > 0 ?
double sampleRate = (jmax (AudioUnitHelpers::getBusCount (processor, true), AudioUnitHelpers::getBusCount (processor, false)) > 0 ?
[[[([inputBusses.get() count] > 0 ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: 0] format] sampleRate] : 44100.0);
processor.setRateAndBufferSizeDetails (sampleRate, static_cast<int> (maxFrames));
@ -1125,7 +1125,7 @@ private:
{
std::unique_ptr<NSMutableArray<AUAudioUnitBus*>, NSObjectDeleter> array ([[NSMutableArray<AUAudioUnitBus*> alloc] init]);
AudioProcessor& processor = getAudioProcessor();
const int n = AudioUnitHelpers::getBusCount (&processor, isInput);
const int n = AudioUnitHelpers::getBusCount (processor, isInput);
for (int i = 0; i < n; ++i)
{
@ -1383,7 +1383,7 @@ private:
OwnedArray<BusBuffer>& busBuffers = isInput ? inBusBuffers : outBusBuffers;
busBuffers.clear();
const int n = AudioUnitHelpers::getBusCount (&getAudioProcessor(), isInput);
const int n = AudioUnitHelpers::getBusCount (getAudioProcessor(), isInput);
const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender];
for (int busIdx = 0; busIdx < n; ++busIdx)

View file

@ -43,8 +43,8 @@ struct AudioUnitHelpers
void alloc()
{
const int numInputBuses = AudioUnitHelpers::getBusCount (&processor, true);
const int numOutputBuses = AudioUnitHelpers::getBusCount (&processor, false);
const int numInputBuses = AudioUnitHelpers::getBusCount (processor, true);
const int numOutputBuses = AudioUnitHelpers::getBusCount (processor, false);
initializeChannelMapArray (true, numInputBuses);
initializeChannelMapArray (false, numOutputBuses);
@ -334,8 +334,8 @@ struct AudioUnitHelpers
{
Array<AUChannelInfo> channelInfo;
auto hasMainInputBus = (AudioUnitHelpers::getBusCount (&processor, true) > 0);
auto hasMainOutputBus = (AudioUnitHelpers::getBusCount (&processor, false) > 0);
auto hasMainInputBus = (AudioUnitHelpers::getBusCountForWrapper (processor, true) > 0);
auto hasMainOutputBus = (AudioUnitHelpers::getBusCountForWrapper (processor, false) > 0);
if ((! hasMainInputBus) && (! hasMainOutputBus))
{
@ -471,9 +471,9 @@ struct AudioUnitHelpers
}
//==============================================================================
static int getBusCount (const AudioProcessor* juceFilter, bool isInput)
static int getBusCount (const AudioProcessor& juceFilter, bool isInput)
{
int busCount = juceFilter->getBusCount (isInput);
int busCount = juceFilter.getBusCount (isInput);
#ifdef JucePlugin_PreferredChannelConfigurations
short configs[][2] = {JucePlugin_PreferredChannelConfigurations};
@ -491,6 +491,17 @@ struct AudioUnitHelpers
return busCount;
}
static int getBusCountForWrapper (const AudioProcessor& juceFilter, bool isInput)
{
#if JucePlugin_IsMidiEffect
const auto numRequiredBuses = isInput ? 0 : 1;
#else
const auto numRequiredBuses = 0;
#endif
return jmax (numRequiredBuses, getBusCount (juceFilter, isInput));
}
static bool setBusesLayout (AudioProcessor* juceFilter, const AudioProcessor::BusesLayout& requestedLayouts)
{
#ifdef JucePlugin_PreferredChannelConfigurations