/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

#include "SC_CoreAudio.h"
#include <stdarg.h>
#include "SC_SequencedCommand.h"
#include "SC_Prototypes.h"
#include "SC_HiddenWorld.h"
#include "SC_WorldOptions.h"
#include "SC_Endian.h"
#include "SC_Lib_Cintf.h"
#include "SC_Lock.h"
#include "SC_Time.hpp"
#include "SC_InlineBinaryOp.h"
#include <stdlib.h>
#include <algorithm>

#ifndef _WIN32
#    include <sys/time.h>
#endif

#ifdef SC_IPHONE
#    include "SC_VFP11.h"
#endif

#include "nova-tt/thread_priority.hpp"


int64 gStartupOSCTime = -1;

double gSampleRate, gSampleDur;

void sc_SetDenormalFlags();


// =====================================================================
// Timing (CoreAudio)

#if SC_AUDIO_API == SC_AUDIO_API_COREAUDIO || SC_AUDIO_API == SC_AUDIO_API_AUDIOUNITS

int64 gOSCoffset = 0;

int32 server_timeseed() {
    static int32 count = 0;
    int64 time = AudioGetCurrentHostTime();
    return (int32)(time >> 32) ^ (int32)time ^ count--;
}

inline int64 CoreAudioHostTimeToOSC(int64 hostTime) {
    return (int64)((double)AudioConvertHostTimeToNanos(hostTime) * kNanosToOSCunits) + gOSCoffset;
}

int64 oscTimeNow() { return CoreAudioHostTimeToOSC(AudioGetCurrentHostTime()); }

static void syncOSCOffsetWithTimeOfDay() {
    // generate a value gOSCoffset such that
    // (gOSCOffset + systemTimeInOSCunits)
    // is equal to gettimeofday time in OSCunits.
    // Then if this machine is synced via NTP, we are synced with the world.
    // more accurate way to do this??

    struct timeval tv;

    int64 systemTimeBefore, systemTimeAfter;
    int64 diff, minDiff = 0x7fffFFFFffffFFFFLL;

    // take best of several tries
    const int numberOfTries = 5;
    int64 newOffset = gOSCoffset;
    for (int i = 0; i < numberOfTries; ++i) {
        systemTimeBefore = AudioGetCurrentHostTime();
        gettimeofday(&tv, 0);
        systemTimeAfter = AudioGetCurrentHostTime();

        diff = systemTimeAfter - systemTimeBefore;
        if (diff < minDiff) {
            minDiff = diff;
            // assume that gettimeofday happens halfway between AudioGetCurrentHostTime() calls
            int64 systemTimeBetween = systemTimeBefore + diff / 2;
            int64 systemTimeInOSCunits =
                (int64)((double)AudioConvertHostTimeToNanos(systemTimeBetween) * kNanosToOSCunits);
            int64 timeOfDayInOSCunits =
                ((int64)(tv.tv_sec + kSECONDS_FROM_1900_to_1970) << 32) + (int64)(tv.tv_usec * kMicrosToOSCunits);
            newOffset = timeOfDayInOSCunits - systemTimeInOSCunits;
        }
    }

    gOSCoffset = newOffset;
    // scprintf("gOSCoffset %016llX\n", gOSCoffset);
}

static void resyncThreadFunc() {
#    ifdef NOVA_TT_PRIORITY_RT
    std::pair<int, int> priorities = nova::thread_priority_interval_rt();
    nova::thread_set_priority_rt((priorities.first + priorities.second) / 2);
#    else
    std::pair<int, int> priorities = nova::thread_priority_interval();
    nova::thread_set_priority(priorities.second);
#    endif

    while (true) {
        sleep(20);
        syncOSCOffsetWithTimeOfDay();
    }
}

void initializeScheduler() {
    syncOSCOffsetWithTimeOfDay();

    SC_Thread resyncThread(resyncThreadFunc);
    resyncThread.detach();
}
#endif // SC_AUDIO_API_COREAUDIO


// =====================================================================
// Timing (CoreAudioIPHONE)

#if SC_AUDIO_API == SC_AUDIO_API_COREAUDIOIPHONE

int64 gOSCoffset = 0;

static inline int64 GetMicroseconds() {
    struct timeval tv;
    gettimeofday(&tv, 0);
    return (int64)tv.tv_sec * 1000000 + tv.tv_usec;
}

static inline int64 GetCurrentOSCTime() {
    struct timeval tv;
    uint64 s, f;
    gettimeofday(&tv, 0);
    s = (uint64)tv.tv_sec + (uint64)kSECONDS_FROM_1900_to_1970;
    f = (uint64)((double)tv.tv_usec * kMicrosToOSCunits);

    return (s << 32) + f;
}

int32 server_timeseed() {
    int64 time = GetCurrentOSCTime();
    return Hash((int32)(time >> 32) + Hash((int32)time));
}

int64 oscTimeNow() { return GetCurrentOSCTime(); }

void initializeScheduler() { gOSCoffset = GetCurrentOSCTime(); }
#endif // SC_AUDIO_API_COREAUDIO


// =====================================================================
// Packets (Common)

bool ProcessOSCPacket(World* inWorld, OSC_Packet* inPacket);
void PerformOSCBundle(World* inWorld, OSC_Packet* inPacket);
int PerformOSCMessage(World* inWorld, int inSize, char* inData, ReplyAddress* inReply);
PacketStatus PerformOSCPacket(World* world, OSC_Packet* packet, SC_ScheduledEvent::PacketFreeFunc);

void Perform_ToEngine_Msg(FifoMsg* inMsg);
void FreeOSCPacket(FifoMsg* inMsg);

struct IsBundle {
    IsBundle() { str4cpy(s, "#bundle"); }
    bool checkIsBundle(int32* in) { return in[0] == s[0] && in[1] == s[1]; }
    int32 s[2];
};
IsBundle gIsBundle;

bool ProcessOSCPacket(World* inWorld, OSC_Packet* inPacket) {
    // scprintf("ProcessOSCPacket %d, '%s'\n", inPacket->mSize, inPacket->mData);
    if (!inPacket)
        return false;
    bool result;
    static_cast<SC_Lock*>(inWorld->mDriverLock)->lock();
    SC_AudioDriver* driver = AudioDriver(inWorld);
    if (!driver) {
        static_cast<SC_Lock*>(inWorld->mDriverLock)->unlock();
        return false;
    }
    inPacket->mIsBundle = gIsBundle.checkIsBundle((int32*)inPacket->mData);
    FifoMsg fifoMsg;
    fifoMsg.Set(inWorld, Perform_ToEngine_Msg, FreeOSCPacket, (void*)inPacket);
    result = driver->SendOscPacketMsgToEngine(fifoMsg);
    static_cast<SC_Lock*>(inWorld->mDriverLock)->unlock();
    if (!result)
        scprintf("command FIFO full\n");
    return result;
}

int PerformOSCMessage(World* inWorld, int inSize, char* inData, ReplyAddress* inReply) {
    // scprintf("->PerformOSCMessage %d\n", inData[0]);
    SC_LibCmd* cmdObj;
    int cmdNameLen;
    if (inData[0] == 0) {
        cmdNameLen = 4;
        uint32 index = inData[3];
        if (index >= NUMBER_OF_COMMANDS)
            cmdObj = nullptr;
        else
            cmdObj = gCmdArray[index];
    } else {
        cmdNameLen = OSCstrlen(inData);
        cmdObj = gCmdLib->Get((int32*)inData);
    }
    if (!cmdObj) {
        CallSendFailureCommand(inWorld, inData, "Command not found", inReply);
        scprintf("FAILURE IN SERVER: %s Command not found\n", inData);
        return kSCErr_NoSuchCommand;
    }

    int err = cmdObj->Perform(inWorld, inSize - cmdNameLen, inData + cmdNameLen, inReply);
    // scprintf("<-PerformOSCMessage %d\n", inData[0]);
    return err;
}

void PerformOSCBundle(World* inWorld, OSC_Packet* inPacket) {
    // scprintf("->PerformOSCBundle %d\n", inPacket->mSize);
    char* data = inPacket->mData + 16;
    char* dataEnd = inPacket->mData + inPacket->mSize;

    while (data < dataEnd) {
        int32 msgSize = sc_ntohl(*(int32*)data);
        data += sizeof(int32);
        // scprintf("msgSize %d\n", msgSize);
        PerformOSCMessage(inWorld, msgSize, data, &inPacket->mReplyAddr);
        data += msgSize;
    }

    // reset so next command uses permanent error notification status
    inWorld->mLocalErrorNotification = 0;

    // scprintf("<-PerformOSCBundle %d\n", inPacket->mSize);
}

PacketStatus PerformOSCPacket(World* world, OSC_Packet* packet, SC_ScheduledEvent::PacketFreeFunc freeFunc) {
    SC_AudioDriver* driver = world->hw->mAudioDriver;

    if (!packet->mIsBundle) {
        PerformOSCMessage(world, packet->mSize, packet->mData, &packet->mReplyAddr);
        world->mLocalErrorNotification = 0;
        return PacketPerformed;
    } else {
        // in real time engine, schedule the packet
        int64 time = OSCtime(packet->mData + 8);
        if (time == 0 || time == 1) {
            PerformOSCBundle(world, packet);
            return PacketPerformed;
        } else {
            if ((time < driver->mOSCbuftime) && (world->mVerbosity >= 0)) {
                double seconds = (driver->mOSCbuftime - time) * kOSCtoSecs;
                scprintf("late %.9f\n", seconds);
                // FifoMsg outMsg;

                // ReportLateness(packet->mReply, seconds)
            }
            // DEBUG
            // else
            // scprintf("scheduled in %.6f at time %.6f\n",
            //	(time-driver->mOSCbuftime)*kOSCtoSecs,
            //	(time-gStartupOSCTime)*kOSCtoSecs);

            SC_ScheduledEvent event(world, time, packet, freeFunc);
            driver->AddEvent(event);
            return PacketScheduled;
        }
    }
}

////////////////////////////////////////////////////////////////////////////

void Perform_ToEngine_Msg(FifoMsg* inMsg) {
    World* world = inMsg->mWorld;
    OSC_Packet* packet = (OSC_Packet*)inMsg->mData;
    if (!packet)
        return;

    PacketStatus status = PerformOSCPacket(world, packet, SC_ScheduledEvent::FreeInNRT);
    if (status == PacketScheduled) {
        // Transfer ownership
        inMsg->mData = nullptr;
        inMsg->mFreeFunc = nullptr;
    }
}

PacketStatus PerformCompletionMsg(World* inWorld, const OSC_Packet& inPacket) {
    OSC_Packet* packet = (OSC_Packet*)World_Alloc(inWorld, sizeof(OSC_Packet));
    if (packet == NULL)
        throw std::runtime_error("PerformCompletionMsg allocation failed: out of memory!\n");
    *packet = inPacket;
    packet->mIsBundle = gIsBundle.checkIsBundle((int32*)packet->mData);
    PacketStatus status = PerformOSCPacket(inWorld, packet, SC_ScheduledEvent::FreeInRT);
    if (status == PacketPerformed) {
        World_Free(inWorld, packet);
    }
    return status;
}

