Refactor methods in PortaudioBackend to get ASIO buffer sizes
[ardour.git] / libs / backends / portaudio / portaudio_io.cc
index 77ea0536fb830c454c4882641b9c57074e20937d..c007e29436037a02632236e8726e2b38355d5f9a 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 <glibmm.h>
 #include "portaudio_io.h"
 
+#ifdef WITH_ASIO
+#include "pa_asio.h"
+#endif
+
+#include "pbd/compose.h"
+
+#include "debug.h"
+
 #define INTERLEAVED_INPUT
 #define INTERLEAVED_OUTPUT
 
@@ -39,6 +48,7 @@ PortAudioIO::PortAudioIO ()
        , _cur_sample_rate (0)
        , _cur_input_latency (0)
        , _cur_output_latency (0)
+       , _host_api_index(-1)
 {
 }
 
@@ -51,30 +61,64 @@ PortAudioIO::~PortAudioIO ()
                Pa_Terminate();
        }
 
-       for (std::map<int, paDevice*>::const_iterator i = _devices.begin (); i != _devices.end(); ++i) {
-               delete i->second;
-       }
-       _devices.clear();
+       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
+{
+       const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
+       std::string app_name;
+
+       if (info == NULL) {
+               DEBUG_AUDIO (string_compose ("Unable to determine Host API from index %1\n",
+                                            _host_api_index));
+               return app_name;
+       }
+
+       PaHostApiTypeId type_id = info->type;
+
+#ifdef WITH_ASIO
+       if (type_id == paASIO) {
+               // is this used for anything, or just acts as a boolean?
+               return "PortaudioASIO";
+       }
+#endif
+
+       return app_name;
+}
+
+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
+}
 
 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};
 
-       assert(_initialized);
+       if (!initialize_pa()) return -1;
 
        // TODO use  separate int device_input, int device_output ?!
        if (device_id == -1) {
                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);
@@ -114,22 +158,123 @@ PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRat
        return 0;
 }
 
-int
-PortAudioIO::available_buffer_sizes(int device_id, std::vector<uint32_t>& bufferSizes)
+#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;
+       }
+
+       const PaHostApiInfo* info = Pa_GetHostApiInfo (device_info->hostApi);
+
+       if (info == NULL) {
+               DEBUG_AUDIO (string_compose (
+                   "Unable to determine Host API from device index %1\n", device_id));
+               return false;
+       }
+
+       if (info->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;
+}
+
+bool
+PortAudioIO::get_asio_buffer_sizes (int device_id, std::vector<uint32_t>& buffer_sizes)
+{
+       long min_size_frames, max_size_frames, preferred_size_frames, granularity;
+
+       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;
+       }
+
+       buffer_sizes.push_back(preferred_size_frames);
+}
+#endif
+
+bool
+PortAudioIO::get_default_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]);
+               buffer_sizes.push_back (ardourSizes[i]);
        }
+       return true;
+}
+
+int
+PortAudioIO::available_buffer_sizes(int device_id, std::vector<uint32_t>& buffer_sizes)
+{
+#ifdef WITH_ASIO
+       const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
+
+       if (info == NULL) {
+               DEBUG_AUDIO (string_compose ("Unable to determine Host API from index %1\n",
+                                            _host_api_index));
+               return -1;
+       }
+
+       PaHostApiTypeId type_id = info->type;
+
+       if (type_id == paASIO) {
+               if (get_asio_buffer_sizes (device_id, buffer_sizes)) {
+                       return 0;
+               }
+       }
+#endif
+
+       get_default_buffer_sizes (device_id, buffer_sizes);
+
        return 0;
 }
 
 void
-PortAudioIO::device_list (std::map<int, std::string> &devices) const {
-       devices.clear();
-       for (std::map<int, paDevice*>::const_iterator i = _devices.begin (); i != _devices.end(); ++i) {
-               devices.insert (std::pair<int, std::string> (i->first, Glib::locale_to_utf8(i->second->name)));
+PortAudioIO::input_device_list(std::map<int, std::string> &devices) const
+{
+       for (std::map<int, paDevice*>::const_iterator i = _input_devices.begin ();
+            i != _input_devices.end ();
+            ++i) {
+               devices.insert (std::pair<int, std::string>(i->first, Glib::locale_to_utf8(i->second->name)));
+       }
+}
+
+void
+PortAudioIO::output_device_list(std::map<int, std::string> &devices) const
+{
+       for (std::map<int, paDevice*>::const_iterator i = _output_devices.begin ();
+            i != _output_devices.end ();
+            ++i) {
+               devices.insert (std::pair<int, std::string>(i->first, Glib::locale_to_utf8(i->second->name)));
        }
 }
 
@@ -166,14 +311,18 @@ PortAudioIO::host_api_list (std::vector<std::string>& api_list)
        }
 }
 
-void
+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
@@ -183,14 +332,21 @@ PortAudioIO::get_host_api_index_from_name (const std::string& name)
 
        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;
 }
 
@@ -211,16 +367,21 @@ PortAudioIO::get_default_output_device ()
 }
 
 void
