diff options
author | Robin Gareus <robin@gareus.org> | 2020-03-05 21:17:25 +0100 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2020-03-06 01:49:48 +0100 |
commit | 751f9f96540e6b1517725b0a75a4e416df04af58 (patch) | |
tree | 69ce60eeafe323afb729e0d3fc3a4b21509c392d | |
parent | 18514408637c9b49a1858443a4f1a37023244599 (diff) |
Fix export alignment (#7916)
Ardour's playback is aligned to master-out:
"When the playback clock reads 01:00:00:00, the sample corresponding
to 01:00:00:00 is audible at the speaker(s)"
When exporting, and grabbing data from output ports, the signal
is offset by the master-bus physical playback latency. This was
compensated for, but lead to initial silence in the exported file.
New approach is to start capturing export data during pre-roll,
at the time when playback is written to the output buffers.
To also shaves off a common offset to make this work with
realtime export. Effectively this emulates processing with
disconnected master-output port, while still keeping any
latency of effects on the master-bus itself.
Last but not least: jack updates latencies when freewheeling,
(setting HW latency to zero). The callback arrives asynchronously
some time after enabling freewheeling, but after Export
Ports have been configured. Those callbacks are ignored.
-rw-r--r-- | libs/ardour/ardour/export_channel.h | 8 | ||||
-rw-r--r-- | libs/ardour/ardour/export_graph_builder.h | 5 | ||||
-rw-r--r-- | libs/ardour/export_channel.cc | 30 | ||||
-rw-r--r-- | libs/ardour/export_graph_builder.cc | 48 | ||||
-rw-r--r-- | libs/ardour/export_handler.cc | 13 | ||||
-rw-r--r-- | libs/ardour/session.cc | 2 | ||||
-rw-r--r-- | libs/ardour/session_export.cc | 25 | ||||
-rw-r--r-- | libs/ardour/session_transport.cc | 2 |
8 files changed, 95 insertions, 38 deletions
diff --git a/libs/ardour/ardour/export_channel.h b/libs/ardour/ardour/export_channel.h index 2d002ee1e4..ca23eadc67 100644 --- a/libs/ardour/ardour/export_channel.h +++ b/libs/ardour/ardour/export_channel.h @@ -50,7 +50,8 @@ class LIBARDOUR_API ExportChannel : public boost::less_than_comparable<ExportCha virtual ~ExportChannel () {} - virtual void set_max_buffer_size(samplecnt_t) { } + virtual samplecnt_t common_port_playback_latency () const { return 0; } + virtual void prepare_export (samplecnt_t max_samples, sampleoffset_t common_latency) { } virtual void read (Sample const *& data, samplecnt_t samples) const = 0; virtual bool empty () const = 0; @@ -74,7 +75,8 @@ class LIBARDOUR_API PortExportChannel : public ExportChannel PortExportChannel (); ~PortExportChannel (); - void set_max_buffer_size(samplecnt_t samples); + samplecnt_t common_port_playback_latency () const; + void prepare_export (samplecnt_t max_samples, sampleoffset_t common_latency); void read (Sample const *& data, samplecnt_t samples) const; bool empty () const { return ports.empty(); } @@ -171,7 +173,7 @@ class LIBARDOUR_API RouteExportChannel : public ExportChannel static void create_from_route(std::list<ExportChannelPtr> & result, boost::shared_ptr<Route> route); public: // ExportChannel interface - void set_max_buffer_size(samplecnt_t samples); + void prepare_export (samplecnt_t max_samples, sampleoffset_t common_latency); void read (Sample const *& data, samplecnt_t samples) const; bool empty () const { return false; } diff --git a/libs/ardour/ardour/export_graph_builder.h b/libs/ardour/ardour/export_graph_builder.h index ff957926ce..b70db3c96b 100644 --- a/libs/ardour/ardour/export_graph_builder.h +++ b/libs/ardour/ardour/export_graph_builder.h @@ -69,7 +69,7 @@ class LIBARDOUR_API ExportGraphBuilder ExportGraphBuilder (Session const & session); ~ExportGraphBuilder (); - int process (samplecnt_t samples, bool last_cycle); + samplecnt_t process (samplecnt_t samples, bool last_cycle); bool post_process (); // returns true when finished bool need_postprocessing () const { return !intermediates.empty(); } bool realtime() const { return _realtime; } @@ -275,7 +275,8 @@ class LIBARDOUR_API ExportGraphBuilder AnalysisMap analysis_map; - bool _realtime; + bool _realtime; + samplecnt_t _master_align; Glib::ThreadPool thread_pool; }; diff --git a/libs/ardour/export_channel.cc b/libs/ardour/export_channel.cc index af64a03ce3..a5d032a153 100644 --- a/libs/ardour/export_channel.cc +++ b/libs/ardour/export_channel.cc @@ -46,17 +46,35 @@ PortExportChannel::~PortExportChannel () _delaylines.clear (); } -void PortExportChannel::set_max_buffer_size(samplecnt_t samples) +samplecnt_t PortExportChannel::common_port_playback_latency () const { - _buffer_size = samples; - _buffer.reset (new Sample[samples]); + samplecnt_t l = 0; + bool first = true; + for (PortSet::const_iterator it = ports.begin(); it != ports.end(); ++it) { + boost::shared_ptr<AudioPort> p = it->lock (); + if (!p) { continue; } + samplecnt_t latency = p->private_latency_range (true).max; + if (first) { + first = false; + l = p->private_latency_range (true).max; + continue; + } + l = std::min (l, latency); + } + return l; +} + +void PortExportChannel::prepare_export (samplecnt_t max_samples, sampleoffset_t common_latency) +{ + _buffer_size = max_samples; + _buffer.reset (new Sample[max_samples]); _delaylines.clear (); for (PortSet::const_iterator it = ports.begin(); it != ports.end(); ++it) { boost::shared_ptr<AudioPort> p = it->lock (); if (!p) { continue; } - samplecnt_t latency = p->private_latency_range (true).max; + samplecnt_t latency = p->private_latency_range (true).max - common_latency; PBD::RingBuffer<Sample>* rb = new PBD::RingBuffer<Sample> (latency + 1 + _buffer_size); for (samplepos_t i = 0; i < latency; ++i) { Sample zero = 0; @@ -258,10 +276,10 @@ RouteExportChannel::create_from_route(std::list<ExportChannelPtr> & result, boos } void -RouteExportChannel::set_max_buffer_size(samplecnt_t samples) +RouteExportChannel::prepare_export (samplecnt_t max_samples, sampleoffset_t) { if (processor) { - processor->set_block_size (samples); + processor->set_block_size (max_samples); } } diff --git a/libs/ardour/export_graph_builder.cc b/libs/ardour/export_graph_builder.cc index f4d6d21f2d..eb74d44fd4 100644 --- a/libs/ardour/export_graph_builder.cc +++ b/libs/ardour/export_graph_builder.cc @@ -77,20 +77,33 @@ ExportGraphBuilder::~ExportGraphBuilder () { } -int +samplecnt_t ExportGraphBuilder::process (samplecnt_t samples, bool last_cycle) { assert(samples <= process_buffer_samples); + sampleoffset_t off = 0; for (ChannelMap::iterator it = channels.begin(); it != channels.end(); ++it) { Sample const * process_buffer = 0; it->first->read (process_buffer, samples); - ConstProcessContext<Sample> context(process_buffer, samples, 1); + + if (session.remaining_latency_preroll () >= _master_align + samples) { + /* Skip processing during pre-roll, only read/write export ringbuffers */ + return 0; + } + + off = 0; + if (session.remaining_latency_preroll () > _master_align) { + off = session.remaining_latency_preroll () - _master_align; + assert (off < samples); + } + + ConstProcessContext<Sample> context(&process_buffer[off], samples - off, 1); if (last_cycle) { context().set_flag (ProcessContext<Sample>::EndOfInput); } it->second->process (context); } - return 0; + return samples - off; } bool @@ -126,6 +139,7 @@ ExportGraphBuilder::reset () intermediates.clear (); analysis_map.clear(); _realtime = false; + _master_align = 0; } void @@ -148,17 +162,25 @@ ExportGraphBuilder::set_current_timespan (boost::shared_ptr<ExportTimespan> span void ExportGraphBuilder::add_config (FileSpec const & config, bool rt) { - ExportChannelConfiguration::ChannelList const & channels = - config.channel_config->get_channels(); - for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); - it != channels.end(); ++it) { - (*it)->set_max_buffer_size(process_buffer_samples); + /* calculate common latency, shave off master-bus hardware playback latency (if any) */ + _master_align = session.master_out() ? session.master_out()->output()->connected_latency (true) : 0; + + ExportChannelConfiguration::ChannelList const & channels = config.channel_config->get_channels(); + + for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) { + _master_align = std::min (_master_align, (*it)->common_port_playback_latency ()); + } + + /* now set-up port-data sniffing and delay-ringbuffers */ + for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) { + (*it)->prepare_export (process_buffer_samples, _master_align); } _realtime = rt; - // If the sample rate is "session rate", change it to the real value. - // However, we need to copy it to not change the config which is saved... + /* If the sample rate is "session rate", change it to the real value. + * However, we need to copy it to not change the config which is saved... + */ FileSpec new_config (config); new_config.format.reset(new ExportFormatSpecification(*new_config.format, false)); if(new_config.format->sample_rate() == ExportFormatBase::SR_Session) { @@ -166,14 +188,14 @@ ExportGraphBuilder::add_config (FileSpec const & config, bool rt) new_config.format->set_sample_rate(ExportFormatBase::nearest_sample_rate(session_rate)); } - if (!new_config.channel_config->get_split ()) { add_split_config (new_config); return; } - // Split channel configurations are split into several channel configurations, - // each corresponding to a file, at this stage + /* Split channel configurations are split into several channel configurations, + * each corresponding to a file, at this stage + */ typedef std::list<boost::shared_ptr<ExportChannelConfiguration> > ConfigList; ConfigList file_configs; new_config.channel_config->configurations_for_files (file_configs); diff --git a/libs/ardour/export_handler.cc b/libs/ardour/export_handler.cc index 76869c7a79..df64e87154 100644 --- a/libs/ardour/export_handler.cc +++ b/libs/ardour/export_handler.cc @@ -299,12 +299,13 @@ ExportHandler::process_timespan (samplecnt_t samples) samples_to_read = samples; } - process_position += samples_to_read; - export_status->processed_samples += samples_to_read; - export_status->processed_samples_current_timespan += samples_to_read; - /* Do actual processing */ - int ret = graph_builder->process (samples_to_read, last_cycle); + samplecnt_t ret = graph_builder->process (samples_to_read, last_cycle); + if (ret > 0) { + process_position += ret; + export_status->processed_samples += ret; + export_status->processed_samples_current_timespan += ret; + } /* Start post-processing/normalizing if necessary */ if (last_cycle) { @@ -318,7 +319,7 @@ ExportHandler::process_timespan (samplecnt_t samples) } } - return ret; + return 0; } int diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 74c0fbb22e..92d62274f7 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -6533,7 +6533,7 @@ Session::update_latency (bool playback) if (inital_connect_or_deletion_in_progress () || _adding_routes_in_progress || _route_deletion_in_progress) { return; } - if (!_engine.running()) { + if (!_engine.running() || _exporting) { return; } diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc index 93ca0d5afe..4e1dda441e 100644 --- a/libs/ardour/session_export.cc +++ b/libs/ardour/session_export.cc @@ -285,20 +285,33 @@ Session::process_export_fw (pframes_t nframes) _engine.main_thread()->get_buffers (); } - process_without_events (remain); + assert (_count_in_samples == 0); + while (remain > 0) { + samplecnt_t ns = calc_preroll_subcycle (remain); + + bool session_needs_butler = false; + if (process_routes (ns, session_needs_butler)) { + fail_roll (ns); + } + + ProcessExport (ns); + + _remaining_latency_preroll -= ns; + remain -= ns; + nframes -= ns; + + if (remain != 0) { + _engine.split_cycle (ns); + } + } if (need_buffers) { _engine.main_thread()->drop_buffers (); } - _remaining_latency_preroll -= remain; - _transport_sample -= remain; - nframes -= remain; - if (nframes == 0) { return; } - _engine.split_cycle (remain); } if (need_buffers) { diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 065b2e0c7b..7abf43b062 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -568,7 +568,7 @@ Session::start_transport () _last_roll_location = _transport_sample; _last_roll_or_reversal_location = _transport_sample; - if (!have_looped) { + if (!have_looped && !_exporting) { _remaining_latency_preroll = worst_latency_preroll_buffer_size_ceil (); } |