void FreeOSCPacket(FifoMsg* inMsg) {
    OSC_Packet* packet = (OSC_Packet*)inMsg->mData;
    if (packet) {
        inMsg->mData = nullptr;
#if _MSC_VER == 1310
#    pragma message("$$$todo fixme hack for the 'uninitialized packet->mData ptr when using MSVC 7.1 debug")
        if (packet->mData != reinterpret_cast<char*>(0xcdcdcdcd))
            free(packet->mData);
#else //#ifdef _MSC_VER
        free(packet->mData);
#endif //#ifdef _MSC_VER
        free(packet);
    }
}

void Free_FromEngine_Msg(FifoMsg* inMsg);
void Free_FromEngine_Msg(FifoMsg* inMsg) { World_Free(inMsg->mWorld, inMsg->mData); }

// =====================================================================
// Audio driver (Common)

SC_AudioDriver::SC_AudioDriver(struct World* inWorld):
    mWorld(inWorld),
    mSampleTime(0),
    mNumSamplesPerCallback(0),
    mSafetyClipThreshold(1.26) {}

SC_AudioDriver::~SC_AudioDriver() {
    mRunThreadFlag = false;
    mAudioSync.Signal();
    mThread.join();
}

void SC_AudioDriver::RunThread() {
#ifdef NOVA_TT_PRIORITY_RT
    std::pair<int, int> priorities = nova::thread_priority_interval_rt();
    nova::thread_set_priority_rt((priorities.first + priorities.second) / 2);
#else
    std::pair<int, int> priorities = nova::thread_priority_interval();
    nova::thread_set_priority(priorities.second);
#endif

    TriggersFifo* trigfifo = &mWorld->hw->mTriggers;
    NodeReplyFifo* nodereplyfifo = &mWorld->hw->mNodeMsgs;
    NodeEndsFifo* nodeendfifo = &mWorld->hw->mNodeEnds;
    DeleteGraphDefsFifo* deletegraphfifo = &mWorld->hw->mDeleteGraphDefs;

    while (mRunThreadFlag) {
        // wait for sync
        mAudioSync.WaitNext();
#ifdef SC_BELA
        rt_print_flush_buffers();
#endif // SC_BELA

        reinterpret_cast<SC_Lock*>(mWorld->mNRTLock)->lock();

        // send /tr messages
        trigfifo->Perform();

        // send node reply messages
        nodereplyfifo->Perform();

        // send node status messages
        nodeendfifo->Perform();

        // free GraphDefs
        deletegraphfifo->Perform();

        // perform messages
        mFromEngine.Perform();

        reinterpret_cast<SC_Lock*>(mWorld->mNRTLock)->unlock();
    }
}

bool SC_AudioDriver::SendMsgFromEngine(FifoMsg& inMsg) { return mFromEngine.Write(inMsg); }


bool SC_AudioDriver::SendMsgToEngine(FifoMsg& inMsg) {
    mToEngine.Free();
    return mToEngine.Write(inMsg);
}

bool SC_AudioDriver::SendOscPacketMsgToEngine(FifoMsg& inMsg) {
    mOscPacketsToEngine.Free();
    return mOscPacketsToEngine.Write(inMsg);
}

void SC_ScheduledEvent::FreeInRT(struct World* world, OSC_Packet* packet) {
    World_Free(world, packet->mData);
    World_Free(world, packet);
}

void SC_ScheduledEvent::FreeInNRT(struct World* world, OSC_Packet* packet) {
    FifoMsg msg;
    msg.Set(world, FreeOSCPacket, nullptr, (void*)packet);
    world->hw->mAudioDriver->SendMsgFromEngine(msg);
}

void SC_ScheduledEvent::Perform() {
    PerformOSCBundle(mWorld, mPacket);
    (*mPacketFreeFunc)(mWorld, mPacket);
}

bool SC_AudioDriver::Setup() {
    mRunThreadFlag = true;
    SC_Thread thread(std::bind(&SC_AudioDriver::RunThread, this));
    mThread = std::move(thread);

    int numSamples;
    double sampleRate;

    if (!DriverSetup(&numSamples, &sampleRate))
        return false;

    mNumSamplesPerCallback = numSamples;
    // scprintf("mNumSamplesPerCallback %d\n", mNumSamplesPerCallback);
    // scprintf("mHardwareBufferSize %lu\n", mHardwareBufferSize);

    // compute a per sample increment to the OpenSoundControl Time
    mOSCincrementNumerator = (double)mWorld->mBufLength * pow(2., 32.);
    mOSCincrement = (int64)(mOSCincrementNumerator / sampleRate);
    mOSCtoSamples = sampleRate / pow(2., 32.);

    World_SetSampleRate(mWorld, sampleRate);
    mSampleRate = mSmoothSampleRate = sampleRate;
    mBuffersPerSecond = sampleRate / mNumSamplesPerCallback;
    mMaxPeakCounter = (int)mBuffersPerSecond;

    if (mWorld->mVerbosity >= 0) {
        scprintf("SC_AudioDriver: sample rate = %f, driver's block size = %d\n", sampleRate, mNumSamplesPerCallback);
    }

    return true;
}

bool SC_AudioDriver::Start() {
    mAvgCPU = 0.;
    mPeakCPU = 0.;
    mPeakCounter = 0;

    mStartHostSecs = 0.;
    mPrevHostSecs = 0.;
    mStartSampleTime = 0.;
    mPrevSampleTime = 0.;

    World_Start(mWorld);

    gStartupOSCTime = oscTimeNow();

    return DriverStart();
}

bool SC_AudioDriver::Stop() {
    if (!DriverStop())
        return false;
    return true;
}


// =====================================================================
// Audio driver (CoreAudio)
#if SC_AUDIO_API == SC_AUDIO_API_COREAUDIO

SC_AudioDriver* SC_NewAudioDriver(struct World* inWorld) { return new SC_CoreAudioDriver(inWorld); }

#endif

#if SC_AUDIO_API == SC_AUDIO_API_COREAUDIO || SC_AUDIO_API == SC_AUDIO_API_AUDIOUNITS

SC_CoreAudioDriver::SC_CoreAudioDriver(struct World* inWorld): SC_AudioDriver(inWorld), mInputBufList(0) {}

SC_CoreAudioDriver::~SC_CoreAudioDriver() {
    if (mInputBufList) {
        int i;
        for (i = 0; i < mInputBufList->mNumberBuffers; i++) {
            free(mInputBufList->mBuffers[i].mData);
        }
        free(mInputBufList);
    }
}

std::vector<AudioValueRange> GetAvailableNominalSampleRates(const AudioDeviceID& device) {
    std::vector<AudioValueRange> result;
    OSStatus err;
    UInt32 size;
    UInt32 count;
    std::unique_ptr<AudioValueRange[]> validSampleRateRanges;

    AudioObjectPropertyAddress addr = { kAudioDevicePropertyAvailableNominalSampleRates,
                                        kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };

    err = AudioObjectGetPropertyDataSize(device, &addr, 0, NULL, &size);

    if (err != kAudioHardwareNoError) {
        scprintf("get kAudioDevicePropertyAvailableNominalSampleRates data size error %4.4s\n", (char*)&err);
    } else {
        if (size > 0) {
            count = size / sizeof(AudioValueRange);
            validSampleRateRanges = std::unique_ptr<AudioValueRange[]>(new AudioValueRange[count]);

            err = AudioObjectGetPropertyData(device, &addr, 0, NULL, &size, validSampleRateRanges.get());
            if (err != kAudioHardwareNoError) {
                scprintf("get kAudioDevicePropertyAvailableNominalSampleRates error %4.4s\n", (char*)&err);
            } else {
                for (int i = 0; i < count; i++) {
                    result.push_back(validSampleRateRanges[i]);
                }
            }
        }
    }

    return result;
}