-PortAudioIO::clear_device_list ()
+PortAudioIO::clear_device_lists ()
 {
-       for (std::map<int, paDevice*>::const_iterator i = _devices.begin (); i != _devices.end(); ++i) {
+       for (std::map<int, paDevice*>::const_iterator i = _input_devices.begin (); i != _input_devices.end(); ++i) {
                delete i->second;
        }
-       _devices.clear();
+       _input_devices.clear();
+
+       for (std::map<int, paDevice*>::const_iterator i = _output_devices.begin (); i != _output_devices.end(); ++i) {
+               delete i->second;
+       }
+       _output_devices.clear();
 }
 
 void
-PortAudioIO::add_default_device ()
+PortAudioIO::add_default_devices ()
 {
        const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
        if (info == NULL) return;
@@ -228,7 +389,12 @@ PortAudioIO::add_default_device ()
        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) {
-               _devices.insert (std::pair<int, paDevice*> (-1,
+               _input_devices.insert (std::pair<int, paDevice*> (-1,
+                                       new paDevice("Default",
+                                               nfo_i->maxInputChannels,
+                                               nfo_o->maxOutputChannels
+                                               )));
+               _output_devices.insert (std::pair<int, paDevice*> (-1,
                                        new paDevice("Default",
                                                nfo_i->maxInputChannels,
                                                nfo_o->maxOutputChannels
@@ -243,45 +409,57 @@ 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;
                }
 
-               _devices.insert (std::pair<int, paDevice*> (i, new paDevice(
-                                               nfo->name,
-                                               nfo->maxInputChannels,
-                                               nfo->maxOutputChannels
-                                               )));
+               if (nfo->maxInputChannels > 0) {
+                       _input_devices.insert (std::pair<int, paDevice*> (i, new paDevice(
+                                                       nfo->name,
+                                                       nfo->maxInputChannels,
+                                                       nfo->maxOutputChannels
+                                                       )));
+               }
+               if (nfo->maxOutputChannels > 0) {
+                       _output_devices.insert (std::pair<int, paDevice*> (i, new paDevice(
+                                                       nfo->name,
+                                                       nfo->maxInputChannels,
+                                                       nfo->maxOutputChannels
+                                                       )));
+               }
        }
 }
 
 void
 PortAudioIO::discover()
 {
+       DEBUG_AUDIO ("PortAudio: discover\n");
        if (!initialize_pa()) return;
 
-       clear_device_list ();
-       add_default_device ();
+       clear_device_lists ();
+       add_default_devices ();
        add_devices ();
 }
 
@@ -336,15 +514,13 @@ PortAudioIO::pcm_setup (
 {
        _state = -2;
 
-       // TODO error reporting sans fprintf()
-
        PaError err = paNoError;
        const PaDeviceInfo *nfo_in;
        const PaDeviceInfo *nfo_out;
        const PaStreamInfo *nfo_s;
                
        if (!initialize_pa()) {
-               fprintf(stderr, "PortAudio Initialization Failed\n");
+               DEBUG_AUDIO ("PortAudio Initialization Failed\n");
                goto error;
        }
 
@@ -361,15 +537,14 @@ PortAudioIO::pcm_setup (
        _cur_input_latency = 0;
        _cur_output_latency = 0;
 
-#ifndef NDEBUG
-       printf("PortAudio Device IDs: i:%d o:%d\n", device_input, device_output);
-#endif
+       DEBUG_AUDIO (string_compose (
+           "PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
 
        nfo_in = Pa_GetDeviceInfo(device_input);
        nfo_out = Pa_GetDeviceInfo(device_output);
 
        if (!nfo_in && ! nfo_out) {
-               fprintf(stderr, "PortAudio Cannot Query Device Info\n");
+               DEBUG_AUDIO ("PortAudio Cannot Query Device Info\n");
                goto error;
        }
 
@@ -381,29 +556,29 @@ PortAudioIO::pcm_setup (
        }
 
        if(_capture_channels == 0 && _playback_channels == 0) {
-               fprintf(stderr, "PortAudio no Input and no output channels.\n");
+               DEBUG_AUDIO ("PortAudio no input or output channels.\n");
                goto error;
        }
 
-
 #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");
+               DEBUG_AUDIO (
+                   "Adjusted capture channels to power of two (portaudio rb bug)\n");
                _capture_channels = lower_power_of_two (_capture_channels);
        }
 
        if ((_playback_channels & (_playback_channels - 1)) != 0) {
-               printf("Adjusted capture channes to power of two (portaudio rb bug)\n");
+               DEBUG_AUDIO (
+                   "Adjusted capture channels to power of two (portaudio rb bug)\n");
                _playback_channels = lower_power_of_two (_playback_channels);
        }
 #endif
-       
-#ifndef NDEBUG
-       printf("PortAudio Channels: in:%d out:%d\n",
-                       _capture_channels, _playback_channels);
-#endif
+
+       DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n",
+                                    _capture_channels,
+                                    _playback_channels));
 
        PaStreamParameters inputParam;
        PaStreamParameters outputParam;
@@ -439,17 +614,17 @@ PortAudioIO::pcm_setup (
                        _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");
+               DEBUG_AUDIO ("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");
+               DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
                pcm_stop();
                goto error;
        }
@@ -458,18 +633,22 @@ PortAudioIO::pcm_setup (
        _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
+       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));
 
        _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");
+                       DEBUG_AUDIO ("PortAudio failed to allocate input buffer.\n");
                        pcm_stop();
                        goto error;
                }
@@ -478,7 +657,7 @@ PortAudioIO::pcm_setup (
        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");
+                       DEBUG_AUDIO ("PortAudio failed to allocate output buffer.\n");
                        pcm_stop();
                        goto error;
                }