mackie: more debug tracing
[ardour.git] / libs / backends / portaudio / portaudio_io.cc
index b7ea02ff22dab96c4d68d3369cb7e69f3129f8bb..9ec8f6d6fb9d0e292d0603955af5398e5ba24e6f 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.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
 #include <string.h>
 #include <assert.h>
 #include <glibmm.h>
+
 #include "portaudio_io.h"
 
+#ifdef WITH_ASIO
+#include "pa_asio.h"
+#endif
+
+#include "pbd/compose.h"
+
+#include "ardour/audio_backend.h"
+
+#include "debug.h"
+
 #define INTERLEAVED_INPUT
 #define INTERLEAVED_OUTPUT
 
+using namespace PBD;
 using namespace ARDOUR;
 
 PortAudioIO::PortAudioIO ()
-       : _state (-1)
-       , _initialized (false)
-       , _capture_channels (0)
+       : _capture_channels (0)
        , _playback_channels (0)
        , _stream (0)
        , _input_buffer (0)
@@ -39,39 +50,81 @@ PortAudioIO::PortAudioIO ()
        , _cur_sample_rate (0)
        , _cur_input_latency (0)
        , _cur_output_latency (0)
+       , _host_api_index(-1)
 {
 }
 
 PortAudioIO::~PortAudioIO ()
 {
-       if (_state == 0) {
-               pcm_stop();
-       }
-       if (_initialized) {
-               Pa_Terminate();
-       }
+       close_stream();
 
+       pa_deinitialize ();
        clear_device_lists ();
 
        free (_input_buffer); _input_buffer = NULL;
        free (_output_buffer); _output_buffer = NULL;
 }
 
