MacOS Audio Thread Workgroups

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, &param) == 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);
    }
3 Likes