/* -*- c++ -*- */
/*
 * Copyright 2006-2011,2013-2014 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "../audio_registry.h"
#include "osx_sink.h"

#include <gnuradio/io_signature.h>
#include <gnuradio/logger.h>
#include <gnuradio/prefs.h>
#include <stdexcept>

namespace gr {
namespace audio {

sink::sptr
osx_sink_fcn(int sampling_rate, const std::string& device_name, bool ok_to_block)
{
    return sink::sptr(new osx_sink(sampling_rate, device_name, ok_to_block));
}

static std::string default_device_name()
{
    return prefs::singleton()->get_string(
        "audio_osx", "default_output_device", "built-in");
}

osx_sink::osx_sink(int sample_rate, const std::string& device_name, bool ok_to_block)
    : sync_block(
          "audio_osx_sink", io_signature::make(0, 0, 0), io_signature::make(0, 0, 0)),
      d_input_sample_rate(0.0),
      d_n_user_channels(0),
      d_n_dev_channels(0),
      d_queue_sample_count(0),
      d_buffer_size_samples(0),
      d_ok_to_block(ok_to_block),
      d_do_reset(false),
      d_hardware_changed(false),
      d_using_default_device(false),
      d_waiting_for_data(false),
      d_desired_name(device_name.empty() ? default_device_name() : device_name),
      d_output_au(0),
      d_output_ad_id(0)
{
    if (sample_rate <= 0) {
        d_logger->error("Invalid Sample Rate: {:d}", sample_rate);
        throw std::invalid_argument("audio_osx_sink");
    } else {
        d_input_sample_rate = (Float64)sample_rate;
    }

    // set up for audio output using the stored desired parameters

    setup();
}

void osx_sink::setup()
{
    OSStatus err = noErr;

    // set the default output audio device id to "unknown"

    d_output_ad_id = kAudioDeviceUnknown;

    // try to find the output audio device, if specified

    std::vector<AudioDeviceID> all_ad_ids;
    std::vector<std::string> all_names;

    osx::find_audio_devices(d_desired_name, false, &all_ad_ids, &all_names);

    // check number of device(s) returned

    if (d_desired_name.length() != 0) {
        if (all_ad_ids.size() == 1) {

            // exactly 1 match was found; see if it was partial

            if (all_names[0].compare(d_desired_name) != 0) {

                // yes: log the full device name
                d_debug_logger->info("Using output audio device '{:s}'.", all_names[0]);
            }

            // store info on this device

            d_output_ad_id = all_ad_ids[0];
            d_selected_name = all_names[0];

        } else {

            // either 0 or more than 1 device was found; get all output
            // device names, print those, and error out.

            osx::find_audio_devices("", false, NULL, &all_names);

            std::string err_str("A unique output audio device name "
                                "matching the string '");
            err_str += d_desired_name;
            err_str += "' was not found.";
            err_str += "The current known output audio device name";
            err_str += ((all_names.size() > 1) ? "s are" : " is");
            err_str += ": ";
            for (UInt32 nn = 0; nn < all_names.size(); ++nn) {
                err_str += all_names[nn];
                if (nn != all_names.size() - 1) {
                    err_str += ", ";
                }
            }
            d_logger->error(err_str);
            throw std::runtime_error("audio_osx_sink::setup");
        }
    }

    // if no output audio device id was found, use the default
    // output audio device as set in System Preferences.

    if (d_output_ad_id == kAudioDeviceUnknown) {

        UInt32 size = sizeof(AudioDeviceID);
        AudioObjectPropertyAddress ao_address = {
            kAudioHardwarePropertyDefaultOutputDevice,
            kAudioObjectPropertyScopeGlobal,
            kAudioObjectPropertyElementMaster
        };

        err = AudioObjectGetPropertyData(
            kAudioObjectSystemObject, &ao_address, 0, NULL, &size, &d_output_ad_id);
        check_error_and_throw(err,
                              "Getting the default output audio device ID failed",
                              "audio_osx_sink::setup");

        {
            // retrieve the device name; max name length is 64 characters.

            UInt32 prop_size = 65;
            char c_name_buf[prop_size];
            bzero((void*)c_name_buf, prop_size);
            --prop_size;

            AudioObjectPropertyAddress ao_address = { kAudioDevicePropertyDeviceName,
                                                      kAudioDevicePropertyScopeOutput,
                                                      0 };

            if ((err = AudioObjectGetPropertyData(d_output_ad_id,
                                                  &ao_address,
                                                  0,
                                                  NULL,
                                                  &prop_size,
                                                  (void*)c_name_buf)) != noErr) {

                check_error(err, "Unable to retrieve output audio device name");

            } else {

                d_debug_logger->info(
                    "Using current default output audio device '{:s}'. Changing the "
                    "default audio device in the System Preferences will result in "
                    "changing it here, too (with an internal reconfiguration).",
                    std::string(c_name_buf));
            }

            d_selected_name = c_name_buf;
        }

        d_using_default_device = true;
    }

    // retrieve the total number of channels for the selected audio
    // output device

    osx::get_num_channels_for_audio_device_id(d_output_ad_id, NULL, &d_n_dev_channels);

    // set the block input signature, if not already set
    // (d_n_user_channels is set in check_topology, which is called
    // before the flow-graph is running)

    if (d_n_user_channels == 0) {
        set_input_signature(io_signature::make(1, d_n_dev_channels, sizeof(float)));
    }

    // set the interim buffer size; to work with the GR scheduler,
    // must be at least 16kB.  Pick 50 kS since that's plenty yet
    // not very much.

    d_buffer_size_samples =
        (d_input_sample_rate < 50000.0 ? 50000 : (UInt32)d_input_sample_rate);

#if _OSX_AU_DEBUG_
    d_debug_logger->info("{:p} : audio_osx_sink: max # samples = {:d}",
                         (void*)pthread_self(),
                         d_buffer_size_samples);
#endif

    // create the default AudioUnit for output:

    // Open the default output unit

    AudioComponentDescription desc;

    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_DefaultOutput;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;

    AudioComponent comp = AudioComponentFindNext(NULL, &desc);
    if (!comp) {
        d_logger->fatal("AudioComponentFindNext Failed");
        throw std::runtime_error("audio_osx_sink::setup");
    }
    err = AudioComponentInstanceNew(comp, &d_output_au);
    check_error_and_throw(
        err, "AudioComponentInstanceNew Failed", "audio_osx_sink::setup");

    // set the selected device ID as the current output device

    err = AudioUnitSetProperty(d_output_au,
                               kAudioOutputUnitProperty_CurrentDevice,
                               kAudioUnitScope_Global,
                               0,
                               &d_output_ad_id,
                               sizeof(d_output_ad_id));
    check_error_and_throw(
        err, "Setting selected output device as current failed", "audio_osx_sink::setup");

    // Set up a callback function to generate output to the output unit

    AURenderCallbackStruct au_callback = { reinterpret_cast<AURenderCallback>(
                                               &osx_sink::au_output_callback),
                                           reinterpret_cast<void*>(this) };
    UInt32 prop_size = (UInt32)sizeof(au_callback);

    err = AudioUnitSetProperty(d_output_au,
                               kAudioUnitProperty_SetRenderCallback,
                               kAudioUnitScope_Input,
                               0,
                               &au_callback,
                               prop_size);
    check_error_and_throw(err, "Set Render Callback", "audio_osx_sink::setup");

    // create the stream format for the output unit, so that it
    // handles any format conversions.  Set number of channels in
    // ::start, once the actual number of channels is known.

    memset((void*)(&d_stream_format), 0, sizeof(d_stream_format));
    d_stream_format.mSampleRate = d_input_sample_rate;
    d_stream_format.mFormatID = kAudioFormatLinearPCM;
    d_stream_format.mFormatFlags =
        (kLinearPCMFormatFlagIsFloat | GR_PCM_ENDIANNESS | kLinearPCMFormatFlagIsPacked |
         kAudioFormatFlagIsNonInterleaved);
    d_stream_format.mBytesPerPacket = sizeof(float);
    d_stream_format.mFramesPerPacket = 1;
    d_stream_format.mBytesPerFrame = sizeof(float);
    d_stream_format.mBitsPerChannel = 8 * sizeof(float);

    // set the render quality to maximum

    UInt32 render_quality = kRenderQuality_Max;
    prop_size = (UInt32)sizeof(render_quality);
    err = AudioUnitSetProperty(d_output_au,
                               kAudioUnitProperty_RenderQuality,
                               kAudioUnitScope_Global,
                               0,
                               &render_quality,
                               prop_size);
    check_error(err, "Setting render quality failed");

    // clear the RunLoop (whatever that is); needed, for some
    // reason, before a listener will work.

    {
        CFRunLoopRef the_run_loop = NULL;
        AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop,
                                                kAudioObjectPropertyScopeGlobal,
                                                kAudioObjectPropertyElementMaster };
        prop_size = (UInt32)sizeof(the_run_loop);
        err = AudioObjectSetPropertyData(
            kAudioObjectSystemObject, &property, 0, NULL, prop_size, &the_run_loop);
        check_error(err,
                    "Clearing RunLoop failed; "
                    "Audio Output Device Listener might not work.");
    }

    // set up listeners

    {

        // set up a listener if hardware changes (at all)

        AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices,
                                                kAudioObjectPropertyScopeGlobal,
                                                kAudioObjectPropertyElementMaster };

        err = AudioObjectAddPropertyListener(
            kAudioObjectSystemObject,
            &property,
            reinterpret_cast<AudioObjectPropertyListenerProc>(
                &osx_sink::hardware_listener),
            reinterpret_cast<void*>(this));
        check_error(err, "Adding Audio Hardware Listener failed");
    }

    if (d_using_default_device) {

        // set up a listener for the default output device so that if
        // the device changes, this routine will be called and we can
        // internally handle this change (if/as necessary)

        {
            AudioObjectPropertyAddress property = {
                kAudioHardwarePropertyDefaultOutputDevice,
                kAudioObjectPropertyScopeGlobal,
                kAudioObjectPropertyElementMaster
            };
            err = AudioObjectAddPropertyListener(
                kAudioObjectSystemObject,
                &property,
                reinterpret_cast<AudioObjectPropertyListenerProc>(
                    &osx_sink::hardware_listener),
                reinterpret_cast<void*>(this));
            check_error(err, "Adding Default Output Audio Listener failed");
        }
    }

    // initialize the AU for output, so that it is ready to be used

    err = AudioUnitInitialize(d_output_au);
    check_error_and_throw(err, "AudioUnit Initialize Failed", "audio_osx_sink::setup");


#if _OSX_AU_DEBUG_
    debug_logger->info("{:p} : audio_osx_sink Parameters:  Sample Rate is {:g}"
                       "  Max # samples to store per channel is {:d}",
                       (void*)pthread_self(),
                       d_input_sample_rate,
                       d_buffer_size_samples);
#endif
}

void osx_sink::teardown()
{
#if _OSX_AU_DEBUG_
    d_debug_logger->info("{:p} : starting", (void*)pthread_self());
#endif

    OSStatus err = noErr;

    // stop the AudioUnit

    stop();

    if (d_using_default_device) {
        // remove the listener

        OSStatus err = noErr;
        AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultOutputDevice,
                                                kAudioObjectPropertyScopeGlobal,
                                                kAudioObjectPropertyElementMaster };
        err = AudioObjectRemovePropertyListener(
            kAudioObjectSystemObject,
            &property,
            reinterpret_cast<AudioObjectPropertyListenerProc>(
                &osx_sink::hardware_listener),
            reinterpret_cast<void*>(this));
#if _OSX_AU_DEBUG_
        check_error(err,
                    "teardown: AudioObjectRemovePropertyListener "
                    "hardware failed");
#endif
    }

    // uninitialize the AudioUnit

    err = AudioUnitUninitialize(d_output_au);
#if _OSX_AU_DEBUG_
    check_error(err, "teardown: AudioUnitUninitialize failed");
#endif

    // dispose / close the AudioUnit

    err = AudioComponentInstanceDispose(d_output_au);
#if _OSX_AU_DEBUG_
    check_error(err, "teardown: AudioComponentInstanceDispose failed");
#endif

    // delete buffers

    for (UInt32 nn = 0; nn < d_buffers.size(); ++nn) {
        delete d_buffers[nn];
        d_buffers[nn] = 0;
    }
    d_buffers.resize(0);

    // clear important variables; not # user channels

    d_n_dev_channels = d_n_buffer_channels = d_queue_sample_count =
        d_buffer_size_samples = 0;
    d_using_default_device = false;
    d_output_au = 0;
    d_output_ad_id = 0;

#if _OSX_AU_DEBUG_
    d_debug_logger->info("{:p} : audio_osx_sink::teardown: finished",
                         (void*)pthread_self());
#endif
}

bool osx_sink::is_running()
{
    UInt32 au_running = 0;

    if (d_output_au) {

        UInt32 prop_size = (UInt32)sizeof(UInt32);
        OSStatus err = AudioUnitGetProperty(d_output_au,
                                            kAudioOutputUnitProperty_IsRunning,
                                            kAudioUnitScope_Global,
                                            0,
                                            &au_running,
                                            &prop_size);
        check_error_and_throw(
            err, "AudioUnitGetProperty IsRunning", "audio_osx_sink::is_running");
    }

    return (au_running != 0);
}

bool osx_sink::check_topology(int ninputs, int noutputs)
{
    // check # output to make sure it's valid
    if (noutputs != 0) {

        d_logger->fatal("check_topology(): number of output "
                        "streams provided ({:d}) should be 0.",
                        noutputs);
        throw std::runtime_error("audio_osx_sink::check_topology");
    }

    // check # outputs to make sure it's valid
    if ((ninputs < 1) | (ninputs > (int)d_n_dev_channels)) {

        d_logger->fatal("check_topology(): number of input "
                        "streams provided ({:d}) should be in [1,{:d}] "
                        "for the selected output audio device.",
                        ninputs,
                        d_n_dev_channels);
        throw std::runtime_error("audio_osx_sink::check_topology");
    }

    // save the actual number of input (user) channels

    d_n_user_channels = ninputs;

#if _OSX_AU_DEBUG_
    d_debug_logger->info("{:p} : audio_osx_sink::check_topology: "
                         "Actual # user input channels = {:d}",
                         (void*)pthread_self(),
                         d_n_user_channels);
#endif

    return (true);
}

void osx_sink::check_channels(bool force_reset)
{
    if (d_buffers.size() == 0) {

        // allocate the output circular buffer(s), one per user channel

        d_buffers.resize(d_n_user_channels);
        for (UInt32 nn = 0; nn < d_n_user_channels; ++nn) {
            d_buffers[nn] =
                new circular_buffer<float>(d_buffer_size_samples, false, false);
        }
    } else {
        if (d_buffers.size() == d_n_user_channels) {
            if (force_reset) {
                for (UInt32 nn = 0; nn < d_buffers.size(); ++nn) {
                    d_buffers[nn]->reset();
                }
            }
            return;
        }

        // reallocate the output circular buffer(s)

        if (d_n_user_channels < d_buffers.size()) {

            // too many buffers; delete some

            for (UInt32 nn = d_n_user_channels; nn < d_buffers.size(); ++nn) {
                delete d_buffers[nn];
                d_buffers[nn] = 0;
            }
            d_buffers.resize(d_n_user_channels);

            // reset remaining buffers

            for (UInt32 nn = 0; nn < d_buffers.size(); ++nn) {
                d_buffers[nn]->reset();
            }
        } else {

            // too few buffers; create some more

            // reset old buffers first

            for (UInt32 nn = 0; nn < d_buffers.size(); ++nn) {
                d_buffers[nn]->reset();
            }

            d_buffers.resize(d_n_user_channels);
            for (UInt32 nn = d_buffers.size(); nn < d_n_user_channels; ++nn) {
                d_buffers[nn] =
                    new circular_buffer<float>(d_buffer_size_samples, false, false);
            }
        }
    }

    // reset the output audio unit for the correct number of channels
    // have to uninitialize, set, initialize.

    OSStatus err = AudioUnitUninitialize(d_output_au);
    check_error(err, "AudioUnitUninitialize");

    d_stream_format.mChannelsPerFrame = d_n_user_channels;

    err = AudioUnitSetProperty(d_output_au,
                               kAudioUnitProperty_StreamFormat,
                               kAudioUnitScope_Input,
                               0,
                               &d_stream_format,
                               sizeof(d_stream_format));
    check_error_and_throw(
        err, "AudioUnitSetProperty StreamFormat", "audio_osx_sink::check_channels");

    // initialize the AU for output, so that it is ready to be used

    err = AudioUnitInitialize(d_output_au);
    check_error_and_throw(err, "AudioUnitInitialize", "audio_osx_sink::check_channels");
}

bool osx_sink::start()
{
    if (!is_running() && d_output_au) {

#if _OSX_AU_DEBUG_
        d_debug_logger->info("{:p} start: starting Output AudioUnit.",
                             (void*)pthread_self());
#endif

        // check channels, (re)allocate and reset buffers if/as necessary

        check_channels(true);

        // start the audio unit (should never fail)

        OSStatus err = AudioOutputUnitStart(d_output_au);
        check_error_and_throw(err, "AudioOutputUnitStart", "audio_osx_sink::start");
    }

#if _OSX_AU_DEBUG_
    else {
        d_debug_logger->info("{:p} start: already running.", (void*)pthread_self());
    }
#endif

    return (true);
}

bool osx_sink::stop()
{
    if (is_running()) {

#if _OSX_AU_DEBUG_
        d_debug_logger->info("{:p} stop: stopping Output AudioUnit.",
                             (void*)pthread_self());
#endif

        // if waiting in ::work, signal to wake up
        if (d_waiting_for_data) {
#if _OSX_AU_DEBUG_
            d_debug_logger->info("{:p} stop: signaling waiting condition",
                                 (void*)pthread_self());
#endif
            d_cond_data.notify_one();
        }

        // stop the audio unit (should never fail)

        OSStatus err = AudioOutputUnitStop(d_output_au);
        check_error_and_throw(err, "AudioOutputUnitStop", "audio_osx_sink::stop");

        // abort and reset all buffers

        for (UInt32 nn = 0; nn < d_n_user_channels; ++nn) {
            d_buffers[nn]->abort();
            d_buffers[nn]->reset();
        }

        // reset local knowledge of amount of data in queues

        d_queue_sample_count = 0;

    }
#if _OSX_AU_DEBUG_
    else {
        d_debug_logger->info("{:p} stop: already stopped.", (void*)pthread_self());
    }
#endif

    return (true);
}

int osx_sink::work(int noutput_items,
                   gr_vector_const_void_star& input_items,
                   gr_vector_void_star& output_items)
{
#if _OSX_AU_DEBUG_RENDER_
    {
        gr::thread::scoped_lock l(d_internal);
        d_debug_logger->info(
            "{:p} : audio_osx_sink::work: Starting: #OI = {:d}, reset = {:s}",
            (void*)pthread_self(),
            noutput_items,
            d_do_reset ? "true" : "false");
    }
#endif
    if (d_do_reset) {
        if (d_hardware_changed) {

            // see if the current AudioDeviceID is still available

            std::vector<AudioDeviceID> all_ad_ids;
            osx::find_audio_devices(d_desired_name, false, &all_ad_ids, NULL);
            bool found = false;
            for (UInt32 nn = 0; (nn < all_ad_ids.size()) && (!found); ++nn) {
                found = (all_ad_ids[nn] == d_output_ad_id);
            }
            if (!found) {

                d_logger->fatal("The selected output audio device ('{:s}') "
                                "is no longer available.",
                                d_selected_name);
                return (gr::block::WORK_DONE);
            }

            d_do_reset = d_hardware_changed = false;

        } else {

#if _OSX_AU_DEBUG_RENDER_
            {
                gr::thread::scoped_lock l(d_internal);
                d_debug_logger->info("{:p} : audio_osx_sink::work: doing reset.",
                                     (void*)pthread_self());
            }
#endif

            d_logger->warn("The default output audio device has "
                           "changed; resetting audio. There may "
                           "be a sound glitch while resetting.");

            // for any changes, just tear down the current
            // configuration, then set it up again using the user's
            // parameters to try to make selections.

            teardown();

            gr::thread::scoped_lock l(d_internal);

            setup();
            start();

#if _OSX_AU_DEBUG_RENDER_
            d_debug_logger->info("{:p} returning 0 after reset.", (void*)pthread_self());
#endif
            return (0);
        }
    }

    gr::thread::scoped_lock l(d_internal);

    // take the input data, copy it, and push it to the bottom of
    // the queue.  mono input is pushed onto queue[0]; stereo input
    // is pushed onto queue[1].  If the number of user/graph
    // channels is less than the number of device channels, copy the
    // data from the last / highest number channel to remaining
    // device channels.

    // find the maximum amount of buffer space available right now

    UInt32 l_max_count;
    int diff_count = ((int)d_buffer_size_samples) - noutput_items;
    if (diff_count < 0) {
        l_max_count = 0;
    } else {
        l_max_count = (UInt32)diff_count;
    }

#if _OSX_AU_DEBUG_RENDER_
    d_debug_logger->info(
        "{:p} : audio_osx_sink::work: qSC = {:d}, lMC = {:d}, dBSC = {:d}, #OI = {:d}",
        (void*)pthread_self(),
        d_queue_sample_count,
        l_max_count,
        d_buffer_size_samples,
        noutput_items);
#endif

    if (d_queue_sample_count > l_max_count) {

        // data coming in too fast; ok_to_block decides what to do: if
        // ok to block, then wait until the render callback makes
        // enough space.  If not blocking, detect overflow via writing
        // data to the circular buffer.

        if (d_ok_to_block == true) {
            // block until there is data to return, or on reset
            while (d_queue_sample_count > l_max_count) {
                // release control so-as to allow data to be retrieved;
                // block until there is data to return
#if _OSX_AU_DEBUG_RENDER_
                d_debug_logger->info("{:p} work: waiting.", (void*)pthread_self());

#endif
                d_waiting_for_data = true;
                d_cond_data.wait(l);
                d_waiting_for_data = false;
#if _OSX_AU_DEBUG_RENDER_
                d_debug_logger->info("{:p} work: done waiting", (void*)pthread_self());
#endif
                // the condition's 'notify' was called; acquire control to
                // keep thread safe

                // if doing a reset, just return here; reset will pick
                // up the next time this method is called.
                if (d_do_reset) {
#if _OSX_AU_DEBUG_RENDER_
                    d_debug_logger->info("{:p} work: returning 0 for reset.",
                                         (void*)pthread_self());
#endif
                    return (0);
                }
            }
        }
    }

    // not blocking and overflow is handled by the circular buffer,
    // or enough data space is available

    // add the input frames to the buffers' queue, checking for overflow

    UInt32 nn;
    int res = 0;
    float* inBuffer = (float*)input_items[0];
    const UInt32 l_size = input_items.size();
    for (nn = 0; nn < l_size; ++nn) {
        inBuffer = (float*)input_items[nn];
        int l_res = d_buffers[nn]->enqueue(inBuffer, noutput_items);
        if (l_res == -1) {
            res = -1;
        }
    }
    while (nn < d_n_user_channels) {
        // for extra channels, copy the last input's data
        int l_res = d_buffers[nn++]->enqueue(inBuffer, noutput_items);
        if (l_res == -1) {
            res = -1;
        }
    }

    // did overflow occur?

    if (res == -1) {
        // yes: data coming in too fast; drop oldest data.
        fputs("aO", stderr);
        fflush(stderr);
        // set the local number of samples available to the max
        d_queue_sample_count = d_buffers[0]->buffer_length_items();
    } else {
        // no: keep up the local sample count
        d_queue_sample_count += noutput_items;
    }

#if _OSX_AU_DEBUG_RENDER_
    d_debug_logger->info("{:p} work: returning: #OI = {:d}, qSC = {:d}, bSS = {:d}",
                         (void*)pthread_self(),
                         noutput_items,
                         d_queue_sample_count,
                         d_buffer_size_samples);
#endif

    return (noutput_items);
}

OSStatus osx_sink::au_output_callback(void* in_ref_con,
                                      AudioUnitRenderActionFlags* io_action_flags,
                                      const AudioTimeStamp* in_time_stamp,
                                      UInt32 in_bus_number,
                                      UInt32 in_number_frames,
                                      AudioBufferList* io_data)
{
    // NOTE: This is a callback from the OS, so throwing here does
    // not work; return an error instead when something does not go
    // as planned.

    osx_sink* This = reinterpret_cast<osx_sink*>(in_ref_con);
    OSStatus err = noErr;

    gr::thread::scoped_lock l(This->d_internal);

#if _OSX_AU_DEBUG_RENDER_
    This->d_debug_logger->info("{:p} : audio_osx_sink::au_output_callback: "
                               "starting: qSC = {:d}, in#F = {:d}, in#C = {:d}",
                               (void*)pthread_self(),
                               This->d_queue_sample_count,
                               in_number_frames,
                               This->d_n_user_channels);
#endif

    if (This->d_queue_sample_count < in_number_frames) {

        // not enough data to fill request; probably happened on
        // start-up, where this callback was called before ::work was.

        fputs("aU", stderr);
        fflush(stderr);
        err = kAudioUnitErr_Initialized;

    } else {
        // enough data; remove data from our buffers into the AU's buffers
        int nn = This->d_n_user_channels;
        while (--nn >= 0) {

            size_t t_n_output_items = in_number_frames;
            float* out_buffer = (float*)(io_data->mBuffers[nn].mData);
            int rv = This->d_buffers[nn]->dequeue(out_buffer, &t_n_output_items);

            if ((rv != 1) || (t_n_output_items != in_number_frames)) {
                This->d_logger->error(
                    "audio_osx_sink::au_output_callback: "
                    "number of available items changing "
                    "unexpectedly (should never happen): was {:d} now {:d}",
                    in_number_frames,
                    t_n_output_items);
                err = kAudioUnitErr_TooManyFramesToProcess;
            }
        }

        This->d_queue_sample_count -= in_number_frames;
    }

    // signal that data is available, if appropriate

    if (This->d_waiting_for_data) {
#if _OSX_AU_DEBUG_RENDER_
        This->d_debug_logger->info("{:p} au_output_callback: signaling waiting condition",
                                   (void*)pthread_self());
#endif
        This->d_cond_data.notify_one();
    }

#if _OSX_AU_DEBUG_RENDER_
    This->d_debug_logger->info("{:p} au_output_callback: returning: qSC = {:d}, err = {}",
                               (void*)pthread_self(),
                               This->d_queue_sample_count,
                               err);
#endif

    return (err);
}

OSStatus osx_sink::hardware_listener(AudioObjectID in_object_id,
                                     UInt32 in_num_addresses,
                                     const AudioObjectPropertyAddress in_addresses[],
                                     void* in_client_data)
{
    osx_sink* This = static_cast<osx_sink*>(in_client_data);
    This->reset(true);
    return (noErr);
}

OSStatus osx_sink::default_listener(AudioObjectID in_object_id,
                                    UInt32 in_num_addresses,
                                    const AudioObjectPropertyAddress in_addresses[],
                                    void* in_client_data)
{
    osx_sink* This = reinterpret_cast<osx_sink*>(in_client_data);
    This->reset(false);
    return (noErr);
}

} /* namespace audio */
} /* namespace gr */