bool SC_CoreAudioDriver::DriverSetup(int* outNumSamplesPerCallback, double* outSampleRate) {
    OSStatus err = kAudioHardwareNoError;
    UInt32 count;
    mOutputDevice = kAudioDeviceUnknown;
    mInputDevice = kAudioDeviceUnknown;
    AudioObjectPropertyAddress propertyAddress;

    propertyAddress.mSelector = kAudioHardwarePropertyDevices;
    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
    propertyAddress.mElement = kAudioObjectPropertyElementMaster;

    // scprintf("SC_CoreAudioDriver::Setup world %p\n", mWorld);

    ////////////////////////////////////////////////////////////////////////////////////////////////

    do {
        // err = AudioHardwareGetPropertyInfo( kAudioHardwarePropertyDevices, &count, 0);

        propertyAddress.mSelector = kAudioHardwarePropertyDevices;

        err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &count);

        if (err != noErr) {
            scprintf("get kAudioHardwarePropertyDevices error %4.4s\n", (char*)&err);
            break;
        }

        AudioDeviceID* devices = (AudioDeviceID*)malloc(count);

        // err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &count, devices);

        err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &count, devices);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioHardwarePropertyDevices error %4.4s\n", (char*)&err);
            free(devices);
            break;
        }

        int numdevices = count / sizeof(AudioDeviceID);
        if (mWorld->mVerbosity >= 0) {
            scprintf("Number of Devices: %d\n", numdevices);
        }
        for (int i = 0; i < numdevices; ++i) {
            propertyAddress.mSelector = kAudioDevicePropertyDeviceName;

            // err = AudioDeviceGetPropertyInfo(devices[i], 0, false, kAudioDevicePropertyDeviceName, &count, 0);

            err = AudioObjectGetPropertyDataSize(devices[i], &propertyAddress, 0, NULL, &count);

            if (err != kAudioHardwareNoError) {
                scprintf("info kAudioDevicePropertyDeviceName error %4.4s A %d %p\n", (char*)&err, i, devices[i]);
                break;
            }

            char* name = (char*)malloc(count);
            // err = AudioDeviceGetProperty(devices[i], 0, false, kAudioDevicePropertyDeviceName, &count, name);

            err = AudioObjectGetPropertyData(devices[i], &propertyAddress, 0, NULL, &count, name);

            if (err != kAudioHardwareNoError) {
                scprintf("get kAudioDevicePropertyDeviceName error %4.4s A %d %p\n", (char*)&err, i, devices[i]);
                free(name);
                break;
            }
            if (mWorld->mVerbosity >= 0) {
                scprintf("   %d : \"%s\"\n", i, name);
            }
            free(name);
        }
        free(devices);
        if (mWorld->mVerbosity >= 0) {
            scprintf("\n");
        }
    } while (false);

    if (mWorld->hw->mInDeviceName || mWorld->hw->mOutDeviceName) {
        propertyAddress.mSelector = kAudioHardwarePropertyDevices;

        // err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &count, 0);

        err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &count);

        if (err != kAudioHardwareNoError) {
            scprintf("info kAudioHardwarePropertyDevices error %4.4s\n", (char*)&err);
            return false;
        }

        AudioDeviceID* devices = (AudioDeviceID*)malloc(count);

        // err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &count, devices);
        err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &count, devices);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioHardwarePropertyDevices error %4.4s\n", (char*)&err);
            return false;
        }

        int numdevices = count / sizeof(AudioDeviceID);
        for (int i = 0; i < numdevices; ++i) {
            // err = AudioDeviceGetPropertyInfo(devices[i], 0, false, kAudioDevicePropertyDeviceName, &count, 0);
            propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
            err = AudioObjectGetPropertyDataSize(devices[i], &propertyAddress, 0, NULL, &count);

            if (err != kAudioHardwareNoError) {
                scprintf("info kAudioDevicePropertyDeviceName error %4.4s B %d %p\n", (char*)&err, i, devices[i]);
                break;
            }

            char* name = (char*)malloc(count);

            // err = AudioDeviceGetProperty(devices[i], 0, false, kAudioDevicePropertyDeviceName, &count, name);

            err = AudioObjectGetPropertyData(devices[i], &propertyAddress, 0, NULL, &count, name);

            if (err != kAudioHardwareNoError) {
                scprintf("get kAudioDevicePropertyDeviceName error %4.4s B %d %p\n", (char*)&err, i, devices[i]);
                return false;
            }
            if (strcmp(name, mWorld->hw->mInDeviceName) == 0) {
                mInputDevice = devices[i];
            }
            if (strcmp(name, mWorld->hw->mOutDeviceName) == 0) {
                mOutputDevice = devices[i];
            }
            free(name);
            if (mInputDevice != kAudioDeviceUnknown && mOutputDevice != kAudioDeviceUnknown)
                break;
        }
        free(devices);
        if (mOutputDevice == kAudioDeviceUnknown || mInputDevice == kAudioDeviceUnknown)
            goto getDefault;
    } else {
    getDefault:

        // get the default output device for the HAL
        if (mOutputDevice == kAudioDeviceUnknown) {
            count = sizeof(mOutputDevice);
            // get the output device:
            //			err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &count, (void *) &
            // mOutputDevice);

            propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;

            err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &count,
                                             (void*)&mOutputDevice);

            if (err != kAudioHardwareNoError) {
                scprintf("get kAudioHardwarePropertyDefaultOutputDevice error %4.4s\n", (char*)&err);
                return false;
            }
        }

        // get the input device
        if (mInputDevice == kAudioDeviceUnknown) {
            count = sizeof(mInputDevice);
            //			err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &count, (void *) &
            // mInputDevice);

            propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;

            err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &count,
                                             (void*)&mInputDevice);


            // get the input device:
            if (err != kAudioHardwareNoError) {
                scprintf("get kAudioHardwarePropertyDefaultInputDevice error %4.4s\n", (char*)&err);
                return false;
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    AudioTimeStamp now;
    now.mFlags = kAudioTimeStampHostTimeValid;
    now.mHostTime = AudioGetCurrentHostTime();
    if (mPreferredHardwareBufferFrameSize) {
        count = sizeof(UInt32);
        //		err = AudioDeviceSetProperty(mOutputDevice, &now, 0, false, kAudioDevicePropertyBufferFrameSize, count,
        //&mPreferredHardwareBufferFrameSize);

        propertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize;
        propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

        err = AudioObjectSetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, count,
                                         &mPreferredHardwareBufferFrameSize);

        if (err != kAudioHardwareNoError) {
            scprintf("set kAudioDevicePropertyBufferFrameSize error %4.4s\n", (char*)&err);
            // return false;
        }
        if (UseSeparateIO()) {
            count = sizeof(UInt32);
            // err = AudioDeviceSetProperty(mOutputDevice, &now, 0, false, kAudioDevicePropertyBufferFrameSize, count,
            // &mPreferredHardwareBufferFrameSize);

            propertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize;
            propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

            err = AudioObjectSetPropertyData(mInputDevice, &propertyAddress, 0, NULL, count,
                                             &mPreferredHardwareBufferFrameSize);

            if (err != kAudioHardwareNoError) {
                scprintf("set kAudioDevicePropertyNominalSampleRate error %4.4s\n", (char*)&err);
                // return false;
            }
        }
    }
    if (mPreferredSampleRate) {
        Float64 sampleRate = mPreferredSampleRate;
        count = sizeof(Float64);

        bool sampleRateSupported = false;
        auto availableSampleRates = GetAvailableNominalSampleRates(mOutputDevice);

        // If we've got two devices, we need to use a sample rate list from both
        //  This won't account for cases where there are overlapping ranges, but
        //  that seems sufficiently edge-case to ignore for now.
        if (UseSeparateIO()) {
            auto inputSampleRates = GetAvailableNominalSampleRates(mInputDevice);

            std::vector<AudioValueRange> availableSampleRatesInputOutput(
                std::min(availableSampleRates.size(), inputSampleRates.size()));
            auto rateListIter =
                std::set_union(availableSampleRates.begin(), availableSampleRates.end(), inputSampleRates.begin(),
                               inputSampleRates.end(), availableSampleRatesInputOutput.begin(),
                               [](const AudioValueRange& lhs, const AudioValueRange& rhs) {
                                   return (lhs.mMaximum != rhs.mMaximum) ? (lhs.mMaximum < rhs.mMaximum)
                                                                         : (lhs.mMinimum < rhs.mMinimum);
                               });

            availableSampleRatesInputOutput.resize(rateListIter - availableSampleRatesInputOutput.begin());
            availableSampleRates = availableSampleRatesInputOutput;
        }

        for (const AudioValueRange& range : availableSampleRates) {
            if (mPreferredSampleRate >= range.mMinimum && mPreferredSampleRate <= range.mMaximum) {
                sampleRateSupported = true;
                break;
            }
        }

        if (!sampleRateSupported) {
#    define SR_MAX_VALUE 9999999
#    define SR_MIN_VALUE -9999999

            Float64 nextHighestMatch = SR_MAX_VALUE;
            Float64 nextLowestMatch = SR_MIN_VALUE;

            for (const AudioValueRange& range : availableSampleRates) {
                if (range.mMinimum > mPreferredSampleRate && range.mMinimum < nextHighestMatch) {
                    nextHighestMatch = range.mMinimum;
                }
                if (range.mMaximum < mPreferredSampleRate && range.mMaximum > nextLowestMatch) {
                    nextLowestMatch = range.mMaximum;
                }
            }

            if (nextHighestMatch != SR_MAX_VALUE) {
                sampleRate = nextHighestMatch;
                sampleRateSupported = true;
            } else if (nextLowestMatch != SR_MIN_VALUE) {
                sampleRate = nextLowestMatch;
                sampleRateSupported = true;
            } else {
                sampleRateSupported = false;
            }

            if (sampleRateSupported) {
                scprintf("Requested sample rate %f was not available - attempting to use sample rate of %f\n",
                         (double)mPreferredSampleRate, (double)sampleRate);
            } else {
                scprintf("Could not set requested sample rate of %f\n", (double)mPreferredSampleRate);
                scprintf("Available sample rates:\n");
                for (const AudioValueRange& range : availableSampleRates) {
                    if (range.mMaximum == range.mMinimum) {
                        scprintf("\t%f\n", range.mMaximum);
                    } else {
                        scprintf("\t%f - %f\n", range.mMinimum, range.mMaximum);
                    }
                }
            }
        }

        // Requested sample rate is available - we're going to set and, if we don't hit an error,
        // assume that our set was successful.
        if (sampleRateSupported) {
            bool sampleRateSetSuccess = true;

            propertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
            propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

            err = AudioObjectSetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, count, &sampleRate);

            if (err != kAudioHardwareNoError) {
                sampleRateSetSuccess = false;
                scprintf("set kAudioDevicePropertyNominalSampleRate error %4.4s\n", (char*)&err);
            }

            if (UseSeparateIO()) {
                count = sizeof(Float64);

                propertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
                propertyAddress.mScope = kAudioDevicePropertyScopeInput;

                err = AudioObjectSetPropertyData(mInputDevice, &propertyAddress, 0, NULL, count, &sampleRate);

                if (err != kAudioHardwareNoError) {
                    sampleRateSetSuccess = false;
                    scprintf("set kAudioDevicePropertyNominalSampleRate error %4.4s\n", (char*)&err);
                }
            }

            if (sampleRateSetSuccess) {
                mExplicitSampleRate = sampleRate;
            } else {
                return false;
            }
        }
    }

    // get the buffersize for the out device
    count = sizeof(mHardwareBufferSize);
    // err = AudioDeviceGetProperty(mOutputDevice, 0, false, kAudioDevicePropertyBufferSize, &count,
    // &mHardwareBufferSize);
    propertyAddress.mSelector = kAudioDevicePropertyBufferSize;
    propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

    err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &count, &mHardwareBufferSize);

    if (err != kAudioHardwareNoError) {
        scprintf("get kAudioDevicePropertyBufferSize error %4.4s\n", (char*)&err);
        return false;
    }
    // scprintf("mHardwareBufferSize = %ld\n", mHardwareBufferSize);

    // get a description of the data format used by the output device
    count = sizeof(AudioStreamBasicDescription);
    // err = AudioDeviceGetProperty(mOutputDevice, 0, false, kAudioDevicePropertyStreamFormat, &count,
    // &outputStreamDesc);
    propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
    propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

    err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &count, &outputStreamDesc);

    if (err != kAudioHardwareNoError) {
        scprintf("get kAudioDevicePropertyStreamFormat error %4.4s\n", (char*)&err);
        return false;
    }

    if (mInputDevice != kAudioDeviceUnknown) {
        // get a description of the data format used by the input device
        count = sizeof(AudioStreamBasicDescription);
        // err = AudioDeviceGetProperty(mInputDevice, 0, true, kAudioDevicePropertyStreamFormat, &count,
        // &inputStreamDesc);
        propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
        propertyAddress.mScope = kAudioDevicePropertyScopeInput;

        err = AudioObjectGetPropertyData(mInputDevice, &propertyAddress, 0, NULL, &count, &inputStreamDesc);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyStreamFormat error on input %4.4s\n", (char*)&err);
            return false;
        }

        if (
            // We do not support mismatched input and output sample rates, but there's no harm in skipping
            // this check if numInputBusChannels == 0. This allows the user to disable input entirely as
            // a workaround for a sample rate mismatch.
            mWorld->mNumInputs > 0 && !mExplicitSampleRate
            && inputStreamDesc.mSampleRate != outputStreamDesc.mSampleRate) {
            scprintf("ERROR: Input sample rate is %g, but output is %g. "
                     "Mismatched sample rates are not supported. "
                     "To disable input, set the number of input channels to 0.\n",
                     inputStreamDesc.mSampleRate, outputStreamDesc.mSampleRate);
            return false;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////

    do {
        // err = AudioDeviceGetPropertyInfo(mInputDevice, 0, false, kAudioDevicePropertyDeviceName, &count, 0);
        propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
        propertyAddress.mScope = kAudioDevicePropertyScopeInput;

        err = AudioObjectGetPropertyDataSize(mInputDevice, &propertyAddress, 0, NULL, &count);

        if (err != kAudioHardwareNoError) {
            scprintf("info kAudioDevicePropertyDeviceName error %4.4s C %p\n", (char*)&err, mInputDevice);
            break;
        }

        char* name = (char*)malloc(count);
        // err = AudioDeviceGetProperty(mInputDevice, 0, false, kAudioDevicePropertyDeviceName, &count, name);

        err = AudioObjectGetPropertyData(mInputDevice, &propertyAddress, 0, NULL, &count, name);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyDeviceName error %4.4s C %p\n", (char*)&err, mInputDevice);
            free(name);
            break;
        }

        if (mWorld->mVerbosity >= 0) {
            scprintf("\"%s\" Input Device\n", name);
        }
        free(name);

        Boolean writeable;

        // err = AudioDeviceGetPropertyInfo(mInputDevice, 0, 1, kAudioDevicePropertyStreamConfiguration, &count,
        // &writeable);
        propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
        propertyAddress.mScope = kAudioDevicePropertyScopeInput;

        err = AudioObjectIsPropertySettable(mInputDevice, &propertyAddress, &writeable);

        if (err != kAudioHardwareNoError) {
            scprintf("info kAudioDevicePropertyStreamConfiguration settable error %4.4s\n", (char*)&err);
            break;
        }

        err = AudioObjectGetPropertyDataSize(mInputDevice, &propertyAddress, 0, NULL, &count);

        if (err != kAudioHardwareNoError) {
            scprintf("info kAudioDevicePropertyStreamConfiguration size error %4.4s\n", (char*)&err);
            break;
        }

        AudioBufferList* bufList = (AudioBufferList*)malloc(count);
        // err = AudioDeviceGetProperty(mInputDevice, 0, 1, kAudioDevicePropertyStreamConfiguration, &count, bufList);
        propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
        propertyAddress.mScope = kAudioDevicePropertyScopeInput;

        err = AudioObjectGetPropertyData(mInputDevice, &propertyAddress, 0, NULL, &count, bufList);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyStreamConfiguration error %4.4s\n", (char*)&err);
            free(bufList);
            break;
        }

        if (mWorld->mVerbosity >= 0) {
            scprintf("   Streams: %d\n", bufList->mNumberBuffers);
            for (unsigned int j = 0; j < bufList->mNumberBuffers; ++j) {
                scprintf("      %d  channels %d\n", j, bufList->mBuffers[j].mNumberChannels);
            }
        }

        free(bufList);
    } while (false);
    if (mWorld->mVerbosity >= 0) {
        scprintf("\n");
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////

    do {
        // err = AudioDeviceGetPropertyInfo(mOutputDevice, 0, false, kAudioDevicePropertyDeviceName, &count, 0);
        propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
        propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

        err = AudioObjectGetPropertyDataSize(mOutputDevice, &propertyAddress, 0, NULL, &count);

        char* name = (char*)malloc(count);
        // err = AudioDeviceGetProperty(mOutputDevice, 0, false, kAudioDevicePropertyDeviceName, &count, name);
        err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &count, name);
        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyDeviceName error %4.4s\n", (char*)&err);
            free(name);
            break;
        }

        if (mWorld->mVerbosity >= 0) {
            scprintf("\"%s\" Output Device\n", name);
        }
        free(name);

        Boolean writeable;
        // err = AudioDeviceGetPropertyInfo(mOutputDevice, 0, 0, kAudioDevicePropertyStreamConfiguration, &count,
        // &writeable);

        propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
        propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

        err = AudioObjectIsPropertySettable(mOutputDevice, &propertyAddress, &writeable);

        if (err != kAudioHardwareNoError) {
            scprintf("info kAudioDevicePropertyStreamConfiguration settable error %4.4s\n", (char*)&err);
            break;
        }

        err = AudioObjectGetPropertyDataSize(mOutputDevice, &propertyAddress, 0, NULL, &count);

        if (err != kAudioHardwareNoError) {
            scprintf("info kAudioDevicePropertyStreamConfiguration size error %4.4s\n", (char*)&err);
            break;
        }

        AudioBufferList* bufList = (AudioBufferList*)malloc(count);
        // err = AudioDeviceGetProperty(mOutputDevice, 0, 0, kAudioDevicePropertyStreamConfiguration, &count, bufList);

        propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
        propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

        err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &count, bufList);
        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyStreamConfiguration error %4.4s\n", (char*)&err);
            free(bufList);
            break;
        }
        if (mWorld->mVerbosity >= 0) {
            scprintf("   Streams: %d\n", bufList->mNumberBuffers);
            for (unsigned int j = 0; j < bufList->mNumberBuffers; ++j) {
                scprintf("      %d  channels %d\n", j, bufList->mBuffers[j].mNumberChannels);
            }
        }
        free(bufList);
    } while (false);
    if (mWorld->mVerbosity >= 0) {
        scprintf("\n");
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////


    if (UseSeparateIO()) {
        count = sizeof(UInt32);
        // err = AudioDeviceGetProperty(mInputDevice, 0, true, kAudioDevicePropertySafetyOffset, &count,
        // &mSafetyOffset);
        propertyAddress.mSelector = kAudioDevicePropertySafetyOffset;
        propertyAddress.mScope = kAudioDevicePropertyScopeInput;

        err = AudioObjectGetPropertyData(mInputDevice, &propertyAddress, 0, NULL, &count, &mSafetyOffset);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertySafetyOffset error %4.4s\n", (char*)&err);
            return false;
        }
        if (mWorld->mVerbosity >= 1) {
            scprintf("mSafetyOffset %lu\n", mSafetyOffset);
        }

        Boolean writeable;
        //		err = AudioDeviceGetPropertyInfo(mInputDevice, 0, true, kAudioDevicePropertyStreamConfiguration, &count,
        //&writeable);

        propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
        propertyAddress.mScope = kAudioDevicePropertyScopeInput;

        err = AudioObjectIsPropertySettable(mInputDevice, &propertyAddress, &writeable);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyStreamConfiguration writeable error %4.4s\n", (char*)&err);
            return false;
        }

        err = AudioObjectGetPropertyDataSize(mInputDevice, &propertyAddress, 0, NULL, &count);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyStreamConfiguration size error %4.4s\n", (char*)&err);
            return false;
        }

        mInputBufList = (AudioBufferList*)malloc(count);

        // err = AudioDeviceGetProperty(mInputDevice, 0, true, kAudioDevicePropertyStreamConfiguration, &count,
        // mInputBufList);

        err = AudioObjectGetPropertyData(mInputDevice, &propertyAddress, 0, NULL, &count, mInputBufList);

        if (err != kAudioHardwareNoError) {
            scprintf("get kAudioDevicePropertyStreamConfiguration error %4.4s\n", (char*)&err);
            return false;
        }

        if (mWorld->mVerbosity >= 1) {
            scprintf("mNumberBuffers %lu\n", mInputBufList->mNumberBuffers);
        }
        for (uint32 i = 0; i < mInputBufList->mNumberBuffers; ++i) {
            if (mWorld->mVerbosity >= 1) {
                scprintf("  mDataByteSize %d %lu\n", i, mInputBufList->mBuffers[i].mDataByteSize);
            }
            mInputBufList->mBuffers[i].mData = zalloc(1, mInputBufList->mBuffers[i].mDataByteSize);
        }

        /*
        AudioTimeStamp	now;
        now.mFlags = kAudioTimeStampHostTimeValid;
        now.mHostTime = AudioGetCurrentHostTime();


        err = AudioDeviceSetProperty(mInputDevice, &now, 0, true, kAudioDevicePropertyRegisterBufferList, count,
        mInputBufList); if (err != kAudioHardwareNoError) { scprintf("get kAudioDevicePropertyRegisterBufferList error
        %4.4s\n", (char*)&err); return false;
        }
        */
    }

    *outNumSamplesPerCallback = mHardwareBufferSize / outputStreamDesc.mBytesPerFrame;
    if (mExplicitSampleRate) {
        *outSampleRate = mExplicitSampleRate.get();
    } else {
        *outSampleRate = outputStreamDesc.mSampleRate;
    }

    if (mWorld->mVerbosity >= 1) {
        scprintf("<-SC_CoreAudioDriver::Setup world %p\n", mWorld);
    }


    // check if using built-in output, and thus whether there could be headphone plug/un-plug issues
    // our assumption otherwise is that we don't respond, and SC will stay with the pre-arranged or default device, and
    // not restart just because headphones switched

    // err = AudioDeviceGetPropertyInfo(mOutputDevice, 0, false, kAudioDevicePropertyDeviceName, &count, 0);

    propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
    propertyAddress.mScope = kAudioDevicePropertyScopeOutput;

    err = AudioObjectGetPropertyDataSize(mOutputDevice, &propertyAddress, 0, NULL, &count);

    if (err != kAudioHardwareNoError) {
        scprintf("info kAudioDevicePropertyDeviceName error %4.4s %p\n", (char*)&err, mOutputDevice);
        return false;
    }

    char* outputname = (char*)malloc(count);
    const char* testname = "Built-in Output";

    // err = AudioDeviceGetProperty(mOutputDevice, 0, false, kAudioDevicePropertyDeviceName, &count, outputname);
    err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &count, outputname);

    if (err != kAudioHardwareNoError) {
        scprintf("get kAudioDevicePropertyDeviceName error %4.4s %p\n", (char*)&err, mOutputDevice);
        return false;
    }
    builtinoutputflag_ = 0;

    if (strcmp(testname, outputname) == 0) {
        builtinoutputflag_ = 1;
    }
    // else {
    //
    //		//check for an Aggregate Devices with a subdevice which is Built-in Output
    //		//http://lists.apple.com/archives/coreaudio-api/2009/Oct/msg00182.html
    //
    //
    //	}

    free(outputname);


    return true;
}

