Dynamic ALSA MIDI I/O device discovery and re/connect
authorRobin Gareus <robin@gareus.org>
Sun, 23 Dec 2018 20:48:07 +0000 (21:48 +0100)
committerRobin Gareus <robin@gareus.org>
Sun, 23 Dec 2018 20:58:56 +0000 (21:58 +0100)
libs/backends/alsa/alsa_audiobackend.cc
libs/backends/alsa/alsa_audiobackend.h

index dc085aadbda1c928c2fbcd56b3ab4a8696b4ed54..ecbeb5a058b28e8d8f557869331a55a4df2e0e43 100644 (file)
@@ -71,6 +71,7 @@ AlsaAudioBackend::AlsaAudioBackend (AudioEngine& e, AudioBackendInfo& info)
        , _n_outputs (0)
        , _systemic_audio_input_latency (0)
        , _systemic_audio_output_latency (0)
+       , _midi_device_thread_active (false)
        , _dsp_load (0)
        , _processed_samples (0)
        , _port_change_flag (false)
@@ -947,6 +948,8 @@ AlsaAudioBackend::_start (bool for_latency_measurement)
                return ProcessThreadStartError;
        }
 
+       _midi_device_thread_active = listen_for_midi_device_changes ();
+
 #if 1
        if (NULL != getenv ("ALSAEXT")) {
                boost::char_separator<char> sep (";");
@@ -992,6 +995,8 @@ AlsaAudioBackend::stop ()
                return -1;
        }
 
+       stop_listen_for_midi_device_changes ();
+
        while (!_rmidi_out.empty ()) {
                AlsaMidiIO *m = _rmidi_out.back ();
                m->stop();
@@ -1386,6 +1391,142 @@ AlsaAudioBackend::register_system_audio_ports()
        return 0;
 }
 
