Properly aligned export (Stem + Session)

Delay ports being exported by their playback latency.
This commit is contained in:
Robin Gareus
2017-09-26 17:49:24 +02:00
parent a6cc58d757
commit 24ec0b974d
6 changed files with 63 additions and 79 deletions

View File

@@ -22,11 +22,13 @@
#define __ardour_export_channel_h__
#include <set>
#include <list>
#include <boost/scoped_array.hpp>
#include <boost/shared_ptr.hpp>
#include "pbd/signals.h"
#include "pbd/ringbuffer.h"
#include "ardour/buffer_set.h"
#include "ardour/export_pointers.h"
@@ -68,6 +70,8 @@ class LIBARDOUR_API PortExportChannel : public ExportChannel
typedef std::set<boost::weak_ptr<AudioPort> > PortSet;
PortExportChannel ();
~PortExportChannel ();
void set_max_buffer_size(samplecnt_t samples);
void read (Sample const *& data, samplecnt_t samples) const;
@@ -83,8 +87,9 @@ class LIBARDOUR_API PortExportChannel : public ExportChannel
private:
PortSet ports;
boost::scoped_array<Sample> buffer;
samplecnt_t buffer_size;
samplecnt_t _buffer_size;
boost::scoped_array<Sample> _buffer;
std::list <boost::shared_ptr<PBD::RingBuffer<Sample> > > _delaylines;
};

View File

@@ -751,7 +751,7 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
boost::shared_ptr<ExportHandler> get_export_handler ();
boost::shared_ptr<ExportStatus> get_export_status ();
int start_audio_export (samplepos_t position, bool realtime = false, bool region_export = false, bool comensate_master_latency = false);
int start_audio_export (samplepos_t position, bool realtime = false, bool region_export = false);
PBD::Signal1<int, samplecnt_t> ProcessExport;
static PBD::Signal2<void,std::string, std::string> Exported;
@@ -1345,7 +1345,6 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
bool _realtime_export;
bool _region_export;
samplepos_t _export_preroll;
samplepos_t _export_latency;
boost::shared_ptr<ExportHandler> export_handler;
boost::shared_ptr<ExportStatus> export_status;

View File

@@ -35,14 +35,33 @@
using namespace ARDOUR;
PortExportChannel::PortExportChannel ()
: buffer_size(0)
: _buffer_size (0)
{
}
PortExportChannel::~PortExportChannel ()
{
_delaylines.clear ();
}
void PortExportChannel::set_max_buffer_size(samplecnt_t samples)
{
buffer_size = samples;
buffer.reset (new Sample[samples]);
_buffer_size = samples;
_buffer.reset (new Sample[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;
PBD::RingBuffer<Sample>* rb = new PBD::RingBuffer<Sample> (latency + 1 + _buffer_size);
for (samplepos_t i = 0; i < latency; ++i) {
Sample zero = 0;
rb->write (&zero, 1);
}
_delaylines.push_back (boost::shared_ptr<PBD::RingBuffer<Sample> >(rb));
}
}
bool
@@ -58,10 +77,10 @@ PortExportChannel::operator< (ExportChannel const & other) const
void
PortExportChannel::read (Sample const *& data, samplecnt_t samples) const
{
assert(buffer);
assert(samples <= buffer_size);
assert(_buffer);
assert(samples <= _buffer_size);
if (ports.size() == 1) {
if (ports.size() == 1 && _delaylines.size() ==1 && _delaylines.front()->bufsize () == _buffer_size + 1) {
boost::shared_ptr<AudioPort> p = ports.begin()->lock ();
AudioBuffer& ab (p->get_audio_buffer(samples)); // unsets AudioBuffer::_written
data = ab.data();
@@ -69,22 +88,28 @@ PortExportChannel::read (Sample const *& data, samplecnt_t samples) const
return;
}
memset (buffer.get(), 0, samples * sizeof (Sample));
memset (_buffer.get(), 0, samples * sizeof (Sample));
std::list <boost::shared_ptr<PBD::RingBuffer<Sample> > >::const_iterator di = _delaylines.begin ();
for (PortSet::const_iterator it = ports.begin(); it != ports.end(); ++it) {
boost::shared_ptr<AudioPort> p = it->lock ();
if (p) {
AudioBuffer& ab (p->get_audio_buffer(samples)); // unsets AudioBuffer::_written
Sample* port_buffer = ab.data();
ab.set_written (true);
for (uint32_t i = 0; i < samples; ++i) {
buffer[i] += (float) port_buffer[i];
}
if (!p) {
continue;
}
AudioBuffer& ab (p->get_audio_buffer(samples)); // unsets AudioBuffer::_written
Sample* port_buffer = ab.data();
ab.set_written (true);
(*di)->write (port_buffer, samples);
// TODO optimze, get_read_vector()
for (uint32_t i = 0; i < samples; ++i) {
Sample spl;
(*di)->read (&spl, 1);
_buffer[i] += spl;
}
++di;
}
data = buffer.get();
data = _buffer.get();
}
void

View File

@@ -193,7 +193,6 @@ ExportHandler::start_timespan ()
handle_duplicate_format_extensions();
bool realtime = current_timespan->realtime ();
bool region_export = true;
bool incl_master_bus = false;
for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
// Filenames can be shared across timespans
FileSpec & spec = it->second;
@@ -206,33 +205,6 @@ ExportHandler::start_timespan ()
default:
break;
}
#if 1 // hack alert -- align master bus, compensate master latency
/* there's no easier way to get this information here.
* Ports are configured in the PortExportChannelSelector GUI,
* This ExportHandler has no context of routes.
*/
boost::shared_ptr<Route> master_bus = session.master_out ();
if (master_bus) {
const PortSet& ps = master_bus->output ()->ports();
const ExportChannelConfiguration::ChannelList& channels = spec.channel_config->get_channels ();
for (ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
boost::shared_ptr <PortExportChannel> pep = boost::dynamic_pointer_cast<PortExportChannel> (*it);
if (!pep) {
continue;
}
PortExportChannel::PortSet const& ports = pep->get_ports ();
for (PortExportChannel::PortSet::const_iterator it = ports.begin(); it != ports.end(); ++it) {
boost::shared_ptr<AudioPort> ap = (*it).lock();
if (ps.contains (ap)) {
incl_master_bus = true;
}
}
}
}
#endif
graph_builder->add_config (spec, realtime);
}
@@ -245,7 +217,7 @@ ExportHandler::start_timespan ()
session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
process_position = current_timespan->get_start();
// TODO check if it's a RegionExport.. set flag to skip process_without_events()
session.start_audio_export (process_position, realtime, region_export, incl_master_bus);
session.start_audio_export (process_position, realtime, region_export);
}
void