/*
OSStatus appIOProc2 (AudioDeviceID inDevice, const AudioTimeStamp* inNow,
                    const AudioBufferList* inInputData,
                    const AudioTimeStamp* inInputTime,
                    AudioBufferList* outOutputData,
                    const AudioTimeStamp* inOutputTime,
                    void* defptr);
OSStatus appIOProc2 (AudioDeviceID inDevice, const AudioTimeStamp* inNow,
                    const AudioBufferList* inInputData,
                    const AudioTimeStamp* inInputTime,
                    AudioBufferList* outOutputData,
                    const AudioTimeStamp* inOutputTime,
                    void* defptr)
{
    SC_CoreAudioDriver* def = (SC_CoreAudioDriver*)defptr;

    int64 oscTime = CoreAudioHostTimeToOSC(inOutputTime->mHostTime);

    AudioTimeStamp readTime;
    readTime.mSampleTime = inNow->mSampleTime - def->SafetyOffset() - def->NumSamplesPerCallback();
    readTime.mFlags = kAudioTimeStampSampleTimeValid;

    AudioDeviceRead(def->InputDevice(), &readTime, def->GetInputBufferList());

    def->Run(def->GetInputBufferList(), outOutputData, oscTime);

    return kAudioHardwareNoError;
}
*/


OSStatus appIOProcSeparateIn(AudioDeviceID device, const AudioTimeStamp* inNow, const AudioBufferList* inInputData,
                             const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData,
                             const AudioTimeStamp* inOutputTime, void* defptr) {
    SC_CoreAudioDriver* def = (SC_CoreAudioDriver*)defptr;

    // copy input data to driver's private buffer list
    int i;
    for (i = 0; i < inInputData->mNumberBuffers; i++) {
        memcpy(def->mInputBufList->mBuffers[i].mData, inInputData->mBuffers[i].mData,
               inInputData->mBuffers[i].mDataByteSize);
    }

    return kAudioHardwareNoError;
}

