From 86fa1ce238af051878b387b09b2cb81172c5f7c2 Mon Sep 17 00:00:00 2001 From: Jonathan Moore Liles Date: Sun, 18 Jan 2009 21:25:28 -0600 Subject: [PATCH] Implement capture-offset latency compensation. --- Timeline/Engine/Engine.C | 1 - Timeline/Engine/Engine.H | 7 +++++++ Timeline/Engine/Playback_DS.C | 22 ++++++++++++++++++---- Timeline/Engine/Playback_DS.H | 6 ++++++ Timeline/Engine/Port.C | 27 +++++++++++++++++++++++++++ Timeline/Engine/Port.H | 4 +++- Timeline/Engine/Timeline.C | 12 ++++++++++++ Timeline/Engine/Track.C | 29 +++++++++++++++++++++++++++++ Timeline/TLE.fl | 20 ++++++++++++-------- Timeline/Timeline.H | 1 + Timeline/Track.H | 1 + 11 files changed, 116 insertions(+), 14 deletions(-) diff --git a/Timeline/Engine/Engine.C b/Timeline/Engine/Engine.C index 512317b..9d7ea38 100644 --- a/Timeline/Engine/Engine.C +++ b/Timeline/Engine/Engine.C @@ -205,7 +205,6 @@ Engine::timebase ( jack_transport_state_t, jack_nframes_t, jack_position_t *pos, /* FIXME: fill this in */ pos->bar_start_tick = 0; - } /* THREAD: RT */ diff --git a/Timeline/Engine/Engine.H b/Timeline/Engine/Engine.H index 6e1eca1..8411c99 100644 --- a/Timeline/Engine/Engine.H +++ b/Timeline/Engine/Engine.H @@ -86,7 +86,14 @@ public: void freewheeling ( bool yes ); bool zombified ( void ) const { return _zombified; } + nframes_t system_latency ( void ) const { return nframes(); } + float cpu_load ( void ) const { return jack_cpu_load( _client ); } + + float frames_to_milliseconds ( nframes_t frames ) + { + return ( frames * 1000 ) / (float)frame_rate(); + } }; diff --git a/Timeline/Engine/Playback_DS.C b/Timeline/Engine/Playback_DS.C index 9802339..5d15833 100644 --- a/Timeline/Engine/Playback_DS.C +++ b/Timeline/Engine/Playback_DS.C @@ -60,6 +60,14 @@ Playback_DS::seek ( nframes_t frame ) flush(); } +/** set the playback delay to /frames/ frames. This be called prior to +a seek. */ +void +Playback_DS::delay ( nframes_t frames ) +{ + _delay = frames; +} + /** read /nframes/ from the attached track into /buf/ */ void Playback_DS::read_block ( sample_t *buf, nframes_t nframes ) @@ -76,16 +84,22 @@ Playback_DS::read_block ( sample_t *buf, nframes_t nframes ) if ( ! sequence() ) { + /* FIXME: what to do here? */ // _frame += _nframes; return; } timeline->rdlock(); - if ( sequence()->play( buf, _frame, nframes, channels() ) ) - _frame += nframes; - else - WARNING( "Programming error?" ); + /* FIXME: how does this work if _delay is not a multiple of bufsize? */ + + if ( _frame >= _delay ) + { + if ( ! sequence()->play( buf, _frame - _delay, nframes, channels() ) ) + WARNING( "Programming error?" ); + } + + _frame += nframes; timeline->unlock(); } diff --git a/Timeline/Engine/Playback_DS.H b/Timeline/Engine/Playback_DS.H index bced5f3..cea5c75 100644 --- a/Timeline/Engine/Playback_DS.H +++ b/Timeline/Engine/Playback_DS.H @@ -27,11 +27,15 @@ class Playback_DS : public Disk_Stream void flush ( void ) { base_flush( true ); } + volatile nframes_t _delay; /* number of frames this diskstream should be delayed by */ + public: Playback_DS ( Track *th, float frame_rate, nframes_t nframes, int channels ) : Disk_Stream( th, frame_rate, nframes, channels ) { + _delay = 0; + run(); } @@ -39,4 +43,6 @@ public: void seek ( nframes_t frame ); nframes_t process ( nframes_t nframes ); + void delay ( nframes_t v ); + }; diff --git a/Timeline/Engine/Port.C b/Timeline/Engine/Port.C index dc73f59..57464c6 100644 --- a/Timeline/Engine/Port.C +++ b/Timeline/Engine/Port.C @@ -95,6 +95,33 @@ Port::activate ( const char *name, type_e dir ) 0 ); } +/** 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 +{ + return jack_port_get_total_latency( engine->client(), _port ); +} + +/** returns the number of frames of latency assigned to this port */ +nframes_t +Port::latency ( void ) const +{ + return jack_port_get_latency( _port ); +} + +/** inform JACK that port has /frames/ frames of latency */ +void +Port::latency ( nframes_t frames ) +{ + jack_port_set_latency( _port, frames ); +} + void Port::shutdown ( void ) { diff --git a/Timeline/Engine/Port.H b/Timeline/Engine/Port.H index fc6f81f..f9716d6 100644 --- a/Timeline/Engine/Port.H +++ b/Timeline/Engine/Port.H @@ -63,7 +63,9 @@ public: const char * name ( void ) const { return _name; } bool name ( const char *name ); 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 activate ( const char *name, type_e dir ); void shutdown ( void ); diff --git a/Timeline/Engine/Timeline.C b/Timeline/Engine/Timeline.C index 8c980cc..c0acba8 100644 --- a/Timeline/Engine/Timeline.C +++ b/Timeline/Engine/Timeline.C @@ -235,3 +235,15 @@ 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/Engine/Track.C b/Timeline/Engine/Track.C index 23d4122..4c59be9 100644 --- a/Timeline/Engine/Track.C +++ b/Timeline/Engine/Track.C @@ -204,6 +204,15 @@ Track::seek ( nframes_t frame ) return playback_ds->seek( frame ); } +void +Track::delay ( nframes_t frames ) +{ +// THREAD_ASSERT( RT ); + + if ( playback_ds ) + playback_ds->delay( frames ); +} + /* THREAD: RT (non-RT) */ void Track::resize_buffers ( nframes_t nframes ) @@ -266,9 +275,29 @@ Track::finalize ( Capture *c, nframes_t frame ) { THREAD_ASSERT( Capture ); + /* adjust region start for latency */ + /* FIXME: is just looking at the first channel good enough? */ + c->region->finalize( frame ); DMESSAGE( "finalizing audio file" ); c->audio_file->finalize(); + nframes_t capture_offset = 0; + + /* 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 ); + delete c->audio_file; } diff --git a/Timeline/TLE.fl b/Timeline/TLE.fl index b3ef65e..50d3cee 100644 --- a/Timeline/TLE.fl +++ b/Timeline/TLE.fl @@ -205,7 +205,7 @@ Loggable::progress_callback( &TLE::progress_cb, this );} {} label Timeline callback {if ( Fl::event_key() != FL_Escape ) o->hide();} open - private xywh {133 113 1025 770} type Double resizable xclass Non_DAW visible + private xywh {102 111 1025 770} type Double resizable xclass Non_DAW visible } { Fl_Menu_Bar menubar {open private xywh {0 0 1024 25} @@ -577,7 +577,7 @@ ab.run();} } } Fl_Group {} {open - xywh {0 23 1025 51} + xywh {0 1 1025 73} } { Fl_Pack {} {open xywh {0 23 483 46} type HORIZONTAL @@ -669,6 +669,10 @@ ab.run();} code0 {\#include "FL/Fl_Blinker.H"} class Fl_Blinker } + Fl_Box stats_box { + label {} selected + xywh {810 1 215 21} labelsize 13 labelcolor 53 align 88 + } } Fl_Progress progress { label {0%} @@ -682,13 +686,9 @@ ab.run();} } Fl_Box project_name { label {} - private xywh {450 0 475 22} labeltype SHADOW_LABEL labelfont 2 + private xywh {450 0 365 22} labeltype SHADOW_LABEL labelfont 2 code0 {o->label( Project::name() );} } - Fl_Value_Output xruns_output { - label {xruns:} - private xywh {980 2 44 20} maximum 40000 step 1 - } } } Function {menu_picked_value( const Fl_Menu_ *m )} {private return_type {static int} @@ -757,9 +757,13 @@ if ( timeline->total_capture_xruns() ) if ( timeline->total_playback_xruns() ) playback_buffer_progress->selection_color( FL_RED ); +static char stats[100]; +snprintf( stats, sizeof( stats ), "latency: %.1fms, xruns: %d", + engine->frames_to_milliseconds( timeline->total_output_latency() ), + engine->xruns() ); -xruns_output->value( engine->xruns() ); +stats_box->label( stats ); static bool zombie = false; diff --git a/Timeline/Timeline.H b/Timeline/Timeline.H index ed68f5d..894fbfb 100644 --- a/Timeline/Timeline.H +++ b/Timeline/Timeline.H @@ -205,6 +205,7 @@ 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/Track.H b/Timeline/Track.H index 20ff13d..5bad8cb 100644 --- a/Timeline/Track.H +++ b/Timeline/Track.H @@ -215,6 +215,7 @@ public: nframes_t process_input ( nframes_t nframes ); nframes_t process_output ( nframes_t nframes ); void seek ( nframes_t frame ); + void delay ( nframes_t frames ); void record ( Capture *c, nframes_t frame ); void write ( Capture *c, sample_t *buf, nframes_t nframes );