+void
+AlsaAudioBackend::auto_update_midi_devices ()
+{
+       std::map<std::string, std::string> devices;
+       if (_midi_driver_option == _("ALSA raw devices")) {
+               get_alsa_rawmidi_device_names (devices);
+       } else if (_midi_driver_option == _("ALSA sequencer")) {
+               get_alsa_sequencer_names (devices);
+       } else {
+               return;
+       }
+
+       /* find new devices */
+       for (std::map<std::string, std::string>::const_iterator i = devices.begin (); i != devices.end(); ++i) {
+               if (_midi_devices.find (i->first) != _midi_devices.end()) {
+                       continue;
+               }
+               printf ("NEW MIDI DEVICE %s", i->first.c_str());
+               _midi_devices[i->first] = new AlsaMidiDeviceInfo (false);
+               set_midi_device_enabled (i->first, true);
+       }
+
+       for (std::map<std::string, struct AlsaMidiDeviceInfo*>::iterator i = _midi_devices.begin (); i != _midi_devices.end(); ) {
+               if (devices.find (i->first) != devices.end()) {
+                       ++i;
+                       continue;
+               }
+               printf ("REMOVE MIDI DEVICE %s", i->first.c_str());
+               set_midi_device_enabled (i->first, false);
+               i = _midi_devices.erase (i);
+       }
+}
+
+void*
+AlsaAudioBackend::_midi_device_thread (void* arg)
+{
+       AlsaAudioBackend* self = static_cast<AlsaAudioBackend*>(arg);
+       self->midi_device_thread ();
+       pthread_exit (0);
+       return 0;
+}
+
+void
+AlsaAudioBackend::midi_device_thread ()
+{
+       snd_seq_t* seq;
+       if (snd_seq_open (&seq, "hw", SND_SEQ_OPEN_INPUT, 0) < 0) {
+               return;
+       }
+       if (snd_seq_set_client_name (seq, "Ardour")) {
+               snd_seq_close (seq);
+               return;
+       }
+       if (snd_seq_nonblock (seq, 1) < 0) {
+               snd_seq_close (seq);
+               return;
+       }
+
+       int npfds = snd_seq_poll_descriptors_count (seq, POLLIN);
+       if (npfds < 1) {
+               snd_seq_close (seq);
+               return;
+       }
+
+       int port = snd_seq_create_simple_port (seq, "port", SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT, SND_SEQ_PORT_TYPE_APPLICATION);
+       snd_seq_connect_from (seq, port, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
+
+       struct pollfd* pfds = (struct pollfd*) malloc (npfds * sizeof(struct pollfd));
+       snd_seq_poll_descriptors (seq, pfds, npfds, POLLIN);
+       snd_seq_drop_input (seq);
+
+       bool do_poll = true;
+       while (_run) {
+               if (do_poll) {
+                       int perr = poll (pfds, npfds, 200 /* ms */);
+                       if (perr == 0) {
+                               continue;
+                       }
+                       if (perr < 0) {
+                               break;
+                       }
+               }
+
+               snd_seq_event_t *event;
+               ssize_t err = snd_seq_event_input (seq, &event);
+#if EAGAIN == EWOULDBLOCK
+               if ((err == -EAGAIN) || (err == -ENOSPC))
+#else
+               if ((err == -EAGAIN) || (err == -EWOULDBLOCK) || (err == -ENOSPC))
+#endif
+               {
+                       do_poll = true;
+                       continue;
+               }
+               if (err < 0) {
+                       break;
+               }
+
+               assert (event->source.client == SND_SEQ_CLIENT_SYSTEM);
+
+               const snd_seq_addr_t addr = event->data.addr;
+               switch (event->type) {
+                       case SND_SEQ_EVENT_PORT_START:
+                       case SND_SEQ_EVENT_PORT_EXIT:
+                       case SND_SEQ_EVENT_PORT_CHANGE:
+                               auto_update_midi_devices ();
+                               engine.request_device_list_update();
+                       default:
+                               break;
+               }
+               do_poll = (0 == err);
+       }
+       free (pfds);
+       snd_seq_delete_simple_port (seq, port);
+       snd_seq_close (seq);
+}
+
+bool
+AlsaAudioBackend::listen_for_midi_device_changes ()
+{
+       if (pthread_create (&_midi_device_thread_id, NULL, _midi_device_thread, this)) {
+               return false;
+       }
+       return true;
+}
+
+void
+AlsaAudioBackend::stop_listen_for_midi_device_changes ()
+{
+       if (!_midi_device_thread_active) {
+               return;
+       }
+       pthread_join (_midi_device_thread_id, NULL);
+       _midi_device_thread_active = false;
+}
+
 /* set playback-latency for _system_inputs
  * and capture-latency for _system_outputs
  */
index 37889c78cb42c2c4dfe0e299356fda2229b3fca9..ef218b49a126a714b52910e6a927ce4cdff43da0 100644 (file)
@@ -374,8 +374,8 @@ class AlsaAudioBackend : public AudioBackend {
                        bool     enabled;
                        uint32_t systemic_input_latency;
                        uint32_t systemic_output_latency;
-                       AlsaMidiDeviceInfo()
-                               : enabled (true)
+                       AlsaMidiDeviceInfo (bool en = true)
+                               : enabled (en)
                                , systemic_input_latency (0)
                                , systemic_output_latency (0)
                        {}
@@ -384,6 +384,15 @@ class AlsaAudioBackend : public AudioBackend {
                mutable std::map<std::string, struct AlsaMidiDeviceInfo *> _midi_devices;
                struct AlsaMidiDeviceInfo * midi_device_info(std::string const) const;
 
+               /* midi device changes */
+               void auto_update_midi_devices();
+               bool listen_for_midi_device_changes ();
+               void stop_listen_for_midi_device_changes ();
+               void midi_device_thread ();
+               static void* _midi_device_thread (void *arg);
+               pthread_t _midi_device_thread_id;
+               bool _midi_device_thread_active;
+
                /* processing */
                float  _dsp_load;
                ARDOUR::DSPLoadCalculator  _dsp_load_calc;