template <bool IsClipping>
OSStatus appIOProc(AudioDeviceID device, const AudioTimeStamp* inNow, const AudioBufferList* inInputData,
                   const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData,
                   const AudioTimeStamp* inOutputTime, void* defptr) {
    SC_CoreAudioDriver* def = (SC_CoreAudioDriver*)defptr;
    int64 oscTime = CoreAudioHostTimeToOSC(inOutputTime->mHostTime);

    double hostSecs = (double)AudioConvertHostTimeToNanos(inOutputTime->mHostTime) * 1e-9;
    double sampleTime = inOutputTime->mSampleTime;
    if (def->mStartHostSecs == 0) {
        def->mStartHostSecs = hostSecs;
        def->mStartSampleTime = sampleTime;
    } else {
        double instSampleRate = (sampleTime - def->mPrevSampleTime) / (hostSecs - def->mPrevHostSecs);
        double smoothSampleRate = def->mSmoothSampleRate;
        smoothSampleRate = smoothSampleRate + 0.002 * (instSampleRate - smoothSampleRate);
        def->mOSCincrement = (int64)(def->mOSCincrementNumerator / smoothSampleRate);
        def->mSmoothSampleRate = smoothSampleRate;

#    if 0
		double avgSampleRate  = (sampleTime - def->mStartSampleTime)/(hostSecs - def->mStartHostSecs);
		double jitter = (smoothSampleRate * (hostSecs - def->mPrevHostSecs)) - (sampleTime - def->mPrevSampleTime);
		double drift = (smoothSampleRate - def->mSampleRate) * (hostSecs - def->mStartHostSecs);
		//if (fabs(jitter) > 0.01) {
			scprintf("avgSR %.6f   smoothSR %.6f   instSR %.6f   jitter %.6f   drift %.6f   inc %lld\n",
				avgSampleRate, smoothSampleRate, instSampleRate, jitter, drift, def->mOSCincrement);
		//}
#    endif
    }
    def->mPrevHostSecs = hostSecs;
    def->mPrevSampleTime = sampleTime;

    if (!def->UseSeparateIO()) {
        def->Run<IsClipping>(inInputData, outOutputData, oscTime);
        return kAudioHardwareNoError;
    }


    def->Run<IsClipping>(def->mInputBufList, outOutputData, oscTime);
    return kAudioHardwareNoError;
}

template <bool IsClipping>
void SC_CoreAudioDriver::Run(const AudioBufferList* inInputData, AudioBufferList* outOutputData, int64 oscTime) {
    int64 systemTimeBefore = AudioGetCurrentHostTime();
    World* world = mWorld;

    try {
        int numSamplesPerCallback = NumSamplesPerCallback();
        mOSCbuftime = oscTime;

#    ifdef __APPLE__
        // FIXME: aren't denormal flags set per-process?
        sc_SetDenormalFlags();
#    endif

        mFromEngine.Free();
        /*if (mToEngine.HasData()) {
            scprintf("oscTime %.9f %.9f\n", oscTime*kOSCtoSecs,
        CoreAudioHostTimeToOSC(AudioGetCurrentHostTime())*kOSCtoSecs);
        }*/
        mToEngine.Perform();
        mOscPacketsToEngine.Perform();

        int bufFrames = world->mBufLength;
        int numBufs = numSamplesPerCallback / bufFrames;

        int numInputBuses = world->mNumInputs;
        int numOutputBuses = world->mNumOutputs;
        float* inputBuses = world->mAudioBus + world->mNumOutputs * bufFrames;
        float* outputBuses = world->mAudioBus;
        int32* inputTouched = world->mAudioBusTouched + world->mNumOutputs;
        int32* outputTouched = world->mAudioBusTouched;
        int numInputStreams = inInputData ? inInputData->mNumberBuffers : 0;
        int numOutputStreams = outOutputData ? outOutputData->mNumberBuffers : 0;

        // static int go = 0;

        int64 oscInc = mOSCincrement;
        double oscToSamples = mOSCtoSamples;

        int bufFramePos = 0;

        for (int i = 0; i < numBufs; ++i, world->mBufCounter++, bufFramePos += bufFrames) {
            int32 bufCounter = world->mBufCounter;

            // de-interleave input
            if (inInputData) {
                const AudioBuffer* inInputDataBuffers = inInputData->mBuffers;
                for (int s = 0, b = 0; b < numInputBuses && s < numInputStreams; s++) {
                    const AudioBuffer* buf = inInputDataBuffers + s;
                    int nchan = buf->mNumberChannels;
                    if (buf->mData) {
                        float* busdata = inputBuses + b * bufFrames;
                        float* bufdata = (float*)buf->mData + bufFramePos * nchan;
                        if (nchan == 1) {
                            for (int k = 0; k < bufFrames; ++k) {
                                busdata[k] = bufdata[k];
                            }
                            inputTouched[b] = bufCounter;
                        } else {
                            int minchan = sc_min(nchan, numInputBuses - b);
                            for (int j = 0; j < minchan; ++j, busdata += bufFrames) {
                                for (int k = 0, m = j; k < bufFrames; ++k, m += nchan) {
                                    busdata[k] = bufdata[m];
                                }
                                inputTouched[b + j] = bufCounter;
                            }
                        }
                        b += nchan;
                    }
                }
            }
            // count++;

            int64 schedTime;
            int64 nextTime = oscTime + oscInc;

            /*if (mScheduler.Ready(nextTime)) {
                double diff = (mScheduler.NextTime() - mOSCbuftime)*kOSCtoSecs;
                scprintf("rdy %.9f %.9f %.9f\n", (mScheduler.NextTime()-gStartupOSCTime) * kOSCtoSecs,
            (mOSCbuftime-gStartupOSCTime)*kOSCtoSecs, diff);
            }*/

            while ((schedTime = mScheduler.NextTime()) <= nextTime) {
                float diffTime = (float)(schedTime - oscTime) * oscToSamples + 0.5;
                float diffTimeFloor = floor(diffTime);
                world->mSampleOffset = (int)diffTimeFloor;
                world->mSubsampleOffset = diffTime - diffTimeFloor;

                if (world->mSampleOffset < 0)
                    world->mSampleOffset = 0;
                else if (world->mSampleOffset >= world->mBufLength)
                    world->mSampleOffset = world->mBufLength - 1;

                SC_ScheduledEvent event = mScheduler.Remove();
                event.Perform();
            }
            world->mSampleOffset = 0;
            world->mSubsampleOffset = 0.f;

            World_Run(world);

            // interleave output
            AudioBuffer* outOutputDataBuffers = outOutputData->mBuffers;
            for (int s = 0, b = 0; b < numOutputBuses && s < numOutputStreams; s++) {
                AudioBuffer* buf = outOutputDataBuffers + s;
                int nchan = buf->mNumberChannels;
                if (buf->mData) {
                    float* busdata = outputBuses + b * bufFrames;
                    float* bufdata = (float*)buf->mData + bufFramePos * nchan;
                    if (nchan == 1) {
                        if (outputTouched[b] == bufCounter) {
                            for (int k = 0; k < bufFrames; ++k) {
                                if (IsClipping)
                                    bufdata[k] = sc_clip2(busdata[k], mSafetyClipThreshold);
                                else
                                    bufdata[k] = busdata[k];
                            }
                        }
                    } else {
                        int minchan = sc_min(nchan, numOutputBuses - b);
                        for (int j = 0; j < minchan; ++j, busdata += bufFrames) {
                            if (outputTouched[b + j] == bufCounter) {
                                for (int k = 0, m = j; k < bufFrames; ++k, m += nchan) {
                                    if (IsClipping)
                                        bufdata[m] = sc_clip2(busdata[k], mSafetyClipThreshold);
                                    else
                                        bufdata[m] = busdata[k];
                                }
                            }
                        }
                    }
                    b += nchan;
                }
            }
            oscTime = mOSCbuftime = nextTime;
        }
    } catch (std::exception& exc) {
        scprintf("exception in real time: %s\n", exc.what());
    } catch (...) {
        scprintf("unknown exception in real time\n");
    }
    int64 systemTimeAfter = AudioGetCurrentHostTime();
    double calcTime = (double)AudioConvertHostTimeToNanos(systemTimeAfter - systemTimeBefore) * 1e-9;
    double cpuUsage = calcTime * mBuffersPerSecond * 100.;
    mAvgCPU = mAvgCPU + 0.1 * (cpuUsage - mAvgCPU);
    if (cpuUsage > mPeakCPU || --mPeakCounter <= 0) {
        mPeakCPU = cpuUsage;
        mPeakCounter = mMaxPeakCounter;
    }

    mAudioSync.Signal();
}


//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// These are not linked in yet, but we'll need to listen for the properties and stop/restart synthesis
// if sample-rate, format, or device change.

OSStatus hardwareListenerProc(AudioHardwarePropertyID inPropertyID, void* inClientData) {
    OSStatus err = noErr;
    char cStr[255];
    UInt32 outSize;
    Boolean outWritable;
    AudioDeviceID deviceID;

    AudioObjectPropertyAddress propertyAddress;

    propertyAddress.mSelector = kAudioHardwarePropertyDevices;
    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
    propertyAddress.mElement = kAudioObjectPropertyElementMaster;

    switch (inPropertyID) {
    case kAudioHardwarePropertyDefaultOutputDevice:
        scprintf("%s\n", "***** HARDWARE NOTIFICATION - kAudioHardwarePropertyDefaultOutputDevice\r");

        // err =  AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDefaultOutputDevice,  &outSize, &outWritable);

        propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;

        err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &outSize);

        if (err)
            break;

        err = AudioObjectIsPropertySettable(kAudioObjectSystemObject, &propertyAddress, &outWritable);

        if (err)
            break;

        // err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &outSize, &deviceID);

        err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &outSize, &deviceID);

        if (err)
            break;
        //			err =  AudioDeviceGetPropertyInfo(deviceID, 0, false, kAudioDevicePropertyDeviceName,  &outSize,
        //&outWritable);

        propertyAddress.mSelector = kAudioDevicePropertyDeviceName;

        err = AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, NULL, &outSize);

        if (err)
            break;

        err = AudioObjectIsPropertySettable(deviceID, &propertyAddress, &outWritable);

        if (err)
            break;

        // err = AudioDeviceGetProperty(deviceID, 0, false, kAudioDevicePropertyDeviceName, &outSize, cStr);

        err = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &outSize, cStr);

        if (err)
            break;

        // do something

        break;

    case kAudioHardwarePropertyDefaultInputDevice:
        scprintf("%s\n", "***** HARDWARE NOTIFICATION - kAudioHardwarePropertyDefaultInputDevice\r");
        // err =  AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDefaultInputDevice,  &outSize, &outWritable);

        propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;

        err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &outSize);

        if (err)
            break;

        err = AudioObjectIsPropertySettable(kAudioObjectSystemObject, &propertyAddress, &outWritable);

        if (err)
            break;

        // err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &outSize, &deviceID);

        err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &outSize, &deviceID);

        if (err)
            break;

        // err =  AudioDeviceGetPropertyInfo(deviceID, 0, false, kAudioDevicePropertyDeviceName,  &outSize,
        // &outWritable);


        propertyAddress.mSelector = kAudioDevicePropertyDeviceName;

        err = AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, NULL, &outSize);

        if (err)
            break;

        err = AudioObjectIsPropertySettable(deviceID, &propertyAddress, &outWritable);

        if (err)
            break;

        // err = AudioDeviceGetProperty(deviceID, 0, false, kAudioDevicePropertyDeviceName, &outSize, cStr);


        err = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &outSize, cStr);

        if (err)
            break;

        // do something

        break;

    case kAudioHardwarePropertyDefaultSystemOutputDevice:
        scprintf("%s\n", "***** HARDWARE NOTIFICATION - kAudioHardwarePropertyDefaultSystemOutputDevice\r");
        // err =  AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDefaultSystemOutputDevice,  &outSize,
        // &outWritable);

        propertyAddress.mSelector = kAudioHardwarePropertyDefaultSystemOutputDevice;

        err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &outSize);

        if (err)
            break;

        err = AudioObjectIsPropertySettable(kAudioObjectSystemObject, &propertyAddress, &outWritable);

        if (err)
            break;

        // err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultSystemOutputDevice, &outSize, &deviceID);

        err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &outSize, &deviceID);

        if (err)
            break;

        // err =  AudioDeviceGetPropertyInfo(deviceID, 0, false, kAudioDevicePropertyDeviceName,  &outSize,
        // &outWritable);

        propertyAddress.mSelector = kAudioDevicePropertyDeviceName;

        err = AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, NULL, &outSize);

        if (err)
            break;

        err = AudioObjectIsPropertySettable(deviceID, &propertyAddress, &outWritable);

        if (err)
            break;

        // err = AudioDeviceGetProperty(deviceID, 0, false, kAudioDevicePropertyDeviceName, &outSize, cStr);

        err = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &outSize, cStr);

        if (err)
            break;

        // do something

        break;

    case kAudioHardwarePropertyDevices: {
        scprintf("%s\n", "***** HARDWARE NOTIFICATION - kAudioHardwarePropertyDevices\r");
    } break;
    default:
        scprintf("%s\n", "***** HARDWARE NOTIFICATION - %4.4s\r", &inPropertyID);
    }

    fflush(stdout);
    return (noErr);
}

