From eb7308d9927113125d369493593feec6ab169484 Mon Sep 17 00:00:00 2001
From: Andrew Kelley <superjoe30@gmail.com>
Date: Sat, 8 Aug 2015 11:45:21 -0700
Subject: [PATCH] CoreAudio: microphone example works

---
 README.md         |   2 -
 src/coreaudio.cpp | 110 +++++++++++++++++++++++++++++++++++++---------
 src/coreaudio.hpp |   4 +-
 3 files changed, 91 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index 9060eff..8aa1b05 100644
--- a/README.md
+++ b/README.md
@@ -249,8 +249,6 @@ view `coverage/index.html` in a browser.
 
 ## Roadmap
 
- 0. implement CoreAudio (OSX) backend, get examples working
-    - microphone example
  0. ALSA backend for microphone example is broken
  0. Add some builtin channel layouts from
     https://developer.apple.com/library/mac/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/#//apple_ref/doc/constant_group/Audio_Channel_Layout_Tags
diff --git a/src/coreaudio.cpp b/src/coreaudio.cpp
index d9d410e..92834ec 100644
--- a/src/coreaudio.cpp
+++ b/src/coreaudio.cpp
@@ -189,15 +189,6 @@ static int aim_to_scope(SoundIoDeviceAim aim) {
     return (aim == SoundIoDeviceAimInput) ?
         kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput;
 }
-// TODO
-/*
-    @constant       kAudioDevicePropertyLatency
-                        A UInt32 containing the number of frames of latency in the AudioDevice. Note
-                        that input and output latency may differ. Further, the AudioDevice's
-                        AudioStreams may have additional latency so they should be queried as well.
-                        If both the device and the stream say they have latency, then the total
-                        latency for the stream is the device latency summed with the stream latency.
-*/
 
 static SoundIoChannelId from_channel_descr(const AudioChannelDescription *descr) {
     switch (descr->mChannelLabel) {
@@ -637,12 +628,19 @@ static int refresh_devices(struct SoundIoPrivate *si) {
             prop_address.mScope = aim_to_scope(aim);
             prop_address.mElement = kAudioObjectPropertyElementMaster;
             io_size = sizeof(double);
+            double value;
             if ((os_err = AudioObjectGetPropertyData(device_id, &prop_address, 0, nullptr,
-                &io_size, &rd.device->sample_rate_current)))
+                &io_size, &value)))
             {
                 deinit_refresh_devices(&rd);
                 return SoundIoErrorOpeningDevice;
             }
+            double floored_value = floor(value);
+            if (value != floored_value) {
+                deinit_refresh_devices(&rd);
+                return SoundIoErrorIncompatibleDevice;
+            }
+            rd.device->sample_rate_current = (int)floored_value;
 
             prop_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
             prop_address.mScope = aim_to_scope(aim);
@@ -677,6 +675,12 @@ static int refresh_devices(struct SoundIoPrivate *si) {
                 if (rd.device->sample_rate_max == 0 || max_val > rd.device->sample_rate_max)
                     rd.device->sample_rate_max = max_val;
             }
+            // If you try to open an input stream with anything but the current
+            // nominal sample rate, AudioUnitRender returns an error.
+            if (aim == SoundIoDeviceAimInput) {
+                rd.device->sample_rate_min = rd.device->sample_rate_current;
+                rd.device->sample_rate_max = rd.device->sample_rate_current;
+            }
 
             prop_address.mSelector = kAudioDevicePropertyBufferFrameSize;
             prop_address.mScope = aim_to_scope(aim);
@@ -1056,6 +1060,8 @@ static void instream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoInStrea
         AudioOutputUnitStop(isca->instance);
         AudioComponentInstanceDispose(isca->instance);
     }