View File

@@ -224,7 +224,6 @@ Session::Session (AudioEngine &eng,
, _realtime_export (false)
, _region_export (false)
, _export_preroll (0)
, _export_latency (0)
, _pre_export_mmc_enabled (false)
, _name (snapshot_name)
, _is_new (true)

View File

@@ -104,7 +104,7 @@ Session::pre_export ()
/** Called for each range that is being exported */
int
Session::start_audio_export (samplepos_t position, bool realtime, bool region_export, bool comensate_master_latency)
Session::start_audio_export (samplepos_t position, bool realtime, bool region_export)
{
if (!_exporting) {
pre_export ();
@@ -127,31 +127,6 @@ Session::start_audio_export (samplepos_t position, bool realtime, bool region_ex
_export_preroll = 1;
}
/* "worst_track_latency" is the correct value for stem-exports
* see to Route::add_export_point(),
*
* For master-bus export, we also need to add the master's latency.
* (or actually longest-total-session-latency - worst-track-latency)
* to align the export to 00:00:00:00.
*
* We must not use worst_playback_latency because that
* includes external (hardware) latencies and would overcompensate
* during file-export.
*
* (this is all still very [w]hacky. Individual Bus and Track outputs
* are not aligned but one can select them in the PortExportChannelSelector)
*/
_export_latency = worst_track_out_latency ();
boost::shared_ptr<Route> master = master_out ();
if (master && comensate_master_latency) {
_export_latency += master->signal_latency ();
}
if (region_export) {
_export_latency = 0;
}
/* We're about to call Track::seek, so the butler must have finished everything
up otherwise it could be doing do_refill in its thread while we are doing
it here.
@@ -181,6 +156,12 @@ Session::start_audio_export (samplepos_t position, bool realtime, bool region_ex
*/
_transport_sample = position;
if (!region_export) {
_remaining_latency_preroll = worst_latency_preroll ();
} else {
_remaining_latency_preroll = 0;
}
export_status->stop = false;
/* get transport ready. note how this is calling butler functions
@@ -277,18 +258,21 @@ Session::process_export_fw (pframes_t nframes)
return;
}
if (_export_latency > 0) {
samplepos_t remain = std::min ((samplepos_t)nframes, _export_latency);
if (_remaining_latency_preroll > 0) {
samplepos_t remain = std::min ((samplepos_t)nframes, _remaining_latency_preroll);
if (need_buffers) {
_engine.main_thread()->get_buffers ();
}
process_without_events (remain);
if (need_buffers) {
_engine.main_thread()->drop_buffers ();
}
_export_latency -= remain;
_remaining_latency_preroll -= remain;
_transport_sample -= remain;
nframes -= remain;
if (nframes == 0) {