OSStatus AddDeviceListeners(AudioDeviceID inDevice, void* inClientData);

bool SC_CoreAudioDriver::DriverStart() {
    if (mWorld->mVerbosity >= 1) {
        scprintf("->SC_CoreAudioDriver::DriverStart\n");
    }

    auto appIOProcFunc = isClippingEnabled() ? appIOProc<true> : appIOProc<false>;
    OSStatus err = kAudioHardwareNoError;
    // AudioTimeStamp	now;
    UInt32 propertySize;
    Boolean writable;
    AudioObjectPropertyAddress propertyAddress;

    propertyAddress.mSelector = kAudioHardwarePropertyDevices;
    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
    propertyAddress.mElement = kAudioObjectPropertyElementMaster;

    if (mWorld->mVerbosity >= 1) {
        scprintf("start   UseSeparateIO?: %d\n", UseSeparateIO());
    }

    try {
        if (UseSeparateIO()) {
            //		err = AudioDeviceAddIOProc(mOutputDevice, appIOProc, (void *) this);	// setup Out device with an
            // IO proc

            err = AudioDeviceCreateIOProcID(mOutputDevice, appIOProcFunc, (void*)this, &mOutputID);
            if (err != kAudioHardwareNoError) {
                scprintf("AudioDeviceAddIOProc failed %s %d\n", &err, (int)err);
                return false;
            }
            //		err = AudioDeviceAddIOProc(mInputDevice, appIOProcSeparateIn, (void *) this);		// setup In
            // device with an IO proc
            err = AudioDeviceCreateIOProcID(mInputDevice, appIOProcSeparateIn, (void*)this, &mInputID);

            if (err != kAudioHardwareNoError) {
                scprintf("AudioDeviceAddIOProc failed %s %d\n", &err, (int)err);
                return false;
            }


            if (mWorld->hw->mInputStreamsEnabled) {
                propertyAddress.mSelector = kAudioDevicePropertyIOProcStreamUsage;
                propertyAddress.mScope = kAudioDevicePropertyScopeInput;
                propertyAddress.mElement = 0;

                err = AudioObjectGetPropertyDataSize(mInputDevice, &propertyAddress, 0, NULL, &propertySize);
                err = AudioObjectIsPropertySettable(mInputDevice, &propertyAddress, &writable);

                AudioHardwareIOProcStreamUsage* su = (AudioHardwareIOProcStreamUsage*)malloc(propertySize);
                su->mIOProc = (void*)appIOProcSeparateIn;

                err = AudioObjectGetPropertyData(mInputDevice, &propertyAddress, 0, NULL, &propertySize, su);

                int len = std::min(su->mNumberStreams, (UInt32)strlen(mWorld->hw->mInputStreamsEnabled));

                for (int i = 0; i < len; ++i) {
                    su->mStreamIsOn[i] = mWorld->hw->mInputStreamsEnabled[i] == '1';
                }

                err = AudioObjectSetPropertyData(mInputDevice, &propertyAddress, 0, NULL, propertySize, su);
            }

            if (mWorld->hw->mOutputStreamsEnabled) {
                propertyAddress.mSelector = kAudioDevicePropertyIOProcStreamUsage;
                propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
                propertyAddress.mElement = 0;

                err = AudioObjectGetPropertyDataSize(mOutputDevice, &propertyAddress, 0, NULL, &propertySize);
                err = AudioObjectIsPropertySettable(mOutputDevice, &propertyAddress, &writable);

                AudioHardwareIOProcStreamUsage* su = (AudioHardwareIOProcStreamUsage*)malloc(propertySize);
                su->mIOProc = (void*)appIOProcFunc;

                err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &propertySize, su);

                int len = std::min(su->mNumberStreams, (UInt32)strlen(mWorld->hw->mOutputStreamsEnabled));

                for (int i = 0; i < len; ++i) {
                    su->mStreamIsOn[i] = mWorld->hw->mOutputStreamsEnabled[i] == '1';
                }

                err = AudioObjectSetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, propertySize, su);
            }

            err = AudioDeviceStart(mInputDevice, appIOProcSeparateIn); // start playing sound through the device
            if (err != kAudioHardwareNoError) {
                scprintf("AudioDeviceStart failed %d\n", (int)err);
                return false;
            }

            err = AudioDeviceStart(mOutputDevice, appIOProcFunc); // start playing sound through the device
            if (err != kAudioHardwareNoError) {
                scprintf("AudioDeviceStart failed %d\n", (int)err);
                err = AudioDeviceStop(mInputDevice, appIOProcSeparateIn); // stop playing sound through the device
                return false;
            }
        } else {
            // err = AudioDeviceAddIOProc(mOutputDevice, appIOProc, (void *) this);	// setup our device with an IO proc
            err = AudioDeviceCreateIOProcID(mOutputDevice, appIOProcFunc, (void*)this, &mOutputID);

            if (err != kAudioHardwareNoError) {
                scprintf("AudioDeviceAddIOProc failed %d\n", (int)err);
                return false;
            }

            if (mWorld->hw->mInputStreamsEnabled) {
                propertyAddress.mSelector = kAudioDevicePropertyIOProcStreamUsage;
                propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
                propertyAddress.mElement = 0;

                err = AudioObjectGetPropertyDataSize(mOutputDevice, &propertyAddress, 0, NULL, &propertySize);
                err = AudioObjectIsPropertySettable(mOutputDevice, &propertyAddress, &writable);

                AudioHardwareIOProcStreamUsage* su = (AudioHardwareIOProcStreamUsage*)malloc(propertySize);
                su->mIOProc = (void*)appIOProcFunc;

                err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &propertySize, su);

                int len = std::min(su->mNumberStreams, (UInt32)strlen(mWorld->hw->mInputStreamsEnabled));

                for (int i = 0; i < len; ++i) {
                    su->mStreamIsOn[i] = mWorld->hw->mInputStreamsEnabled[i] == '1';
                }

                err = AudioObjectSetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, propertySize, su);
            }

            if (mWorld->hw->mOutputStreamsEnabled) {
                propertyAddress.mSelector = kAudioDevicePropertyIOProcStreamUsage;
                propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
                propertyAddress.mElement = 0;

                err = AudioObjectGetPropertyDataSize(mOutputDevice, &propertyAddress, 0, NULL, &propertySize);
                err = AudioObjectIsPropertySettable(mOutputDevice, &propertyAddress, &writable);

                AudioHardwareIOProcStreamUsage* su = (AudioHardwareIOProcStreamUsage*)malloc(propertySize);
                su->mIOProc = (void*)appIOProcFunc;

                err = AudioObjectGetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, &propertySize, su);

                int len = std::min(su->mNumberStreams, (UInt32)strlen(mWorld->hw->mOutputStreamsEnabled));

                for (int i = 0; i < len; ++i) {
                    su->mStreamIsOn[i] = mWorld->hw->mOutputStreamsEnabled[i] == '1';
                }

                err = AudioObjectSetPropertyData(mOutputDevice, &propertyAddress, 0, NULL, propertySize, su);
            }

            err = AudioDeviceStart(mOutputDevice, appIOProcFunc); // start playing sound through the device
            if (err != kAudioHardwareNoError) {
                scprintf("AudioDeviceStart failed %d\n", (int)err);
                return false;
            }
        }
    } catch (...) {
        scprintf("exception in SC_CoreAudioDriver::DriverStart\n");
    }
    if (mWorld->mVerbosity >= 1) {
        scprintf("<-SC_CoreAudioDriver::DriverStart\n");
    }


    // http://lists.apple.com/archives/coreaudio-api/2010/Aug/msg00114.html
    CFRunLoopRef theRunLoop = NULL;
    AudioObjectPropertyAddress theAddress = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal,
                                              kAudioObjectPropertyElementMaster };
    AudioObjectSetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);

    // for now no spotting of hardware changes, assumption is that ServerOptions inviolate. However, if a device was
    // unplugged, could react to loss of that device by switching to system default? note that the number of listeners
    // is stripped down to only one for now, to react to headphone swaps in the case of Built-in Output
    AddDeviceListeners(mOutputDevice, this);

    return true;
}


bool SC_CoreAudioDriver::StopStart() {
    bool test = DriverStop();

    bool test2 = DriverStart();
    return test && test2;
}


