sorta-kinda working latency compensation, latency reporting and capture alignment ... working except that we report the wrong information to JACK and i've noticed a couple of odd circumstances where turning on a latent plugin caused punch recording to fail

git-svn-id: svn://localhost/ardour2/branches/3.0@9121 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2011-03-11 02:55:52 +00:00
parent e806084402
commit d155f32039
30 changed files with 352 additions and 362 deletions

View File

@ -166,7 +166,7 @@ public:
return _data + offset;
}
void prepare () { _written = false; }
void prepare () { _written = false; _silent = false; }
bool written() const { return _written; }
private:

View File

@ -41,11 +41,11 @@ class AudioPort : public Port
size_t raw_buffer_size (pframes_t nframes) const;
Buffer& get_buffer (framecnt_t nframes) {
Buffer& get_buffer (pframes_t nframes) {
return get_audio_buffer (nframes);
}
AudioBuffer& get_audio_buffer (framecnt_t nframes);
AudioBuffer& get_audio_buffer (pframes_t nframes);
protected:
friend class AudioEngine;

View File

@ -187,7 +187,6 @@ class AudioEngine : public SessionHandlePtr
void get_physical_inputs (DataType type, std::vector<std::string>&);
void update_total_latencies ();
void update_total_latency (const Port&);
Port *get_port_by_name (const std::string &);
@ -320,10 +319,8 @@ _ the regular process() call to session->process() is not made.
void set_jack_callbacks ();
#ifdef HAVE_JACK_NEW_LATENCY
static void _latency_callback (jack_latency_callback_mode_t, void*);
void jack_latency_callback (jack_latency_callback_mode_t);
#endif
int connect_to_jack (std::string client_name, std::string session_uuid);

View File

@ -75,7 +75,6 @@ public:
void flush_buffers (framecnt_t nframes, framepos_t time);
void no_outs_cuz_we_no_monitor(bool);
void cycle_start (pframes_t);
void increment_output_offset (framecnt_t);
void transport_stopped (framepos_t frame);
BufferSet& output_buffers() { return *_output_buffers; }
@ -105,7 +104,6 @@ public:
Role _role;
BufferSet* _output_buffers;
gain_t _current_gain;
framecnt_t _output_offset;
bool _no_outs_cuz_we_no_monitor;
boost::shared_ptr<MuteMaster> _mute_master;
bool no_panner_reset;

View File

@ -90,6 +90,7 @@ class IO : public SessionObject, public Latent
bool set_name (const std::string& str);
virtual void silence (framecnt_t);
void increment_port_buffer_offset (pframes_t offset);
int ensure_io (ChanCount cnt, bool clear, void *src);
@ -111,9 +112,6 @@ class IO : public SessionObject, public Latent
framecnt_t signal_latency () const { return _own_latency; }
framecnt_t latency () const;
void set_port_latency (framecnt_t);
void update_port_total_latencies ();
PortSet& ports() { return _ports; }
const PortSet& ports() const { return _ports; }

View File

@ -65,6 +65,8 @@ class IOProcessor : public Processor
void silence (framecnt_t nframes);
void disconnect ();
void increment_port_buffer_offset (pframes_t);
virtual bool feeds (boost::shared_ptr<Route> other) const;
PBD::Signal2<void,IOProcessor*,bool> AutomationPlaybackChanged;

View File

@ -46,11 +46,11 @@ class MidiPort : public Port {
size_t raw_buffer_size (pframes_t nframes) const;
Buffer& get_buffer (framecnt_t nframes) {
Buffer& get_buffer (pframes_t nframes) {
return get_midi_buffer (nframes);
}
MidiBuffer& get_midi_buffer (framecnt_t nframes);
MidiBuffer& get_midi_buffer (pframes_t nframes);
protected:
friend class AudioEngine;

View File

@ -52,8 +52,6 @@ public:
return DataType::MIDI;
}
void set_latency_delay (framecnt_t);
int export_stuff (BufferSet& bufs, framecnt_t nframes, framepos_t end_frame);
void freeze_me (InterThreadInfo&);

View File

@ -20,6 +20,8 @@
#ifndef __ardour_port_h__
#define __ardour_port_h__
#include "libardour-config.h"
#include <set>
#include <string>
#include <vector>
@ -45,9 +47,6 @@ public:
virtual ~Port ();
static void set_buffer_size (pframes_t sz) {
_buffer_size = sz;
}
static void set_connecting_blocked( bool yn ) {
_connecting_blocked = yn;
}
@ -93,16 +92,22 @@ public:
void ensure_monitor_input (bool);
bool monitoring_input () const;
framecnt_t total_latency () const;
int reestablish ();
int reconnect ();
void request_monitor_input (bool);
void set_latency (framecnt_t);
#ifdef HAVE_JACK_NEW_LATENCY
bool last_monitor() const { return _last_monitor; }
void set_last_monitor (bool yn) { _last_monitor = yn; }
jack_port_t* jack_port() const { return _jack_port; }
void get_connected_latency_range (jack_latency_range_t& range, bool playback) const;
void set_latency_range (jack_latency_range_t& range, bool playback) const;
#endif
void set_private_latency_range (jack_latency_range_t& range, bool playback);
const jack_latency_range_t& private_latency_range (bool playback) const;
void set_public_latency_range (jack_latency_range_t& range, bool playback) const;
jack_latency_range_t public_latency_range (bool playback) const;
virtual void reset ();
@ -110,10 +115,10 @@ public:
virtual size_t raw_buffer_size (pframes_t nframes) const = 0;
virtual DataType type () const = 0;
virtual void cycle_start (pframes_t) = 0;
virtual void cycle_start (pframes_t);
virtual void cycle_end (pframes_t) = 0;
virtual void cycle_split () = 0;
virtual Buffer& get_buffer (framecnt_t nframes) = 0;
virtual Buffer& get_buffer (pframes_t nframes) = 0;
virtual void flush_buffers (pframes_t nframes, framepos_t /*time*/) {}
virtual void transport_stopped () {}
@ -124,15 +129,18 @@ public:
PBD::Signal1<void,bool> MonitorInputChanged;
static framecnt_t port_offset() { return _port_offset; }
static void set_cycle_framecnt (pframes_t n) {
_cycle_nframes = n;
}
static framecnt_t port_offset() { return _global_port_buffer_offset; }
static void set_global_port_buffer_offset (pframes_t off) {
_global_port_buffer_offset = off;
}
static void increment_global_port_buffer_offset (pframes_t n) {
_global_port_buffer_offset += n;
}
static void set_port_offset (framecnt_t off) {
_port_offset = off;
}
static void increment_port_offset (framecnt_t n) {
_port_offset += n;
}
virtual void increment_port_buffer_offset (pframes_t n);
protected:
@ -140,25 +148,25 @@ protected:
jack_port_t* _jack_port; ///< JACK port
static pframes_t _buffer_size;
static bool _connecting_blocked;
static framecnt_t _port_offset;
static pframes_t _global_port_buffer_offset; /* access only from process() tree */
static pframes_t _cycle_nframes; /* access only from process() tree */
framecnt_t _port_buffer_offset; /* access only from process() tree */
jack_latency_range_t _private_playback_latency;
jack_latency_range_t _private_capture_latency;
static AudioEngine* _engine; ///< the AudioEngine
private:
friend class AudioEngine;
void recompute_total_latency () const;
/* XXX */
bool _last_monitor;
std::string _name; ///< port short name
Flags _flags; ///< flags
bool _last_monitor;
/** ports that we are connected to, kept so that we can
reconnect to JACK when required */
reconnect to JACK when required
*/
std::set<std::string> _connections;
};

View File

@ -250,11 +250,15 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember,
void all_processors_flip();
void all_processors_active (Placement, bool state);
void set_latency_ranges (bool playback) const;
virtual framecnt_t update_total_latency();
void set_latency_delay (framecnt_t);
framecnt_t set_private_port_latencies (bool playback) const;
void set_public_port_latencies (framecnt_t, bool playback) const;
framecnt_t update_signal_latency();
virtual void set_latency_compensation (framecnt_t);
void set_user_latency (framecnt_t);
framecnt_t initial_delay() const { return _initial_delay; }
framecnt_t signal_latency() const { return _signal_latency; }
PBD::Signal0<void> active_changed;
PBD::Signal0<void> phase_invert_changed;
@ -426,6 +430,7 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember,
boost::shared_ptr<IO> _output;
bool _active;
framecnt_t _signal_latency;
framecnt_t _initial_delay;
framecnt_t _roll_delay;
@ -517,7 +522,7 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember,
void set_mute_master_solo ();
void set_processor_positions ();
void update_port_latencies (const PortSet& ports, const PortSet& feeders, bool playback, framecnt_t) const;
framecnt_t update_port_latencies (const PortSet& ports, const PortSet& feeders, bool playback, framecnt_t) const;
void setup_invisible_processors ();

View File

@ -20,6 +20,8 @@
#ifndef __ardour_session_h__
#define __ardour_session_h__
#include "libardour-config.h"
#include <list>
#include <map>
#include <set>
@ -818,7 +820,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
protected:
friend class Route;
void schedule_curve_reallocation ();
void update_latency_compensation (bool, bool);
void update_latency_compensation (bool, bool, bool force=false);
private:
int create (const std::string& mix_template, BusProfile*);
@ -1364,9 +1366,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
void reset_jack_connection (jack_client_t* jack);
void process_rtop (SessionEvent*);
#ifdef HAVE_JACK_NEW_LATENCY
void update_latency (bool playback);
#endif
XMLNode& state(bool);

View File

@ -67,8 +67,7 @@ class Track : public Route, public PublicDiskstream
virtual void use_new_diskstream () = 0;
virtual void set_diskstream (boost::shared_ptr<Diskstream>);
framecnt_t update_total_latency();
void set_latency_delay (framecnt_t);
void set_latency_compensation (framecnt_t);
enum FreezeState {
NoFreeze,

View File

@ -390,12 +390,12 @@ AudioDiskstream::prepare_record_status(framepos_t capture_start_frame)
boost::shared_ptr<ChannelList> c = channels.reader();
for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
RingBufferNPT<CaptureTransition>::rw_vector transvec;
(*chan)->capture_transition_buf->get_write_vector(&transvec);
RingBufferNPT<CaptureTransition>::rw_vector transitions;
(*chan)->capture_transition_buf->get_write_vector (&transitions);
if (transvec.len[0] > 0) {
transvec.buf[0]->type = CaptureStart;
transvec.buf[0]->capture_val = capture_start_frame;
if (transitions.len[0] > 0) {
transitions.buf[0]->type = CaptureStart;
transitions.buf[0]->capture_val = capture_start_frame;
(*chan)->capture_transition_buf->increment_write_ptr(1);
} else {
// bad!
@ -1481,7 +1481,7 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo
RegionFactory::region_name (region_name, whole_file_region_name, false);
cerr << _name << ": based on ci of " << (*ci)->start << " for " << (*ci)->frames << " add region " << region_name << endl;
// cerr << _name << ": based on ci of " << (*ci)->start << " for " << (*ci)->frames << " add region " << region_name << endl;
try {
@ -1548,7 +1548,7 @@ AudioDiskstream::transport_looped (framepos_t transport_frame)
capture_captured += _capture_offset;
if (_alignment_style == ExistingMaterial) {
capture_captured += _session.worst_playback_latency();
capture_captured += _session.worst_output_latency();
} else {
capture_captured += _roll_delay;
}

View File

@ -45,6 +45,8 @@ AudioPort::cycle_start (pframes_t nframes)
{
/* caller must hold process lock */
Port::cycle_start (nframes);
if (sends_output()) {
_buffer->prepare ();
}
@ -67,10 +69,11 @@ AudioPort::cycle_split ()
}
AudioBuffer&
AudioPort::get_audio_buffer (framecnt_t nframes)
AudioPort::get_audio_buffer (pframes_t nframes)
{
/* caller must hold process lock */
_buffer->set_data ((Sample *) jack_port_get_buffer (_jack_port, nframes) + _port_offset, nframes);
_buffer->set_data ((Sample *) jack_port_get_buffer (_jack_port, _cycle_nframes) +
_global_port_buffer_offset + _port_buffer_offset, nframes);
return *_buffer;
}

View File

@ -387,6 +387,7 @@ AudioTrack::roll (pframes_t nframes, framepos_t start_frame, framepos_t end_fram
playback distance to zero, thus causing diskstream::commit
to do nothing.
*/
cerr << name() << " Can't operate at " << transport_frame << " since roll delay is only " << _roll_delay << endl;
return diskstream->process (transport_frame, 0, can_record, rec_monitors_input, need_butler);
}

View File

@ -190,11 +190,10 @@ AudioEngine::set_jack_callbacks ()
if( jack_set_session_callback)
jack_set_session_callback (_priv_jack, _session_callback, this);
#endif
#if HAVE_JACK_NEW_LATENCY
if (jack_set_latency_callback) {
jack_set_latency_callback (_priv_jack, _latency_callback, this);
}
#endif
jack_set_error_function (ardour_jack_error);
}
@ -389,13 +388,11 @@ AudioEngine::_registration_callback (jack_port_id_t /*id*/, int /*reg*/, void* a
ae->PortRegisteredOrUnregistered (); /* EMIT SIGNAL */
}
#ifdef HAVE_JACK_NEW_LATENCY
void
AudioEngine::_latency_callback (jack_latency_callback_mode_t mode, void* arg)
{
return static_cast<AudioEngine *> (arg)->jack_latency_callback (mode);
}
#endif
void
AudioEngine::_connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn, void* arg)
@ -413,9 +410,9 @@ AudioEngine::_connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int co
boost::shared_ptr<Ports> pr = ae->ports.reader ();
Ports::iterator i = pr->begin ();
while (i != pr->end() && (port_a == 0 || port_b == 0)) {
if (jack_port_a == (*i)->_jack_port) {
if (jack_port_a == (*i)->jack_port()) {
port_a = *i;
} else if (jack_port_b == (*i)->_jack_port) {
} else if (jack_port_b == (*i)->jack_port()) {
port_b = *i;
}
++i;
@ -429,7 +426,7 @@ AudioEngine::split_cycle (pframes_t offset)
{
/* caller must hold process lock */
AudioPort::increment_port_offset (offset);
Port::increment_global_port_buffer_offset (offset);
/* tell all Ports that we're going to start a new (split) cycle */
@ -512,7 +509,8 @@ AudioEngine::process_callback (pframes_t nframes)
/* tell all relevant objects that we're starting a new cycle */
Delivery::CycleStart (nframes);
AudioPort::set_port_offset (0);
Port::set_global_port_buffer_offset (0);
Port::set_cycle_framecnt (nframes);
InternalReturn::CycleStart (nframes);
/* tell all Ports that we're starting a new cycle */
@ -560,8 +558,8 @@ AudioEngine::process_callback (pframes_t nframes)
Port *port = (*i);
bool x;
if (port->_last_monitor != (x = port->monitoring_input ())) {
port->_last_monitor = x;
if (port->last_monitor() != (x = port->monitoring_input ())) {
port->set_last_monitor (x);
/* XXX I think this is dangerous, due to
a likely mutex in the signal handlers ...
*/
@ -621,7 +619,6 @@ AudioEngine::jack_sample_rate_callback (pframes_t nframes)
return 0;
}
#ifdef HAVE_JACK_NEW_LATENCY
void
AudioEngine::jack_latency_callback (jack_latency_callback_mode_t mode)
{
@ -629,7 +626,6 @@ AudioEngine::jack_latency_callback (jack_latency_callback_mode_t mode)
_session->update_latency (mode == JackPlaybackLatency);
}
}
#endif
int
AudioEngine::_bufsize_callback (pframes_t nframes, void *arg)
@ -1188,12 +1184,6 @@ AudioEngine::get_physical_outputs (DataType type, vector<string>& outs)
get_physical (type, JackPortIsInput, outs);
}
void
AudioEngine::update_total_latency (const Port& port)
{
port.recompute_total_latency ();
}
void
AudioEngine::transport_stop ()
{

View File

@ -59,7 +59,6 @@ Delivery::Delivery (Session& s, boost::shared_ptr<IO> io, boost::shared_ptr<Pann
, _role (r)
, _output_buffers (new BufferSet())
, _current_gain (1.0)
, _output_offset (0)
, _no_outs_cuz_we_no_monitor (false)
, _mute_master (mm)
, no_panner_reset (false)
@ -81,7 +80,6 @@ Delivery::Delivery (Session& s, boost::shared_ptr<Pannable> pannable, boost::sha
, _role (r)
, _output_buffers (new BufferSet())
, _current_gain (1.0)
, _output_offset (0)
, _no_outs_cuz_we_no_monitor (false)
, _mute_master (mm)
, no_panner_reset (false)
@ -123,16 +121,9 @@ Delivery::display_name () const
void
Delivery::cycle_start (pframes_t /*nframes*/)
{
_output_offset = 0;
_no_outs_cuz_we_no_monitor = false;
}
void
Delivery::increment_output_offset (framecnt_t n)
{
_output_offset += n;
}
bool
Delivery::can_support_io_configuration (const ChanCount& in, ChanCount& out) const
{

View File

@ -640,6 +640,9 @@ Diskstream::check_record_status (framepos_t transport_frame, bool can_record)
if (possibly_recording == last_possibly_recording) {
return;
}
framecnt_t existing_material_offset = _session.worst_playback_latency();
if (possibly_recording == fully_rec_enabled) {
if (last_possibly_recording == fully_rec_enabled) {
@ -650,52 +653,30 @@ Diskstream::check_record_status (framepos_t transport_frame, bool can_record)
first_recordable_frame = transport_frame + _capture_offset;
last_recordable_frame = max_framepos;
capture_start_frame = transport_frame;
capture_start_frame = _session.transport_frame();
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: @ %7 basic FRF = %2 LRF = %3 CSF = %4 CO = %5, WPL = %6\n",
/* in theory, we should be offsetting by _session.worst_playback_latency() when we adjust
for ExistingMaterial alignment. But that number includes the worst processor latency
across all routes, and each track will already be roll-delay adjusted to handle that.
so don't use worst_playback_latency(), just worst_output_latency() which covers
only downstream latency from IO ports.
*/
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: @ %7 basic FRF = %2 CSF = %4 CO = %5, EMO = %6 RD = %8\n",
name(), first_recordable_frame, last_recordable_frame, capture_start_frame,
_capture_offset,
_session.worst_playback_latency(),
transport_frame));
existing_material_offset,
transport_frame,
_roll_delay));
if (change & transport_rolling) {
/* transport-change (started rolling) */
if (_alignment_style == ExistingMaterial) {
/* audio played by ardour will take (up to) _session.worst_playback_latency() ("WOL") to
appear at the speakers; audio played at the time when it does appear at
the speakers will take _capture_offset to arrive back here. we've
already added _capture_offset, so now add WOL.
*/
first_recordable_frame += _session.worst_playback_latency();
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tROLL: shift FRF by delta between WOL %1\n",
first_recordable_frame));
} else {
first_recordable_frame += _roll_delay;
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tROLL: shift FRF by roll delay of %1 to %2\n",
_roll_delay, first_recordable_frame));
}
if (_alignment_style == ExistingMaterial) {
first_recordable_frame += existing_material_offset;
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tshift FRF by EMO %1\n",
first_recordable_frame));
} else {
/* punch in */
if (_alignment_style == ExistingMaterial) {
/* see comment in ExistingMaterial block above */
first_recordable_frame += _session.worst_playback_latency();
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tMANUAL PUNCH: shift FRF by delta between WOL and CO to %1\n",
first_recordable_frame));
} else {
capture_start_frame -= _roll_delay;
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tPUNCH: shift CSF by roll delay of %1 to %2\n",
_roll_delay, capture_start_frame));
}
capture_start_frame += _roll_delay;
DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tshift CFS by roll delay of %1 to %2\n",
_roll_delay, capture_start_frame));
}
prepare_record_status (capture_start_frame);
@ -707,7 +688,11 @@ Diskstream::check_record_status (framepos_t transport_frame, bool can_record)
/* we were recording last time */
if (change & transport_rolling) {
/* transport-change (stopped rolling): last_recordable_frame was set in ::prepare_to_stop() */
/* transport-change (stopped rolling): last_recordable_frame was set in ::prepare_to_stop(). We
had to set it there because we likely rolled past the stopping point to declick out,
and then backed up.
*/
} else {
/* punch out */
@ -715,7 +700,7 @@ Diskstream::check_record_status (framepos_t transport_frame, bool can_record)
last_recordable_frame = transport_frame + _capture_offset;
if (_alignment_style == ExistingMaterial) {
last_recordable_frame += _session.worst_input_latency();
last_recordable_frame += existing_material_offset;
} else {
last_recordable_frame += _roll_delay;
}

View File

@ -96,6 +96,18 @@ IO::~IO ()
}
}
void
IO::increment_port_buffer_offset (pframes_t offset)
{
/* io_lock, not taken: function must be called from Session::process() calltree */
if (_direction == Output) {
for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) {
i->increment_port_buffer_offset (offset);
}
}
}
void
IO::silence (framecnt_t nframes)
{
@ -1133,16 +1145,6 @@ IO::set_name (const string& requested_name)
return r;
}
void
IO::set_port_latency (framecnt_t nframes)
{
Glib::Mutex::Lock lm (io_lock);
for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) {
i->set_latency (nframes);
}
}
framecnt_t
IO::latency () const
{
@ -1154,26 +1156,17 @@ IO::latency () const
/* io lock not taken - must be protected by other means */
for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) {
if ((latency = i->total_latency ()) > max_latency) {
if ((latency = i->public_latency_range (_direction == Output).max) > max_latency) {
max_latency = latency;
}
}
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: max latency from %2 ports = %3\n",
name(), _ports.num_ports(), max_latency));
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: max %4 latency from %2 ports = %3\n",
name(), _ports.num_ports(), max_latency,
((_direction == Output) ? "PLAYBACK" : "CAPTURE")));
return max_latency;
}
void
IO::update_port_total_latencies ()
{
/* io_lock, not taken: function must be called from Session::process() calltree */
for (PortSet::iterator i = _ports.begin(); i != _ports.end(); ++i) {
_session.engine().update_total_latency (*i);
}
}
int
IO::connect_ports_to_bundle (boost::shared_ptr<Bundle> c, void* src)
{

View File

@ -249,6 +249,14 @@ IOProcessor::silence (framecnt_t nframes)
}
}
void
IOProcessor::increment_port_buffer_offset (pframes_t offset)
{
if (_own_output && _output) {
_output->increment_port_buffer_offset (offset);
}
}
ChanCount
IOProcessor::natural_output_streams() const
{

View File

@ -1104,7 +1104,7 @@ MidiDiskstream::transport_looped (framepos_t transport_frame)
capture_captured += _capture_offset;
if (_alignment_style == ExistingMaterial) {
capture_captured += _session.worst_playback_latency();
capture_captured += _session.worst_output_latency();
} else {
capture_captured += _roll_delay;
}

View File

@ -41,7 +41,10 @@ MidiPort::~MidiPort()
void
MidiPort::cycle_start (pframes_t nframes)
{
Port::cycle_start (nframes);
_buffer->clear ();
assert (_buffer->size () == 0);
if (sends_output ()) {
@ -50,7 +53,7 @@ MidiPort::cycle_start (pframes_t nframes)
}
MidiBuffer &
MidiPort::get_midi_buffer (framecnt_t nframes)
MidiPort::get_midi_buffer (pframes_t nframes)
{
if (_has_been_mixed_down) {
return *_buffer;
@ -59,7 +62,7 @@ MidiPort::get_midi_buffer (framecnt_t nframes)
if (receives_input ()) {
void* jack_buffer = jack_port_get_buffer (_jack_port, nframes);
const pframes_t event_count = jack_midi_get_event_count(jack_buffer);
const pframes_t event_count = jack_midi_get_event_count (jack_buffer);
assert (event_count < _buffer->capacity());
@ -78,17 +81,18 @@ MidiPort::get_midi_buffer (framecnt_t nframes)
continue;
}
if (ev.time >= _port_offset && ev.time < (_port_offset + nframes)) {
/* check that the event is in the acceptable time range */
if ((ev.time >= (_global_port_buffer_offset + _port_buffer_offset)) &&
(ev.time < (_global_port_buffer_offset + _port_buffer_offset + nframes))) {
_buffer->push_back (ev);
} else {
cerr << "Dropping incoming MIDI at time " << ev.time << "; offset=" << _port_offset << " limit=" << (_port_offset + nframes) << "\n";
cerr << "Dropping incoming MIDI at time " << ev.time << "; offset="
<< _global_port_buffer_offset << " limit="
<< (_global_port_buffer_offset + _port_buffer_offset + nframes) << "\n";
}
}
if (nframes) {
_has_been_mixed_down = true;
}
} else {
_buffer->silence (nframes);
}
@ -137,14 +141,16 @@ MidiPort::flush_buffers (pframes_t nframes, framepos_t time)
// event times are in frames, relative to cycle start
assert (ev.time() < (nframes + _port_offset));
assert (ev.time() < (nframes + _global_port_buffer_offset + _port_buffer_offset));
if (ev.time() >= _port_offset) {
if (ev.time() >= _global_port_buffer_offset + _port_buffer_offset) {
if (jack_midi_event_write (jack_buffer, (jack_nframes_t) ev.time(), ev.buffer(), ev.size()) != 0) {
cerr << "write failed, drop flushed note off on the floor, time " << ev.time() << " > " << _port_offset << endl;
cerr << "write failed, drop flushed note off on the floor, time "
<< ev.time() << " > " << _global_port_buffer_offset + _port_buffer_offset << endl;
}
} else {
cerr << "drop flushed event on the floor, time " << ev.time() << " < " << _port_offset << endl;
cerr << "drop flushed event on the floor, time " << ev.time()
<< " < " << _global_port_buffer_offset + _port_buffer_offset << endl;
}
}
}

View File

@ -464,13 +464,6 @@ MidiTrack::export_stuff (BufferSet& /*bufs*/, framecnt_t /*nframes*/, framepos_t
return -1;
}
void
MidiTrack::set_latency_delay (framecnt_t longest_session_latency)
{
Route::set_latency_delay (longest_session_latency);
_diskstream->set_roll_delay (_roll_delay);
}
boost::shared_ptr<Region>
MidiTrack::bounce (InterThreadInfo& /*itt*/)
{

View File

@ -40,15 +40,16 @@ using namespace ARDOUR;
using namespace PBD;
AudioEngine* Port::_engine = 0;
pframes_t Port::_buffer_size = 0;
bool Port::_connecting_blocked = false;
framecnt_t Port::_port_offset = 0;
pframes_t Port::_global_port_buffer_offset = 0;
pframes_t Port::_cycle_nframes = 0;
/** @param n Port short name */
Port::Port (std::string const & n, DataType t, Flags f)
: _last_monitor (false)
: _port_buffer_offset (0)
, _name (n)
, _flags (f)
, _last_monitor (false)
{
/* Unfortunately we have to pass the DataType into this constructor so that we can
@ -217,41 +218,79 @@ void
Port::reset ()
{
_last_monitor = false;
// XXX
// _metering = 0;
// reset_meters ();
}
void
Port::recompute_total_latency () const
Port::cycle_start (pframes_t nframes)
{
#ifndef HAVE_JACK_NEW_LATENCY
#ifdef HAVE_JACK_RECOMPUTE_LATENCY
jack_client_t* jack = _engine->jack();
if (!jack) {
return;
}
jack_recompute_total_latency (jack, _jack_port);
#endif
#endif
_port_buffer_offset = 0;
}
#ifdef HAVE_JACK_NEW_LATENCY
void
Port::set_latency_range (jack_latency_range_t& range, bool playback) const
Port::increment_port_buffer_offset (pframes_t nframes)
{
_port_buffer_offset += nframes;
}
void
Port::set_public_latency_range (jack_latency_range_t& range, bool playback) const
{
/* this sets the visible latency that the rest of JACK sees. because we do latency
compensation, all (most) of our visible port latency values are identical.
*/
if (!jack_port_set_latency_range) {
return;
}
DEBUG_TRACE (DEBUG::Latency, string_compose ("PORT %1 %4 latency now [%2 - %3]\n", name(),
range.min,
range.max,
(playback ? "PLAYBACK" : "CAPTURE")));;
jack_port_set_latency_range (_jack_port, (playback ? JackPlaybackLatency : JackCaptureLatency), &range);
}
#endif
#ifdef HAVE_JACK_NEW_LATENCY
void
Port::set_private_latency_range (jack_latency_range_t& range, bool playback)
{
if (playback) {
_private_playback_latency = range;
DEBUG_TRACE (DEBUG::Latency, string_compose ("PORT %1 playback latency now [%2 - %3]\n", name(),
_private_playback_latency.min,
_private_playback_latency.max));
} else {
_private_capture_latency = range;
DEBUG_TRACE (DEBUG::Latency, string_compose ("PORT %1 capture latency now [%2 - %3]\n", name(),
_private_playback_latency.min,
_private_playback_latency.max));
}
}
const jack_latency_range_t&
Port::private_latency_range (bool playback) const
{
if (playback) {
return _private_playback_latency;
} else {
return _private_capture_latency;
}
}
jack_latency_range_t
Port::public_latency_range (bool playback) const
{
jack_latency_range_t r;
jack_port_get_latency_range (_jack_port,
sends_output() ? JackPlaybackLatency : JackCaptureLatency,
&r);
DEBUG_TRACE (DEBUG::Latency, string_compose ("PORT %1: %4 public latency range %2 .. %3\n",
name(), r.min, r.max,
sends_output() ? "PLAYBACK" : "CAPTURE"));
return r;
}
void
Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) const
{
@ -280,11 +319,9 @@ Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) c
jack_port_t* remote_port = jack_port_by_name (_engine->jack(), (*c).c_str());
jack_latency_range_t lr;
DEBUG_TRACE (DEBUG::Latency, string_compose ("\t%1 connected to %2\n", name(), *c));
if (remote_port) {
jack_port_get_latency_range (remote_port, (playback ? JackPlaybackLatency : JackCaptureLatency), &lr);
DEBUG_TRACE (DEBUG::Latency, string_compose ("\t\tremote has latency range %1 .. %2\n", lr.min, lr.max));
DEBUG_TRACE (DEBUG::Latency, string_compose ("\t\%1 has latency range %2 .. %3\n", *c, lr.min, lr.max));
range.min = min (range.min, lr.min);
range.max = max (range.max, lr.max);
}
@ -296,29 +333,6 @@ Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) c
range.max = 0;
}
}
#endif /* HAVE_JACK_NEW_LATENCY */
framecnt_t
Port::total_latency () const
{
#ifndef HAVE_JACK_NEW_LATENCY
jack_client_t* jack = _engine->jack();
if (!jack) {
return 0;
}
return jack_port_get_total_latency (jack, _jack_port);
#else
jack_latency_range_t r;
jack_port_get_latency_range (_jack_port,
sends_output() ? JackPlaybackLatency : JackCaptureLatency,
&r);
DEBUG_TRACE (DEBUG::Latency, string_compose ("PORT %1: latency range %2 .. %3\n",
name(), r.min, r.max));
return r.max;
#endif
}
int
Port::reestablish ()
@ -329,7 +343,6 @@ Port::reestablish ()
return -1;
}
cerr << "RE-REGISTER: " << _name.c_str() << endl;
_jack_port = jack_port_register (jack, _name.c_str(), type().to_jack_type(), _flags, 0);
if (_jack_port == 0) {
@ -380,14 +393,6 @@ Port::request_monitor_input (bool yn)
jack_port_request_monitor (_jack_port, yn);
}
void
Port::set_latency (framecnt_t n)
{
#ifndef HAVE_JACK_NEW_LATENCY
jack_port_set_latency (_jack_port, n);
#endif
}
bool
Port::physically_connected () const
{

View File

@ -84,6 +84,7 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type)
, Automatable (sess)
, GraphNode( sess.route_graph )
, _active (true)
, _signal_latency (0)
, _initial_delay (0)
, _roll_delay (0)
, _flags (flg)
@ -919,7 +920,7 @@ Route::add_processor (boost::shared_ptr<Processor> processor, ProcessorList::ite
processor->activate ();
}
processor->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false, false));
processor->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false, false, false));
_output->set_user_latency (0);
}
@ -1056,7 +1057,7 @@ Route::add_processors (const ProcessorList& others, boost::shared_ptr<Processor>
}
}
(*i)->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false, false));
(*i)->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false, false, false));
}
for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
@ -2420,6 +2421,9 @@ Route::set_processor_state (const XMLNode& node)
}
for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
(*i)->ActiveChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, &_session, false, false, false));
boost::shared_ptr<PluginInsert> pi;
if ((pi = boost::dynamic_pointer_cast<PluginInsert>(*i)) != 0) {
@ -2837,16 +2841,24 @@ Route::check_initial_delay (framecnt_t nframes, framecnt_t& transport_frame)
nframes -= _roll_delay;
silence_unlocked (_roll_delay);
/* we've written _roll_delay of samples into the
output ports, so make a note of that for
future reference.
*/
_main_outs->increment_output_offset (_roll_delay);
transport_frame += _roll_delay;
/* shuffle all the port buffers for things that lead "out" of this Route
to reflect that we just wrote _roll_delay frames of silence.
*/
Glib::RWLock::ReaderLock lm (_processor_lock);
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
boost::shared_ptr<IOProcessor> iop = boost::dynamic_pointer_cast<IOProcessor> (*i);
if (iop) {
iop->increment_port_buffer_offset (_roll_delay);
}
}
_output->increment_port_buffer_offset (_roll_delay);
_roll_delay = 0;
}
}
return nframes;
}
@ -3040,41 +3052,24 @@ Route::add_export_point()
}
framecnt_t
Route::update_total_latency ()
Route::update_signal_latency ()
{
framecnt_t old = _output->effective_latency();
framecnt_t own_latency = _output->user_latency();
framecnt_t l = _output->user_latency();
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
if ((*i)->active ()) {
own_latency += (*i)->signal_latency ();
l += (*i)->signal_latency ();
}
}
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: bus: internal redirect latency = %2\n", _name, own_latency));
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: internal signal latency = %2\n", _name, l));
_output->set_port_latency (own_latency);
if (_output->user_latency() == 0) {
/* this (virtual) function is used for pure Routes,
not derived classes like AudioTrack. this means
that the data processed here comes from an input
port, not prerecorded material, and therefore we
have to take into account any input latency.
*/
own_latency += _input->signal_latency ();
}
if (old != own_latency) {
_output->set_latency_delay (own_latency);
if (_signal_latency != l) {
_signal_latency = l;
signal_latency_changed (); /* EMIT SIGNAL */
}
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: input latency = %2 total = %3\n", _name, _input->signal_latency(), own_latency));
return _output->effective_latency ();
return _signal_latency;
}
void
@ -3085,16 +3080,19 @@ Route::set_user_latency (framecnt_t nframes)
}
void
Route::set_latency_delay (framecnt_t longest_session_latency)
Route::set_latency_compensation (framecnt_t longest_session_latency)
{
framecnt_t old = _initial_delay;
if (_output->effective_latency() < longest_session_latency) {
_initial_delay = longest_session_latency - _output->effective_latency();
if (_signal_latency < longest_session_latency) {
_initial_delay = longest_session_latency - _signal_latency;
} else {
_initial_delay = 0;
}
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: compensate for maximum latency of %2, given own latency of %3, using initial delay of %4\n",
name(), longest_session_latency, _signal_latency, _initial_delay));
if (_initial_delay != old) {
initial_delay_changed (); /* EMIT SIGNAL */
}
@ -3595,33 +3593,10 @@ Route::unknown_processors () const
return p;
}
void
Route::set_latency_ranges (bool playback) const
framecnt_t
Route::update_port_latencies (const PortSet& from, const PortSet& to, bool playback, framecnt_t our_latency) const
{
framecnt_t own_latency = 0;
/* Processor list not protected by lock: MUST BE CALLED FROM PROCESS THREAD OR
LATENCY CALLBACK
*/
for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
if ((*i)->active ()) {
own_latency += (*i)->signal_latency ();
}
}
if (playback) {
update_port_latencies (_input->ports (), _output->ports (), true, own_latency);
} else {
update_port_latencies (_output->ports (), _input->ports (), false, own_latency);
}
}
void
Route::update_port_latencies (const PortSet& operands, const PortSet& feeders, bool playback, framecnt_t our_latency) const
{
#ifdef HAVE_JACK_NEW_LATENCY
/* we assume that all our input ports feed all our output ports. its not
universally true, but the alternative is way too corner-case to worry about.
*/
@ -3631,11 +3606,11 @@ Route::update_port_latencies (const PortSet& operands, const PortSet& feeders, b
all_connections.min = ~((jack_nframes_t) 0);
all_connections.max = 0;
/* iterate over all feeder ports and determine their relevant latency, taking
the maximum and minimum across all of them.
/* iterate over all "from" ports and determine the latency range for all of their
connections to the "outside" (outside of this Route).
*/
for (PortSet::const_iterator p = feeders.begin(); p != feeders.end(); ++p) {
for (PortSet::const_iterator p = from.begin(); p != from.end(); ++p) {
jack_latency_range_t range;
@ -3644,24 +3619,81 @@ Route::update_port_latencies (const PortSet& operands, const PortSet& feeders, b
all_connections.min = min (all_connections.min, range.min);
all_connections.max = max (all_connections.max, range.max);
}
/* set the "from" port latencies to the max/min range of all their connections */
for (PortSet::const_iterator p = from.begin(); p != from.end(); ++p) {
p->set_public_latency_range (all_connections, playback);
}
/* set the ports "in the direction of the flow" to the same value as above plus our own signal latency */
all_connections.min += our_latency;
all_connections.max += our_latency;
for (PortSet::const_iterator p = operands.begin(); p != operands.end(); ++p) {
p->set_latency_range (all_connections, playback);
DEBUG_TRACE (DEBUG::Latency, string_compose ("Port %1 %5 latency range %2 .. %3 (including route latency of %4)\n",
p->name(),
all_connections.min,
all_connections.max,
our_latency,
(playback ? "PLAYBACK" : "CAPTURE")));
for (PortSet::const_iterator p = to.begin(); p != to.end(); ++p) {
p->set_public_latency_range (all_connections, playback);
}
#endif
return all_connections.max;
}
framecnt_t
Route::set_private_port_latencies (bool playback) const
{
framecnt_t own_latency = 0;
/* Processor list not protected by lock: MUST BE CALLED FROM PROCESS THREAD OR
LATENCY CALLBACK.
This is called (early) from the latency callback. It computes the REAL latency associated
with each port and stores the result as the "private" latency of the port. A later
call to Route::set_public_port_latencies() sets all ports to the same value to reflect
the fact that we do latency compensation and so all signals are delayed by the
same amount as they flow through ardour.
*/
for (ProcessorList::const_iterator i = _processors.begin(); i != _processors.end(); ++i) {
if ((*i)->active ()) {
own_latency += (*i)->signal_latency ();
}
}
if (playback) {
/* playback: propagate latency from "outside the route" to outputs to inputs */
return update_port_latencies (_output->ports (), _input->ports (), true, own_latency);
} else {
/* capture: propagate latency from "outside the route" to inputs to outputs */
return update_port_latencies (_input->ports (), _output->ports (), false, own_latency);
}
}
void
Route::set_public_port_latencies (framecnt_t value, bool playback) const
{
/* this is called to set the JACK-visible port latencies, which take latency compensation
into account.
*/
jack_latency_range_t range;
range.min = value;
range.max = value;
{
const PortSet& ports (_input->ports());
for (PortSet::const_iterator p = ports.begin(); p != ports.end(); ++p) {
p->set_public_latency_range (range, playback);
}
}
{
const PortSet& ports (_output->ports());
for (PortSet::const_iterator p = ports.begin(); p != ports.end(); ++p) {
p->set_public_latency_range (range, playback);
}
}
}
/** Put the invisible processors in the right place in _processors.
* Must be called with a writer lock on _processor_lock held.

View File

@ -649,16 +649,14 @@ Session::when_engine_running ()
}
}
set_worst_io_latencies ();
_state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
/* hook us up to the engine */
BootMessage (_("Connect to engine"));
_engine.set_session (this);
_engine.update_total_latencies ();
update_latency_compensation (false, false, true);
}
void
@ -4153,13 +4151,13 @@ Session::unknown_processors () const
return p;
}
#ifdef HAVE_JACK_NEW_LATENCY
void
Session::update_latency (bool playback)
{
DEBUG_TRACE (DEBUG::Latency, "JACK latency callback\n");
DEBUG_TRACE (DEBUG::Latency, string_compose ("\n\nJACK latency callback: %1\n", (playback ? "PLAYBACK" : "CAPTURE")));
boost::shared_ptr<RouteList> r = routes.reader ();
framecnt_t max_latency = 0;
if (playback) {
/* reverse the list so that we work backwards from the last route to run to the first */
@ -4167,9 +4165,14 @@ Session::update_latency (bool playback)
}
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
DEBUG_TRACE (DEBUG::Latency, string_compose ("------------- Working on latency for %1\n", (*i)->name()));
(*i)->set_latency_ranges (playback);
DEBUG_TRACE (DEBUG::Latency, string_compose ("------------- Done working on latency for %1\n\n", (*i)->name()));
max_latency = max (max_latency, (*i)->set_private_port_latencies (playback));
}
#if 0
DEBUG_TRACE (DEBUG::Latency, string_compose ("Set public port latencies to %1\n", max_latency));
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->set_public_port_latencies (max_latency, playback);
}
}
#endif
}

