I made a hack (in juce 7.0.2) to add the AudioDeviceCombiner thread to the device Workgroup and it made a big difference. Here’s the code. I had to change CoreAudioIODeviceType
to expose the device IDs as public variables so I could retrieve them when opening a device. I just used the output device to get the workgroup. Hopefully it helps.
Beware, its definitely a quick hack and was not done carefully or built on anything other than my mac.
If you need to make a patch to JUCE, I’d recommend forking it and keeping your own patched repo that you can then rebase whenever JUCE gets updated. We have a few custom patches that we manage this way.
static bool setThisThreadToRealtime (uint64 periodMs)
{
const auto thread = pthread_self();
#if JUCE_MAC || JUCE_IOS
mach_timebase_info_data_t timebase;
mach_timebase_info (&timebase);
const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer;
const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits<uint32_t>::max(), periodMs * ticksPerMs);
thread_time_constraint_policy_data_t policy;
policy.period = periodTicks/2;
policy.computation = jmin ((uint32_t) 50000, policy.period)/2;
policy.constraint = policy.period;
policy.preemptible = true;
return thread_policy_set (pthread_mach_thread_np (thread),
THREAD_TIME_CONSTRAINT_POLICY,
(thread_policy_t) &policy,
THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS;
#else
ignoreUnused (periodMs);
struct sched_param param;
param.sched_priority = sched_get_priority_max (SCHED_RR);
return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0;
#endif
}
Then in your thread run function:
AudioDeviceID anID = 0;
os_workgroup_join_token_s JoinToken;
void run() override
{
setThisThreadToRealtime (jmax<int> (1000.0 * currentBufferSize
/ float(currentSampleRate),1));
UInt32 Count = sizeof(os_workgroup_t);
os_workgroup_t pWorkgroup = NULL;
::AudioDeviceGetProperty(anID, 0, 0,
kAudioDevicePropertyIOThreadOSWorkgroup, &Count, &pWorkgroup);
int Result = ::os_workgroup_join(pWorkgroup, &JoinToken);
auto numSamples = currentBufferSize;
AudioBuffer<float> buffer (fifos.getNumChannels(), numSamples);
buffer.clear();
Array<const float*> inputChans;
Array<float*> outputChans;
for (auto* d : devices)
{
for (int j = 0; j < d->numInputChans; ++j) inputChans.add (buffer.getReadPointer (d->inputIndex + j));
for (int j = 0; j < d->numOutputChans; ++j) outputChans.add (buffer.getWritePointer (d->outputIndex + j));
}
auto numInputChans = inputChans.size();
auto numOutputChans = outputChans.size();
inputChans.add (nullptr);
outputChans.add (nullptr);
auto blockSizeMs = jmax (1, (int) (1000 * numSamples / currentSampleRate));
jassert (numInputChans + numOutputChans == buffer.getNumChannels());
threadInitialised.signal();
while (! threadShouldExit())
{
readInput (buffer, numSamples, blockSizeMs);
bool didCallback = true;
{
const ScopedLock sl (callbackLock);
if (callback != nullptr)
callback->audioDeviceIOCallbackWithContext ((const float**) inputChans.getRawDataPointer(),
numInputChans,
outputChans.getRawDataPointer(),
numOutputChans,
numSamples,
{}); // Can't predict when the next output callback will happen
else
didCallback = false;
}
if (didCallback)
{
pushOutputData (buffer, numSamples, blockSizeMs);
}
else
{
for (int i = 0; i < numOutputChans; ++i)
FloatVectorOperations::clear (outputChans[i], numSamples);
reset();
}
}
os_workgroup_leave(pWorkgroup, &JoinToken);
}