+std::string
+PortAudioIO::control_app_name (int device_id) const
+{
+#ifdef WITH_ASIO
+       if (get_current_host_api_type() == paASIO) {
+               // is this used for anything, or just acts as a boolean?
+               return "PortaudioASIO";
+       }
+#endif
+
+       return std::string();
+}
+
+void
+PortAudioIO::launch_control_app (int device_id)
+{
+#ifdef WITH_ASIO
+       PaError err = PaAsio_ShowControlPanel (device_id, NULL);
+
+       if (err != paNoError) {
+               // error << ?
+               DEBUG_AUDIO (string_compose (
+                   "Unable to show control panel for device with index %1\n", device_id));
+       }
+#endif
+}
+
+void
+PortAudioIO::get_default_sample_rates (std::vector<float>& rates)
+{
+       rates.push_back(8000.0);
+       rates.push_back(22050.0);
+       rates.push_back(24000.0);
+       rates.push_back(44100.0);
+       rates.push_back(48000.0);
+       rates.push_back(88200.0);
+       rates.push_back(96000.0);
+       rates.push_back(176400.0);
+       rates.push_back(192000.0);
+}
 
 int
 PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRates)
 {
-       static const float ardourRates[] = { 8000.0, 22050.0, 24000.0, 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0};
+       if (!pa_initialize()) return -1;
 
-       if (!initialize_pa()) return -1;
+#ifdef WITH_ASIO
+       if (get_current_host_api_type() == paASIO) {
+               get_default_sample_rates(sampleRates);
+               return 0;
+       }
+#endif
 
        // TODO use  separate int device_input, int device_output ?!
-       if (device_id == -1) {
+       if (device_id == DeviceDefault) {
                device_id = get_default_input_device ();
        }
-#ifndef NDEBUG
-       printf("PortAudio: Querying Samplerates for device %d\n", device_id);
-#endif
+
+       DEBUG_AUDIO (
+           string_compose ("Querying Samplerates for device %1\n", device_id));
 
        sampleRates.clear();
        const PaDeviceInfo* nfo = Pa_GetDeviceInfo(device_id);
@@ -92,33 +145,175 @@ PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRat
                outputParam.suggestedLatency = 0;
                outputParam.hostApiSpecificStreamInfo = 0;
 
-               for (uint32_t i = 0; i < sizeof(ardourRates)/sizeof(float); ++i) {
-                       if (paFormatIsSupported == Pa_IsFormatSupported(
-                                               nfo->maxInputChannels > 0 ? &inputParam : NULL,
-                                               nfo->maxOutputChannels > 0 ? &outputParam : NULL,
-                                               ardourRates[i])) {
-                               sampleRates.push_back (ardourRates[i]);
+               std::vector<float> rates;
+               get_default_sample_rates(rates);
+
+               for (std::vector<float>::const_iterator i = rates.begin(); i != rates.end();
+                    ++i) {
+                       if (paFormatIsSupported ==
+                           Pa_IsFormatSupported(nfo->maxInputChannels > 0 ? &inputParam : NULL,
+                                                nfo->maxOutputChannels > 0 ? &outputParam : NULL,
+                                                *i)) {
+                               sampleRates.push_back(*i);
                        }
                }
        }
 
        if (sampleRates.empty()) {
                // fill in something..
-               sampleRates.push_back (44100.0);
-               sampleRates.push_back (48000.0);
+               get_default_sample_rates(sampleRates);
        }
 
        return 0;
 }
 
+#ifdef WITH_ASIO
+bool
+PortAudioIO::get_asio_buffer_properties (int device_id,
+                                         long& min_size_frames,
+                                         long& max_size_frames,
+                                         long& preferred_size_frames,
+                                         long& granularity)
+{
+       // we shouldn't really need all these checks but it shouldn't hurt
+       const PaDeviceInfo* device_info = Pa_GetDeviceInfo(device_id);
+
+       if (!device_info) {
+               DEBUG_AUDIO (string_compose (
+                   "Unable to get device info from device index %1\n", device_id));
+               return false;
+       }
+
+       if (get_current_host_api_type() != paASIO) {
+               DEBUG_AUDIO (string_compose (
+                   "ERROR device_id %1 is not an ASIO device\n", device_id));
+               return false;
+       }
+
+       PaError err = PaAsio_GetAvailableBufferSizes (device_id,
+                                                     &min_size_frames,
+                                                     &max_size_frames,
+                                                     &preferred_size_frames,
+                                                     &granularity);
+
+       if (err != paNoError) {
+               DEBUG_AUDIO (string_compose (
+                   "Unable to determine available buffer sizes for device %1\n", device_id));
+               return false;
+       }
+       return true;
+}
+
+static
+bool
+is_power_of_two (uint32_t v)
+{
+       return ((v != 0) && !(v & (v - 1)));
+}
+
+bool
+PortAudioIO::get_asio_buffer_sizes(int device_id,
+                                   std::vector<uint32_t>& buffer_sizes,
+                                   bool preferred_only)
+{
+       long min_size_frames = 0;
+       long max_size_frames = 0;
+       long preferred_size_frames = 0;
+       long granularity = 0;
+
+       if (!get_asio_buffer_properties (device_id,
+                                        min_size_frames,
+                                        max_size_frames,
+                                        preferred_size_frames,
+                                        granularity)) {
+               DEBUG_AUDIO (string_compose (
+                   "Unable to get device buffer properties from device index %1\n", device_id));
+               return false;
+       }
+
+       DEBUG_AUDIO (string_compose ("ASIO buffer properties for device %1, "
+                                    "min_size_frames: %2, max_size_frames: %3, "
+                                    "preferred_size_frames: %4, granularity: %5\n",
+                                    device_id,
+                                    min_size_frames,
+                                    max_size_frames,
+                                    preferred_size_frames,
+                                    granularity));
+
+       bool driver_returns_one_size = (min_size_frames == max_size_frames) &&
+                                      (min_size_frames == preferred_size_frames);
+
+       if (preferred_only || driver_returns_one_size) {
+               buffer_sizes.push_back(preferred_size_frames);
+               return true;
+       }
+
+       long buffer_size = min_size_frames;
+
+       // If min size and granularity are power of two then just use values that
+       // are power of 2 even if the granularity allows for more values
+       bool use_power_of_two =
+           is_power_of_two(min_size_frames) && is_power_of_two(granularity);
+
+       if (granularity <= 0 || use_power_of_two) {
+               // driver uses buffer sizes that are power of 2
+               while (buffer_size <= max_size_frames) {
+                       buffer_sizes.push_back(buffer_size);
+                       buffer_size *= 2;
+               }
+       } else {
+               if (min_size_frames == max_size_frames) {
+                       // The devices I have tested either return the same values for
+                       // min/max/preferred and changing buffer size is intended to only be
+                       // done via the control dialog or they return a range where min != max
+                       // but I guess min == max could happen if a driver only supports a single
+                       // buffer size
+                       buffer_sizes.push_back(min_size_frames);
+                       return true;
+               }
+
+               // If min_size_frames is not power of 2 use at most 8 of the possible
+               // buffer sizes spread evenly between min and max
+               long max_values = 8;
+               while (((max_size_frames - min_size_frames) / granularity) > max_values) {
+                       granularity *= 2;
+               }
+
+               while (buffer_size < max_size_frames) {
+                       buffer_sizes.push_back(buffer_size);
+                       buffer_size += granularity;
+               }
+               buffer_sizes.push_back(max_size_frames);
+       }
+       return true;
+}
+#endif
+
+void
+PortAudioIO::get_default_buffer_sizes(std::vector<uint32_t>& buffer_sizes)
+{
+       buffer_sizes.push_back(64);
+       buffer_sizes.push_back(128);
+       buffer_sizes.push_back(256);
+       buffer_sizes.push_back(512);
+       buffer_sizes.push_back(1024);
+       buffer_sizes.push_back(2048);
+       buffer_sizes.push_back(4096);
+}
+
 int
-PortAudioIO::available_buffer_sizes(int device_id, std::vector<uint32_t>& bufferSizes)
+PortAudioIO::available_buffer_sizes(int device_id, std::vector<uint32_t>& buffer_sizes)
 {
-       // TODO
-       static const uint32_t ardourSizes[] = { 64, 128, 256, 512, 1024, 2048, 4096 };
-       for(uint32_t i = 0; i < sizeof(ardourSizes)/sizeof(uint32_t); ++i) {
-               bufferSizes.push_back (ardourSizes[i]);
+#ifdef WITH_ASIO
+       if (get_current_host_api_type() == paASIO) {
+               if (get_asio_buffer_sizes (device_id, buffer_sizes, false)) {
+                       return 0;
+               }
        }
+#endif
+
+       get_default_buffer_sizes (buffer_sizes);
+
        return 0;
 }
 
@@ -142,26 +337,44 @@ PortAudioIO::output_device_list(std::map<int, std::string> &devices) const
        }
 }
 
+bool&
+PortAudioIO::pa_initialized()
+{
+       static bool s_initialized = false;
+       return s_initialized;
+}
+
 bool
-PortAudioIO::initialize_pa ()
+PortAudioIO::pa_initialize()
 {
-       PaError err = paNoError;
+       if (pa_initialized()) return true;
 
-       if (!_initialized) {
-               err = Pa_Initialize();
-               if (err != paNoError) {
-                       return false;
-               }
-               _initialized = true;
+       PaError err = Pa_Initialize();
+       if (err != paNoError) {
+               return false;
        }
+       pa_initialized() = true;
 
        return true;
 }
 
+bool
+PortAudioIO::pa_deinitialize()
+{
+       if (!pa_initialized()) return true;
+
+       PaError err = Pa_Terminate();
+       if (err != paNoError) {
+               return false;
+       }
+       pa_initialized() = false;
+       return true;
+}
+
 void
 PortAudioIO::host_api_list (std::vector<std::string>& api_list)
 {
-       if (!initialize_pa()) return;
+       if (!pa_initialize()) return;
 
        PaHostApiIndex count = Pa_GetHostApiCount();
 
@@ -175,36 +388,70 @@ PortAudioIO::host_api_list (std::vector<std::string>& api_list)
        }
 }
 
-void
+
+PaHostApiTypeId
+PortAudioIO::get_current_host_api_type () const
+{
+       const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
+
+       if (info == NULL) {
+               DEBUG_AUDIO(string_compose(
+                   "Unable to determine Host API type from index %1\n", _host_api_index));
+               return (PaHostApiTypeId)0;
+       }
+
+       return info->type;
+}
+
+std::string
+PortAudioIO::get_host_api_name_from_index (PaHostApiIndex index)
+{
+       std::vector<std::string> api_list;
+       host_api_list(api_list);
+       return api_list[index];
+}
+
+bool
 PortAudioIO::set_host_api (const std::string& host_api_name)
 {
-       _host_api_index = get_host_api_index_from_name (host_api_name);
+       PaHostApiIndex new_index = get_host_api_index_from_name (host_api_name);
 
-       if (_host_api_index < 0) {
-               fprintf(stderr, "Error setting host API\n");
+       if (new_index < 0) {
+               DEBUG_AUDIO ("Portaudio: Error setting host API\n");
+               return false;
        }
+       _host_api_index = new_index;
+       _host_api_name = host_api_name;
+       return true;
 }
 
 PaHostApiIndex
 PortAudioIO::get_host_api_index_from_name (const std::string& name)
 {
-       if (!initialize_pa()) return -1;
+       if (!pa_initialize()) return -1;
 
        PaHostApiIndex count = Pa_GetHostApiCount();
 
-       if (count < 0) return -1;
+       if (count < 0) {
+               DEBUG_AUDIO ("Host API count < 0\n");
+               return -1;
+       }
 
        for (int i = 0; i < count; ++i) {
                const PaHostApiInfo* info = Pa_GetHostApiInfo (i);
-               if (info->name != NULL) { // possible?
-                       if (name == info->name) return i;
+               if (info != NULL && info->name != NULL) { // possible?
+                       if (name == info->name) {
+                               return i;
+                       }
                }
        }
+       DEBUG_AUDIO (string_compose ("Unable to get host API from name: %1\n", name));
+
        return -1;
 }
 
 PaDeviceIndex
-PortAudioIO::get_default_input_device ()
+PortAudioIO::get_default_input_device () const
 {
        const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
        if (info == NULL) return -1;
@@ -212,7 +459,7 @@ PortAudioIO::get_default_input_device ()
 }
 
 PaDeviceIndex
-PortAudioIO::get_default_output_device ()
+PortAudioIO::get_default_output_device () const
 {
        const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
        if (info == NULL) return -1;
@@ -233,6 +480,15 @@ PortAudioIO::clear_device_lists ()
        _output_devices.clear();
 }
 
+void
+PortAudioIO::add_none_devices ()
+{
+       _input_devices.insert(std::pair<int, paDevice*>(
+           DeviceNone, new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceNone), 0, 0)));
+       _output_devices.insert(std::pair<int, paDevice*>(
+           DeviceNone, new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceNone), 0, 0)));
+}
+
 void
 PortAudioIO::add_default_devices ()
 {
@@ -242,13 +498,13 @@ PortAudioIO::add_default_devices ()
        const PaDeviceInfo* nfo_i = Pa_GetDeviceInfo(get_default_input_device());
        const PaDeviceInfo* nfo_o = Pa_GetDeviceInfo(get_default_output_device());
        if (nfo_i && nfo_o) {
-               _input_devices.insert (std::pair<int, paDevice*> (-1,
-                                       new paDevice("Default",
+               _input_devices.insert (std::pair<int, paDevice*> (DeviceDefault,
+                                       new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceDefault),
                                                nfo_i->maxInputChannels,
                                                nfo_o->maxOutputChannels
                                                )));
-               _output_devices.insert (std::pair<int, paDevice*> (-1,
-                                       new paDevice("Default",
+               _output_devices.insert (std::pair<int, paDevice*> (DeviceDefault,
+                                       new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceDefault),
                                                nfo_i->maxInputChannels,
                                                nfo_o->maxOutputChannels
                                                )));
@@ -262,26 +518,28 @@ PortAudioIO::add_devices ()
        if (info == NULL) return;
 
        int n_devices = Pa_GetDeviceCount();
-#ifndef NDEBUG
-       printf("PortAudio %d devices found:\n", n_devices);
-#endif
+
+       DEBUG_AUDIO (string_compose ("PortAudio found %1 devices\n", n_devices));
 
        for (int i = 0 ; i < n_devices; ++i) {
                const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i);
 
                if (!nfo) continue;
                if (nfo->hostApi != _host_api_index) continue;
-#ifndef NDEBUG
-               printf(" (%d) '%s' '%s' in: %d (lat: %.1f .. %.1f) out: %d (lat: %.1f .. %.1f) sr:%.2f\n",
-                               i, info->name, nfo->name,
-                               nfo->maxInputChannels,
-                               nfo->defaultLowInputLatency * 1e3,
-                               nfo->defaultHighInputLatency * 1e3,
-                               nfo->maxOutputChannels,
-                               nfo->defaultLowOutputLatency * 1e3,
-                               nfo->defaultHighOutputLatency * 1e3,
-                               nfo->defaultSampleRate);
-#endif
+
+               DEBUG_AUDIO (string_compose (" (%1) '%2' '%3' in: %4 (lat: %5 .. %6) out: %7 "
+                                            "(lat: %8 .. %9) sr:%10\n",
+                                            i,
+                                            info->name,
+                                            nfo->name,
+                                            nfo->maxInputChannels,
+                                            nfo->defaultLowInputLatency * 1e3,
+                                            nfo->defaultHighInputLatency * 1e3,
+                                            nfo->maxOutputChannels,
+                                            nfo->defaultLowOutputLatency * 1e3,
+                                            nfo->defaultHighOutputLatency * 1e3,
+                                            nfo->defaultSampleRate));
+
                if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) {
                        continue;
                }
@@ -303,223 +561,309 @@ PortAudioIO::add_devices ()
        }
 }
 
-void
-PortAudioIO::discover()
+bool
+PortAudioIO::update_devices()
 {
-       if (!initialize_pa()) return;
+       DEBUG_AUDIO ("Update devices\n");
+       if (_stream != NULL) return false;
+       pa_deinitialize();
+       if (!pa_initialize()) return false;
 
        clear_device_lists ();
-       add_default_devices ();
+
+       // ASIO doesn't support separate input/output devices so adding None
+       // doesn't make sense
+       if (get_current_host_api_type() != paASIO) {
+               add_none_devices ();
+       }
        add_devices ();
+       return true;
 }
 
 void
-PortAudioIO::pcm_stop ()
+PortAudioIO::reset_stream_dependents ()
 {
-       if (_stream) {
-               Pa_CloseStream (_stream);
-       }
-       _stream = NULL;
-
        _capture_channels = 0;
        _playback_channels = 0;
        _cur_sample_rate = 0;
        _cur_input_latency = 0;
        _cur_output_latency = 0;
+}
+
+PaErrorCode
+PortAudioIO::close_stream()
+{
+       if (!_stream) return paNoError;
+
+       PaError err = Pa_CloseStream (_stream);
+
+       if (err != paNoError) {
+               return (PaErrorCode)err;
+       }
+       _stream = NULL;
+
+       reset_stream_dependents();
 
        free (_input_buffer); _input_buffer = NULL;
        free (_output_buffer); _output_buffer = NULL;
-       _state = -1;
+       return paNoError;
 }
 
-int
-PortAudioIO::pcm_start()
+PaErrorCode
+PortAudioIO::start_stream()
 {
        PaError err = Pa_StartStream (_stream);
 
        if (err != paNoError) {
-               _state = -1;
-               return -1;
+               DEBUG_AUDIO(string_compose("PortAudio failed to start stream %1\n",
+                                          Pa_GetErrorText(err)));
+               return (PaErrorCode)err;
        }
-       return 0;
+       return paNoError;
 }
 
-#ifdef __APPLE__
-static uint32_t lower_power_of_two (uint32_t v) {
-       v--;
-       v |= v >> 1;
-       v |= v >> 2;
-       v |= v >> 4;
-       v |= v >> 8;
-       v |= v >> 16;
-       v++;
-       return v >> 1;
+bool
+PortAudioIO::set_sample_rate_and_latency_from_stream ()
+{
+       const PaStreamInfo* nfo_s = Pa_GetStreamInfo(_stream);
+
+       if (nfo_s == NULL) {
+               return false;
+       }
+
+       _cur_sample_rate = nfo_s->sampleRate;
+       _cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
+       _cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
+
+       DEBUG_AUDIO (string_compose ("PA Sample Rate %1 SPS\n", _cur_sample_rate));
+
+       DEBUG_AUDIO (string_compose ("PA Input Latency %1ms, %2 spl\n",
+                                    1e3 * nfo_s->inputLatency,
+                                    _cur_input_latency));
+
+       DEBUG_AUDIO (string_compose ("PA Output Latency %1ms, %2 spl\n",
+                                    1e3 * nfo_s->outputLatency,
+                                    _cur_output_latency));
+       return true;
 }
-#endif
 
-int
-PortAudioIO::pcm_setup (
-               int device_input, int device_output,
-               double sample_rate, uint32_t samples_per_period)
+bool
+PortAudioIO::allocate_buffers_for_blocking_api (uint32_t samples_per_period)
 {
-       _state = -2;
-
-       // TODO error reporting sans fprintf()
+       if (_capture_channels > 0) {
+               _input_buffer =
+                   (float*)malloc(samples_per_period * _capture_channels * sizeof(float));
+               if (!_input_buffer) {
+                       DEBUG_AUDIO("PortAudio failed to allocate input buffer.\n");
+                       return false;
+               }
+       }
 
-       PaError err = paNoError;
-       const PaDeviceInfo *nfo_in;
-       const PaDeviceInfo *nfo_out;
-       const PaStreamInfo *nfo_s;
-               
-       if (!initialize_pa()) {
-               fprintf(stderr, "PortAudio Initialization Failed\n");
-               goto error;
+       if (_playback_channels > 0) {
+               _output_buffer =
+                   (float*)calloc(samples_per_period * _playback_channels, sizeof(float));
+               if (!_output_buffer) {
+                       DEBUG_AUDIO("PortAudio failed to allocate output buffer.\n");
+                       return false;
+               }
        }
+       return true;
+}
+
+bool
+PortAudioIO::get_input_stream_params(int device_input,
+                                     PaStreamParameters& inputParam) const
+{
+       const PaDeviceInfo *nfo_in = NULL;
 
-       if (device_input == -1) {
+       if (device_input == DeviceDefault) {
                device_input = get_default_input_device ();
        }
-       if (device_output == -1) {
-               device_output = get_default_output_device ();
+
+       if (device_input == DeviceNone) {
+               return false;
        }
 
-       _capture_channels = 0;
-       _playback_channels = 0;
-       _cur_sample_rate = 0;
-       _cur_input_latency = 0;
-       _cur_output_latency = 0;
+       nfo_in = Pa_GetDeviceInfo(device_input);
 
-#ifndef NDEBUG
-       printf("PortAudio Device IDs: i:%d o:%d\n", device_input, device_output);
+       if (nfo_in == NULL) {
+               DEBUG_AUDIO ("PortAudio Cannot Query Input Device Info\n");
+               return false;
+       }
+
+       inputParam.device = device_input;
+       inputParam.channelCount = nfo_in->maxInputChannels;
+#ifdef INTERLEAVED_INPUT
+       inputParam.sampleFormat = paFloat32;
+#else
+       inputParam.sampleFormat = paFloat32 | paNonInterleaved;
 #endif
+       inputParam.suggestedLatency = nfo_in->defaultLowInputLatency;
+       inputParam.hostApiSpecificStreamInfo = NULL;
 
-       nfo_in = Pa_GetDeviceInfo(device_input);
-       nfo_out = Pa_GetDeviceInfo(device_output);
+       return true;
+}
+
+bool
+PortAudioIO::get_output_stream_params(int device_output,
+                                      PaStreamParameters& outputParam) const
+{
+       const PaDeviceInfo *nfo_out = NULL;
 
-       if (!nfo_in && ! nfo_out) {
-               fprintf(stderr, "PortAudio Cannot Query Device Info\n");
-               goto error;
+       if (device_output == DeviceDefault) {
+               device_output = get_default_output_device ();
        }
 
-       if (nfo_in) {
-               _capture_channels = nfo_in->maxInputChannels;
+       if (device_output == DeviceNone) {
+               return false;
        }
-       if (nfo_out) {
-               _playback_channels = nfo_out->maxOutputChannels;
+
+       nfo_out = Pa_GetDeviceInfo(device_output);
+
+       if (nfo_out == NULL) {
+               DEBUG_AUDIO ("PortAudio Cannot Query Output Device Info\n");
+               return false;
        }
 
-       if(_capture_channels == 0 && _playback_channels == 0) {
-               fprintf(stderr, "PortAudio no Input and no output channels.\n");
-               goto error;
+       outputParam.device = device_output;
+       outputParam.channelCount = nfo_out->maxOutputChannels;
+#ifdef INTERLEAVED_OUTPUT
+       outputParam.sampleFormat = paFloat32;
+#else
+       outputParam.sampleFormat = paFloat32 | paNonInterleaved;
+#endif
+       outputParam.suggestedLatency = nfo_out->defaultLowOutputLatency;
+       outputParam.hostApiSpecificStreamInfo = NULL;
+
+       return true;
+}
+
+PaErrorCode
+PortAudioIO::pre_stream_open(int device_input,
+                             PaStreamParameters& inputParam,
+                             int device_output,
+                             PaStreamParameters& outputParam)
+{
+       if (!pa_initialize()) {
+               DEBUG_AUDIO ("PortAudio Initialization Failed\n");
+               return paNotInitialized;
        }
 
+       reset_stream_dependents ();
 
-#ifdef __APPLE__
-       // pa_mac_core_blocking.c pa_stable_v19_20140130
-       // BUG: ringbuffer alloc requires power-of-two chn count.
-       if ((_capture_channels & (_capture_channels - 1)) != 0) {
-               printf("Adjusted capture channes to power of two (portaudio rb bug)\n");
-               _capture_channels = lower_power_of_two (_capture_channels);
+       DEBUG_AUDIO (string_compose (
+           "PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
+
+       if (device_input == DeviceNone && device_output == DeviceNone) {
+               return paBadIODeviceCombination;
        }
 
-       if ((_playback_channels & (_playback_channels - 1)) != 0) {
-               printf("Adjusted capture channes to power of two (portaudio rb bug)\n");
-               _playback_channels = lower_power_of_two (_playback_channels);
+       if (get_input_stream_params(device_input, inputParam)) {
+               _capture_channels = inputParam.channelCount;
        }
-#endif
-       
-#ifndef NDEBUG
-       printf("PortAudio Channels: in:%d out:%d\n",
-                       _capture_channels, _playback_channels);
-#endif
 
+       if (get_output_stream_params(device_output, outputParam)) {
+               _playback_channels = outputParam.channelCount;
+       }
+
+       if (_capture_channels == 0 && _playback_channels == 0) {
+               DEBUG_AUDIO("PortAudio no input or output channels.\n");
+               return paBadIODeviceCombination;
+       }
+
+       DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n",
+                                    _capture_channels,
+                                    _playback_channels));
+
+       return paNoError;
+}
+
+PaErrorCode
+PortAudioIO::open_callback_stream(int device_input,
+                                  int device_output,
+                                  double sample_rate,
+                                  uint32_t samples_per_period,
+                                  PaStreamCallback* callback,
+                                  void* data)
+{
        PaStreamParameters inputParam;
        PaStreamParameters outputParam;
 
-       if (nfo_in) {
-               inputParam.device = device_input;
-               inputParam.channelCount = _capture_channels;
-#ifdef INTERLEAVED_INPUT
-               inputParam.sampleFormat = paFloat32;
-#else
-               inputParam.sampleFormat = paFloat32 | paNonInterleaved;
-#endif
-               inputParam.suggestedLatency = nfo_in->defaultLowInputLatency;
-               inputParam.hostApiSpecificStreamInfo = NULL;
+       PaErrorCode error_code =
+           pre_stream_open(device_input, inputParam, device_output, outputParam);
+
+       if (error_code != paNoError) return error_code;
+
+       PaError err = paNoError;
+
+       DEBUG_AUDIO ("Open Callback Stream\n");
+
+       err = Pa_OpenStream(&_stream,
+                           _capture_channels > 0 ? &inputParam : NULL,
+                           _playback_channels > 0 ? &outputParam : NULL,
+                           sample_rate,
+                           samples_per_period,
+                           paDitherOff,
+                           callback,
+                           data);
+
+       if (err != paNoError) {
+               DEBUG_AUDIO(string_compose("PortAudio failed to open stream %1\n",
+                                          Pa_GetErrorText(err)));
+               return paInternalError;
        }
 
-       if (nfo_out) {
-               outputParam.device = device_output;
-               outputParam.channelCount = _playback_channels;
-#ifdef INTERLEAVED_OUTPUT
-               outputParam.sampleFormat = paFloat32;
-#else
-               outputParam.sampleFormat = paFloat32 | paNonInterleaved;
-#endif
-               outputParam.suggestedLatency = nfo_out->defaultLowOutputLatency;
-               outputParam.hostApiSpecificStreamInfo = NULL;
+       if (!set_sample_rate_and_latency_from_stream()) {
+               DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
+               close_stream();
+               return paInternalError;
        }
 
-       // XXX re-consider using callback API, testing needed.
+       return paNoError;
+}
+
+PaErrorCode
+PortAudioIO::open_blocking_stream(int device_input,
+                                  int device_output,
+                                  double sample_rate,
+                                  uint32_t samples_per_period)
+{
+       PaStreamParameters inputParam;
+       PaStreamParameters outputParam;
+
+       PaErrorCode error_code =
+           pre_stream_open(device_input, inputParam, device_output, outputParam);
+
+       if (error_code != paNoError) return error_code;
+
+       PaError err = paNoError;
+
        err = Pa_OpenStream (
                        &_stream,
                        _capture_channels > 0 ? &inputParam: NULL,
                        _playback_channels > 0 ? &outputParam: NULL,
                        sample_rate,
                        samples_per_period,
-                       paClipOff | paDitherOff,
+                       paDitherOff,
                        NULL, NULL);
 
        if (err != paNoError) {
-               fprintf(stderr, "PortAudio failed to start stream.\n");
-               goto error;
-       }
-
-       nfo_s = Pa_GetStreamInfo (_stream);
-       if (!nfo_s) {
-               fprintf(stderr, "PortAudio failed to query stream information.\n");
-               pcm_stop();
-               goto error;
+               DEBUG_AUDIO(string_compose("PortAudio failed to open stream %1\n",
+                                          Pa_GetErrorText(err)));
+               return (PaErrorCode)err;
        }
 
-       _cur_sample_rate = nfo_s->sampleRate;
-       _cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
-       _cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
-
-#ifndef NDEBUG
-       printf("PA Sample Rate  %.1f SPS\n", _cur_sample_rate);
-       printf("PA Input Latency  %.1fms  %d spl\n", 1e3 * nfo_s->inputLatency, _cur_input_latency);
-       printf("PA Output Latency %.1fms  %d spl\n", 1e3 * nfo_s->outputLatency, _cur_output_latency);
-#endif
-
-       _state = 0;
-
-       if (_capture_channels > 0) {
-               _input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float));
-               if (!_input_buffer) {
-                       fprintf(stderr, "PortAudio failed to allocate input buffer.\n");
-                       pcm_stop();
-                       goto error;
-               }
+       if (!set_sample_rate_and_latency_from_stream()) {
+               DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
+               close_stream();
+               return paInternalError;
        }
 
-       if (_playback_channels > 0) {
-               _output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float));
-               if (!_output_buffer) {
-                       fprintf(stderr, "PortAudio failed to allocate output buffer.\n");
-                       pcm_stop();
-                       goto error;
-               }
+       if (!allocate_buffers_for_blocking_api(samples_per_period)) {
+               close_stream();
+               return paInternalError;
        }
-
-       return 0;
-
-error:
-       _capture_channels = 0;
-       _playback_channels = 0;
-       free (_input_buffer); _input_buffer = NULL;
-       free (_output_buffer); _output_buffer = NULL;
-       return -1;
+       return paNoError;
 }
 
 int
@@ -554,6 +898,47 @@ PortAudioIO::next_cycle (uint32_t n_samples)
        return xrun ? 1 : 0;
 }
 
+std::string
+PortAudioIO::get_input_channel_name (int device_id, uint32_t channel) const
+{
+#ifdef WITH_ASIO
+       const char* channel_name;
+
+       // This will return an error for non-ASIO devices so no need to check if
+       // the device_id corresponds to an ASIO device.
+       PaError err = PaAsio_GetInputChannelName (device_id, channel, &channel_name);
+
+       if (err == paNoError) {
+               DEBUG_AUDIO (
+                   string_compose ("Input channel name for device %1, channel %2 is %3\n",
+                                   device_id,
+                                   channel,
+                                   channel_name));
+               return channel_name;
+       }
+#endif
+       return std::string();
+}
+
+std::string
+PortAudioIO::get_output_channel_name (int device_id, uint32_t channel) const
+{
+#ifdef WITH_ASIO
+       const char* channel_name;
+
+       PaError err = PaAsio_GetOutputChannelName (device_id, channel, &channel_name);
+
+       if (err == paNoError) {
+               DEBUG_AUDIO (
+                   string_compose ("Output channel name for device %1, channel %2 is %3\n",
+                                   device_id,
+                                   channel,
+                                   channel_name));
+               return channel_name;
+       }
+#endif
+       return std::string();
+}
 
 #ifdef INTERLEAVED_INPUT