X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession_transport.cc;h=ff36e30743e7750453b48f4ab64d2c85e2262b0b;hb=33da74c8e353ac56194956cae8e2b7d74ec1a1b0;hp=55bdac064b4fb6ab47fdcd4a7218b49565316f6e;hpb=1f1c4981de73b13d0b7617d8ebe89d9f22dddec3;p=ardour.git diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 55bdac064b..ff36e30743 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 1999-2003 Paul Davis + Copyright (C) 1999-2003 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,7 +15,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ #include @@ -25,22 +24,23 @@ #include #include -#include -#include +#include "pbd/undo.h" +#include "pbd/error.h" #include -#include -#include +#include "pbd/pthread_utils.h" +#include "pbd/memento_command.h" +#include "pbd/stacktrace.h" -#include -#include +#include "midi++/mmc.h" +#include "midi++/port.h" -#include -#include -#include -#include -#include -#include -#include +#include "ardour/ardour.h" +#include "ardour/audioengine.h" +#include "ardour/session.h" +#include "ardour/audio_diskstream.h" +#include "ardour/auditioner.h" +#include "ardour/slave.h" +#include "ardour/location.h" #include "i18n.h" @@ -52,35 +52,36 @@ using namespace PBD; void Session::request_input_change_handling () { - Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0); - queue_event (ev); + if (!(_state_of_the_state & (InitialConnecting|Deletion))) { + Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0); + queue_event (ev); + } } void -Session::request_slave_source (SlaveSource src, jack_nframes_t pos) +Session::request_slave_source (SlaveSource src) { - Event* ev = new Event (Event::SetSlaveSource, Event::Add, Event::Immediate, pos, 0.0); + Event* ev = new Event (Event::SetSlaveSource, Event::Add, Event::Immediate, 0, 0.0); - if (src == Session::JACK) { - /* could set_seamless_loop() be disposed of entirely?*/ - set_seamless_loop (false); + if (src == JACK) { + /* could set_seamless_loop() be disposed of entirely?*/ + Config->set_seamless_loop (false); } else { - - set_seamless_loop (true); + Config->set_seamless_loop (true); } ev->slave = src; queue_event (ev); } void -Session::request_transport_speed (float speed) +Session::request_transport_speed (double speed) { Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, speed); queue_event (ev); } void -Session::request_diskstream_speed (AudioDiskstream& ds, float speed) +Session::request_diskstream_speed (Diskstream& ds, double speed) { Event* ev = new Event (Event::SetDiskstreamSpeed, Event::Add, Event::Immediate, 0, speed); ev->set_ptr (&ds); @@ -95,23 +96,23 @@ Session::request_stop (bool abort) } void -Session::request_locate (jack_nframes_t target_frame, bool with_roll) +Session::request_locate (nframes_t target_frame, bool with_roll) { Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, false); queue_event (ev); } void -Session::force_locate (jack_nframes_t target_frame, bool with_roll) +Session::force_locate (nframes_t target_frame, bool with_roll) { Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, true); queue_event (ev); } void -Session::request_auto_loop (bool yn) +Session::request_play_loop (bool yn) { - Event* ev; + Event* ev; Location *location = _locations.auto_loop_location(); if (location == 0 && yn) { @@ -123,25 +124,10 @@ Session::request_auto_loop (bool yn) ev = new Event (Event::SetLoop, Event::Add, Event::Immediate, 0, 0.0, yn); queue_event (ev); - if (!yn && seamless_loop && transport_rolling()) { + if (!yn && Config->get_seamless_loop() && transport_rolling()) { // request an immediate locate to refresh the diskstreams // after disabling looping - request_locate (_transport_frame-1, true); - } -} - -void -Session::set_seamless_loop (bool yn) -{ - if (seamless_loop != yn) { - seamless_loop = yn; - - if (auto_loop && transport_rolling()) { - // to reset diskstreams etc - request_auto_loop (true); - } - - ControlChanged (SeamlessLoop); /* EMIT */ + request_locate (_transport_frame-1, false); } } @@ -150,6 +136,11 @@ Session::realtime_stop (bool abort) { /* assume that when we start, we'll be moving forwards */ + // FIXME: where should this really be? [DR] + //send_full_time_code(); + deliver_mmc (MIDI::MachineControl::cmdStop, 0); + deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); + if (_transport_speed < 0.0f) { post_transport_work = PostTransportWork (post_transport_work | PostTransportStop | PostTransportReverse); } else { @@ -183,26 +174,36 @@ Session::realtime_stop (bool abort) disable_record (true); reset_slave_state (); - + _transport_speed = 0; + _target_transport_speed = 0; + + if (config.get_use_video_sync()) { + waiting_for_sync_offset = true; + } - transport_sub_state = (auto_return ? AutoReturning : 0); + transport_sub_state = ((Config->get_slave_source() == None && config.get_auto_return()) ? AutoReturning : 0); } void Session::butler_transport_work () { - Glib::RWLock::ReaderLock rm (route_lock); - Glib::RWLock::ReaderLock dsm (diskstream_lock); - + restart: + bool finished; + boost::shared_ptr r = routes.reader (); + boost::shared_ptr dsl = diskstreams.reader(); + + int on_entry = g_atomic_int_get (&butler_should_do_transport_work); + finished = true; + if (post_transport_work & PostTransportCurveRealloc) { - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { (*i)->curve_reallocate(); } } if (post_transport_work & PostTransportInputChange) { - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { (*i)->non_realtime_input_change (); } } @@ -213,29 +214,45 @@ Session::butler_transport_work () if (post_transport_work & PostTransportReverse) { - clear_clicks(); cumulative_rf_motion = 0; reset_rf_scale (0); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (!(*i)->hidden()) { - if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) { - (*i)->seek ((jack_nframes_t) (_transport_frame * (double) (*i)->speed())); + /* don't seek if locate will take care of that in non_realtime_stop() */ + + if (!(post_transport_work & PostTransportLocate)) { + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->hidden()) { + (*i)->non_realtime_locate (_transport_frame); } - else { - (*i)->seek (_transport_frame); + if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) { + /* new request, stop seeking, and start again */ + g_atomic_int_dec_and_test (&butler_should_do_transport_work); + goto restart; } } } } - if (post_transport_work & (PostTransportStop|PostTransportLocate)) { - non_realtime_stop (post_transport_work & PostTransportAbort); + if (post_transport_work & PostTransportLocate) { + non_realtime_locate (); + } + + if (post_transport_work & PostTransportStop) { + non_realtime_stop (post_transport_work & PostTransportAbort, on_entry, finished); + if (!finished) { + g_atomic_int_dec_and_test (&butler_should_do_transport_work); + goto restart; + } } if (post_transport_work & PostTransportOverWrite) { - non_realtime_overwrite (); + non_realtime_overwrite (on_entry, finished); + if (!finished) { + g_atomic_int_dec_and_test (&butler_should_do_transport_work); + goto restart; + } } if (post_transport_work & PostTransportAudition) { @@ -248,35 +265,55 @@ Session::butler_transport_work () void Session::non_realtime_set_speed () { - Glib::RWLock::ReaderLock lm (diskstream_lock); + boost::shared_ptr dsl = diskstreams.reader(); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { (*i)->non_realtime_set_speed (); } } void -Session::non_realtime_overwrite () +Session::non_realtime_overwrite (int on_entry, bool& finished) { - Glib::RWLock::ReaderLock lm (diskstream_lock); + boost::shared_ptr dsl = diskstreams.reader(); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if ((*i)->pending_overwrite) { (*i)->overwrite_existing_buffers (); } + if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) { + finished = false; + return; + } + } +} + + +void +Session::non_realtime_locate () +{ + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->non_realtime_locate (_transport_frame); } } + void -Session::non_realtime_stop (bool abort) +Session::non_realtime_stop (bool abort, int on_entry, bool& finished) { struct tm* now; time_t xnow; bool did_record; - + bool saved; + did_record = false; - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + saved = false; + + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if ((*i)->get_captured_frames () != 0) { did_record = true; break; @@ -284,7 +321,7 @@ Session::non_realtime_stop (bool abort) } /* stop and locate are merged here because they share a lot of common stuff */ - + time (&xnow); now = localtime (&xnow); @@ -298,7 +335,7 @@ Session::non_realtime_stop (bool abort) if (did_record) { begin_reversible_command ("capture"); - + Location* loc = _locations.end_location(); bool change_end = false; @@ -306,88 +343,108 @@ Session::non_realtime_stop (bool abort) /* stopped recording before current end */ - if (_end_location_is_free) { + if (config.get_end_marker_is_free()) { /* first capture for this session, move end back to where we are */ change_end = true; - } + } } else if (_transport_frame > loc->end()) { - + /* stopped recording after the current end, extend it */ change_end = true; } - + if (change_end) { XMLNode &before = loc->get_state(); loc->set_end(_transport_frame); XMLNode &after = loc->get_state(); - add_command (new MementoCommand(*loc, before, after)); + add_command (new MementoCommand(*loc, &before, &after)); } - _end_location_is_free = false; + config.set_end_marker_is_free (false); _have_captured = true; } - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { (*i)->transport_stopped (*now, xnow, abort); } - - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { - if (!(*i)->hidden()) { + + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + if (!(*i)->is_hidden()) { (*i)->set_pending_declick (0); } } if (did_record) { commit_reversible_command (); - } + } if (_engine.running()) { update_latency_compensation (true, abort); } - if (auto_return || (post_transport_work & PostTransportLocate) || synced_to_jack()) { + bool const auto_return_enabled = + (Config->get_slave_source() == None && config.get_auto_return()); + + if (auto_return_enabled || + (post_transport_work & PostTransportLocate) || + (_requested_return_frame >= 0) || + synced_to_jack()) { if (pending_locate_flush) { - flush_all_redirects (); + flush_all_inserts (); } - if ((auto_return || synced_to_jack()) && !(post_transport_work & PostTransportLocate)) { + if ((auto_return_enabled || synced_to_jack() || _requested_return_frame >= 0) && + !(post_transport_work & PostTransportLocate)) { + + bool do_locate = false; + + if (_requested_return_frame >= 0) { + _transport_frame = _requested_return_frame; + _requested_return_frame = -1; + do_locate = true; + } else { + _transport_frame = _last_roll_location; + } - _transport_frame = last_stop_frame; + if (synced_to_jack() && !play_loop) { + do_locate = true; + } - if (synced_to_jack()) { + if (do_locate) { + // cerr << "non-realtimestop: transport locate to " << _transport_frame << endl; _engine.transport_locate (_transport_frame); } - } + } #ifndef LEAVE_TRANSPORT_UNADJUSTED } #endif - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if (!(*i)->hidden()) { - if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) { - (*i)->seek ((jack_nframes_t) (_transport_frame * (double) (*i)->speed())); - } - else { - (*i)->seek (_transport_frame); - } + (*i)->non_realtime_locate (_transport_frame); + } + if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) { + finished = false; + /* we will be back */ + return; } } - deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); - #ifdef LEAVE_TRANSPORT_UNADJUSTED } #endif - last_stop_frame = _transport_frame; + have_looped = false; - send_full_time_code (); + send_full_time_code (0); deliver_mmc (MIDI::MachineControl::cmdStop, 0); deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); @@ -396,28 +453,31 @@ Session::non_realtime_stop (bool abort) /* XXX its a little odd that we're doing this here when realtime_stop(), which has already executed, will have done this. + JLC - so let's not because it seems unnecessary and breaks loop record */ - +#if 0 if (!Config->get_latched_record_enable()) { g_atomic_int_set (&_record_status, Disabled); } else { g_atomic_int_set (&_record_status, Enabled); } RecordStateChanged (); /* emit signal */ +#endif } - + if ((post_transport_work & PostTransportLocate) && get_record_enabled()) { /* capture start has been changed, so save pending state */ save_state ("", true); + saved = true; } /* always try to get rid of this */ remove_pending_capture_state (); - + /* save the current state of things if appropriate */ - if (did_record) { + if (did_record && !saved) { save_state (_current_snapshot_name); } @@ -425,19 +485,21 @@ Session::non_realtime_stop (bool abort) DurationChanged (); /* EMIT SIGNAL */ } - if (post_transport_work & PostTransportStop) { + if (post_transport_work & PostTransportStop) { _play_range = false; /* do not turn off autoloop on stop */ } - PositionChanged (_transport_frame); /* EMIT SIGNAL */ + nframes_t tf = _transport_frame; + + PositionChanged (tf); /* EMIT SIGNAL */ TransportStateChange (); /* EMIT SIGNAL */ /* and start it up again if relevant */ - if ((post_transport_work & PostTransportLocate) && _slave_type == None && pending_locate_roll) { + if ((post_transport_work & PostTransportLocate) && Config->get_slave_source() == None && pending_locate_roll) { request_transport_speed (1.0); pending_locate_roll = false; } @@ -451,7 +513,7 @@ Session::check_declick_out () /* this is called after a process() iteration. if PendingDeclickOut was set, it means that we were waiting to declick the output (which has just been done) before doing something else. this is where we do that "something else". - + note: called from the audio thread. */ @@ -468,34 +530,35 @@ Session::check_declick_out () } void -Session::set_auto_loop (bool yn) +Session::set_play_loop (bool yn) { /* Called from event-handling context */ - + if ((actively_recording() && yn) || _locations.auto_loop_location() == 0) { return; } - + set_dirty(); - if (yn && seamless_loop && synced_to_jack()) { + if (yn && Config->get_seamless_loop() && synced_to_jack()) { warning << _("Seamless looping cannot be supported while Ardour is using JACK transport.\n" "Recommend changing the configured options") << endmsg; return; } - - if ((auto_loop = yn)) { + + if ((play_loop = yn)) { Location *loc; - + if ((loc = _locations.auto_loop_location()) != 0) { - if (seamless_loop) { + if (Config->get_seamless_loop()) { // set all diskstreams to use internal looping - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + boost::shared_ptr dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if (!(*i)->hidden()) { (*i)->set_loop (loc); } @@ -503,15 +566,16 @@ Session::set_auto_loop (bool yn) } else { // set all diskstreams to NOT use internal looping - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + boost::shared_ptr dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if (!(*i)->hidden()) { (*i)->set_loop (0); } } } - + /* stick in the loop event */ - + Event* event = new Event (Event::AutoLoop, Event::Replace, loc->end(), loc->start(), 0.0f); merge_event (event); @@ -533,32 +597,33 @@ Session::set_auto_loop (bool yn) clear_events (Event::AutoLoop); // set all diskstreams to NOT use internal looping - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + boost::shared_ptr dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if (!(*i)->hidden()) { (*i)->set_loop (0); } } - + } - - ControlChanged (AutoLoop); /* EMIT SIGNAL */ } void -Session::flush_all_redirects () +Session::flush_all_inserts () { - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { - (*i)->flush_redirects (); + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->flush_processors (); } } void -Session::start_locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) +Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) { if (synced_to_jack()) { - float sp; - jack_nframes_t pos; + double sp; + nframes_t pos; _slave->speed_and_position (sp, pos); @@ -577,15 +642,33 @@ Session::start_locate (jack_nframes_t target_frame, bool with_roll, bool with_fl } } else { - locate (target_frame, with_roll, with_flush, with_loop); } } +int +Session::micro_locate (nframes_t distance) +{ + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->can_internal_playback_seek (distance)) { + return -1; + } + } + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->internal_playback_seek (distance); + } + + _transport_frame += distance; + return 0; +} + void -Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) +Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) { - if (actively_recording()) { + if (actively_recording() && !with_loop) { return; } @@ -597,7 +680,12 @@ Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, b return; } + // Update SMPTE time + // [DR] FIXME: find out exactly where this should go below _transport_frame = target_frame; + smpte_time(_transport_frame, transmitting_smpte_time); + outbound_mtc_smpte_frame = _transport_frame; + next_quarter_frame_to_send = 0; if (_transport_speed && (!with_loop || loop_changing)) { /* schedule a declick. we'll be called again when its done */ @@ -608,17 +696,17 @@ Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, b pending_locate_roll = with_roll; pending_locate_flush = with_flush; return; - } + } } - if (transport_rolling() && !auto_play && !with_roll && !(synced_to_jack() && auto_loop)) { + if (transport_rolling() && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_jack() && play_loop)) { realtime_stop (false); - } + } if ( !with_loop || loop_changing) { post_transport_work = PostTransportWork (post_transport_work | PostTransportLocate); - + if (with_roll) { post_transport_work = PostTransportWork (post_transport_work | PostTransportRoll); } @@ -630,41 +718,36 @@ Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, b /* this is functionally what clear_clicks() does but with a tentative lock */ Glib::RWLock::WriterLock clickm (click_lock, Glib::TRY_LOCK); - + if (clickm.locked()) { - + for (Clicks::iterator i = clicks.begin(); i != clicks.end(); ++i) { delete *i; } - + clicks.clear (); } } if (with_roll) { /* switch from input if we're going to roll */ - if (Config->get_use_hardware_monitoring()) { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + if (Config->get_monitoring_model() == HardwareMonitoring) { + + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if ((*i)->record_enabled ()) { //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl; - (*i)->monitor_input (!auto_input); + (*i)->monitor_input (!config.get_auto_input()); } } } } else { /* otherwise we're going to stop, so do the opposite */ - if (Config->get_use_hardware_monitoring()) { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + if (Config->get_monitoring_model() == HardwareMonitoring) { + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if ((*i)->record_enabled ()) { //cerr << "switching to input" << __FILE__ << __LINE__ << endl << endl; (*i)->monitor_input (true); @@ -673,45 +756,74 @@ Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, b } } - /* cancel autoloop if transport pos outside of loop range */ - if (auto_loop) { + /* cancel looped playback if transport pos outside of loop range */ + if (play_loop) { Location* al = _locations.auto_loop_location(); - + if (al && (_transport_frame < al->start() || _transport_frame > al->end())) { // cancel looping directly, this is called from event handling context - set_auto_loop(false); + set_play_loop (false); + } + else if (al && _transport_frame == al->start()) { + if (with_loop) { + // this is only necessary for seamless looping + + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->record_enabled ()) { + // tell it we've looped, so it can deal with the record state + (*i)->transport_looped(_transport_frame); + } + } + } + have_looped = true; + TransportLooped(); // EMIT SIGNAL } } - + loop_changing = false; + + _send_smpte_update = true; + + Located (); /* EMIT SIGNAL */ } +/** Set the transport speed. + * @param speed New speed + * @param abort + */ void -Session::set_transport_speed (float speed, bool abort) +Session::set_transport_speed (double speed, bool abort) { if (_transport_speed == speed) { return; } + _target_transport_speed = fabs(speed); + + /* 8.0 max speed is somewhat arbitrary but based on guestimates regarding disk i/o capability + and user needs. We really need CD-style "skip" playback for ffwd and rewind. + */ + if (speed > 0) { - speed = min (8.0f, speed); + speed = min (8.0, speed); } else if (speed < 0) { - speed = max (-8.0f, speed); + speed = max (-8.0, speed); } if (transport_rolling() && speed == 0.0) { - if (Config->get_use_hardware_monitoring()) + /* we are rolling and we want to stop */ + + if (Config->get_monitoring_model() == HardwareMonitoring) { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if ((*i)->record_enabled ()) { //cerr << "switching to input" << __FILE__ << __LINE__ << endl << endl; - (*i)->monitor_input (true); + (*i)->monitor_input (true); } } } @@ -724,20 +836,20 @@ Session::set_transport_speed (float speed, bool abort) } else if (transport_stopped() && speed == 1.0) { + /* we are stopped and we want to start rolling at speed 1 */ + if (!get_record_enabled() && Config->get_stop_at_session_end() && _transport_frame >= current_end_frame()) { return; } - if (Config->get_use_hardware_monitoring()) { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (auto_input && (*i)->record_enabled ()) { + if (Config->get_monitoring_model() == HardwareMonitoring) { + + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (config.get_auto_input() && (*i)->record_enabled ()) { //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl; - (*i)->monitor_input (false); + (*i)->monitor_input (false); } } } @@ -764,39 +876,42 @@ Session::set_transport_speed (float speed, bool abort) return; } - if (speed > 0.0f && _transport_frame == current_end_frame()) { + if (speed > 0.0 && _transport_frame == current_end_frame()) { return; } - if (speed < 0.0f && _transport_frame == 0) { + if (speed < 0.0 && _transport_frame == 0) { return; } - + clear_clicks (); /* if we are reversing relative to the current speed, or relative to the speed before the last stop, then we have to do extra work. */ - if ((_transport_speed && speed * _transport_speed < 0.0f) || (_last_transport_speed * speed < 0.0f)) { + if ((_transport_speed && speed * _transport_speed < 0.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0f && speed < 0.0f)) { post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse); } - + _last_transport_speed = _transport_speed; _transport_speed = speed; - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + + boost::shared_ptr dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if ((*i)->realtime_set_speed ((*i)->speed(), true)) { post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); } } - + if (post_transport_work & (PostTransportSpeed|PostTransportReverse)) { schedule_butler_transport_work (); } } } + +/** Stop the transport. */ void Session::stop_transport (bool abort) { @@ -804,10 +919,10 @@ Session::stop_transport (bool abort) return; } - if (actively_recording() && !(transport_sub_state & StopPendingCapture) && - _worst_output_latency > current_block_size) + if (actively_recording() && !(transport_sub_state & StopPendingCapture) && + _worst_output_latency > current_block_size) { - + /* we need to capture the audio that has still not yet been received by the system at the time the stop is requested, so we have to roll past that time. @@ -815,20 +930,22 @@ Session::stop_transport (bool abort) block before the actual end. we'll declick in the subsequent block, and then we'll really be stopped. */ - - Event *ev = new Event (Event::StopOnce, Event::Replace, + + Event *ev = new Event (Event::StopOnce, Event::Replace, _transport_frame + _worst_output_latency - current_block_size, 0, 0, abort); - + merge_event (ev); transport_sub_state |= StopPendingCapture; pending_abort = abort; return; - } + } + if ((transport_sub_state & PendingDeclickOut) == 0) { transport_sub_state |= PendingDeclickOut; /* we'll be called again after the declick */ + pending_abort = abort; return; } @@ -840,50 +957,47 @@ void Session::start_transport () { _last_roll_location = _transport_frame; + have_looped = false; /* if record status is Enabled, move it to Recording. if its - already Recording, move it to Disabled. + already Recording, move it to Disabled. */ switch (record_status()) { case Enabled: - if (!punch_in) { + if (!config.get_punch_in()) { enable_record (); } break; case Recording: - disable_record (false); + if (!play_loop) { + disable_record (false); + } break; default: break; } - if (!synced_to_jack() || _exporting) { - actually_start_transport (); - } else { - waiting_to_start = true; - } -} - -void -Session::actually_start_transport () -{ - waiting_to_start = false; - transport_sub_state |= PendingDeclickIn; + _transport_speed = 1.0; - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + _target_transport_speed = 1.0; + + boost::shared_ptr dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { (*i)->realtime_set_speed ((*i)->speed(), true); } - send_mmc_in_another_thread (MIDI::MachineControl::cmdDeferredPlay, 0); - + deliver_mmc(MIDI::MachineControl::cmdDeferredPlay, _transport_frame); + TransportStateChange (); /* EMIT SIGNAL */ } +/** Do any transport work in the audio thread that needs to be done after the + * transport thread is finished. Audio thread, realtime safe. + */ void Session::post_transport () { @@ -902,10 +1016,9 @@ Session::post_transport () if (post_transport_work & PostTransportLocate) { - if ((auto_play && !_exporting) || (post_transport_work & PostTransportRoll)) { - + if (((Config->get_slave_source() == None && (auto_play_legal && config.get_auto_play())) && !_exporting) || (post_transport_work & PostTransportRoll)) { start_transport (); - + } else { transport_sub_state = 0; } @@ -917,15 +1030,7 @@ Session::post_transport () } void -Session::set_rf_speed (float speed) -{ - rf_speed = speed; - cumulative_rf_motion = 0; - reset_rf_scale (0); -} - -void -Session::reset_rf_scale (jack_nframes_t motion) +Session::reset_rf_scale (nframes_t motion) { cumulative_rf_motion += motion; @@ -944,32 +1049,23 @@ Session::reset_rf_scale (jack_nframes_t motion) } } -int -Session::set_slave_source (SlaveSource src, jack_nframes_t frame) +void +Session::set_slave_source (SlaveSource src) { bool reverse = false; bool non_rt_required = false; - if (src == _slave_type) { - return 0; - } - if (_transport_speed) { error << _("please stop the transport before adjusting slave settings") << endmsg; - /* help out non-MVC friendly UI's by telling them the slave type changed */ - ControlChanged (SlaveType); /* EMIT SIGNAL */ - return 0; + return; } -// if (src == JACK && Config->get_jack_time_master()) { -// return -1; -// } - - if (_slave) { - delete _slave; - _slave = 0; - _slave_type = None; - } +// if (src == JACK && Config->get_jack_time_master()) { +// return; +// } + + delete _slave; + _slave = 0; if (_transport_speed < 0.0) { reverse = true; @@ -987,25 +1083,41 @@ Session::set_slave_source (SlaveSource src, jack_nframes_t frame) } catch (failed_constructor& err) { - return -1; + return; } } else { error << _("No MTC port defined: MTC slaving is impossible.") << endmsg; - return -1; + return; } - _desired_transport_speed = _transport_speed; break; - + + case MIDIClock: + if (_midi_clock_port) { + try { + _slave = new MIDIClock_Slave (*this, *_midi_clock_port, 24); + } + + catch (failed_constructor& err) { + return; + } + + } else { + error << _("No MIDI Clock port defined: MIDI Clock slaving is impossible.") << endmsg; + return; + } + break; + case JACK: _slave = new JACK_Slave (_engine.jack()); - _desired_transport_speed = _transport_speed; break; + }; - - _slave_type = src; - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + Config->set_slave_source (src); + + boost::shared_ptr dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { if (!(*i)->hidden()) { if ((*i)->realtime_set_speed ((*i)->speed(), true)) { non_rt_required = true; @@ -1024,9 +1136,6 @@ Session::set_slave_source (SlaveSource src, jack_nframes_t frame) } set_dirty(); - ControlChanged (SlaveType); /* EMIT SIGNAL */ - - return 0; } void @@ -1037,7 +1146,7 @@ Session::reverse_diskstream_buffers () } void -Session::set_diskstream_speed (AudioDiskstream* stream, float speed) +Session::set_diskstream_speed (Diskstream* stream, double speed) { if (stream->realtime_set_speed (speed, false)) { post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); @@ -1075,8 +1184,6 @@ Session::set_play_range (bool yn) Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false); merge_event (ev); } - - ControlChanged (PlayRange); /* EMIT SIGNAL */ } } @@ -1086,7 +1193,7 @@ Session::setup_auto_play () /* Called from event-processing context */ Event* ev; - + _clear_event_type (Event::RangeStop); _clear_event_type (Event::RangeLocate); @@ -1095,57 +1202,65 @@ Session::setup_auto_play () } list::size_type sz = current_audio_range.size(); - + if (sz > 1) { - - list::iterator i = current_audio_range.begin(); + + list::iterator i = current_audio_range.begin(); list::iterator next; - + while (i != current_audio_range.end()) { - + next = i; ++next; - + /* locating/stopping is subject to delays for declicking. */ - - jack_nframes_t requested_frame = (*i).end; - + + nframes_t requested_frame = (*i).end; + if (requested_frame > current_block_size) { requested_frame -= current_block_size; } else { requested_frame = 0; } - + if (next == current_audio_range.end()) { ev = new Event (Event::RangeStop, Event::Add, requested_frame, 0, 0.0f); } else { ev = new Event (Event::RangeLocate, Event::Add, requested_frame, (*next).start, 0.0f); } - + merge_event (ev); - + i = next; } - + } else if (sz == 1) { - + ev = new Event (Event::RangeStop, Event::Add, current_audio_range.front().end, 0, 0.0f); merge_event (ev); - - } + + } /* now start rolling at the right place */ - + ev = new Event (Event::LocateRoll, Event::Add, Event::Immediate, current_audio_range.front().start, 0.0f, false); merge_event (ev); } void -Session::request_bounded_roll (jack_nframes_t start, jack_nframes_t end) +Session::request_roll_at_and_return (nframes_t start, nframes_t return_to) +{ + Event *ev = new Event (Event::LocateRollLocate, Event::Add, Event::Immediate, return_to, 1.0); + ev->target2_frame = start; + queue_event (ev); +} + +void +Session::request_bounded_roll (nframes_t start, nframes_t end) { request_stop (); - Event *ev = new Event (Event::StopOnce, Event::Replace, Event::Immediate, end, 0.0); + Event *ev = new Event (Event::StopOnce, Event::Replace, end, Event::Immediate, 0.0); queue_event (ev); request_locate (start, true); } @@ -1153,19 +1268,21 @@ Session::request_bounded_roll (jack_nframes_t start, jack_nframes_t end) void Session::engine_halted () { + bool ignored; + /* there will be no more calls to process(), so we'd better clean up for ourselves, right now. - but first, make sure the butler is out of + but first, make sure the butler is out of the picture. */ g_atomic_int_set (&butler_should_do_transport_work, 0); post_transport_work = PostTransportWork (0); stop_butler (); - + realtime_stop (false); - non_realtime_stop (false); + non_realtime_stop (false, 0, ignored); transport_sub_state = 0; TransportStateChange (); /* EMIT SIGNAL */ @@ -1175,16 +1292,16 @@ Session::engine_halted () void Session::xrun_recovery () { - if (Config->get_stop_recording_on_xrun() && actively_recording()) { + Xrun (transport_frame()); //EMIT SIGNAL - HaltOnXrun (); /* EMIT SIGNAL */ + if (Config->get_stop_recording_on_xrun() && actively_recording()) { /* it didn't actually halt, but we need to handle things in the same way. */ engine_halted(); - } + } } void @@ -1196,50 +1313,72 @@ Session::update_latency_compensation (bool with_stop, bool abort) return; } - Glib::RWLock::ReaderLock lm (route_lock); - Glib::RWLock::ReaderLock lm2 (diskstream_lock); _worst_track_latency = 0; - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { +#undef DEBUG_LATENCY +#ifdef DEBUG_LATENCY + cerr << "\n---------------------------------\nUPDATE LATENCY\n"; +#endif + + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + if (with_stop) { - (*i)->handle_transport_stopped (abort, (post_transport_work & PostTransportLocate), + (*i)->handle_transport_stopped (abort, (post_transport_work & PostTransportLocate), (!(post_transport_work & PostTransportLocate) || pending_locate_flush)); } - jack_nframes_t old_latency = (*i)->signal_latency (); - jack_nframes_t track_latency = (*i)->update_total_latency (); + nframes_t old_latency = (*i)->output()->signal_latency (); + nframes_t track_latency = (*i)->update_total_latency (); if (old_latency != track_latency) { + (*i)->input()->update_port_total_latencies (); + (*i)->output()->update_port_total_latencies (); update_jack = true; } - - if (!(*i)->hidden() && ((*i)->active())) { + + if (!(*i)->is_hidden() && ((*i)->active())) { _worst_track_latency = max (_worst_track_latency, track_latency); } } - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { - (*i)->set_latency_delay (_worst_track_latency); + if (update_jack) { + _engine.update_total_latencies (); } - /* tell JACK to play catch up */ +#ifdef DEBUG_LATENCY + cerr << "\tworst was " << _worst_track_latency << endl; +#endif - if (update_jack) { - _engine.update_total_latencies (); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->set_latency_delay (_worst_track_latency); } - set_worst_io_latencies (false); + set_worst_io_latencies (); /* reflect any changes in latencies into capture offsets */ - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { + + boost::shared_ptr dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { (*i)->set_capture_offset (); } } void -Session::update_latency_compensation_proxy (void* ignored) +Session::allow_auto_play (bool yn) { - update_latency_compensation (false, false); + auto_play_legal = yn; +} + +void +Session::reset_jack_connection (jack_client_t* jack) +{ + JACK_Slave* js; + + if (_slave && ((js = dynamic_cast (_slave)) != 0)) { + js->reset_client (jack); + } }