summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2020-03-05 21:17:25 +0100
committerRobin Gareus <robin@gareus.org>2020-03-06 01:49:48 +0100
commit751f9f96540e6b1517725b0a75a4e416df04af58 (patch)
tree69ce60eeafe323afb729e0d3fc3a4b21509c392d
parent18514408637c9b49a1858443a4f1a37023244599 (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.h8
-rw-r--r--libs/ardour/ardour/export_graph_builder.h5
-rw-r--r--libs/ardour/export_channel.cc30
-rw-r--r--libs/ardour/export_graph_builder.cc48
-rw-r--r--libs/ardour/export_handler.cc13
-rw-r--r--libs/ardour/session.cc2
-rw-r--r--libs/ardour/session_export.cc25
-rw-r--r--libs/ardour/session_transport.cc2
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 ();
}