+
+    free(isca->buffer_list);
 }
 
 static OSStatus read_callback_ca(void *userdata, AudioUnitRenderActionFlags *io_action_flags,
@@ -1066,11 +1072,38 @@ static OSStatus read_callback_ca(void *userdata, AudioUnitRenderActionFlags *io_
     SoundIoInStream *instream = &is->pub;
     SoundIoInStreamCoreAudio *isca = &is->backend_data.coreaudio;
 
-    isca->io_data = io_data;
-    isca->buffer_index = 0;
+    for (int i = 0; i < isca->buffer_list->mNumberBuffers; i += 1) {
+        isca->buffer_list->mBuffers[i].mData = nullptr;
+    }
+
+    OSStatus os_err;
+    if ((os_err = AudioUnitRender(isca->instance, io_action_flags, in_time_stamp, 
+        in_bus_number, in_number_frames, isca->buffer_list)))
+    {
+        instream->error_callback(instream, SoundIoErrorStreaming);
+        return noErr;
+    }
+
+    if (isca->buffer_list->mNumberBuffers == 1) {
+        AudioBuffer *audio_buffer = &isca->buffer_list->mBuffers[0];
+        assert(audio_buffer->mNumberChannels == instream->layout.channel_count);
+        assert(audio_buffer->mDataByteSize == in_number_frames * instream->bytes_per_frame);
+        for (int ch = 0; ch < instream->layout.channel_count; ch += 1) {
+            isca->areas[ch].ptr = ((char*)audio_buffer->mData) + instream->bytes_per_sample;
+            isca->areas[ch].step = instream->bytes_per_frame;
+        }
+    } else {
+        assert(isca->buffer_list->mNumberBuffers == instream->layout.channel_count);
+        for (int ch = 0; ch < instream->layout.channel_count; ch += 1) {
+            AudioBuffer *audio_buffer = &isca->buffer_list->mBuffers[ch];
+            assert(audio_buffer->mDataByteSize == in_number_frames * instream->bytes_per_sample);
+            isca->areas[ch].ptr = (char*)audio_buffer->mData;
+            isca->areas[ch].step = instream->bytes_per_sample;
+        }
+    }
+
     isca->frames_left = in_number_frames;
     instream->read_callback(instream, isca->frames_left, isca->frames_left);
-    isca->io_data = nullptr;
 
     return noErr;
 }
@@ -1081,6 +1114,8 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri
     SoundIoDevice *device = instream->device;
     SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
     SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio;
+    UInt32 io_size;
+    OSStatus os_err;
 
     if (instream->buffer_duration == 0.0)
         instream->buffer_duration = device->buffer_duration_current;
@@ -1090,6 +1125,33 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri
             instream->buffer_duration,
             device->buffer_duration_max);
 
+
+    AudioObjectPropertyAddress prop_address;
+    prop_address.mSelector = kAudioDevicePropertyStreamConfiguration;
+    prop_address.mScope = kAudioObjectPropertyScopeInput;
+    prop_address.mElement = kAudioObjectPropertyElementMaster;
+    io_size = 0;
+    if ((os_err = AudioObjectGetPropertyDataSize(dca->device_id, &prop_address,
+        0, nullptr, &io_size)))
+    {
+        instream_destroy_ca(si, is);
+        return SoundIoErrorOpeningDevice;
+    }
+
+    isca->buffer_list = (AudioBufferList*)allocate_nonzero<char>(io_size);
+    if (!isca->buffer_list) {
+        instream_destroy_ca(si, is);
+        return SoundIoErrorNoMem;
+    }
+
+    if ((os_err = AudioObjectGetPropertyData(dca->device_id, &prop_address,
+        0, nullptr, &io_size, isca->buffer_list)))
+    {
+        instream_destroy_ca(si, is);
+        return SoundIoErrorOpeningDevice;
+    }
+
+
     AudioComponentDescription desc = {0};
     desc.componentType = kAudioUnitType_Output;
     desc.componentSubType = kAudioUnitSubType_HALOutput;
@@ -1101,7 +1163,6 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri
         return SoundIoErrorOpeningDevice;
     }
 
-    OSStatus os_err;
     if ((os_err = AudioComponentInstanceNew(component, &isca->instance))) {
         instream_destroy_ca(si, is);
         return SoundIoErrorOpeningDevice;
@@ -1162,11 +1223,9 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri
     }
 
 
-    AudioObjectPropertyAddress prop_address = {
-        kAudioDevicePropertyBufferFrameSize,
-        kAudioObjectPropertyScopeOutput,
-        INPUT_ELEMENT
-    };
+    prop_address.mSelector = kAudioDevicePropertyBufferFrameSize;
+    prop_address.mScope = kAudioObjectPropertyScopeOutput;
+    prop_address.mElement = INPUT_ELEMENT;
     UInt32 buffer_frame_size = instream->buffer_duration * instream->sample_rate;
     if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address,
         0, nullptr, sizeof(UInt32), &buffer_frame_size)))
@@ -1210,11 +1269,20 @@ static int instream_start_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPr
 static int instream_begin_read_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is,
         SoundIoChannelArea **out_areas, int *frame_count)
 {
-    soundio_panic("TODO begin read");
+    SoundIoInStreamCoreAudio *isca = &is->backend_data.coreaudio;
+
+    if (*frame_count != isca->frames_left)
+        return SoundIoErrorInvalid;
+
+    *out_areas = isca->areas;
+
+    return 0;
 }
 
 static int instream_end_read_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
-    soundio_panic("TODO end read");
+    SoundIoInStreamCoreAudio *isca = &is->backend_data.coreaudio;
+    isca->frames_left = 0;
+    return 0;
 }
 
 
diff --git a/src/coreaudio.hpp b/src/coreaudio.hpp
index 25449e7..45df501 100644
--- a/src/coreaudio.hpp
+++ b/src/coreaudio.hpp
@@ -52,9 +52,9 @@ struct SoundIoOutStreamCoreAudio {
 
 struct SoundIoInStreamCoreAudio {
     AudioComponentInstance instance;
-    AudioBufferList *io_data;
-    int buffer_index;
+    AudioBufferList *buffer_list;
     int frames_left;
+    SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
 };
 
 #endif