#include <boost/algorithm/string/erase.hpp>
-#include "pbd/error.h"
-#include "pbd/boost_debug.h"
-#include "pbd/pathscanner.h"
-#include "pbd/stl_delete.h"
#include "pbd/basename.h"
-#include "pbd/stacktrace.h"
-#include "pbd/file_utils.h"
+#include "pbd/boost_debug.h"
#include "pbd/convert.h"
-#include "pbd/unwind.h"
+#include "pbd/convert.h"
+#include "pbd/error.h"
+#include "pbd/file_utils.h"
+#include "pbd/md5.h"
#include "pbd/search_path.h"
+#include "pbd/stacktrace.h"
+#include "pbd/stl_delete.h"
+#include "pbd/unwind.h"
#include "ardour/amp.h"
#include "ardour/analyser.h"
#include "ardour/control_protocol_manager.h"
#include "ardour/data_type.h"
#include "ardour/debug.h"
+#include "ardour/directory_names.h"
#include "ardour/filename_extensions.h"
#include "ardour/graph.h"
#include "ardour/midiport_manager.h"
#include "ardour/plugin.h"
#include "ardour/plugin_insert.h"
#include "ardour/process_thread.h"
+#include "ardour/profile.h"
#include "ardour/rc_configuration.h"
#include "ardour/recent_sessions.h"
#include "ardour/region.h"
#include "ardour/region_factory.h"
#include "ardour/route_graph.h"
#include "ardour/route_group.h"
+#include "ardour/route_sorters.h"
#include "ardour/send.h"
#include "ardour/session.h"
#include "ardour/session_directory.h"
#include "ardour/smf_source.h"
#include "ardour/source_factory.h"
#include "ardour/speakers.h"
+#include "ardour/tempo.h"
#include "ardour/track.h"
+#include "ardour/user_bundle.h"
#include "ardour/utils.h"
#include "midi++/port.h"
PBD::Signal0<void> Session::SuccessfulGraphSort;
PBD::Signal2<void,std::string,std::string> Session::VersionMismatch;
+const framecnt_t Session::bounce_chunk_size = 65536;
static void clean_up_session_event (SessionEvent* ev) { delete ev; }
const SessionEvent::RTeventCallback Session::rt_cleanup (clean_up_session_event);
: playlists (new SessionPlaylists)
, _engine (eng)
, process_function (&Session::process_with_events)
+ , _bounce_processing_active (false)
, waiting_for_sync_offset (false)
, _base_frame_rate (0)
, _current_frame_rate (0)
, _worst_input_latency (0)
, _worst_track_latency (0)
, _have_captured (false)
- , _meter_hold (0)
- , _meter_falloff (0)
, _non_soloed_outs_muted (false)
, _listen_cnt (0)
, _solo_isolated_cnt (0)
, _writable (false)
, _was_seamless (Config->get_seamless_loop ())
, _under_nsm_control (false)
+ , _xrun_count (0)
, delta_accumulator_cnt (0)
, average_slave_delta (1800) // !!! why 1800 ???
, average_dir (0)
, state_tree (0)
, state_was_pending (false)
, _state_of_the_state (StateOfTheState(CannotSave|InitialConnecting|Loading))
+ , _suspend_save (0)
+ , _save_queued (false)
, _last_roll_location (0)
, _last_roll_or_reversal_location (0)
, _last_record_location (0)
, cumulative_rf_motion (0)
, rf_scale (1.0)
, _locations (new Locations (*this))
+ , _ignore_skips_updates (false)
+ , _rt_thread_active (false)
+ , _rt_emit_pending (false)
, step_speed (0)
, outbound_mtc_timecode_frame (0)
, next_quarter_frame_to_send (-1)
, _all_route_group (new RouteGroup (*this, "all"))
, routes (new RouteList)
, _adding_routes_in_progress (false)
+ , _route_deletion_in_progress (false)
, destructive_index (0)
+ , _track_number_decimals(1)
, solo_update_disabled (false)
, default_fade_steepness (0)
, default_fade_msecs (0)
, first_file_header_format_reset (true)
, have_looped (false)
, _have_rec_enabled_track (false)
+ , _have_rec_disabled_track (true)
, _step_editors (0)
, _suspend_timecode_transmission (0)
, _speakers (new Speakers)
- , _order_hint (0)
+ , _order_hint (-1)
, ignore_route_processor_changes (false)
, _scene_changer (0)
, _midi_ports (0)
{
uint32_t sr = 0;
+ pthread_mutex_init (&_rt_emit_mutex, 0);
+ pthread_cond_init (&_rt_emit_cond, 0);
+
pre_engine_init (fullpath);
if (_is_new) {
* of a template.
*/
- if (!mix_template.empty() && load_state (_current_snapshot_name)) {
- throw failed_constructor ();
+ if (!mix_template.empty()) {
+ if (load_state (_current_snapshot_name)) {
+ throw failed_constructor ();
+ }
+ store_recent_templates (mix_template);
}
+ /* load default session properties - if any */
+ config.load_state();
+
} else {
if (load_state (_current_snapshot_name)) {
_is_new = false;
+ emit_thread_start ();
+
/* hook us up to the engine since we are now completely constructed */
BootMessage (_("Connect to engine"));
return -1;
}
+ /* TODO, connect in different thread. (PortRegisteredOrUnregistered may be in RT context)
+ * can we do that? */
+ _engine.PortRegisteredOrUnregistered.connect_same_thread (*this, boost::bind (&Session::setup_bundles, this));
+
return 0;
}
/* clear state tree so that no references to objects are held any more */
delete state_tree;
+ state_tree = 0;
/* reset dynamic state version back to default */
delete _butler;
_butler = 0;
- delete midi_control_ui;
delete _all_route_group;
+ DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n");
+ for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
+ delete *i;
+ }
+
if (click_data != default_click) {
delete [] click_data;
}
clear_clicks ();
+ /* need to remove auditioner before monitoring section
+ * otherwise it is re-connected */
+ auditioner.reset ();
+
+ /* drop references to routes held by the monitoring section
+ * specifically _monitor_out aux/listen references */
+ remove_monitor_section();
+
/* clear out any pending dead wood from RCU managed objects */
routes.flush ();
/* reset these three references to special routes before we do the usual route delete thing */
- auditioner.reset ();
_master_out.reset ();
_monitor_out.reset ();
sources.clear ();
}
- DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n");
- for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
-
- delete *i;
- }
-
/* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
playlists.reset ();
+ emit_thread_terminate ();
+
+ pthread_cond_destroy (&_rt_emit_cond);
+ pthread_mutex_destroy (&_rt_emit_mutex);
+
delete _scene_changer; _scene_changer = 0;
+ delete midi_control_ui; midi_control_ui = 0;
delete _mmc; _mmc = 0;
delete _midi_ports; _midi_ports = 0;
delete _locations; _locations = 0;
+ delete _tempo_map;
+
DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n");
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
void
Session::setup_bundles ()
{
+
+ {
+ RCUWriter<BundleList> writer (_bundles);
+ boost::shared_ptr<BundleList> b = writer.get_copy ();
+ for (BundleList::iterator i = b->begin(); i != b->end();) {
+ if (boost::dynamic_pointer_cast<UserBundle>(*i)) {
+ ++i;
+ continue;
+ }
+ i = b->erase(i);
+ }
+ }
+
vector<string> inputs[DataType::num_types];
vector<string> outputs[DataType::num_types];
for (uint32_t i = 0; i < DataType::num_types; ++i) {
for (uint32_t np = 0; np < outputs[DataType::AUDIO].size(); ++np) {
char buf[32];
- snprintf (buf, sizeof (buf), _("out %" PRIu32), np+1);
+ std::string pn = _engine.get_pretty_name_by_name (outputs[DataType::AUDIO][np]);
+ if (!pn.empty()) {
+ snprintf (buf, sizeof (buf), _("out %s"), pn.substr(0,12).c_str());
+ } else {
+ snprintf (buf, sizeof (buf), _("out %" PRIu32), np+1);
+ }
boost::shared_ptr<Bundle> c (new Bundle (buf, true));
c->add_channel (_("mono"), DataType::AUDIO);
c->set_port (0, outputs[DataType::AUDIO][np]);
- add_bundle (c);
+ add_bundle (c, false);
}
/* stereo output bundles */
c->add_channel (_("R"), DataType::AUDIO);
c->set_port (1, outputs[DataType::AUDIO][np + 1]);
- add_bundle (c);
+ add_bundle (c, false);
}
}
for (uint32_t np = 0; np < inputs[DataType::AUDIO].size(); ++np) {
char buf[32];
- snprintf (buf, sizeof (buf), _("in %" PRIu32), np+1);
+ std::string pn = _engine.get_pretty_name_by_name (inputs[DataType::AUDIO][np]);
+ if (!pn.empty()) {
+ snprintf (buf, sizeof (buf), _("in %s"), pn.substr(0,12).c_str());
+ } else {
+ snprintf (buf, sizeof (buf), _("in %" PRIu32), np+1);
+ }
boost::shared_ptr<Bundle> c (new Bundle (buf, false));
c->add_channel (_("mono"), DataType::AUDIO);
c->set_port (0, inputs[DataType::AUDIO][np]);
- add_bundle (c);
+ add_bundle (c, false);
}
/* stereo input bundles */
c->add_channel (_("R"), DataType::AUDIO);
c->set_port (1, inputs[DataType::AUDIO][np + 1]);
- add_bundle (c);
+ add_bundle (c, false);
}
}
for (uint32_t np = 0; np < inputs[DataType::MIDI].size(); ++np) {
string n = inputs[DataType::MIDI][np];
- boost::erase_first (n, X_("alsa_pcm:"));
-
+ std::string pn = _engine.get_pretty_name_by_name (n);
+ if (!pn.empty()) {
+ n = pn;
+ } else {
+ boost::erase_first (n, X_("alsa_pcm:"));
+ }
boost::shared_ptr<Bundle> c (new Bundle (n, false));
c->add_channel ("", DataType::MIDI);
c->set_port (0, inputs[DataType::MIDI][np]);
- add_bundle (c);
+ add_bundle (c, false);
}
/* MIDI output bundles */
for (uint32_t np = 0; np < outputs[DataType::MIDI].size(); ++np) {
string n = outputs[DataType::MIDI][np];
- boost::erase_first (n, X_("alsa_pcm:"));
-
+ std::string pn = _engine.get_pretty_name_by_name (n);
+ if (!pn.empty()) {
+ n = pn;
+ } else {
+ boost::erase_first (n, X_("alsa_pcm:"));
+ }
boost::shared_ptr<Bundle> c (new Bundle (n, true));
c->add_channel ("", DataType::MIDI);
c->set_port (0, outputs[DataType::MIDI][np]);
- add_bundle (c);
+ add_bundle (c, false);
}
+ // we trust the backend to only calls us if there's a change
+ BundleAddedOrRemoved (); /* EMIT SIGNAL */
}
void
return;
}
- boost::shared_ptr<Route> r (new Route (*this, _("monitor"), Route::MonitorOut, DataType::AUDIO));
+ boost::shared_ptr<Route> r (new Route (*this, _("Monitor"), Route::MonitorOut, DataType::AUDIO));
if (r->init ()) {
return;
}
}
+void
+Session::reset_monitor_section ()
+{
+ /* Process lock should be held by the caller.*/
+
+ if (!_monitor_out) {
+ return;
+ }
+
+ uint32_t limit = _master_out->n_outputs().n_audio();
+
+ /* connect the inputs to the master bus outputs. this
+ * represents a separate data feed from the internal sends from
+ * each route. as of jan 2011, it allows the monitor section to
+ * conditionally ignore either the internal sends or the normal
+ * input feed, but we should really find a better way to do
+ * this, i think.
+ */
+
+ _master_out->output()->disconnect (this);
+ _monitor_out->output()->disconnect (this);
+
+ _monitor_out->input()->ensure_io (_master_out->output()->n_ports(), false, this);
+ _monitor_out->output()->ensure_io (_master_out->output()->n_ports(), false, this);
+
+ for (uint32_t n = 0; n < limit; ++n) {
+ boost::shared_ptr<AudioPort> p = _monitor_out->input()->ports().nth_audio_port (n);
+ boost::shared_ptr<AudioPort> o = _master_out->output()->ports().nth_audio_port (n);
+
+ if (o) {
+ string connect_to = o->name();
+ if (_monitor_out->input()->connect (p, connect_to, this)) {
+ error << string_compose (_("cannot connect control input %1 to %2"), n, connect_to)
+ << endmsg;
+ break;
+ }
+ }
+ }
+
+ /* connect monitor section to physical outs
+ */
+
+ if (Config->get_auto_connect_standard_busses()) {
+
+ if (!Config->get_monitor_bus_preferred_bundle().empty()) {
+
+ boost::shared_ptr<Bundle> b = bundle_by_name (Config->get_monitor_bus_preferred_bundle());
+
+ if (b) {
+ _monitor_out->output()->connect_ports_to_bundle (b, true, this);
+ } else {
+ warning << string_compose (_("The preferred I/O for the monitor bus (%1) cannot be found"),
+ Config->get_monitor_bus_preferred_bundle())
+ << endmsg;
+ }
+
+ } else {
+
+ /* Monitor bus is audio only */
+
+ 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) {
+
+ for (uint32_t n = 0; n < limit; ++n) {
+
+ boost::shared_ptr<Port> p = _monitor_out->output()->ports().port(DataType::AUDIO, n);
+ string connect_to;
+ if (outputs[DataType::AUDIO].size() > (n % mod)) {
+ connect_to = outputs[DataType::AUDIO][n % mod];
+ }
+
+ if (!connect_to.empty()) {
+ if (_monitor_out->output()->connect (p, connect_to, this)) {
+ error << string_compose (
+ _("cannot connect control output %1 to %2"),
+ n, connect_to)
+ << endmsg;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* Connect tracks to monitor section. Note that in an
+ existing session, the internal sends will already exist, but we want the
+ routes to notice that they connect to the control out specifically.
+ */
+
+
+ boost::shared_ptr<RouteList> rls = routes.reader ();
+
+ PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+
+ for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) {
+
+ if ((*x)->is_monitor()) {
+ /* relax */
+ } else if ((*x)->is_master()) {
+ /* relax */
+ } else {
+ (*x)->enable_monitor_send ();
+ }
+ }
+}
+
void
Session::hookup_io ()
{
framepos_t dcp;
framecnt_t dcl;
auto_loop_declick_range (location, dcp, dcl);
- replace_event (SessionEvent::AutoLoopDeclick, dcp, dcl);
if (transport_rolling() && play_loop) {
+ replace_event (SessionEvent::AutoLoopDeclick, dcp, dcl);
// if (_transport_frame > location->end()) {
}
}
+ } else {
+ clear_events (SessionEvent::AutoLoopDeclick);
+ clear_events (SessionEvent::AutoLoop);
}
last_loopend = location->end();
+ set_dirty ();
}
void
punch_connections.drop_connections ();
- location->start_changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_start_changed, this, _1));
- location->end_changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_end_changed, this, _1));
- location->changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_changed, this, _1));
+ location->StartChanged.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_start_changed, this, location));
+ location->EndChanged.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_end_changed, this, location));
+ location->Changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_changed, this, location));
location->set_auto_punch (true, this);
auto_punch_location_changed (location);
}
+void
+Session::set_session_extents (framepos_t start, framepos_t end)
+{
+ Location* existing;
+ if ((existing = _locations->session_range_location()) == 0) {
+ //if there is no existing session, we need to make a new session location (should never happen)
+ existing = new Location (*this, 0, 0, _("session"), Location::IsSessionRange);
+ }
+
+ if (end <= start) {
+ error << _("Session: you can't use that location for session start/end)") << endmsg;
+ return;
+ }
+
+ existing->set( start, end );
+
+ set_dirty();
+}
+
void
Session::set_auto_loop_location (Location* location)
{
loop_connections.drop_connections ();
- location->start_changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, _1));
- location->end_changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, _1));
- location->changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, _1));
+ location->StartChanged.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
+ location->EndChanged.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
+ location->Changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
location->set_auto_loop (true, this);
}
void
-Session::locations_added (Location *)
+Session::update_loop (Location*)
{
set_dirty ();
}
void
-Session::locations_changed ()
+Session::update_marks (Location*)
{
- _locations->apply (*this, &Session::handle_locations_changed);
+ set_dirty ();
}
void
-Session::handle_locations_changed (Locations::LocationList& locations)
+Session::update_skips (Location* loc, bool consolidate)
{
- Locations::LocationList::iterator i;
- Location* location;
- bool set_loop = false;
- bool set_punch = false;
+ if (_ignore_skips_updates) {
+ return;
+ }
+
+ Locations::LocationList skips;
- for (i = locations.begin(); i != locations.end(); ++i) {
+ if (consolidate) {
+ PBD::Unwinder<bool> uw (_ignore_skips_updates, true);
+ consolidate_skips (loc);
+ }
- location =* i;
+ sync_locations_to_skips ();
+
+ set_dirty ();
+}
- if (location->is_auto_punch()) {
- set_auto_punch_location (location);
- set_punch = true;
- }
- if (location->is_auto_loop()) {
- set_auto_loop_location (location);
- set_loop = true;
- }
+void
+Session::consolidate_skips (Location* loc)
+{
+ Locations::LocationList all_locations = _locations->list ();
+
+ for (Locations::LocationList::iterator l = all_locations.begin(); l != all_locations.end(); ) {
+
+ if (!(*l)->is_skip ()) {
+ ++l;
+ continue;
+ }
- if (location->is_session_range()) {
- _session_range_location = location;
+ /* don't test against self */
+
+ if (*l == loc) {
+ ++l;
+ continue;
+ }
+
+ switch (Evoral::coverage ((*l)->start(), (*l)->end(), loc->start(), loc->end())) {
+ case Evoral::OverlapInternal:
+ case Evoral::OverlapExternal:
+ case Evoral::OverlapStart:
+ case Evoral::OverlapEnd:
+ /* adjust new location to cover existing one */
+ loc->set_start (min (loc->start(), (*l)->start()));
+ loc->set_end (max (loc->end(), (*l)->end()));
+ /* we don't need this one any more */
+ _locations->remove (*l);
+ /* the location has been deleted, so remove reference to it in our local list */
+ l = all_locations.erase (l);
+ break;
+
+ case Evoral::OverlapNone:
+ ++l;
+ break;
+ }
+ }
+}
+
+void
+Session::sync_locations_to_skips ()
+{
+ /* This happens asynchronously (in the audioengine thread). After the clear is done, we will call
+ * Session::_sync_locations_to_skips() from the audioengine thread.
+ */
+ clear_events (SessionEvent::Skip, boost::bind (&Session::_sync_locations_to_skips, this));
+}
+
+void
+Session::_sync_locations_to_skips ()
+{
+ /* called as a callback after existing Skip events have been cleared from a realtime audioengine thread */
+
+ Locations::LocationList const & locs (_locations->list());
+
+ for (Locations::LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) {
+
+ Location* location = *i;
+
+ if (location->is_skip() && location->is_skipping()) {
+ SessionEvent* ev = new SessionEvent (SessionEvent::Skip, SessionEvent::Add, location->start(), location->end(), 1.0);
+ queue_event (ev);
}
}
+}
- if (!set_loop) {
- set_auto_loop_location (0);
- }
- if (!set_punch) {
- set_auto_punch_location (0);
- }
- set_dirty();
+void
+Session::location_added (Location *location)
+{
+ if (location->is_auto_punch()) {
+ set_auto_punch_location (location);
+ }
+
+ if (location->is_auto_loop()) {
+ set_auto_loop_location (location);
+ }
+
+ if (location->is_session_range()) {
+ /* no need for any signal handling or event setting with the session range,
+ because we keep a direct reference to it and use its start/end directly.
+ */
+ _session_range_location = location;
+ }
+
+ if (location->is_mark()) {
+ /* listen for per-location signals that require us to do any * global updates for marks */
+
+ location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ }
+
+ if (location->is_skip()) {
+ /* listen for per-location signals that require us to update skip-locate events */
+
+ location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
+ location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
+ location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
+ location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, false));
+
+ update_skips (location, true);
+ }
+
+ set_dirty ();
+}
+
+void
+Session::location_removed (Location *location)
+{
+ if (location->is_auto_loop()) {
+ set_auto_loop_location (0);
+ set_track_loop (false);
+ }
+
+ if (location->is_auto_punch()) {
+ set_auto_punch_location (0);
+ }
+
+ if (location->is_session_range()) {
+ /* this is never supposed to happen */
+ error << _("programming error: session range removed!") << endl;
+ }
+
+ if (location->is_skip()) {
+
+ update_skips (location, false);
+ }
+
+ set_dirty ();
+}
+
+void
+Session::locations_changed ()
+{
+ _locations->apply (*this, &Session::_locations_changed);
+}
+
+void
+Session::_locations_changed (const Locations::LocationList& locations)
+{
+ /* There was some mass-change in the Locations object.
+
+ We might be re-adding a location here but it doesn't actually matter
+ for all the locations that the Session takes an interest in.
+ */
+
+ for (Locations::LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+ location_added (*i);
+ }
}
void
if (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) {
_last_record_location = _transport_frame;
- _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe));
+ send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe));
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
set_track_monitor_input_status (true);
if ((!Config->get_latched_record_enable () && !play_loop) || force) {
g_atomic_int_set (&_record_status, Disabled);
- _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit));
+ send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit));
} else {
if (rs == Recording) {
g_atomic_int_set (&_record_status, Enabled);
enable_record ();
}
} else {
- _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
+ send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
RecordStateChanged (); /* EMIT SIGNAL */
}
framepos_t tf;
framecnt_t offset;
- /* the first of these two possible settings for "offset"
- mean that the audible frame is stationary until
- audio emerges from the latency compensation
- "pseudo-pipeline".
-
- the second means that the audible frame is stationary
- until audio would emerge from a physical port
- in the absence of any plugin latency compensation
- */
-
offset = worst_playback_latency ();
- if (offset > current_block_size) {
- offset -= current_block_size;
- } else {
- /* XXX is this correct? if we have no external
- physical connections and everything is internal
- then surely this is zero? still, how
- likely is that anyway?
- */
- offset = current_block_size;
- }
-
if (synced_to_engine()) {
+ /* Note: this is basically just sync-to-JACK */
tf = _engine.transport_frame();
} else {
tf = _transport_frame;
failed:
if (!new_routes.empty()) {
- add_routes (new_routes, true, true, true);
+ StateProtector sp (this);
+ if (Profile->get_trx()) {
+ add_routes (new_routes, false, false, false);
+ } else {
+ add_routes (new_routes, true, true, false);
+ }
if (instrument) {
for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) {
}
}
+void
+Session::reconnect_existing_routes (bool withLock, bool reconnect_master, bool reconnect_inputs, bool reconnect_outputs)
+{
+ /* TRX does stuff here, ardour does not (but probably should). This is called after an engine reset (in particular).
+ */
+}
+
+#ifdef USE_TRACKS_CODE_FEATURES
+
+void
+Session::reconnect_midi_scene_ports(bool inputs)
+{
+ if (inputs) {
+ scene_in()->disconnect_all ();
+
+ std::vector<EngineStateController::MidiPortState> midi_port_states;
+ EngineStateController::instance()->get_physical_midi_input_states (midi_port_states);
+
+ std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
+
+ for (; state_iter != midi_port_states.end(); ++state_iter) {
+ if (state_iter->active && state_iter->available && state_iter->connected) {
+ scene_in()->connect (state_iter->name);
+ }
+ }
+
+ } else {
+ scene_out()->disconnect_all ();
+
+ std::vector<EngineStateController::MidiPortState> midi_port_states;
+ EngineStateController::instance()->get_physical_midi_output_states (midi_port_states);
+
+ std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
+
+ for (; state_iter != midi_port_states.end(); ++state_iter) {
+ if (state_iter->active && state_iter->available && state_iter->connected) {
+ scene_out()->connect (state_iter->name);
+ }
+ }
+
+ }
+}
+
+#endif
+
/** Caller must not hold process lock
* @param name_template string to use for the start of the name, or "" to use "Audio".
*/
RouteList new_routes;
list<boost::shared_ptr<AudioTrack> > ret;
- bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Audio");
+ string name_pattern;
+ if (Profile->get_trx() ) {
+ name_pattern = "Track ";
+ } else {
+ name_pattern = "Audio ";
+ }
+
+ bool const use_number = (how_many != 1) || name_template.empty () || name_template == _(name_pattern.c_str() );
+
while (how_many) {
- if (!find_route_name (name_template.empty() ? _("Audio") : name_template, ++track_id, track_name, sizeof(track_name), use_number)) {
+
+ if (!find_route_name (name_template.empty() ? _(name_pattern.c_str()) : name_template, ++track_id, track_name, sizeof(track_name), use_number)) {
error << "cannot find name for new audio track" << endmsg;
goto failed;
}
failed:
if (!new_routes.empty()) {
- add_routes (new_routes, true, true, true);
+ StateProtector sp (this);
+ if (Profile->get_trx()) {
+ add_routes (new_routes, false, false, false);
+ } else {
+ add_routes (new_routes, true, true, false);
+ }
}
return ret;
failure:
if (!ret.empty()) {
- add_routes (ret, false, true, true); // autoconnect outputs only
+ StateProtector sp (this);
+ if (Profile->get_trx()) {
+ add_routes (ret, false, false, false);
+ } else {
+ add_routes (ret, false, true, true); // autoconnect // outputs only
+ }
}
return ret;
/* generate a new name by adding a number to the end of the template name */
if (!find_route_name (route_name.c_str(), ++number, name, sizeof(name), true)) {
fatal << _("Session: UINT_MAX routes? impossible!") << endmsg;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
}
}
out:
if (!ret.empty()) {
- add_routes (ret, true, true, true);
+ StateProtector sp (this);
+ if (Profile->get_trx()) {
+ add_routes (ret, false, false, false);
+ } else {
+ add_routes (ret, true, true, false);
+ }
IO::enable_connecting ();
}
save_state (_current_snapshot_name);
}
+ reassign_track_numbers();
+
+ update_route_record_state ();
+
RouteAdded (new_routes); /* EMIT SIGNAL */
}
ChanCount existing_outputs;
uint32_t order = next_control_id();
- if (_order_hint != 0) {
+ if (_order_hint > -1) {
order = _order_hint;
- _order_hint = 0;
+ _order_hint = -1;
}
count_existing_track_channels (existing_inputs, existing_outputs);
if (tr) {
tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr<Track> (tr)));
track_playlist_changed (boost::weak_ptr<Track> (tr));
- tr->RecordEnableChanged.connect_same_thread (*this, boost::bind (&Session::update_have_rec_enabled_track, this));
+ tr->RecordEnableChanged.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this));
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (tr);
if (mt) {
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((s = (*i)->internal_send_for (dest)) != 0) {
- s->amp()->gain_control()->set_value (0.0);
+ s->amp()->gain_control()->set_value (GAIN_COEFF_ZERO);
}
}
}
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((s = (*i)->internal_send_for (dest)) != 0) {
- s->amp()->gain_control()->set_value (1.0);
+ s->amp()->gain_control()->set_value (GAIN_COEFF_UNITY);
}
}
}
graph_reordered ();
}
+
void
-Session::remove_route (boost::shared_ptr<Route> route)
+Session::remove_routes (boost::shared_ptr<RouteList> routes_to_remove)
{
- if (route == _master_out) {
- return;
- }
-
- route->set_solo (false, this);
-
- {
+ { // RCU Writer scope
RCUWriter<RouteList> writer (routes);
boost::shared_ptr<RouteList> rs = writer.get_copy ();
+
+
+ for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
+
+ if (*iter == _master_out) {
+ continue;
+ }
+
+ (*iter)->set_solo (false, this);
+
+ rs->remove (*iter);
+
+ /* deleting the master out seems like a dumb
+ idea, but its more of a UI policy issue
+ than our concern.
+ */
+
+ if (*iter == _master_out) {
+ _master_out = boost::shared_ptr<Route> ();
+ }
+
+ if (*iter == _monitor_out) {
+ _monitor_out.reset ();
+ }
- rs->remove (route);
-
- /* deleting the master out seems like a dumb
- idea, but its more of a UI policy issue
- than our concern.
- */
-
- if (route == _master_out) {
- _master_out = boost::shared_ptr<Route> ();
- }
-
- if (route == _monitor_out) {
- _monitor_out.reset ();
- }
-
- /* writer goes out of scope, forces route list update */
- }
-
- update_route_solo_state ();
-
- // We need to disconnect the route's inputs and outputs
-
- route->input()->disconnect (0);
- route->output()->disconnect (0);
-
- /* if the route had internal sends sending to it, remove them */
- if (route->internal_return()) {
-
- boost::shared_ptr<RouteList> r = routes.reader ();
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- boost::shared_ptr<Send> s = (*i)->internal_send_for (route);
- if (s) {
- (*i)->remove_processor (s);
+ update_route_solo_state ();
+
+ // We need to disconnect the route's inputs and outputs
+
+ (*iter)->input()->disconnect (0);
+ (*iter)->output()->disconnect (0);
+
+ /* if the route had internal sends sending to it, remove them */
+ if ((*iter)->internal_return()) {
+
+ boost::shared_ptr<RouteList> r = routes.reader ();
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ boost::shared_ptr<Send> s = (*i)->internal_send_for (*iter);
+ if (s) {
+ (*i)->remove_processor (s);
+ }
+ }
+ }
+
+ /* if the monitoring section had a pointer to this route, remove it */
+ if (_monitor_out && !(*iter)->is_master() && !(*iter)->is_monitor()) {
+ Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+ PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+ (*iter)->remove_aux_or_listen (_monitor_out);
+ }
+
+ boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (*iter);
+ if (mt && mt->step_editing()) {
+ if (_step_editors > 0) {
+ _step_editors--;
+ }
}
- }
- }
- boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (route);
- if (mt && mt->step_editing()) {
- if (_step_editors > 0) {
- _step_editors--;
+ RouteAddedOrRemoved (false); /* EMIT SIGNAL */
}
- }
+
+ /* writer goes out of scope, forces route list update */
+ } // end of RCU Writer scope
+
update_latency_compensation ();
set_dirty();
-
+
/* Re-sort routes to remove the graph's current references to the one that is
* going away, then flush old references out of the graph.
+ * Wave Tracks: reconnect routes
*/
- resort_routes ();
+ if (ARDOUR::Profile->get_trx () ) {
+ reconnect_existing_routes(true, false);
+ } else {
+ resort_routes ();
+ }
+
if (_process_graph) {
_process_graph->clear_other_chain ();
}
-
+
/* get rid of it from the dead wood collection in the route list manager */
-
/* XXX i think this is unsafe as it currently stands, but i am not sure. (pd, october 2nd, 2006) */
-
+
routes.flush ();
+
+ /* try to cause everyone to drop their references
+ * and unregister ports from the backend
+ */
+ PBD::Unwinder<bool> uw_flag (_route_deletion_in_progress, true);
- /* try to cause everyone to drop their references */
-
- route->drop_references ();
-
+ for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
+ (*iter)->drop_references ();
+ }
+
Route::RemoteControlIDChange(); /* EMIT SIGNAL */
-
+
/* save the new state of the world */
-
+
if (save_state (_current_snapshot_name)) {
save_history (_current_snapshot_name);
}
+
+ reassign_track_numbers();
+ update_route_record_state ();
+}
+
+void
+Session::remove_route (boost::shared_ptr<Route> route)
+{
+ boost::shared_ptr<RouteList> rl (new RouteList);
+ rl->push_back (route);
+ remove_routes (rl);
}
void
for (RouteList::iterator i = uninvolved.begin(); i != uninvolved.end(); ++i) {
DEBUG_TRACE (DEBUG::Solo, string_compose ("mute change for %1, which neither feeds or is fed by %2\n", (*i)->name(), route->name()));
+ (*i)->act_on_mute ();
(*i)->mute_changed (this);
}
return boost::shared_ptr<Route> ((Route*) 0);
}
+
+void
+Session::reassign_track_numbers ()
+{
+ int64_t tn = 0;
+ int64_t bn = 0;
+ RouteList r (*(routes.reader ()));
+ SignalOrderRouteSorter sorter;
+ r.sort (sorter);
+
+ StateProtector sp (this);
+
+ for (RouteList::iterator i = r.begin(); i != r.end(); ++i) {
+ if (boost::dynamic_pointer_cast<Track> (*i)) {
+ (*i)->set_track_number(++tn);
+ }
+ else if (!(*i)->is_master() && !(*i)->is_monitor() && !(*i)->is_auditioner()) {
+ (*i)->set_track_number(--bn);
+ }
+ }
+ const uint32_t decimals = ceilf (log10f (tn + 1));
+ const bool decimals_changed = _track_number_decimals != decimals;
+ _track_number_decimals = decimals;
+
+ if (decimals_changed && config.get_track_name_number ()) {
+ for (RouteList::iterator i = r.begin(); i != r.end(); ++i) {
+ boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (*i);
+ if (t) {
+ t->resync_track_name();
+ }
+ }
+ // trigger GUI re-layout
+ config.ParameterChanged("track-name-number");
+ }
+}
+
void
Session::playlist_region_added (boost::weak_ptr<Region> w)
{
}
}
- if (!(_state_of_the_state & InCleanup)) {
+ if (!(_state_of_the_state & StateOfTheState (InCleanup|Loading))) {
/* save state so we don't end up with a session file
referring to non-existent sources.
}
boost::shared_ptr<AudioFileSource>
-Session::source_by_path_and_channel (const string& path, uint16_t chn) const
+Session::audio_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.
}
boost::shared_ptr<MidiSource>
-Session::source_by_path (const std::string& path) const
+Session::midi_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.
return cnt;
}
-/** 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)
- */
string
-Session::new_source_path_from_name (DataType type, const string& name)
+Session::peak_path (string base) const
{
- assert(name.find("/") == string::npos);
+ if (Glib::path_is_absolute (base)) {
- SessionDirectory sdir(get_best_session_directory_for_new_source());
+ /* rip the session dir from the audiofile source */
- std::string p;
- if (type == DataType::AUDIO) {
- p = sdir.sound_path();
- } else if (type == DataType::MIDI) {
- p = sdir.midi_path();
- } else {
- error << "Unknown source type, unable to create file path" << endmsg;
- return "";
- }
+ string session_path;
+ string interchange_dir_string = string (interchange_dir_name) + G_DIR_SEPARATOR;
+ bool in_another_session = true;
+
+ if (base.find (interchange_dir_string) != string::npos) {
+
+ session_path = Glib::path_get_dirname (base); /* now ends in audiofiles */
+ session_path = Glib::path_get_dirname (session_path); /* now ends in session name */
+ session_path = Glib::path_get_dirname (session_path); /* now ends in interchange */
+ session_path = Glib::path_get_dirname (session_path); /* now has session path */
- return Glib::build_filename (p, name);
-}
+ /* see if it is within our session */
-string
-Session::peak_path (string base) const
-{
+ for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+ if (i->path == session_path) {
+ in_another_session = false;
+ break;
+ }
+ }
+ } else {
+ in_another_session = false;
+ }
+
+
+ if (in_another_session) {
+ SessionDirectory sd (session_path);
+ return Glib::build_filename (sd.peak_path(), Glib::path_get_basename (base) + peakfile_suffix);
+ }
+ }
+
+ base = Glib::path_get_basename (base);
return Glib::build_filename (_session_dir->peak_path(), base + peakfile_suffix);
}
-/** Return a unique name based on \a base for a new internal audio source */
string
-Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t chan, bool destructive)
+Session::new_audio_source_path_for_embedded (const std::string& path)
{
- uint32_t cnt;
- char buf[PATH_MAX+1];
- const uint32_t limit = 10000;
- string legalized;
- string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
-
- buf[0] = '\0';
- legalized = legalize_for_path (base);
+ /* embedded source:
+ *
+ * we know that the filename is already unique because it exists
+ * out in the filesystem.
+ *
+ * However, when we bring it into the session, we could get a
+ * collision.
+ *
+ * Eg. two embedded files:
+ *
+ * /foo/bar/baz.wav
+ * /frob/nic/baz.wav
+ *
+ * When merged into session, these collide.
+ *
+ * There will not be a conflict with in-memory sources
+ * because when the source was created we already picked
+ * a unique name for it.
+ *
+ * This collision is not likely to be common, but we have to guard
+ * against it. So, if there is a collision, take the md5 hash of the
+ * the path, and use that as the filename instead.
+ */
- // Find a "version" of the base name that doesn't exist in any of the possible directories.
- for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
+ SessionDirectory sdir (get_best_session_directory_for_new_audio());
+ string base = Glib::path_get_basename (path);
+ string newpath = Glib::build_filename (sdir.sound_path(), base);
+
+ if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
- vector<space_and_path>::iterator i;
- uint32_t existing = 0;
+ MD5 md5;
- for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+ md5.digestString (path.c_str());
+ md5.writeToString ();
+ base = md5.digestChars;
+
+ string ext = get_suffix (path);
- if (destructive) {
+ if (!ext.empty()) {
+ base += '.';
+ base += ext;
+ }
+
+ newpath = Glib::build_filename (sdir.sound_path(), base);
- if (nchan < 2) {
- snprintf (buf, sizeof(buf), "T%04d-%s%s",
- cnt, legalized.c_str(), ext.c_str());
- } else if (nchan == 2) {
- if (chan == 0) {
- snprintf (buf, sizeof(buf), "T%04d-%s%%L%s",
- cnt, legalized.c_str(), ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "T%04d-%s%%R%s",
- cnt, legalized.c_str(), ext.c_str());
- }
- } else if (nchan < 26) {
- snprintf (buf, sizeof(buf), "T%04d-%s%%%c%s",
- cnt, legalized.c_str(), 'a' + chan, ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "T%04d-%s%s",
- cnt, legalized.c_str(), ext.c_str());
- }
+ /* if this collides, we're screwed */
- } else {
+ if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
+ error << string_compose (_("Merging embedded file %1: name collision AND md5 hash collision!"), path) << endmsg;
+ return string();
+ }
- if (nchan < 2) {
- snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str());
- } else if (nchan == 2) {
- if (chan == 0) {
- snprintf (buf, sizeof(buf), "%s-%u%%L%s", legalized.c_str(), cnt, ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "%s-%u%%R%s", legalized.c_str(), cnt, ext.c_str());
- }
- } else if (nchan < 26) {
- snprintf (buf, sizeof(buf), "%s-%u%%%c%s", legalized.c_str(), cnt, 'a' + chan, ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str());
- }
- }
+ }
- SessionDirectory sdir((*i).path);
+ return newpath;
+}
- string spath = sdir.sound_path();
+/** Return true if there are no audio file sources that use @param name as
+ * the filename component of their path.
+ *
+ * Return false otherwise.
+ *
+ * This method MUST ONLY be used to check in-session, mono files since it
+ * hard-codes the channel of the audio file source we are looking for as zero.
+ *
+ * If/when Ardour supports native files in non-mono formats, the logic here
+ * will need to be revisited.
+ */
+bool
+Session::audio_source_name_is_unique (const string& name)
+{
+ std::vector<string> sdirs = source_search_path (DataType::AUDIO);
+ vector<space_and_path>::iterator i;
+ uint32_t existing = 0;
- /* note that we search *without* the extension so that
- we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
- in the event that this new name is required for
- a file format change.
- */
+ for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
+
+ /* note that we search *without* the extension so that
+ we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
+ in the event that this new name is required for
+ a file format change.
+ */
- if (matching_unsuffixed_filename_exists_in (spath, buf)) {
- existing++;
- break;
- }
+ const string spath = *i;
+
+ if (matching_unsuffixed_filename_exists_in (spath, 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, name);
- /* 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.
- */
+ if (audio_source_by_path_and_channel (possible_path, 0)) {
+ existing++;
+ break;
+ }
+ }
- string possible_path = Glib::build_filename (spath, buf);
+ return (existing == 0);
+}
- if (source_by_path (possible_path)) {
- existing++;
- break;
- }
+string
+Session::format_audio_source_name (const string& legalized_base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required, uint32_t cnt, bool related_exists)
+{
+ ostringstream sstr;
+ const string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
+
+ if (destructive) {
+ sstr << 'T';
+ sstr << setfill ('0') << setw (4) << cnt;
+ sstr << legalized_base;
+ } else {
+ sstr << legalized_base;
+
+ if (take_required || related_exists) {
+ sstr << '-';
+ sstr << cnt;
+ }
+ }
+
+ if (nchan == 2) {
+ if (chan == 0) {
+ sstr << "%L";
+ } else {
+ sstr << "%R";
+ }
+ } else if (nchan > 2) {
+ if (nchan < 26) {
+ sstr << '%';
+ sstr << 'a' + chan;
+ } else {
+ /* XXX what? more than 26 channels! */
+ sstr << '%';
+ sstr << chan+1;
}
+ }
+
+ sstr << ext;
- if (existing == 0) {
+ return sstr.str();
+}
+
+/** Return a unique name based on \a base for a new internal audio source */
+string
+Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required)
+{
+ uint32_t cnt;
+ string possible_name;
+ const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name
+ string legalized;
+ bool some_related_source_name_exists = false;
+
+ legalized = legalize_for_path (base);
+
+ // Find a "version" of the base name that doesn't exist in any of the possible directories.
+
+ for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
+
+ possible_name = format_audio_source_name (legalized, nchan, chan, destructive, take_required, cnt, some_related_source_name_exists);
+
+ if (audio_source_name_is_unique (possible_name)) {
break;
}
+
+ some_related_source_name_exists = true;
if (cnt > limit) {
error << string_compose(
}
}
- return Glib::path_get_basename (buf);
-}
+ /* We've established that the new name does not exist in any session
+ * directory, so now find out which one we should use for this new
+ * audio source.
+ */
-/** Create a new within-session audio source */
-boost::shared_ptr<AudioFileSource>
-Session::create_audio_source_for_session (size_t n_chans, string const & n, uint32_t chan, bool destructive)
-{
- const string name = new_audio_source_name (n, n_chans, chan, destructive);
- const string path = new_source_path_from_name(DataType::AUDIO, name);
+ SessionDirectory sdir (get_best_session_directory_for_new_audio());
- return boost::dynamic_pointer_cast<AudioFileSource> (
- SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate()));
+ std::string s = Glib::build_filename (sdir.sound_path(), possible_name);
+
+ return s;
}
-/** Return a unique name based on \a owner_name for a new internal MIDI source */
+/** Return a unique name based on `base` for a new internal MIDI source */
string
-Session::new_midi_source_name (const string& owner_name)
+Session::new_midi_source_path (const string& base)
{
uint32_t cnt;
char buf[PATH_MAX+1];
const uint32_t limit = 10000;
string legalized;
+ string possible_path;
string possible_name;
buf[0] = '\0';
- legalized = legalize_for_path (owner_name);
+ legalized = legalize_for_path (base);
// Find a "version" of the file name that doesn't exist in any of the possible directories.
+ std::vector<string> sdirs = source_search_path(DataType::MIDI);
+
+ /* - the main session folder is the first in the vector.
+ * - after checking all locations for file-name uniqueness,
+ * we keep the one from the last iteration as new file name
+ * - midi files are small and should just be kept in the main session-folder
+ *
+ * -> reverse the array, check main session folder last and use that as location
+ * for MIDI files.
+ */
+ std::reverse(sdirs.begin(), sdirs.end());
for (cnt = 1; cnt <= limit; ++cnt) {
vector<space_and_path>::iterator i;
uint32_t existing = 0;
+
+ for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++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 possible_path = Glib::build_filename (sdir.midi_path(), possible_name);
+ possible_path = Glib::build_filename (*i, possible_name);
if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) {
existing++;
}
- if (source_by_path (possible_path)) {
+ if (midi_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, owner_name) << endmsg;
+ limit, base) << endmsg;
destroy ();
- throw failed_constructor();
+ return 0;
}
}
- return possible_name;
+ /* No need to "find best location" for software/app-based RAID, because
+ MIDI is so small that we always put it in the same place.
+ */
+
+ return possible_path;
}
+/** Create a new within-session audio source */
+boost::shared_ptr<AudioFileSource>
+Session::create_audio_source_for_session (size_t n_chans, string const & base, uint32_t chan, bool destructive)
+{
+ const string path = new_audio_source_path (base, n_chans, chan, destructive, true);
+
+ if (!path.empty()) {
+ return boost::dynamic_pointer_cast<AudioFileSource> (
+ SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate()));
+ } else {
+ throw failed_constructor ();
+ }
+}
+
/** Create a new within-session MIDI source */
boost::shared_ptr<MidiSource>
Session::create_midi_source_for_session (string const & basic_name)
{
- std::string name;
-
- if (name.empty()) {
- name = new_midi_source_name (basic_name);
+ const string path = new_midi_source_path (basic_name);
+
+ if (!path.empty()) {
+ return boost::dynamic_pointer_cast<SMFSource> (
+ SourceFactory::createWritable (
+ DataType::MIDI, *this, path, false, frame_rate()));
+ } else {
+ throw failed_constructor ();
}
-
- const string path = new_source_path_from_name (DataType::MIDI, name);
-
- return boost::dynamic_pointer_cast<SMFSource> (
- SourceFactory::createWritable (
- DataType::MIDI, *this, path, false, frame_rate()));
}
/** Create a new within-session MIDI source */
return boost::shared_ptr<MidiSource>();
}
- const string path = new_source_path_from_name (DataType::MIDI, name);
+ /* MIDI files are small, just put them in the first location of the
+ session source search path.
+ */
+
+ const string path = Glib::build_filename (source_search_path (DataType::MIDI).front(), name);
return boost::dynamic_pointer_cast<SMFSource> (
SourceFactory::createWritable (
void
Session::cancel_audition ()
{
+ if (!auditioner) {
+ return;
+ }
if (auditioner->auditioning()) {
auditioner->cancel_audition ();
AuditionActive (false); /* EMIT SIGNAL */
fatal << string_compose (_("programming error: %1"),
X_("illegal native file data format"))
<< endmsg;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
}
double scale = 4096.0 / sample_bytes_on_disk;
}
void
-Session::add_bundle (boost::shared_ptr<Bundle> bundle)
+Session::add_bundle (boost::shared_ptr<Bundle> bundle, bool emit_signal)
{
{
RCUWriter<BundleList> writer (_bundles);
b->push_back (bundle);
}
- BundleAdded (bundle); /* EMIT SIGNAL */
+ if (emit_signal) {
+ BundleAddedOrRemoved (); /* EMIT SIGNAL */
+ }
set_dirty();
}
}
if (removed) {
- BundleRemoved (bundle); /* EMIT SIGNAL */
+ BundleAddedOrRemoved (); /* EMIT SIGNAL */
}
set_dirty();
}
void
-Session::update_locations_after_tempo_map_change (Locations::LocationList& loc)
+Session::update_locations_after_tempo_map_change (const Locations::LocationList& loc)
{
- for (Locations::LocationList::iterator i = loc.begin(); i != loc.end(); ++i) {
+ for (Locations::LocationList::const_iterator i = loc.begin(); i != loc.end(); ++i) {
(*i)->recompute_frames_from_bbt ();
}
}
void
Session::ensure_buffers (ChanCount howmany)
{
- BufferManager::ensure_buffers (howmany);
+ BufferManager::ensure_buffers (howmany, bounce_processing() ? bounce_chunk_size : 0);
}
void
}
boost::shared_ptr<Region>
-Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
+Session::write_one_track (Track& track, framepos_t start, framepos_t end,
bool /*overwrite*/, vector<boost::shared_ptr<Source> >& srcs,
InterThreadInfo& itt,
boost::shared_ptr<Processor> endpoint, bool include_endpoint,
- bool for_export)
+ bool for_export, bool for_freeze)
{
boost::shared_ptr<Region> result;
boost::shared_ptr<Playlist> playlist;
- boost::shared_ptr<AudioFileSource> fsource;
- uint32_t x;
- char buf[PATH_MAX+1];
+ boost::shared_ptr<Source> source;
ChanCount diskstream_channels (track.n_channels());
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();
framepos_t len = end - start;
bool need_block_size_reset = false;
- string ext;
ChanCount const max_proc = track.max_processor_streams ();
+ string legal_playlist_name;
+ string possible_path;
if (end <= start) {
error << string_compose (_("Cannot write a range where end <= start (e.g. %1 <= %2)"),
return result;
}
- diskstream_channels = track.bounce_get_output_streams (diskstream_channels, endpoint, include_endpoint, for_export);
+ diskstream_channels = track.bounce_get_output_streams (diskstream_channels, endpoint,
+ include_endpoint, for_export, for_freeze);
- if (diskstream_channels.n_audio() < 1) {
- error << _("Cannot write a range with no audio.") << endmsg;
+ if (diskstream_channels.n(track.data_type()) < 1) {
+ error << _("Cannot write a range with no data.") << endmsg;
return result;
}
- const framecnt_t chunk_size = (256 * 1024)/4;
-
// block all process callback handling
block_processing ();
Glib::Threads::Mutex::Lock lm (_engine.process_lock());
}
- _engine.main_thread()->get_buffers ();
+ _bounce_processing_active = true;
/* call tree *MUST* hold route_lock */
goto out;
}
- ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
-
- for (uint32_t chan_n = 0; chan_n < diskstream_channels.n_audio(); ++chan_n) {
+ legal_playlist_name = legalize_for_path (playlist->name());
- for (x = 0; x < 99999; ++x) {
- snprintf (buf, sizeof(buf), "%s/%s-%d-bounce-%" PRIu32 "%s", sound_dir.c_str(), playlist->name().c_str(), chan_n, x+1, ext.c_str());
- if (!Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) {
- break;
- }
- }
+ for (uint32_t chan_n = 0; chan_n < diskstream_channels.n(track.data_type()); ++chan_n) {
- if (x == 99999) {
- error << string_compose (_("too many bounced versions of playlist \"%1\""), playlist->name()) << endmsg;
+ string base_name = string_compose ("%1-%2-bounce", playlist->name(), chan_n);
+ string path = ((track.data_type() == DataType::AUDIO)
+ ? new_audio_source_path (legal_playlist_name, diskstream_channels.n_audio(), chan_n, false, true)
+ : new_midi_source_path (legal_playlist_name));
+
+ if (path.empty()) {
goto out;
}
try {
- fsource = boost::dynamic_pointer_cast<AudioFileSource> (
- SourceFactory::createWritable (DataType::AUDIO, *this, buf, false, frame_rate()));
+ source = SourceFactory::createWritable (track.data_type(), *this, path, false, frame_rate());
}
catch (failed_constructor& err) {
- error << string_compose (_("cannot create new audio file \"%1\" for %2"), buf, track.name()) << endmsg;
+ error << string_compose (_("cannot create new file \"%1\" for %2"), path, track.name()) << endmsg;
goto out;
}
- srcs.push_back (fsource);
+ srcs.push_back (source);
}
/* tell redirects that care that we are about to use a much larger
*/
need_block_size_reset = true;
- track.set_block_size (chunk_size);
+ track.set_block_size (bounce_chunk_size);
+ _engine.main_thread()->get_buffers ();
position = start;
to_do = len;
- latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export);
+ latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export, for_freeze);
/* create a set of reasonably-sized buffers */
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
- buffers.ensure_buffers(*t, max_proc.get(*t), chunk_size);
+ buffers.ensure_buffers(*t, max_proc.get(*t), bounce_chunk_size);
}
buffers.set_count (max_proc);
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);
- if (afs)
+ boost::shared_ptr<MidiSource> ms;
+ if (afs) {
afs->prepare_for_peakfile_writes ();
+ } else if ((ms = boost::dynamic_pointer_cast<MidiSource>(*src))) {
+ Source::Lock lock(ms->mutex());
+ ms->mark_streaming_write_started(lock);
+ }
}
while (to_do && !itt.cancel) {
- this_chunk = min (to_do, chunk_size);
+ this_chunk = min (to_do, bounce_chunk_size);
- if (track.export_stuff (buffers, start, this_chunk, endpoint, include_endpoint, for_export)) {
+ if (track.export_stuff (buffers, start, this_chunk, endpoint, include_endpoint, for_export, for_freeze)) {
goto out;
}
to_do -= this_chunk;
itt.progress = (float) (1.0 - ((double) to_do / len));
- if (latency_skip >= chunk_size) {
- latency_skip -= chunk_size;
+ if (latency_skip >= bounce_chunk_size) {
+ latency_skip -= bounce_chunk_size;
continue;
}
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);
+ boost::shared_ptr<MidiSource> ms;
if (afs) {
if (afs->write (buffers.get_audio(n).data(latency_skip), current_chunk) != current_chunk) {
goto out;
}
+ } else if ((ms = boost::dynamic_pointer_cast<MidiSource>(*src))) {
+ Source::Lock lock(ms->mutex());
+
+ const MidiBuffer& buf = buffers.get_midi(0);
+ for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
+ Evoral::Event<framepos_t> ev = *i;
+ ev.set_time(ev.time() - position);
+ ms->append_event_frames(lock, ev, ms->timeline_position());
+ }
}
}
latency_skip = 0;
}
/* post-roll, pick up delayed processor output */
- latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export);
+ latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export, for_freeze);
while (latency_skip && !itt.cancel) {
- this_chunk = min (latency_skip, chunk_size);
+ this_chunk = min (latency_skip, bounce_chunk_size);
latency_skip -= this_chunk;
buffers.silence (this_chunk, 0);
- track.bounce_process (buffers, start, this_chunk, endpoint, include_endpoint, for_export);
+ track.bounce_process (buffers, start, this_chunk, endpoint, include_endpoint, for_export, for_freeze);
uint32_t n = 0;
for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
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);
+ boost::shared_ptr<MidiSource> ms;
if (afs) {
afs->update_header (position, *xnow, now);
afs->flush_header ();
+ } else if ((ms = boost::dynamic_pointer_cast<MidiSource>(*src))) {
+ Source::Lock lock(ms->mutex());
+ ms->mark_streaming_write_completed(lock);
}
}
}
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);
-
- if (afs) {
- afs->mark_for_remove ();
- }
-
+ (*src)->mark_for_remove ();
(*src)->drop_references ();
}
}
}
+ _bounce_processing_active = false;
if (need_block_size_reset) {
+ _engine.main_thread()->drop_buffers ();
track.set_block_size (get_block_size());
}
return ProcessThread::gain_automation_buffer ();
}
+gain_t*
+Session::trim_automation_buffer() const
+{
+ return ProcessThread::trim_automation_buffer ();
+}
+
gain_t*
Session::send_gain_automation_buffer() const
{
return g_atomic_int_get (const_cast<gint*>(&_have_rec_enabled_track)) == 1;
}
+bool
+Session::have_rec_disabled_track () const
+{
+ return g_atomic_int_get (const_cast<gint*>(&_have_rec_disabled_track)) == 1;
+}
+
/** Update the state of our rec-enabled tracks flag */
void
-Session::update_have_rec_enabled_track ()
+Session::update_route_record_state ()
{
boost::shared_ptr<RouteList> rl = routes.reader ();
RouteList::iterator i = rl->begin();
if (g_atomic_int_get (&_have_rec_enabled_track) != old) {
RecordStateChanged (); /* EMIT SIGNAL */
}
+
+
+ i = rl->begin();
+ while (i != rl->end ()) {
+
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+ if (tr && !tr->record_enabled ()) {
+ break;
+ }
+
+ ++i;
+ }
+
+ g_atomic_int_set (&_have_rec_disabled_track, i != rl->end () ? 1 : 0);
}
void
void
Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr<Route> r)
{
- RouteRemovedFromRouteGroup (rg, r);
+ update_route_record_state ();
+ RouteRemovedFromRouteGroup (rg, r); /* EMIT SIGNAL */
+}
+
+boost::shared_ptr<RouteList>
+Session::get_tracks () const
+{
+ boost::shared_ptr<RouteList> rl = routes.reader ();
+ boost::shared_ptr<RouteList> tl (new RouteList);
+
+ for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) {
+ if (boost::dynamic_pointer_cast<Track> (*r)) {
+ if (!(*r)->is_auditioner()) {
+ tl->push_back (*r);
+ }
+ }
+ }
+ return tl;
}
boost::shared_ptr<RouteList>
}
}
+void
+Session::remove_dir_from_search_path (const string& dir, DataType type)
+{
+ Searchpath sp;
+
+ switch (type) {
+ case DataType::AUDIO:
+ sp = Searchpath(config.get_audio_search_path ());
+ break;
+ case DataType::MIDI:
+ sp = Searchpath (config.get_midi_search_path ());
+ break;
+ }
+
+ sp -= dir;
+
+ switch (type) {
+ case DataType::AUDIO:
+ config.set_audio_search_path (sp.to_string());
+ break;
+ case DataType::MIDI:
+ config.set_midi_search_path (sp.to_string());
+ break;
+ }
+
+}
+
boost::shared_ptr<Speakers>
Session::get_speakers()
{
DEBUG_TRACE (DEBUG::OrderKeys, "Sync Order Keys.\n");
+ reassign_track_numbers();
+
Route::SyncOrderKeys (); /* EMIT SIGNAL */
DEBUG_TRACE (DEBUG::OrderKeys, "\tsync done\n");