diff --git a/lib/ntk b/lib/ntk index d006352..38275d6 160000 --- a/lib/ntk +++ b/lib/ntk @@ -1 +1 @@ -Subproject commit d0063527aa360a3f1fd34e76fc0dd9125efb9202 +Subproject commit 38275d616800d5af826fd4a9c761a98068e7311a diff --git a/mixer/src/Chain.C b/mixer/src/Chain.C index f461d93..5814835 100644 --- a/mixer/src/Chain.C +++ b/mixer/src/Chain.C @@ -395,6 +395,32 @@ Chain::configure_ports ( void ) parent()->redraw(); } +/** invoked from the JACK latency callback... We need to update the latency values on this chains ports */ +void +Chain::set_latency ( JACK::Port::direction_e dir ) +{ + nframes_t total_latency = 0; + + if ( dir == JACK::Port::Input ) + { + for ( int i = 0; i < modules(); ++i ) + { + Module *m = module( i ); + total_latency += m->get_latency( dir ); + m->set_latency( dir, total_latency ); + } + } + else + { + for ( int i = modules(); i--; ) + { + Module *m = module( i ); + total_latency += m->get_latency( dir ); + m->set_latency( dir, total_latency ); + } + } +} + int Chain::get_module_instance_number ( Module *m ) { diff --git a/mixer/src/Chain.H b/mixer/src/Chain.H index 3946d6f..c478b5d 100644 --- a/mixer/src/Chain.H +++ b/mixer/src/Chain.H @@ -125,6 +125,8 @@ public: _configure_outputs_userdata = v; } + void set_latency ( JACK::Port::direction_e ); + Fl_Callback * configure_outputs_callback ( void ) const { return _configure_outputs_callback; } virtual void log_children ( void ) const; diff --git a/mixer/src/Group.C b/mixer/src/Group.C index 7e682ea..1f57642 100644 --- a/mixer/src/Group.C +++ b/mixer/src/Group.C @@ -98,6 +98,18 @@ Group::set ( Log_Entry &e ) /* Callbacks */ /*************/ +void +Group::latency ( jack_latency_callback_mode_t mode ) +{ + for ( std::list::iterator i = strips.begin(); + i != strips.end(); + i++ ) + { + if ( (*i)->chain() ) + (*i)->chain()->set_latency(mode == JackCaptureLatency ? JACK::Port::Input : JACK::Port::Output ); + } +} + /* THREAD: RT */ /** This is the jack xrun callback */ int diff --git a/mixer/src/Group.H b/mixer/src/Group.H index 818596e..5d3dd3d 100644 --- a/mixer/src/Group.H +++ b/mixer/src/Group.H @@ -52,6 +52,7 @@ class Group : public Loggable, public JACK::Client, public Mutex int buffer_size ( nframes_t nframes ); void thread_init ( void ); void port_connect ( jack_port_id_t a, jack_port_id_t b, int connect ); + virtual void latency ( jack_latency_callback_mode_t mode ); /* not allowed */ Group ( const Group &rhs ); diff --git a/mixer/src/JACK_Module.H b/mixer/src/JACK_Module.H index f080766..25e3d97 100644 --- a/mixer/src/JACK_Module.H +++ b/mixer/src/JACK_Module.H @@ -79,6 +79,7 @@ public: LOG_CREATE_FUNC( JACK_Module ); + protected: virtual void process ( nframes_t nframes ); diff --git a/mixer/src/Module.C b/mixer/src/Module.C index a7af209..19ff545 100644 --- a/mixer/src/Module.C +++ b/mixer/src/Module.C @@ -127,6 +127,7 @@ Module::~Module ( ) void Module::init ( void ) { +// _latency = 0; _is_default = false; _editor = 0; _chain = 0; @@ -1089,6 +1090,76 @@ Module::thaw_ports ( void ) } } +nframes_t +Module::get_latency ( JACK::Port::direction_e dir ) const +{ + nframes_t tmin = 0; + nframes_t tmax = 0; + + if ( dir == JACK::Port::Input ) + { + if ( aux_audio_input.size() ) + { + for ( unsigned int i = 0; i < aux_audio_input.size(); i++ ) + { + nframes_t min,max; + + aux_audio_input[i].jack_port()->get_latency( dir, &min, &max ); + + tmin += min; + tmax += max; + } + + tmin /= aux_audio_input.size(); + tmax /= aux_audio_input.size(); + } + + return tmin; + /* for ( unsigned int i = 0; i < aux_audio_output.size(); i++ ) */ + /* aux_audio_output[i].set_latency( dir, tmin, tmax ); */ + } + else + { + if ( aux_audio_output.size() ) + { + for ( unsigned int i = 0; i < aux_audio_output.size(); i++ ) + { + nframes_t min,max; + + aux_audio_output[i].jack_port()->get_latency( dir, &min, &max ); + + tmin += min; + tmax += max; + } + + tmin /= aux_audio_output.size(); + tmax /= aux_audio_output.size(); + } + + return tmin; + + + /* for ( unsigned int i = 0; i < aux_audio_output.size(); i++ ) */ + /* aux_audio_output[i].set_latency( dir, tmin, tmax ); */ + } +} + +void +Module::set_latency ( JACK::Port::direction_e dir, nframes_t latency ) +{ + if ( dir == JACK::Port::Input ) + { + for ( unsigned int i = 0; i < aux_audio_output.size(); i++ ) + aux_audio_output[i].jack_port()->set_latency( dir, latency, latency ); + } + else + { + for ( unsigned int i = 0; i < aux_audio_input.size(); i++ ) + aux_audio_input[i].jack_port()->set_latency( dir, latency, latency ); + } +} + + bool Module::add_aux_port ( bool input, const char *prefix, int i ) { diff --git a/mixer/src/Module.H b/mixer/src/Module.H index 716cc0f..1c852ec 100644 --- a/mixer/src/Module.H +++ b/mixer/src/Module.H @@ -46,6 +46,7 @@ class Module : public Fl_Group, public Loggable { nframes_t _nframes; Chain *_chain; bool _is_default; +// nframes_t _latency; Module_Parameter_Editor *_editor; @@ -71,6 +72,9 @@ protected: public: + virtual nframes_t get_latency ( JACK::Port::direction_e dir ) const; + virtual void set_latency ( JACK::Port::direction_e dir, nframes_t latency ); + /* true if this module was added by default and not under normal user control */ bool is_default ( void ) const { return _is_default; } void is_default ( bool v ) { _is_default = v; } @@ -276,7 +280,7 @@ public: } void jack_port ( JACK::Port *v ) { _jack_port = v; } - JACK::Port *jack_port ( void ) { return _jack_port; } + JACK::Port *jack_port ( void ) const { return _jack_port; } private: diff --git a/mixer/src/Plugin_Module.C b/mixer/src/Plugin_Module.C index ab52daa..dd57d97 100644 --- a/mixer/src/Plugin_Module.C +++ b/mixer/src/Plugin_Module.C @@ -124,6 +124,8 @@ Plugin_Module::set ( Log_Entry &e ) void Plugin_Module::init ( void ) { + _latency = 0; + _last_latency = 0; _idata = new Plugin_Module::ImplementationData(); _idata->handle.clear(); /* module will be bypassed until plugin is loaded */ @@ -138,6 +140,19 @@ Plugin_Module::init ( void ) bbox( tx, ty, tw, th ); } +void +Plugin_Module::update ( void ) +{ + if ( _last_latency != _latency ) + { + DMESSAGE( "Plugin latency changed to %lu", (unsigned long)_latency ); + + chain()->client()->recompute_latencies(); + } + + _last_latency = _latency; +} + int Plugin_Module::can_support_inputs ( int n ) { @@ -341,7 +356,7 @@ Plugin_Module::plugin_instances ( unsigned int n ) { LADSPA_Handle h; - DMESSAGE( "Instantiating plugin..." ); + DMESSAGE( "Instantiating plugin... with sample rate %lu", (unsigned long)sample_rate()); if ( ! (h = _idata->descriptor->instantiate( _idata->descriptor, sample_rate() ) ) ) { @@ -391,6 +406,31 @@ Plugin_Module::bypass ( bool v ) } } +nframes_t +Plugin_Module::get_plugin_latency ( void ) const +{ + for ( unsigned int i = ncontrol_outputs(); i--; ) + { + if ( !strcasecmp( "latency", control_output[i].name() ) ) + { + return control_output[i].control_value(); + } + } + + return 0; +} + + +nframes_t +Plugin_Module::get_latency ( JACK::Port::direction_e dir ) const +{ + nframes_t latency = Module::get_latency( dir ); + + latency += get_plugin_latency(); + + return latency; +} + bool Plugin_Module::load ( unsigned long id ) { @@ -467,6 +507,7 @@ Plugin_Module::load ( unsigned long id ) Port p( this, d, Port::CONTROL, _idata->descriptor->PortNames[ i ] ); + p.hints.default_value = 0; LADSPA_PortRangeHintDescriptor hd = _idata->descriptor->PortRangeHints[i].HintDescriptor; @@ -768,6 +809,8 @@ Plugin_Module::process ( nframes_t nframes ) buffer_copy( (sample_t*)audio_output[1].buffer(), (sample_t*)audio_input[0].buffer(), nframes ); } } + + _latency = get_plugin_latency(); } diff --git a/mixer/src/Plugin_Module.H b/mixer/src/Plugin_Module.H index f709207..d7c420c 100644 --- a/mixer/src/Plugin_Module.H +++ b/mixer/src/Plugin_Module.H @@ -65,6 +65,11 @@ public: private: + volatile nframes_t _latency; + nframes_t _last_latency; + + nframes_t get_plugin_latency ( void ) const; + void init ( void ); void bbox ( int &X, int &Y, int &W, int &H ) @@ -106,6 +111,9 @@ private: public: + virtual void update ( void ); + virtual nframes_t get_latency ( JACK::Port::direction_e dir ) const; + static std::list get_all_plugins ( void ); static void spawn_discover_thread ( void ); diff --git a/nonlib/JACK/Client.C b/nonlib/JACK/Client.C index 489f439..7bae8f1 100644 --- a/nonlib/JACK/Client.C +++ b/nonlib/JACK/Client.C @@ -123,6 +123,12 @@ namespace JACK ((Client*)arg)->thread_init(); } + void + Client::latency ( jack_latency_callback_mode_t mode, void *arg ) + { + ((Client*)arg)->latency( mode ); + } + void Client::shutdown ( void *arg ) { @@ -175,6 +181,10 @@ namespace JACK set_callback( port_connect ); jack_set_sample_rate_callback( _client, &Client::sample_rate_changed, this ); + +#ifdef HAVE_JACK_PORT_GET_LATENCY_RANGE + set_callback( latency ); +#endif /* FIXME: should we wait to register this until after the project has been loaded (and we have disk threads running)? */ @@ -296,6 +306,12 @@ namespace JACK return s; } + void + Client::recompute_latencies ( void ) + { + jack_recompute_total_latencies( _client ); + } + void Client::transport_stop ( ) { diff --git a/nonlib/JACK/Client.H b/nonlib/JACK/Client.H index e753b4f..5954188 100644 --- a/nonlib/JACK/Client.H +++ b/nonlib/JACK/Client.H @@ -66,6 +66,9 @@ namespace JACK static void thread_init ( void *arg ); virtual void thread_init ( void ) = 0; + static void latency ( jack_latency_callback_mode_t mode, void *arg ); + virtual void latency ( jack_latency_callback_mode_t mode ) { } + Client ( const Client &rhs ); Client & operator = ( const Client &rhs ); @@ -89,7 +92,7 @@ namespace JACK SLOW_SYNC = 1 << 0, TIMEBASE_MASTER = 1 << 1 }; - jack_client_t * jack_client ( void ) { return _client; } + jack_client_t * jack_client ( void ) const { return _client; } void port_added ( JACK::Port * p ); void port_removed ( JACK::Port *p ); @@ -116,7 +119,8 @@ namespace JACK void transport_start ( void ); void transport_locate ( nframes_t frame ); jack_transport_state_t transport_query ( jack_position_t *pos ); - + + void recompute_latencies ( void ); static int maximum_name_length ( void ) { return jack_client_name_size(); } }; diff --git a/nonlib/JACK/Port.C b/nonlib/JACK/Port.C index 45ccf75..a15cb0c 100644 --- a/nonlib/JACK/Port.C +++ b/nonlib/JACK/Port.C @@ -38,8 +38,7 @@ namespace JACK { return jack_port_name_size() - jack_client_name_size() - 6; } - - + Port::Port ( const Port &rhs ) { _connections = NULL; @@ -131,16 +130,16 @@ namespace JACK { _client->port_removed( this ); - if ( _name ) - { - free( _name ); - _name = NULL; - } - if ( _trackname ) - { - free( _trackname ); - _trackname = NULL; - } + if ( _name ) + { + free( _name ); + _name = NULL; + } + if ( _trackname ) + { + free( _trackname ); + _trackname = NULL; + } } @@ -207,18 +206,13 @@ namespace JACK /** returns the sum of latency of all ports between this one and a terminal port. */ -/* FIMXE: how does JACK know that input A of client Foo connects to - output Z of the same client in order to draw the line through Z to a - terminal port? And, if this determination cannot be made, what use is - this function? */ - nframes_t Port::total_latency ( void ) const { #ifdef HAVE_JACK_PORT_GET_LATENCY_RANGE jack_latency_range_t range; - jack_port_get_latency_range( _port, _direction == Output ? JackPlaybackLatency : JackCaptureLatency, &range ); + jack_port_get_latency_range( _port, _direction == Input ? JackPlaybackLatency : JackCaptureLatency, &range ); return range.max; #else @@ -227,33 +221,36 @@ namespace JACK } /** returns the number of frames of latency assigned to this port */ - nframes_t - Port::latency ( void ) const + void + Port::get_latency ( direction_e dir, nframes_t *min, nframes_t *max ) const { #ifdef HAVE_JACK_PORT_GET_LATENCY_RANGE jack_latency_range_t range; - jack_port_get_latency_range( _port, _direction == Output ? JackPlaybackLatency : JackCaptureLatency, &range ); + jack_port_get_latency_range( _port, dir == Output ? JackPlaybackLatency : JackCaptureLatency, &range ); - return range.max; + *min = range.min; + *max = range.max; #else - return jack_port_get_latency( _port ); + *min = *max = jack_port_get_latency( _port ); #endif } /** inform JACK that port has /frames/ frames of latency */ void - Port::latency ( nframes_t frames ) + Port::set_latency ( direction_e dir, nframes_t min, nframes_t max ) { #ifdef HAVE_JACK_PORT_GET_LATENCY_RANGE jack_latency_range_t range; +// DMESSAGE( "Setting port latency!" ); - range.min = range.max = frames; - - jack_port_set_latency_range( _port, _direction == Output ? JackPlaybackLatency : JackCaptureLatency, &range ); + range.max = max; + range.min = min; + + jack_port_set_latency_range( _port, dir == Output ? JackPlaybackLatency : JackCaptureLatency, &range ); #else - jack_port_set_latency( _port, frames ); + jack_port_set_latency( _port, max ); #endif } diff --git a/nonlib/JACK/Port.H b/nonlib/JACK/Port.H index 8d2558d..27f4ea8 100644 --- a/nonlib/JACK/Port.H +++ b/nonlib/JACK/Port.H @@ -72,8 +72,11 @@ namespace JACK // bool name ( const char *base, int n, const char *type=0 ); nframes_t total_latency ( void ) const; - nframes_t latency ( void ) const; - void latency ( nframes_t frames ); + + void get_latency ( direction_e dir, nframes_t *min, nframes_t *max ) const; + + /* it's only valid to call this in a latency callback! */ + void set_latency ( direction_e dir, nframes_t min, nframes_t max ); void terminal ( bool b ) { _terminal = b; } bool activate ( void ); @@ -96,6 +99,8 @@ namespace JACK private: + friend class Client; + direction_e _direction; type_e _type; bool _terminal; diff --git a/timeline/src/Audio_Region.C b/timeline/src/Audio_Region.C index 92835ae..b6e705d 100644 --- a/timeline/src/Audio_Region.C +++ b/timeline/src/Audio_Region.C @@ -643,7 +643,8 @@ Audio_Region::draw ( void ) } } else - WARNING( "Pbuf == %p, peaks = %lu", pbuf, (unsigned long)peaks ); + ; +// WARNING( "Pbuf == %p, peaks = %lu", pbuf, (unsigned long)peaks ); if ( peaks < loop_peaks_needed ) { diff --git a/timeline/src/Engine/Engine.C b/timeline/src/Engine/Engine.C index ea9b227..4cdab91 100644 --- a/timeline/src/Engine/Engine.C +++ b/timeline/src/Engine/Engine.C @@ -80,6 +80,22 @@ Engine::buffer_size ( nframes_t nframes ) return 0; } +nframes_t +Engine::playback_latency ( void ) const +{ +#ifdef HAVE_JACK_PORT_GET_LATENCY_RANGE + jack_latency_range_t range; + + jack_port_get_latency_range( jack_port_by_name( jack_client(), "system:playback_1" ), + JackPlaybackLatency, + &range ); + + return range.min; +#else + return jack_port_get_latency( jack_port_by_name( jack_client(), "system:playback_1" ) ); +#endif +} + /* THREAD: RT */ /** This is the jack slow-sync callback. */ int diff --git a/timeline/src/Engine/Engine.H b/timeline/src/Engine/Engine.H index cea7713..cf1d426 100644 --- a/timeline/src/Engine/Engine.H +++ b/timeline/src/Engine/Engine.H @@ -61,6 +61,7 @@ public: int dropped ( void ) const { return _buffers_dropped; } nframes_t system_latency ( void ) const { return nframes(); } + nframes_t playback_latency ( void ) const; float frames_to_milliseconds ( nframes_t frames ) { diff --git a/timeline/src/Engine/Timeline.C b/timeline/src/Engine/Timeline.C index 8ef71d0..91f1735 100644 --- a/timeline/src/Engine/Timeline.C +++ b/timeline/src/Engine/Timeline.C @@ -323,15 +323,3 @@ Timeline::total_capture_xruns ( void ) return r; } - -#include "Engine.H" -extern Engine *engine; - -nframes_t -Timeline::total_output_latency ( void ) const -{ - /* Due to flaws in the JACK latency reporting API, we cannot - * reliably account for software latency. Using the system latency - * is the best we can do here. */ - return engine->system_latency(); -} diff --git a/timeline/src/Engine/Track.C b/timeline/src/Engine/Track.C index 1976502..5fa84e6 100644 --- a/timeline/src/Engine/Track.C +++ b/timeline/src/Engine/Track.C @@ -301,6 +301,28 @@ Track::record ( Capture *c, nframes_t frame ) // Fl::unlock(); c->region->prepare(); + + nframes_t min,max; + + input[0].get_latency( JACK::Port::Input, &min, &max ); + + if ( transport->freewheel_enabled() ) + { + /* in freewheeling mode, assume we're bouncing and only + * compensate for capture latency */ + _capture_offset = min; + } + else + { + /* not freewheeling, so assume we're overdubbing and need to + * compensate for both capture and playback latency */ + _capture_offset = min; + + /* since the track output might not be connected to + * anything, just get the playback latency */ + + _capture_offset += engine->playback_latency(); + } } /** write a block to the (already opened) capture file */ @@ -331,22 +353,10 @@ Track::finalize ( Capture *c, nframes_t frame ) c->region->finalize( frame ); - nframes_t capture_offset = 0; + DMESSAGE( "Adjusting capture by %lu frames.", (unsigned long)_capture_offset ); - /* Add the system latency twice. Once for the input (usually - * required) and again for the output latency of whatever we're - * playing along to (should only apply when overdubbing) */ - - /* Limitations in the JACK latency reporting API prevent us from - * compensating from any software latency introduced by other - * clients in our graph... Oh well */ - - capture_offset += engine->system_latency(); - capture_offset += engine->system_latency(); - - DMESSAGE( "Adjusting capture by %lu frames.", (unsigned long)capture_offset ); - - c->region->offset( capture_offset ); + c->region->offset( _capture_offset ); + _capture_offset = 0; timeline->unlock(); } diff --git a/timeline/src/TLE.fl b/timeline/src/TLE.fl index 3dcf1f5..73cf67c 100644 --- a/timeline/src/TLE.fl +++ b/timeline/src/TLE.fl @@ -923,7 +923,7 @@ static char stats[100]; if ( engine && ! engine->zombified() ) { snprintf( stats, sizeof( stats ), "latency: %.1fms, xruns: %d", - engine->frames_to_milliseconds( timeline->total_output_latency() ), + engine->frames_to_milliseconds( engine->system_latency() ), engine->xruns() ); } else diff --git a/timeline/src/Timeline.H b/timeline/src/Timeline.H index 868cd19..e21fde7 100644 --- a/timeline/src/Timeline.H +++ b/timeline/src/Timeline.H @@ -263,7 +263,6 @@ public: int total_playback_xruns ( void ); int total_capture_xruns ( void ); - nframes_t total_output_latency ( void ) const; bool record ( void ); void stop ( void ); diff --git a/timeline/src/Track.C b/timeline/src/Track.C index 9d365fd..f772422 100644 --- a/timeline/src/Track.C +++ b/timeline/src/Track.C @@ -128,6 +128,7 @@ Track::~Track ( ) void Track::init ( void ) { + _capture_offset = 0; _row = 0; _sequence = NULL; _name = NULL; diff --git a/timeline/src/Track.H b/timeline/src/Track.H index b09ecd5..8e7df31 100644 --- a/timeline/src/Track.H +++ b/timeline/src/Track.H @@ -105,6 +105,8 @@ private: int _row; + nframes_t _capture_offset; + enum { AUDIO } _type; Audio_Sequence *_sequence; diff --git a/timeline/src/Transport.C b/timeline/src/Transport.C index 66990c0..8e54c78 100644 --- a/timeline/src/Transport.C +++ b/timeline/src/Transport.C @@ -190,6 +190,12 @@ Transport::toggle_record ( void ) update_record_state(); } +bool +Transport::freewheel_enabled ( void ) const +{ + return _freewheel_button->value(); +} + bool Transport::rec_enabled ( void ) const { diff --git a/timeline/src/Transport.H b/timeline/src/Transport.H index e728f34..8323222 100644 --- a/timeline/src/Transport.H +++ b/timeline/src/Transport.H @@ -58,6 +58,7 @@ public: Transport ( int X, int Y, int W, int H, const char *L=0 ); + bool freewheel_enabled ( void ) const; bool rec_enabled ( void ) const; bool punch_enabled ( void ) const; bool loop_enabled ( void ) const;