bool SC_CoreAudioDriver::DriverStop() {
    if (mWorld->mVerbosity >= 1) {
        scprintf("->SC_CoreAudioDriver::DriverStop\n");
    }
    OSStatus err = kAudioHardwareNoError;

    auto appIOProcFunc = isClippingEnabled() ? appIOProc<true> : appIOProc<false>;

    if (UseSeparateIO()) {
        err = AudioDeviceStop(mOutputDevice, appIOProcFunc);
        if (err != kAudioHardwareNoError) {
            scprintf("Output AudioDeviceStop failed %p\n", err);
            return false;
        }

        err = AudioDeviceDestroyIOProcID(mOutputDevice, mOutputID);
        if (err != kAudioHardwareNoError) {
            scprintf("Output AudioDeviceDestroyIOProcID failed %p\n", err);
            return false;
        }

        err = AudioDeviceStop(mInputDevice, appIOProcSeparateIn);
        if (err != kAudioHardwareNoError) {
            scprintf("Input AudioDeviceStop failed %p\n", err);
            return false;
        }

        err = AudioDeviceDestroyIOProcID(mInputDevice, mInputID);
        if (err != kAudioHardwareNoError) {
            scprintf("Input AudioDeviceDestroyIOProcID failed %p\n", err);
            return false;
        }
    } else {
        err = AudioDeviceStop(mOutputDevice, appIOProcFunc);
        if (err != kAudioHardwareNoError) {
            scprintf("AudioDeviceStop B failed %p\n", err);
            return false;
        }

        err = AudioDeviceDestroyIOProcID(mOutputDevice, mOutputID);
        if (err != kAudioHardwareNoError) {
            scprintf("AudioDeviceDestroyIOProcID B failed %p\n", err);
            return false;
        }
    }
    if (mWorld->mVerbosity >= 1) {
        scprintf("<-SC_CoreAudioDriver::DriverStop\n");
    }
    return true;
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Listen for Device Properties and update interface and globals
OSStatus deviceListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses,
                            const AudioObjectPropertyAddress inAddresses[], void* inClientData) {
    OSStatus err = noErr;

    SC_CoreAudioDriver* coredriver = (SC_CoreAudioDriver*)inClientData;

    for (int i = 0; i < inNumberAddresses; i++) {
        AudioObjectPropertyAddress propAddress = inAddresses[i];
        if (inObjectID == kAudioObjectSystemObject
            && propAddress.mSelector == kAudioHardwarePropertyDefaultOutputDevice) {
            coredriver->StopStart();
        }
    }

    return (err);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OSStatus AddDeviceListeners(AudioDeviceID inDevice, void* inClientData) {
    OSStatus err = noErr;
    AudioObjectPropertyAddress propertyAddress;

    // ONLY REACTING TO HEADPHONE SWAPS FOR NOW - see version control history for removed dead code
    propertyAddress.mSelector = kAudioHardwarePropertyDevices;
    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
    propertyAddress.mElement = kAudioObjectPropertyElementMaster;

    err = AudioObjectAddPropertyListener(inDevice, &propertyAddress, deviceListenerProc, inClientData);
    if (err)
        return err;

    return (err);
}

#endif // SC_AUDIO_API_COREAUDIO


// =====================================================================
// Audio driver (CoreAudioIPHONE)

#if SC_AUDIO_API == SC_AUDIO_API_COREAUDIOIPHONE
SC_iCoreAudioDriver::SC_iCoreAudioDriver(struct World* inWorld): SC_AudioDriver(inWorld) { receivedIn = 0; }

SC_iCoreAudioDriver::~SC_iCoreAudioDriver() {}

/*
OSStatus appIOProc2 (AudioDeviceID inDevice, const AudioTimeStamp* inNow,
                    const AudioBufferList* inInputData,
                    const AudioTimeStamp* inInputTime,
                    AudioBufferList* outOutputData,
                    const AudioTimeStamp* inOutputTime,
                    void* defptr);
OSStatus appIOProc2 (AudioDeviceID inDevice, const AudioTimeStamp* inNow,
                    const AudioBufferList* inInputData,
                    const AudioTimeStamp* inInputTime,
                    AudioBufferList* outOutputData,
                    const AudioTimeStamp* inOutputTime,
                    void* defptr)
{
    SC_CoreAudioDriver* def = (SC_CoreAudioDriver*)defptr;

    int64 oscTime = CoreAudioHostTimeToOSC(inOutputTime->mHostTime);

    AudioTimeStamp readTime;
    readTime.mSampleTime = inNow->mSampleTime - def->SafetyOffset() - def->NumSamplesPerCallback();
    readTime.mFlags = kAudioTimeStampSampleTimeValid;

    AudioDeviceRead(def->InputDevice(), &readTime, def->GetInputBufferList());

    def->Run(def->GetInputBufferList(), outOutputData, oscTime);

    return kAudioHardwareNoError;
}
*/


OSStatus InputCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp,
                       UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) {
    SC_iCoreAudioDriver* driver = (SC_iCoreAudioDriver*)inRefCon;

    if (driver->receivedIn) {
        // printf("exit input with no data \n");
        return noErr;
    }

    OSStatus ret =
        AudioUnitRender(driver->inputUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, driver->buflist);
    if (ret) {
        // printf("no input !\n");
        return noErr;
    }
    driver->receivedIn = 1;

    return noErr;
}

OSStatus RenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp,
                        UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) {
    SC_iCoreAudioDriver* driver = (SC_iCoreAudioDriver*)inRefCon;

#    ifndef SC_IPHONE
    if (!driver->receivedIn) {
        // printf("exit output with no data \n");
        return noErr;
    }
#    endif

    // float *fbuffer = (float *) driver->converter_buffer;

    int i;
    for (i = 0; i < inNumberFrames; i++) {
        signed short s = ((signed short*)driver->buflist->mBuffers[0].mData)[i];
        int k = s + 0x43c00000;
        float f = *((float*)&k);
        ((float*)driver->floatInputList->mBuffers[0].mData)[i] = f - 384.0f;
    }

    int64 oscTime = GetCurrentOSCTime();
    // driver->Run(driver->floatInputList, driver->floatOutputList, oscTime);
    driver->Run(driver->floatInputList, ioData, oscTime);

    for (i = 0; i < inNumberFrames; i++) {
        float f1 = ((float*)ioData->mBuffers[0].mData)[i];
        float f2 = ((float*)ioData->mBuffers[1].mData)[i];
        ((int*)ioData->mBuffers[0].mData)[i] = (int)(f1 * 16777216);
        ((int*)ioData->mBuffers[1].mData)[i] = (int)(f2 * 16777216);
    }


    /*
        unsigned long size = 2*1024*sizeof(unsigned long);
        OSStatus ret = AudioConverterConvertBuffer(driver->converter_in_to_F32,
       driver->buflist->mBuffers[0].mDataByteSize, driver->buflist->mBuffers[0].mData, &size, driver->converter_buffer);
        if (ret)
        {
            printf("couldn't convert !\n");
            return noErr;
        }

        int i;
        for (i=0; i<inNumberFrames; i++)
        {
            ((int *)ioData->mBuffers[0].mData)[i] = ((int *)ioData->mBuffers[1].mData)[i] =
       driver->converter_buffer[2*i];
        }
    */
    driver->receivedIn = 0;
    return noErr;
}


void SC_iCoreAudioDriver::Run(const AudioBufferList* inInputData, AudioBufferList* outOutputData, int64 oscTime) {
    int64 systemTimeBefore = GetMicroseconds();

    World* world = mWorld;

    try {
        int numSamplesPerCallback = NumSamplesPerCallback();
        mOSCbuftime = oscTime;

#    ifdef __APPLE__
        // FIXME: does iOS support x86?
        sc_SetDenormalFlags();
#    endif

        mFromEngine.Free();

        mToEngine.Perform();
        mOscPacketsToEngine.Perform();
        // printf("mOscPacketsToEngine : %d micros\n", (int) (GetMicroseconds()-systemTimeBefore));

        int bufFrames = world->mBufLength;
        int numBufs = numSamplesPerCallback / bufFrames;

        int numInputBuses = world->mNumInputs;
        int numOutputBuses = world->mNumOutputs;
        float* inputBuses = world->mAudioBus + world->mNumOutputs * bufFrames;
        float* outputBuses = world->mAudioBus;
        int32* inputTouched = world->mAudioBusTouched + world->mNumOutputs;
        int32* outputTouched = world->mAudioBusTouched;
        int numInputStreams = inInputData ? inInputData->mNumberBuffers : 0;
        int numOutputStreams = outOutputData ? outOutputData->mNumberBuffers : 0;

        // static int go = 0;

        int64 oscInc = mOSCincrement;
        double oscToSamples = mOSCtoSamples;

        int bufFramePos = 0;

        for (int i = 0; i < numBufs; ++i, world->mBufCounter++, bufFramePos += bufFrames) {
            int32 bufCounter = world->mBufCounter;

            // de-interleave input
            if (inInputData) {
                const AudioBuffer* inInputDataBuffers = inInputData->mBuffers;
                for (int s = 0, b = 0; b < numInputBuses && s < numInputStreams; s++) {
                    const AudioBuffer* buf = inInputDataBuffers + s;
                    int nchan = buf->mNumberChannels;
                    if (buf->mData) {
                        float* busdata = inputBuses + b * bufFrames;
                        float* bufdata = (float*)buf->mData + bufFramePos * nchan;
                        if (nchan == 1) {
#    ifdef IPHONE_VEC
                            vcopy(busdata, bufdata, bufFrames);
#    else
                            for (int k = 0; k < bufFrames; ++k) {
                                busdata[k] = bufdata[k];
                            }
#    endif
                            inputTouched[b] = bufCounter;
                        } else {
                            int minchan = sc_min(nchan, numInputBuses - b);
                            for (int j = 0; j < minchan; ++j, busdata += bufFrames) {
                                for (int k = 0, m = j; k < bufFrames; ++k, m += nchan) {
                                    busdata[k] = bufdata[m];
                                }
                                inputTouched[b + j] = bufCounter;
                            }
                        }
                        b += nchan;
                    }
                }
            }
            // count++;

            int64 schedTime;
            int64 nextTime = oscTime + oscInc;

            while ((schedTime = mScheduler.NextTime()) <= nextTime) {
                float diffTime = (float)(schedTime - oscTime) * oscToSamples + 0.5;
                float diffTimeFloor = floor(diffTime);
                world->mSampleOffset = (int)diffTimeFloor;
                world->mSubsampleOffset = diffTime - diffTimeFloor;

                if (world->mSampleOffset < 0)
                    world->mSampleOffset = 0;
                else if (world->mSampleOffset >= world->mBufLength)
                    world->mSampleOffset = world->mBufLength - 1;

                SC_ScheduledEvent event = mScheduler.Remove();
                event.Perform();
            }
            world->mSampleOffset = 0;
            world->mSubsampleOffset = 0.f;

            // int64 now = GetMicroseconds();

            World_Run(world);

            // printf("world run : %fms\n", (float) (GetMicroseconds()-now)/1000);


            // interleave output
            AudioBuffer* outOutputDataBuffers = outOutputData->mBuffers;
            for (int s = 0, b = 0; b < numOutputBuses && s < numOutputStreams; s++) {
                AudioBuffer* buf = outOutputDataBuffers + s;
                int nchan = buf->mNumberChannels;
                if (buf->mData) {
                    float* busdata = outputBuses + b * bufFrames;
                    float* bufdata = (float*)buf->mData + bufFramePos * nchan;
                    if (nchan == 1) {
                        if (outputTouched[b] == bufCounter) {
#    ifdef IPHONE_VEC
                            vcopy(bufdata, busdata, bufFrames);
#    else
                            for (int k = 0; k < bufFrames; ++k) {
                                bufdata[k] = busdata[k];
                            }
#    endif
                        }
                    } else {
                        int minchan = sc_min(nchan, numOutputBuses - b);
                        for (int j = 0; j < minchan; ++j, busdata += bufFrames) {
                            if (outputTouched[b + j] == bufCounter) {
                                for (int k = 0, m = j; k < bufFrames; ++k, m += nchan) {
                                    bufdata[m] = busdata[k];
                                }
                            }
                        }
                    }
                    b += nchan;
                }
            }
            oscTime = mOSCbuftime = nextTime;
        }
    } catch (std::exception& exc) {
        scprintf("exception in real time: %s\n", exc.what());
    } catch (...) {
        scprintf("unknown exception in real time\n");
    }

    int64 systemTimeAfter = GetMicroseconds();
    double calcTime = (double)(systemTimeAfter - systemTimeBefore) * 1e-6;
    double cpuUsage = calcTime * mBuffersPerSecond * 100.;
    mAvgCPU = mAvgCPU + 0.1 * (cpuUsage - mAvgCPU);
    if (cpuUsage > mPeakCPU || --mPeakCounter <= 0) {
        mPeakCPU = cpuUsage;
        mPeakCounter = mMaxPeakCounter;
    }

    mAudioSync.Signal();
}