View File

@ -932,7 +932,7 @@ Session::maybe_sync_start (pframes_t & nframes)
no_roll (sync_offset);
nframes -= sync_offset;
AudioPort::increment_port_offset (sync_offset);
Port::increment_global_port_buffer_offset (sync_offset);
waiting_for_sync_offset = false;
if (nframes == 0) {

View File

@ -1466,7 +1466,7 @@ Session::route_processors_changed (RouteProcessorChange c)
}
void
Session::update_latency_compensation (bool with_stop, bool abort)
Session::update_latency_compensation (bool with_stop, bool abort, bool force_whole_graph)
{
bool update_jack = false;
PostTransportWork ptw;
@ -1478,7 +1478,7 @@ Session::update_latency_compensation (bool with_stop, bool abort)
_worst_track_latency = 0;
ptw = post_transport_work();
DEBUG_TRACE(DEBUG::Latency, "---------------------------- update latency\n\n")
DEBUG_TRACE(DEBUG::Latency, "---------------------------- update latency compensation\n\n")
boost::shared_ptr<RouteList> r = routes.reader ();
@ -1488,30 +1488,28 @@ Session::update_latency_compensation (bool with_stop, bool abort)
(*i)->nonrealtime_handle_transport_stopped (abort, (ptw & PostTransportLocate), (!(ptw & PostTransportLocate) || pending_locate_flush));
}
framecnt_t old_latency = (*i)->output()->signal_latency ();
framecnt_t track_latency = (*i)->update_total_latency ();
framecnt_t old_latency = (*i)->signal_latency ();
framecnt_t new_latency = (*i)->update_signal_latency ();
if (old_latency != track_latency) {
#ifndef HAVE_JACK_NEW_LATENCY
(*i)->input()->update_port_total_latencies ();
(*i)->output()->update_port_total_latencies ();
#endif
if (old_latency != new_latency) {
update_jack = true;
}
if (!(*i)->is_hidden() && ((*i)->active())) {
_worst_track_latency = max (_worst_track_latency, track_latency);
_worst_track_latency = max (_worst_track_latency, new_latency);
}
}
if (update_jack) {
if (force_whole_graph || update_jack) {
/* trigger a full recompute of latency numbers for the graph
*/
_engine.update_total_latencies ();
}
}
DEBUG_TRACE(DEBUG::Latency, string_compose("worst case route internal latency was %1\n", _worst_track_latency));
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->set_latency_delay (_worst_track_latency);
(*i)->set_latency_compensation (_worst_track_latency);
}
set_worst_io_latencies ();
@ -1526,6 +1524,7 @@ Session::update_latency_compensation (bool with_stop, bool abort)
tr->set_capture_offset ();
}
}
DEBUG_TRACE(DEBUG::Latency, "---------------------------- DONE update latency compensation\n\n")
}
void

