#include "ardour/filename_extensions.h"
#include "ardour/graph.h"
#include "ardour/midiport_manager.h"
+#include "ardour/scene_changer.h"
#include "ardour/midi_track.h"
#include "ardour/midi_ui.h"
#include "ardour/operations.h"
#include "ardour/smf_source.h"
#include "ardour/source_factory.h"
#include "ardour/speakers.h"
+#include "ardour/track.h"
#include "ardour/utils.h"
#include "midi++/port.h"
, _step_editors (0)
, _suspend_timecode_transmission (0)
, _speakers (new Speakers)
+ , _order_hint (0)
, ignore_route_processor_changes (false)
+ , _scene_changer (0)
+ , _midi_ports (0)
+ , _mmc (0)
{
uint32_t sr = 0;
pre_engine_init (fullpath);
if (_is_new) {
+ if (ensure_engine (sr)) {
+ destroy ();
+ throw failed_constructor ();
+ }
+
if (create (mix_template, bus_profile)) {
destroy ();
throw failed_constructor ();
}
+
+ /* if a mix template was provided, then ::create() will
+ * have copied it into the session and we need to load it
+ * so that we have the state ready for ::set_state()
+ * after the engine is started.
+ *
+ * Note that we do NOT try to get the sample rate from
+ * the template at this time, though doing so would
+ * be easy if we decided this was an appropriate part
+ * of a template.
+ */
+
+ if (!mix_template.empty() && load_state (_current_snapshot_name)) {
+ throw failed_constructor ();
+ }
+
} else {
+
if (load_state (_current_snapshot_name)) {
throw failed_constructor ();
}
sr = atoi (prop->value());
}
}
- }
-
- if (_engine.current_backend() == 0 || _engine.setup_required()) {
- boost::optional<int> r = AudioEngineSetupRequired (sr);
- if (r.get_value_or (-1) != 0) {
+
+ if (ensure_engine (sr)) {
destroy ();
- throw failed_constructor();
+ throw failed_constructor ();
}
}
- /* at this point the engine should be connected (i.e. interacting
- with a backend device (or psuedo-device) and available to us
- for determinining sample rates and other settings.
- */
-
- if (!_engine.connected()) {
- destroy ();
- throw failed_constructor();
- }
-
if (post_engine_init ()) {
destroy ();
throw failed_constructor ();
destroy ();
}
+int
+Session::ensure_engine (uint32_t desired_sample_rate)
+{
+ if (_engine.current_backend() == 0) {
+ /* backend is unknown ... */
+ boost::optional<int> r = AudioEngineSetupRequired (desired_sample_rate);
+ if (r.get_value_or (-1) != 0) {
+ return -1;
+ }
+ } else if (_engine.setup_required()) {
+ /* backend is known, but setup is needed */
+ boost::optional<int> r = AudioEngineSetupRequired (desired_sample_rate);
+ if (r.get_value_or (-1) != 0) {
+ return -1;
+ }
+ } else if (!_engine.running()) {
+ if (_engine.start()) {
+ return -1;
+ }
+ }
+
+ /* at this point the engine should be running
+ */
+
+ if (!_engine.running()) {
+ return -1;
+ }
+
+ return immediately_post_engine ();
+
+}
+
+int
+Session::immediately_post_engine ()
+{
+ /* Do various initializations that should take place directly after we
+ * know that the engine is running, but before we either create a
+ * session or set state for an existing one.
+ */
+
+ if (how_many_dsp_threads () > 1) {
+ /* For now, only create the graph if we are using >1 DSP threads, as
+ it is a bit slower than the old code with 1 thread.
+ */
+ _process_graph.reset (new Graph (*this));
+ }
+
+ /* every time we reconnect, recompute worst case output latencies */
+
+ _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this));
+
+ if (synced_to_engine()) {
+ _engine.transport_stop ();
+ }
+
+ if (config.get_jack_time_master()) {
+ _engine.transport_locate (_transport_frame);
+ }
+
+ try {
+ BootMessage (_("Set up LTC"));
+ setup_ltc ();
+ BootMessage (_("Set up Click"));
+ setup_click ();
+ BootMessage (_("Set up standard connections"));
+ setup_bundles ();
+ }
+
+ catch (failed_constructor& err) {
+ return -1;
+ }
+
+ return 0;
+}
+
void
Session::destroy ()
{
}
routes.flush ();
- DEBUG_TRACE (DEBUG::Destruction, "delete sources\n");
- for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
- DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count()));
- i->second->drop_references ();
- }
+ {
+ DEBUG_TRACE (DEBUG::Destruction, "delete sources\n");
+ Glib::Threads::Mutex::Lock lm (source_lock);
+ for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count()));
+ i->second->drop_references ();
+ }
- sources.clear ();
+ sources.clear ();
+ }
DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n");
for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
/* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
playlists.reset ();
- delete _mmc;
- delete _midi_ports;
- delete _locations;
+ delete _scene_changer; _scene_changer = 0;
+
+ delete _mmc; _mmc = 0;
+ delete _midi_ports; _midi_ports = 0;
+ delete _locations; _locations = 0;
DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n");
{
XMLNode* child = 0;
- _ltc_input.reset (new IO (*this, _("LTC In"), IO::Input));
- _ltc_output.reset (new IO (*this, _("LTC Out"), IO::Output));
+ _ltc_input.reset (new IO (*this, X_("LTC In"), IO::Input));
+ _ltc_output.reset (new IO (*this, X_("LTC Out"), IO::Output));
- if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-In")) != 0) {
+ if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC In"))) != 0) {
_ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version);
} else {
{
reconnect_ltc_input ();
}
- if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-Out")) != 0) {
+ if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC Out"))) != 0) {
_ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version);
} else {
{
* IO style of NAME/TYPE-{in,out}N
*/
- _ltc_input->nth (0)->set_name (_("LTC-in"));
- _ltc_output->nth (0)->set_name (_("LTC-out"));
+ _ltc_input->nth (0)->set_name (X_("LTC-in"));
+ _ltc_output->nth (0)->set_name (X_("LTC-out"));
}
void
Session::setup_click ()
{
- XMLNode* child = 0;
-
_clicking = false;
- _click_io.reset (new ClickIO (*this, "click"));
+ _click_io.reset (new ClickIO (*this, X_("Click")));
_click_gain.reset (new Amp (*this));
_click_gain->activate ();
+ if (state_tree) {
+ setup_click_state (state_tree->root());
+ } else {
+ setup_click_state (0);
+ }
+}
+
+void
+Session::setup_click_state (const XMLNode* node)
+{
+ const XMLNode* child = 0;
- if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) {
+ if (node && (child = find_named_node (*node, "Click")) != 0) {
/* existing state for Click */
int c = 0;
}
-int
-Session::when_engine_running ()
-{
- /* every time we reconnect, recompute worst case output latencies */
-
- _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this));
-
- if (synced_to_jack()) {
- _engine.transport_stop ();
- }
-
- if (config.get_jack_time_master()) {
- _engine.transport_locate (_transport_frame);
- }
-
-
- try {
- BootMessage (_("Set up LTC"));
- setup_ltc ();
- BootMessage (_("Set up Click"));
- setup_click ();
- BootMessage (_("Set up standard connections"));
- setup_bundles ();
- }
-
- catch (failed_constructor& err) {
- return -1;
- }
-
- BootMessage (_("Setup signal flow and plugins"));
-
- /* Reset all panners */
-
- Delivery::reset_panners ();
-
- /* this will cause the CPM to instantiate any protocols that are in use
- * (or mandatory), which will pass it this Session, and then call
- * set_state() on each instantiated protocol to match stored state.
- */
-
- ControlProtocolManager::instance().set_session (this);
-
- /* This must be done after the ControlProtocolManager set_session above,
- as it will set states for ports which the ControlProtocolManager creates.
- */
-
- // XXX set state of MIDI::Port's
- // MidiPortManager::instance()->set_port_states (Config->midi_port_states ());
-
- /* And this must be done after the MIDI::Manager::set_port_states as
- * it will try to make connections whose details are loaded by set_port_states.
- */
-
- hookup_io ();
-
- /* Let control protocols know that we are now all connected, so they
- * could start talking to surfaces if they want to.
- */
-
- ControlProtocolManager::instance().midi_connectivity_established ();
-
- if (_is_new && !no_auto_connect()) {
- Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock());
- auto_connect_master_bus ();
- }
-
- _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
-
- /* update latencies */
-
- initialize_latencies ();
-
- return 0;
-}
-
void
Session::auto_connect_master_bus ()
{
/* force reversion to Solo-In-Place */
Config->set_solo_control_is_listen_control (false);
+ /* if we are auditioning, cancel it ... this is a workaround
+ to a problem (auditioning does not execute the process graph,
+ which is needed to remove routes when using >1 core for processing)
+ */
+ cancel_audition ();
+
{
/* Hold process lock while doing this so that we don't hear bits and
* pieces of audio as we work on each route.
remove_route (_monitor_out);
auto_connect_master_bus ();
+
+ if (auditioner) {
+ auditioner->connect ();
+ }
}
void
/* Monitor bus is audio only */
- uint32_t mod = n_physical_outputs.get (DataType::AUDIO);
- uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO);
vector<string> outputs[DataType::num_types];
for (uint32_t i = 0; i < DataType::num_types; ++i) {
_engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]);
}
-
+
+ uint32_t mod = outputs[DataType::AUDIO].size();
+ uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO);
if (mod != 0) {
(*x)->enable_monitor_send ();
}
}
+
+ if (auditioner) {
+ auditioner->connect ();
+ }
}
void
offset = current_block_size;
}
- if (synced_to_jack()) {
+ if (synced_to_engine()) {
tf = _engine.transport_frame();
} else {
tf = _transport_frame;
DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n",
- (*i)->name(), (*i)->order_key (MixerSort)));
+ (*i)->name(), (*i)->order_key ()));
}
#endif
for (uint32_t i = output_start.get(*t); i < route->n_outputs().get(*t); ++i) {
string port;
- if ((*t) == DataType::MIDI || Config->get_output_auto_connect() & AutoConnectPhysical) {
+ if ((*t) == DataType::MIDI && (Config->get_output_auto_connect() & AutoConnectPhysical)) {
port = physoutputs[(out_offset.get(*t) + i) % nphysical_out];
- } else if ((*t) == DataType::AUDIO && Config->get_output_auto_connect() & AutoConnectMaster) {
+ } else if ((*t) == DataType::AUDIO && (Config->get_output_auto_connect() & AutoConnectMaster)) {
/* master bus is audio only */
if (_master_out && _master_out->n_inputs().get(*t) > 0) {
port = _master_out->input()->ports().port(*t,
ChanCount existing_outputs;
uint32_t order = next_control_id();
+ if (_order_hint != 0) {
+ order = _order_hint;
+ _order_hint = 0;
+ }
+
count_existing_track_channels (existing_inputs, existing_outputs);
{
ID in most situations.
*/
- if (!r->has_order_key (EditorSort)) {
+ if (!r->has_order_key ()) {
if (r->is_auditioner()) {
/* use an arbitrarily high value */
- r->set_order_key (EditorSort, UINT_MAX);
- r->set_order_key (MixerSort, UINT_MAX);
+ r->set_order_key (UINT_MAX);
} else {
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("while adding, set %1 to order key %2\n", r->name(), order));
- r->set_order_key (EditorSort, order);
- r->set_order_key (MixerSort, order);
+ r->set_order_key (order);
order++;
}
}
return source;
}
-boost::shared_ptr<Source>
-Session::source_by_path_and_channel (const string& path, uint16_t chn)
+boost::shared_ptr<AudioFileSource>
+Session::source_by_path_and_channel (const string& path, uint16_t chn) const
{
+ /* Restricted to audio files because only audio sources have channel
+ as a property.
+ */
+
Glib::Threads::Mutex::Lock lm (source_lock);
- for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<AudioFileSource> afs
= boost::dynamic_pointer_cast<AudioFileSource>(i->second);
return afs;
}
}
- return boost::shared_ptr<Source>();
+
+ return boost::shared_ptr<AudioFileSource>();
+}
+
+boost::shared_ptr<MidiSource>
+Session::source_by_path (const std::string& path) const
+{
+ /* Restricted to MIDI files because audio sources require a channel
+ for unique identification, in addition to a path.
+ */
+
+ Glib::Threads::Mutex::Lock lm (source_lock);
+
+ for (SourceMap::const_iterator s = sources.begin(); s != sources.end(); ++s) {
+ boost::shared_ptr<MidiSource> ms
+ = boost::dynamic_pointer_cast<MidiSource>(s->second);
+ boost::shared_ptr<FileSource> fs
+ = boost::dynamic_pointer_cast<FileSource>(s->second);
+
+ if (ms && fs && fs->path() == path) {
+ return ms;
+ }
+ }
+
+ return boost::shared_ptr<MidiSource>();
}
uint32_t
return cnt;
}
-
-string
-Session::change_source_path_by_name (string path, string oldname, string newname, bool destructive)
-{
- string look_for;
- string old_basename = PBD::basename_nosuffix (oldname);
- string new_legalized = legalize_for_path (newname);
-
- /* note: we know (or assume) the old path is already valid */
-
- if (destructive) {
-
- /* destructive file sources have a name of the form:
-
- /path/to/Tnnnn-NAME(%[LR])?.wav
-
- the task here is to replace NAME with the new name.
- */
-
- string dir;
- string prefix;
- string::size_type dash;
-
- dir = Glib::path_get_dirname (path);
- path = Glib::path_get_basename (path);
-
- /* '-' is not a legal character for the NAME part of the path */
-
- if ((dash = path.find_last_of ('-')) == string::npos) {
- return "";
- }
-
- prefix = path.substr (0, dash);
-
- path += prefix;
- path += '-';
- path += new_legalized;
- path += native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
- path = Glib::build_filename (dir, path);
-
- } else {
-
- /* non-destructive file sources have a name of the form:
-
- /path/to/NAME-nnnnn(%[LR])?.ext
-
- the task here is to replace NAME with the new name.
- */
-
- string dir;
- string suffix;
- string::size_type dash;
- string::size_type postfix;
-
- dir = Glib::path_get_dirname (path);
- path = Glib::path_get_basename (path);
-
- /* '-' is not a legal character for the NAME part of the path */
-
- if ((dash = path.find_last_of ('-')) == string::npos) {
- return "";
- }
-
- suffix = path.substr (dash+1);
-
- // Suffix is now everything after the dash. Now we need to eliminate
- // the nnnnn part, which is done by either finding a '%' or a '.'
-
- postfix = suffix.find_last_of ("%");
- if (postfix == string::npos) {
- postfix = suffix.find_last_of ('.');
- }
-
- if (postfix != string::npos) {
- suffix = suffix.substr (postfix);
- } else {
- error << "Logic error in Session::change_source_path_by_name(), please report" << endl;
- return "";
- }
-
- const uint32_t limit = 10000;
- char buf[PATH_MAX+1];
-
- for (uint32_t cnt = 1; cnt <= limit; ++cnt) {
-
- snprintf (buf, sizeof(buf), "%s-%u%s", newname.c_str(), cnt, suffix.c_str());
-
- if (!matching_unsuffixed_filename_exists_in (dir, buf)) {
- path = Glib::build_filename (dir, buf);
- break;
- }
-
- path = "";
- }
-
- if (path.empty()) {
- fatal << string_compose (_("FATAL ERROR! Could not find a suitable version of %1 for a rename"),
- newname) << endl;
- /*NOTREACHED*/
- }
- }
-
- return path;
-}
-
/** Return the full path (in some session directory) for a new within-session source.
* \a name must be a session-unique name that does not contain slashes
* (e.g. as returned by new_*_source_name)
existing++;
break;
}
+
+ /* it is possible that we have the path already
+ * assigned to a source that has not yet been written
+ * (ie. the write source for a diskstream). we have to
+ * check this in order to make sure that our candidate
+ * path isn't used again, because that can lead to
+ * two Sources point to the same file with different
+ * notions of their removability.
+ */
+
+ string possible_path = Glib::build_filename (spath, buf);
+
+ if (source_by_path (possible_path)) {
+ existing++;
+ break;
+ }
}
if (existing == 0) {
SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate()));
}
-/** Return a unique name based on \a base for a new internal MIDI source */
+/** Return a unique name based on \a owner_name for a new internal MIDI source */
string
-Session::new_midi_source_name (const string& base)
+Session::new_midi_source_name (const string& owner_name)
{
uint32_t cnt;
char buf[PATH_MAX+1];
const uint32_t limit = 10000;
string legalized;
+ string possible_name;
buf[0] = '\0';
- legalized = legalize_for_path (base);
+ legalized = legalize_for_path (owner_name);
// Find a "version" of the file name that doesn't exist in any of the possible directories.
+
for (cnt = 1; cnt <= limit; ++cnt) {
vector<space_and_path>::iterator i;
for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
SessionDirectory sdir((*i).path);
+
+ snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt);
+ possible_name = buf;
- std::string p = Glib::build_filename (sdir.midi_path(), legalized);
-
- snprintf (buf, sizeof(buf), "%s-%u.mid", p.c_str(), cnt);
+ std::string possible_path = Glib::build_filename (sdir.midi_path(), possible_name);
+
+ if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) {
+ existing++;
+ }
- if (Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) {
+ if (source_by_path (possible_path)) {
existing++;
}
}
if (cnt > limit) {
error << string_compose(
_("There are already %1 recordings for %2, which I consider too many."),
- limit, base) << endmsg;
+ limit, owner_name) << endmsg;
destroy ();
throw failed_constructor();
}
}
- return Glib::path_get_basename(buf);
+ return possible_name;
}
/** Create a new within-session MIDI source */
boost::shared_ptr<MidiSource>
-Session::create_midi_source_for_session (Track* track, string const & n)
+Session::create_midi_source_for_session (string const & basic_name)
{
- /* try to use the existing write source for the track, to keep numbering sane
- */
+ std::string name;
- if (track) {
- /*MidiTrack* mt = dynamic_cast<Track*> (track);
- assert (mt);
- */
+ if (name.empty()) {
+ name = new_midi_source_name (basic_name);
+ }
- list<boost::shared_ptr<Source> > l = track->steal_write_sources ();
+ const string path = new_source_path_from_name (DataType::MIDI, name);
- if (!l.empty()) {
- assert (boost::dynamic_pointer_cast<MidiSource> (l.front()));
- return boost::dynamic_pointer_cast<MidiSource> (l.front());
- }
+ return boost::dynamic_pointer_cast<SMFSource> (
+ SourceFactory::createWritable (
+ DataType::MIDI, *this, path, false, frame_rate()));
+}
+
+/** Create a new within-session MIDI source */
+boost::shared_ptr<MidiSource>
+Session::create_midi_source_by_stealing_name (boost::shared_ptr<Track> track)
+{
+ /* the caller passes in the track the source will be used in,
+ so that we can keep the numbering sane.
+
+ Rationale: a track with the name "Foo" that has had N
+ captures carried out so far will ALREADY have a write source
+ named "Foo-N+1.mid" waiting to be used for the next capture.
+
+ If we call new_midi_source_name() we will get "Foo-N+2". But
+ there is no region corresponding to "Foo-N+1", so when
+ "Foo-N+2" appears in the track, the gap presents the user
+ with odd behaviour - why did it skip past Foo-N+1?
+
+ We could explain this to the user in some odd way, but
+ instead we rename "Foo-N+1.mid" as "Foo-N+2.mid", and then
+ use "Foo-N+1" here.
+
+ If that attempted rename fails, we get "Foo-N+2.mid" anyway.
+ */
+
+ boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (track);
+ assert (mt);
+ std::string name = track->steal_write_source_name ();
+
+ if (name.empty()) {
+ return boost::shared_ptr<MidiSource>();
}
- const string name = new_midi_source_name (n);
const string path = new_source_path_from_name (DataType::MIDI, name);
return boost::dynamic_pointer_cast<SMFSource> (
if (b->is_monitor()) {
return false;
}
- return a->order_key (MixerSort) < b->order_key (MixerSort);
+ return a->order_key () < b->order_key ();
}
bool
framepos_t position;
framecnt_t this_chunk;
framepos_t to_do;
+ framepos_t latency_skip;
BufferSet buffers;
SessionDirectory sdir(get_best_session_directory_for_new_source ());
const string sound_dir = sdir.sound_path();
block_processing ();
+ {
+ // synchronize with AudioEngine::process_callback()
+ // make sure processing is not currently running
+ // and processing_blocked() is honored before
+ // acquiring thread buffers
+ Glib::Threads::Mutex::Lock lm (_engine.process_lock());
+ }
+
+ _engine.main_thread()->get_buffers ();
+
/* call tree *MUST* hold route_lock */
if ((playlist = track.playlist()) == 0) {
position = start;
to_do = len;
+ latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export);
/* create a set of reasonably-sized buffers */
- buffers.ensure_buffers (DataType::AUDIO, max_proc.n_audio(), chunk_size);
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ buffers.ensure_buffers(*t, max_proc.get(*t), chunk_size);
+ }
buffers.set_count (max_proc);
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
goto out;
}
+ start += this_chunk;
+ to_do -= this_chunk;
+ itt.progress = (float) (1.0 - ((double) to_do / len));
+
+ if (latency_skip >= chunk_size) {
+ latency_skip -= chunk_size;
+ continue;
+ }
+
+ const framecnt_t current_chunk = this_chunk - latency_skip;
+
uint32_t n = 0;
for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
if (afs) {
- if (afs->write (buffers.get_audio(n).data(), this_chunk) != this_chunk) {
+ if (afs->write (buffers.get_audio(n).data(latency_skip), current_chunk) != current_chunk) {
goto out;
}
}
}
+ latency_skip = 0;
+ }
- start += this_chunk;
- to_do -= this_chunk;
+ /* post-roll, pick up delayed processor output */
+ latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export);
- itt.progress = (float) (1.0 - ((double) to_do / len));
+ while (latency_skip && !itt.cancel) {
+ this_chunk = min (latency_skip, chunk_size);
+ latency_skip -= this_chunk;
+
+ buffers.silence (this_chunk, 0);
+ track.bounce_process (buffers, start, this_chunk, endpoint, include_endpoint, for_export);
+ uint32_t n = 0;
+ for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
+ boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
+
+ if (afs) {
+ if (afs->write (buffers.get_audio(n).data(), this_chunk) != this_chunk) {
+ goto out;
+ }
+ }
+ }
}
if (!itt.cancel) {
}
out:
+ _engine.main_thread()->drop_buffers ();
if (!result) {
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
}
switch (Config->get_remote_model()) {
- case MixerSort:
- case EditorSort:
+ case MixerOrdered:
Route::RemoteControlIDChange (); /* EMIT SIGNAL */
break;
default:
}
void
-Session::sync_order_keys (RouteSortOrderKey sort_key_changed)
+Session::sync_order_keys ()
{
if (deletion_in_progress()) {
return;
opportunity to keep them in sync if they wish to.
*/
- DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("Sync Order Keys, based on %1\n", enum_2_string (sort_key_changed)));
+ DEBUG_TRACE (DEBUG::OrderKeys, "Sync Order Keys.\n");
- Route::SyncOrderKeys (sort_key_changed); /* EMIT SIGNAL */
+ Route::SyncOrderKeys (); /* EMIT SIGNAL */
DEBUG_TRACE (DEBUG::OrderKeys, "\tsync done\n");
}