Re: [tim-janik/beast] Jack driver new api (#128)



@tim-janik requested changes on this pull request.

Great work Stefan, thanks for the updates.
I've pointed out a few places that still need work.


In bse/driver-jack.cc:

> +fast_copy (uint        n_values,
+           Data       *ovalues,
+	   const Data *ivalues)
+{
+  copy (ivalues, ivalues + n_values, ovalues);
+}
+
+template<> void
+fast_copy (uint         n_values,
+           float       *ovalues,
+           const float *ivalues)
+{
+  Block::copy (n_values, ovalues, ivalues);
+}
+
+#if 0 // <- avoid unused warning

Use BSE_USED or BSE_UNUSED to silence the compiler here.


In bse/driver-jack.cc:

> +}
+
+static void
+list_jack_drivers (Driver::EntryVec &entries)
+{
+  map<std::string, DeviceDetails> devices;
+  jack_client_t *jack_client = connect_jack();
+  if (jack_client)
+    {
+      devices = query_jack_devices (jack_client);
+      disconnect_jack (jack_client);
+    }
+  else
+    {
+      // should we try to generate and show an error message if connecting jack failed?
+    }

No, if there's no jack, there are no devices to list. Simple. Stay silent.


In bse/driver-jack.cc:

> +    }
+
+  for (std::map<std::string, DeviceDetails>::iterator di = devices.begin(); di != devices.end(); di++)
+    {
+      const std::string &device_name = di->first;
+      DeviceDetails &details = di->second;
+
+      /* the default device is usually the hardware device, so things should work as expected
+       * we could show try to show non-default devices as well, but this could be confusing
+       */
+      if (details.default_device)
+        {
+          Driver::Entry entry;
+          entry.devid = device_name;
+          entry.priority = Driver::JACK;
+          entries.push_back (entry);

Two Driver::Entry structures must never have the same priority. That's why we have predefined priority constants for drivers, cards, devices, subdevices. Also, we need the other Entry fields to be filled to allow GUI selection. For now, it doesn't matter too much how to match name/blurb/status fields to extra info, each is just another line in my current UI prototype, but we need something to display devices to the user that make them humanly identifyable.


In bse/driver-jack.cc:

> +      /* the default device is usually the hardware device, so things should work as expected
+       * we could show try to show non-default devices as well, but this could be confusing
+       */
+      if (details.default_device)
+        {
+          Driver::Entry entry;
+          entry.devid = device_name;
+          entry.priority = Driver::JACK;
+          entries.push_back (entry);
+        }
+    }
+}
+
+/* macro for jack dropout tests - see below */
+#define TEST_DROPOUT() if (unlink ("/tmp/drop") == 0) usleep (1.5 * 1000000. * buffer_frames_ / mix_freq_); /* sleep 1.5 * buffer size */
+

I know this is disabled, atm, but please still make this /tmp/bse-dropout (or similar) so we're not stamping over namespaces if this is activated on purpose or by accident.


In bse/driver-jack.cc:

> + *
+ * Implementation: the synchronization between the two threads is only
+ * implemented by two index variables (read_frame_pos and write_frame_pos)
+ * for which atomic integer reads and writes are required. Since the
+ * producer thread only modifies the write_frame_pos and the consumer thread
+ * only modifies the read_frame_pos, no compare-and-swap or similar
+ * operations are needed to avoid concurrent writes.
+ */
+template<class T>
+class FrameRingBuffer {
+  //BIRNET_PRIVATE_COPY (FrameRingBuffer);
+private:
+  vector<vector<T> >  channel_buffer_;
+  std::atomic<int>    atomic_read_frame_pos_;
+  std::atomic<int>    atomic_write_frame_pos_;
+  uint                channel_buffer_size_;       // = n_frames + 1; the extra frame allows us to

This is C++17, always and unconditionally initialize all struct members that don't have a ctor. If in doubt, just add =0, =false, =EnumType(0).


In bse/driver-jack.cc:

> + * implemented by two index variables (read_frame_pos and write_frame_pos)
+ * for which atomic integer reads and writes are required. Since the
+ * producer thread only modifies the write_frame_pos and the consumer thread
+ * only modifies the read_frame_pos, no compare-and-swap or similar
+ * operations are needed to avoid concurrent writes.
+ */
+template<class T>
+class FrameRingBuffer {
+  //BIRNET_PRIVATE_COPY (FrameRingBuffer);
+private:
+  vector<vector<T> >  channel_buffer_;
+  std::atomic<int>    atomic_read_frame_pos_;
+  std::atomic<int>    atomic_write_frame_pos_;
+  uint                channel_buffer_size_;       // = n_frames + 1; the extra frame allows us to
+                                                  // see the difference between an empty/full ringbuffer
+  uint                n_channels_;

n_channels_ = 0;


In bse/driver-jack.cc:

> +  std::atomic<int>              atomic_active_ {0};
+  std::atomic<int>              atomic_xruns_ {0};
+  int                           printed_xruns_ = 0;
+
+  bool                          is_down_ = false;
+  bool                          printed_is_down_ = false;
+
+  uint64                        device_read_counter_ = 0;
+  uint64                        device_write_counter_ = 0;
+  int                           device_open_counter_ = 0;
+
+  static int
+  static_process_callback (jack_nframes_t   n_frames,
+                           void            *jack_handle)
+  {
+    return static_cast <JackPcmDriver *> (jack_handle)->process_callback (n_frames);

Use a lambda instead of static_*().


In bse/driver-jack.cc:

> +
+        if (!p) // first port
+          range = port_range;
+        else
+          {
+            range.min = std::min (range.min, port_range.min);
+            range.max = std::max (range.max, port_range.max);
+          }
+      }
+    return range;
+  }
+  static void
+  static_latency_callback (jack_latency_callback_mode_t  mode,
+                           void                         *jack_handle)
+  {
+    static_cast <JackPcmDriver *> (jack_handle)->latency_callback (mode);

Use a lambda instead of static_*().


In bse/driver-jack.cc:

> +          jack_port_set_latency_range (port, mode, &range);
+      }
+    else
+      {
+        jack_latency_range_t range = get_latency_for_ports (output_ports_, mode);
+        range.min += buffer_frames_;
+        range.max += buffer_frames_;
+
+        for (auto port : input_ports_)
+          jack_port_set_latency_range (port, mode, &range);
+      }
+  }
+  static void
+  static_shutdown_callback (void *jack_handle)
+  {
+    static_cast<JackPcmDriver *> (jack_handle)->shutdown_callback();

Use a lambda instead of static_*(), see below.


In bse/driver-jack.cc:

> +        vector<const float *> silence_buffers (output_ringbuffer_.get_n_channels());
+
+        fill (silence_buffers.begin(), silence_buffers.end(), &silence[0]);
+
+        uint frames_written = output_ringbuffer_.write (buffer_frames, &silence_buffers[0]);
+        if (frames_written != buffer_frames)
+          Bse::warning ("JACK driver: output silence init failed: (frames_written != jack->buffer_frames)\n");
+
+      }
+
+    /* activate */
+    if (error == 0)
+      {
+        jack_set_process_callback (jack_client_, static_process_callback, this);
+        jack_set_latency_callback (jack_client_, static_latency_callback, this);
+        jack_on_shutdown (jack_client_, static_shutdown_callback, this);

Note that lambdas that don't take context arguments boil down to normal functions that can be used as function pointer. For example, here is how it looks when you replace the static_* wrappers with lambda:

jack_on_shutdown (jack_client_, [] (void *p) {
  static_cast<JackPcmDriver*> (p)->shutdown_callback();
}, this); 

This could also be a one liner if you have a wider terminal.


In bse/driver-jack.cc:

> +  void
+  shutdown_callback()
+  {
+    is_down_ = true;
+  }
+public:
+  explicit      JackPcmDriver (const String &devid) : PcmDriver (devid) {}
+  static PcmDriverP
+  create (const String &devid)
+  {
+    auto pdriverp = std::make_shared<JackPcmDriver> (devid);
+    return pdriverp;
+  }
+  ~JackPcmDriver()
+  {
+  }

The dtor should release all resources, e.g. call close(), either unconditionally, or with if (jack_client_). It may also be executed without open() being called previously, or after a failing open() without close().


In bse/driver-jack.cc:

> +      }
+
+    /* setup PCM handle or shutdown */
+    if (error == 0)
+      {
+        flags_ |= Flags::OPENED;
+
+        uint dummy;
+        pcm_latency (&dummy, &dummy);   // debugging only: print latency values
+      }
+    else
+      {
+        disconnect_jack (jack_client_);
+        jack_client_ = nullptr;
+      }
+    JDEBUG ("JACK: opening PCM \"%s\" readupble=%d writable=%d: %s", devid_.c_str(), readable(), writable(), bse_error_blurb (error));

Please make this "JACK: %s: ...", devid_, ...
And fix "readable" ;-)


In bse/driver-jack.cc:

> +
+    /* enable processing in callback (if not already active) */
+    atomic_active_ = 1;
+
+    /* report jack driver xruns */
+    if (atomic_xruns_ != printed_xruns_)
+      {
+        printed_xruns_ = atomic_xruns_;
+        Bse::printerr ("JACK: %d beast driver xruns\n", printed_xruns_);
+      }
+    /* report jack shutdown */
+    if (is_down_ && !printed_is_down_)
+      {
+        printed_is_down_ = true;
+        Bse::printerr ("JACK: connection to jack server lost\n");
+        Bse::printerr ("JACK:  -> to continue, manually stop playback and restart\n");

Please add the devid_ to all printerr calls, I've also fixed this in the ALSA driver.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]