View File

@ -85,30 +85,6 @@ Track::toggle_monitor_input ()
}
}
ARDOUR::framecnt_t
Track::update_total_latency ()
{
framecnt_t old = _output->effective_latency();
framecnt_t own_latency = _output->user_latency();
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
if ((*i)->active ()) {
own_latency += (*i)->signal_latency ();
}
}
DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: track: internal redirect latency = %2\n", _name, own_latency));
_output->set_port_latency (own_latency);
if (old != own_latency) {
_output->set_latency_delay (own_latency);
signal_latency_changed (); /* EMIT SIGNAL */
}
return _output->effective_latency();
}
Track::FreezeRecord::~FreezeRecord ()
{
for (vector<FreezeRecordProcessorInfo*>::iterator i = processor_info.begin(); i != processor_info.end(); ++i) {
@ -218,9 +194,9 @@ Track::set_name (const string& str)
}
void
Track::set_latency_delay (framecnt_t longest_session_latency)
Track::set_latency_compensation (framecnt_t longest_session_latency)
{
Route::set_latency_delay (longest_session_latency);
Route::set_latency_compensation (longest_session_latency);
_diskstream->set_roll_delay (_roll_delay);
}

View File

@ -273,9 +273,9 @@ def configure(conf):
conf.check_cc(fragment = "#include <jack/jack.h>\nint main(int argc, char **argv) { jack_port_t* p; jack_latency_range_t r; jack_port_set_latency_range (p, JackCaptureLatency, &r); return 0; }\n",
linkflags = ['-ljack'],
msg = 'Checking for new JACK latency API',
define_name = 'HAVE_JACK_NEW_LATENCY',
uselib_store = "JACK_NEW_LATENCY",
okmsg = 'present')
okmsg = 'present',
mandatory = True,
errmsg = 'missing - a version of JACK that supports jack_port_set_latency_range() is required to compile Ardour3')
conf.check_cc(fragment = '#include <jack/jack.h>\nint main(int argc, char **argv) { jack_port_type_get_buffer_size ((jack_client_t*)0, ""); }\n',
linkflags = ['-ljack'],