/*
OSStatus appIOProc (AudioDeviceID device, const AudioTimeStamp* inNow,
                    const AudioBufferList* inInputData,
                    const AudioTimeStamp* inInputTime,
                    AudioBufferList* outOutputData,
                    const AudioTimeStamp* inOutputTime,
                    void* defptr)
{
    SC_CoreAudioDriver* def = (SC_CoreAudioDriver*)defptr;
    int64 oscTime = CoreAudioHostTimeToOSC(inOutputTime->mHostTime);

    double hostSecs = (double)AudioConvertHostTimeToNanos(inOutputTime->mHostTime) * 1e-9;
    double sampleTime = inOutputTime->mSampleTime;
    if (def->mStartHostSecs == 0) {
        def->mStartHostSecs = hostSecs;
        def->mStartSampleTime = sampleTime;
    } else {
        double instSampleRate = (sampleTime -  def->mPrevSampleTime)/(hostSecs -  def->mPrevHostSecs);
        double smoothSampleRate = def->mSmoothSampleRate;
        smoothSampleRate = smoothSampleRate + 0.002 * (instSampleRate - smoothSampleRate);
        def->mOSCincrement = (int64)(def->mOSCincrementNumerator / smoothSampleRate);
        def->mSmoothSampleRate = smoothSampleRate;

#if 0
        double avgSampleRate  = (sampleTime - def->mStartSampleTime)/(hostSecs - def->mStartHostSecs);
        double jitter = (smoothSampleRate * (hostSecs - def->mPrevHostSecs)) - (sampleTime - def->mPrevSampleTime);
        double drift = (smoothSampleRate - def->mSampleRate) * (hostSecs - def->mStartHostSecs);
        //if (fabs(jitter) > 0.01) {
            scprintf("avgSR %.6f   smoothSR %.6f   instSR %.6f   jitter %.6f   drift %.6f   inc %lld\n",
                avgSampleRate, smoothSampleRate, instSampleRate, jitter, drift, def->mOSCincrement);
        //}
#endif
    }
    def->mPrevHostSecs = hostSecs;
    def->mPrevSampleTime = sampleTime;

    if (!def->UseSeparateIO())
    {
        def->Run(inInputData, outOutputData, oscTime);
        return kAudioHardwareNoError;
    }


    def->Run(lastInputData, outOutputData, oscTime);
    lastInputData = 0;

    return kAudioHardwareNoError;
}
*/


void AudioSessionInterruptionCbk(void* inClientData, UInt32 inInterruptionState) {}


bool SC_iCoreAudioDriver::DriverSetup(int* outNumSamplesPerCallback, double* outSampleRate) {
    AudioSessionInitialize(0, 0, AudioSessionInterruptionCbk, 0);
    unsigned long category = kAudioSessionCategory_PlayAndRecord;
#    ifdef SC_IPHONE
    UInt32 micInput, micInputSize = sizeof(&micInput);
    AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &micInputSize, &micInput);
    if (!micInput) {
        category = kAudioSessionCategory_MediaPlayback;
        scprintf("SC_IPHONE: WARNING - no audio input available\n");
    }
#    endif
    AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);

    if (mPreferredHardwareBufferFrameSize) {
        Float32 preferredBufferSize = (float)mPreferredHardwareBufferFrameSize / 44100.f;
        AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(preferredBufferSize),
                                &preferredBufferSize);
    }

    AudioSessionSetActive(true);

    float actualBufferDuration;
    UInt32 size = sizeof(actualBufferDuration);
    AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration, &size, &actualBufferDuration);

    *outNumSamplesPerCallback = (int)(actualBufferDuration * 44100.f + 0.5f);
    *outSampleRate = 44100;

    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;

    NewAUGraph(&graph);
    AUNode node;
    AudioUnit unit;
    OSStatus ret = AUGraphAddNode(graph, &desc, &node);
    // printf("node : %d\n", node);
    AUGraphOpen(graph);

    ret = AUGraphNodeInfo(graph, node, &desc, &unit);
    // printf("%d\n", unit);


    AudioComponent remoteIOComp = AudioComponentFindNext(0, &desc);
    if (AudioComponentInstanceNew(remoteIOComp, &inputUnit) != noErr) {
        // printf("error instantiating RemoteIO\n");
        return false;
    }
    // printf("instantiated : %d\n", inputUnit);

    int enableIO = 1;
    ret = AudioUnitSetProperty(inputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enableIO,
                               sizeof(enableIO));
    if (ret != noErr) {
        // printf("can't set input : %d\n", ret);
        return false;
    }
    enableIO = 0;
    ret = AudioUnitSetProperty(inputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &enableIO,
                               sizeof(enableIO));
    if (ret != noErr) {
        // printf("can't set output : %d\n", ret);
        return false;
    }

    AudioUnitInitialize(inputUnit);

    unsigned long bufferSizeBytes = 1024 * sizeof(unsigned long);
    buflist = (AudioBufferList*)malloc(sizeof(AudioBufferList));
    buflist->mNumberBuffers = 1;
    buflist->mBuffers[0].mDataByteSize = bufferSizeBytes;
    buflist->mBuffers[0].mData = malloc(bufferSizeBytes);
    buflist->mBuffers[0].mNumberChannels = 1;

    floatInputList = (AudioBufferList*)malloc(sizeof(AudioBufferList));
    floatInputList->mNumberBuffers = 1;
    floatInputList->mBuffers[0].mDataByteSize = bufferSizeBytes;
    floatInputList->mBuffers[0].mData = malloc(bufferSizeBytes);
    floatInputList->mBuffers[0].mNumberChannels = 1;

    /*
        floatOutputList = (AudioBufferList *) malloc(sizeof(AudioBufferList)+sizeof(AudioBuffer));
        floatOutputList->mNumberBuffers = 2;
        floatOutputList->mBuffers[0].mDataByteSize = bufferSizeBytes;
        floatOutputList->mBuffers[0].mData = malloc(bufferSizeBytes);
        floatOutputList->mBuffers[0].mNumberChannels = 1;
        floatOutputList->mBuffers[1].mDataByteSize = bufferSizeBytes;
        floatOutputList->mBuffers[1].mData = malloc(bufferSizeBytes);
        floatOutputList->mBuffers[1].mNumberChannels = 1;
    */

    AURenderCallbackStruct inputStruct;
    inputStruct.inputProc = InputCallback;
    inputStruct.inputProcRefCon = this;
    ret = AudioUnitSetProperty(inputUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Input, 0,
                               &inputStruct, sizeof(inputStruct));

    AURenderCallbackStruct renderStruct;
    renderStruct.inputProc = RenderCallback;
    renderStruct.inputProcRefCon = this;
    ret = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderStruct,
                               sizeof(renderStruct));

    AudioStreamBasicDescription streamFormat;
    streamFormat.mFormatID = kAudioFormatLinearPCM;
    streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian
        | kLinearPCMFormatFlagIsNonInterleaved | (24 << kLinearPCMFormatFlagsSampleFractionShift);
    streamFormat.mSampleRate = 44100;
    streamFormat.mBitsPerChannel = 32;
    streamFormat.mChannelsPerFrame = 2;
    streamFormat.mFramesPerPacket = 1;
    streamFormat.mBytesPerFrame = (streamFormat.mBitsPerChannel / 8);
    streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket;

    ret = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat,
                               sizeof(streamFormat));


    AudioStreamBasicDescription audioFormat;
    audioFormat.mSampleRate = 44100.00;
    audioFormat.mFormatID = kAudioFormatLinearPCM;
    audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    audioFormat.mFramesPerPacket = 1;
    audioFormat.mChannelsPerFrame = 1;
    audioFormat.mBitsPerChannel = 16;
    audioFormat.mBytesPerPacket = 2;
    audioFormat.mBytesPerFrame = 2;
    ret = AudioUnitSetProperty(inputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &audioFormat,
                               sizeof(audioFormat));


    /*
        AudioStreamBasicDescription d;
        size = sizeof(d);
        ret = AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &d, &size);

        AudioStreamBasicDescription inputFormat;
        AudioStreamBasicDescription outputFormat;
        AudioStreamBasicDescription f32Format;

        size = sizeof(AudioStreamBasicDescription);
        ret = AudioUnitGetProperty(inputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inputFormat,
       &size); ret = AudioUnitGetProperty(inputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1,
       &inputFormat, &size); ret = AudioUnitGetProperty(inputUnit, kAudioUnitProperty_StreamFormat,
       kAudioUnitScope_Output, 0, &outputFormat, &size); ret = AudioUnitGetProperty(inputUnit,
       kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &outputFormat, &size);

        outputFormat.mChannelsPerFrame = 1;

        f32Format.mSampleRate = inputFormat.mSampleRate;
        f32Format.mFormatID = inputFormat.mFormatID;
        f32Format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
        f32Format.mBytesPerPacket = 4;
        f32Format.mFramesPerPacket = 1;
        f32Format.mBytesPerFrame = 4;
        f32Format.mChannelsPerFrame = 1;
        f32Format.mBitsPerChannel = 32;
        f32Format.mReserved = 0;

        ret = AudioConverterNew(&inputFormat, &f32Format, &converter_in_to_F32);
        ret = AudioConverterNew(&f32Format, &outputFormat, &converter_F32_to_out);
        ret = AudioConverterNew(&inputFormat, &outputFormat, &converter_in_to_out);
    */
    AUGraphInitialize(graph);

    if (mWorld->mVerbosity >= 0) {
        scprintf("<-SC_CoreAudioDriver::Setup world %p\n", mWorld);
    }
    return true;
}

bool SC_iCoreAudioDriver::DriverStart() {
    if (mWorld->mVerbosity >= 0) {
        scprintf("->SC_CoreAudioDriver::DriverStart\n");
    }

    try {
        OSStatus ret = AUGraphStart(graph);
        AudioOutputUnitStart(inputUnit);
    } catch (...) {
        scprintf("exception in SC_CoreAudioDriver::DriverStart\n");
    }
    if (mWorld->mVerbosity >= 0) {
        scprintf("<-SC_CoreAudioDriver::DriverStart\n");
    }
    return true;
}

bool SC_iCoreAudioDriver::DriverStop() {
    if (mWorld->mVerbosity >= 0) {
        scprintf("->SC_CoreAudioDriver::DriverStop\n");
    }

    AUGraphStop(graph);
    AudioOutputUnitStop(inputUnit);

    if (mWorld->mVerbosity >= 0) {
        scprintf("<-SC_CoreAudioDriver::DriverStop\n");
    }
    return true;
}

#endif // SC_AUDIO_API_COREAUDIOIPHONE
