diff options
Diffstat (limited to 'libs/ardour/session_process.cc')
-rw-r--r-- | libs/ardour/session_process.cc | 816 |
1 files changed, 816 insertions, 0 deletions
diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc new file mode 100644 index 0000000000..835d4ccdc2 --- /dev/null +++ b/libs/ardour/session_process.cc @@ -0,0 +1,816 @@ +/* + Copyright (C) 1999-2002 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <cmath> +#include <cerrno> +#include <algorithm> +#include <unistd.h> + +#include <ardour/timestamps.h> + +#include <pbd/error.h> +#include <pbd/atomic.h> +#include <pbd/lockmonitor.h> + +#include <ardour/ardour.h> +#include <ardour/session.h> +#include <ardour/diskstream.h> +#include <ardour/audioengine.h> +#include <ardour/slave.h> +#include <ardour/auditioner.h> +#include <ardour/cycles.h> +#include <ardour/cycle_timer.h> + +#include "i18n.h" + +using namespace ARDOUR; +//using namespace sigc; +using namespace std; + +void +Session::process (jack_nframes_t nframes) +{ + if (synced_to_jack() && waiting_to_start) { + if ( _engine.transport_state() == AudioEngine::TransportRolling) { + actually_start_transport (); + } + } + + if (non_realtime_work_pending()) { + if (atomic_read (&butler_should_do_transport_work) == 0) { + post_transport (); + } + } + + (this->*process_function) (nframes); +} + +void +Session::prepare_diskstreams () +{ + for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) { + (*i)->prepare (); + } +} + +int +Session::no_roll (jack_nframes_t nframes, jack_nframes_t offset) +{ + jack_nframes_t end_frame = _transport_frame + nframes; + int ret = 0; + bool declick = get_transport_declick_required(); + + if (_click_io) { + _click_io->silence (nframes, offset); + } + + /* XXX we're supposed to have the route_lock while doing this. + this is really bad ... + */ + + if (atomic_read (&processing_prohibited)) { + for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { + (*i)->silence (nframes, offset); + } + return 0; + } + + for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { + + if ((*i)->hidden()) { + continue; + } + + (*i)->set_pending_declick (declick); + + if ((*i)->no_roll (nframes, _transport_frame, end_frame, offset, non_realtime_work_pending(), + actively_recording(), declick)) { + error << compose(_("Session: error in no roll for %1"), (*i)->name()) << endmsg; + ret = -1; + break; + } + } + + return ret; +} + +int +Session::process_routes (jack_nframes_t nframes, jack_nframes_t offset) +{ + bool record_active; + int declick = get_transport_declick_required(); + bool rec_monitors = get_rec_monitors_input(); + + if (transport_sub_state & StopPendingCapture) { + /* force a declick out */ + declick = -1; + } + + record_active = actively_recording(); // || (get_record_enabled() && get_punch_in()); + + for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { + + int ret; + + if ((*i)->hidden()) { + continue; + } + + (*i)->set_pending_declick (declick); + + if ((ret = (*i)->roll (nframes, _transport_frame, _transport_frame + nframes, offset, declick, record_active, rec_monitors)) < 0) { + + /* we have to do this here. Route::roll() for an AudioTrack will have called DiskStream::process(), + and the DS will expect DiskStream::commit() to be called. but we're aborting from that + call path, so make sure we release any outstanding locks here before we return failure. + */ + + for (DiskStreamList::iterator ids = diskstreams.begin(); ids != diskstreams.end(); ++ids) { + (*ids)->recover (); + } + + stop_transport (); + return -1; + } + } + + return 0; +} + +int +Session::silent_process_routes (jack_nframes_t nframes, jack_nframes_t offset) +{ + bool record_active = actively_recording(); + int declick = get_transport_declick_required(); + bool rec_monitors = get_rec_monitors_input(); + + if (transport_sub_state & StopPendingCapture) { + /* force a declick out */ + declick = -1; + } + + for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { + + int ret; + + if ((*i)->hidden()) { + continue; + } + + if ((ret = (*i)->silent_roll (nframes, _transport_frame, _transport_frame + nframes, offset, record_active, rec_monitors)) < 0) { + + /* we have to do this here. Route::roll() for an AudioTrack will have called DiskStream::process(), + and the DS will expect DiskStream::commit() to be called. but we're aborting from that + call path, so make sure we release any outstanding locks here before we return failure. + */ + + for (DiskStreamList::iterator ids = diskstreams.begin(); ids != diskstreams.end(); ++ids) { + (*ids)->recover (); + } + + stop_transport (); + return -1; + } + } + + return 0; +} + +void +Session::commit_diskstreams (jack_nframes_t nframes, bool &needs_butler) +{ + int dret; + float pworst = 1.0f; + float cworst = 1.0f; + + for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) { + + if ((*i)->hidden()) { + continue; + } + + /* force all diskstreams not handled by a Route to call do their stuff. + */ + + if ((dret = (*i)->process (_transport_frame, nframes, 0, actively_recording(), get_rec_monitors_input())) == 0) { + if ((*i)->commit (nframes)) { + needs_butler = true; + } + + } else if (dret < 0) { + (*i)->recover(); + } + + pworst = min (pworst, (*i)->playback_buffer_load()); + cworst = min (cworst, (*i)->capture_buffer_load()); + } + + uint32_t pmin = atomic_read (&_playback_load); + uint32_t pminold = atomic_read (&_playback_load_min); + uint32_t cmin = atomic_read (&_capture_load); + uint32_t cminold = atomic_read (&_capture_load_min); + + atomic_set (&_playback_load, (uint32_t) floor (pworst * 100.0f)); + atomic_set (&_capture_load, (uint32_t) floor (cworst * 100.0f)); + atomic_set (&_playback_load_min, min (pmin, pminold)); + atomic_set (&_capture_load_min, min (cmin, cminold)); + + if (actively_recording()) { + set_dirty(); + } +} + +void +Session::process_with_events (jack_nframes_t nframes) +{ + Event* ev; + jack_nframes_t this_nframes; + jack_nframes_t end_frame; + jack_nframes_t offset; + bool session_needs_butler = false; + jack_nframes_t stop_limit; + long frames_moved; + + if (auditioner) { + auditioner->silence (nframes, 0); + } + + while (pending_events.read (&ev, 1) == 1) { + merge_event (ev); + } + + /* if we are not in the middle of a state change, + and there are immediate events queued up, + process them. + */ + + while (!non_realtime_work_pending() && !immediate_events.empty()) { + Event *ev = immediate_events.front (); + immediate_events.pop_front (); + process_event (ev); + } + + if (!process_can_proceed()) { + no_roll (nframes, 0); + return; + } + + if (events.empty() || next_event == events.end()) { + process_without_events (nframes); + return; + } + + end_frame = _transport_frame + nframes; + + { + TentativeLockMonitor rm (route_lock, __LINE__, __FILE__); + Event* this_event; + Events::iterator the_next_one; + + if (!rm.locked() || (post_transport_work & (PostTransportLocate|PostTransportStop))) { + no_roll (nframes, 0); + return; + } + + if (!_exporting && _slave) { + if (!follow_slave (nframes, 0)) { + return; + } + } + + if (_transport_speed == 0) { + no_roll (nframes, 0); + return; + } + + if (actively_recording()) { + stop_limit = max_frames; + } else { + + if (Config->get_stop_at_session_end()) { + stop_limit = current_end_frame(); + } else { + stop_limit = max_frames; + } + } + + if (maybe_stop (stop_limit)) { + no_roll (nframes, 0); + return; + } + + this_event = *next_event; + the_next_one = next_event; + ++the_next_one; + + offset = 0; + + while (nframes) { + + if (this_event == 0 || this_event->action_frame > end_frame || this_event->action_frame < _transport_frame) { + + this_nframes = nframes; + + } else { + + /* compute nframes to next event */ + + if (this_event->action_frame < end_frame) { + this_nframes = nframes - (end_frame - this_event->action_frame); + } else { + this_nframes = nframes; + } + } + + if (this_nframes) { + + click (_transport_frame, nframes, offset); + + /* now process frames between now and the first event in this block */ + prepare_diskstreams (); + + if (process_routes (this_nframes, offset)) { + no_roll (nframes, 0); + return; + } + + commit_diskstreams (this_nframes, session_needs_butler); + + nframes -= this_nframes; + offset += this_nframes; + + frames_moved = (jack_nframes_t) floor (_transport_speed * this_nframes); + + if (frames_moved < 0) { + decrement_transport_position (-frames_moved); + } else { + increment_transport_position (frames_moved); + } + + maybe_stop (stop_limit); + check_declick_out (); + } + + /* now handle this event and all others scheduled for the same time */ + + while (this_event && this_event->action_frame == _transport_frame) { + process_event (this_event); + + if (the_next_one == events.end()) { + this_event = 0; + } else { + this_event = *the_next_one; + ++the_next_one; + } + } + + /* if an event left our state changing, do the right thing */ + + if (non_realtime_work_pending()) { + no_roll (nframes, offset); + break; + } + + /* this is necessary to handle the case of seamless looping */ + /* not sure if it will work in conjuction with varispeed */ + end_frame = _transport_frame + nframes; + + } + + set_next_event (); + + } /* implicit release of route lock */ + + + if (session_needs_butler) { + summon_butler (); + } + + if (!_engine.freewheeling() && send_mtc) { + send_midi_time_code_in_another_thread (); + } + + return; +} + +void +Session::reset_slave_state () +{ + average_slave_delta = 1800; + delta_accumulator_cnt = 0; + have_first_delta_accumulator = false; + slave_state = Stopped; +} + +bool +Session::follow_slave (jack_nframes_t nframes, jack_nframes_t offset) +{ + float slave_speed; + jack_nframes_t slave_transport_frame; + jack_nframes_t this_delta; + int dir; + bool starting; + + if (!_slave->ok()) { + stop_transport (); + set_slave_source (None, 0); + goto noroll; + } + + _slave->speed_and_position (slave_speed, slave_transport_frame); + + if (!_slave->locked()) { + goto noroll; + } + + if (slave_transport_frame > _transport_frame) { + this_delta = slave_transport_frame - _transport_frame; + dir = 1; + } else { + this_delta = _transport_frame - slave_transport_frame; + dir = -1; + } + + if ((starting = _slave->starting())) { + slave_speed = 0.0f; + } + +#if 0 + cerr << "delta = " << (int) (dir * this_delta) + << " speed = " << slave_speed + << " ts = " << _transport_speed + << " M@"<< slave_transport_frame << " S@" << _transport_frame + << " avgdelta = " << average_slave_delta + << endl; +#endif + + if (Config->get_timecode_source_is_synced()) { + + /* if the TC source is synced, then we assume that its + speed is binary: 0.0 or 1.0 + */ + + if (slave_speed != 0.0f) { + slave_speed = 1.0f; + } + + } else { + + /* TC source is able to drift relative to us (slave) + so we need to keep track of the drift and adjust + our speed to remain locked. + */ + + if (delta_accumulator_cnt >= delta_accumulator_size) { + have_first_delta_accumulator = true; + delta_accumulator_cnt = 0; + } + + if (delta_accumulator_cnt != 0 || this_delta < _current_frame_rate) { + delta_accumulator[delta_accumulator_cnt++] = dir*this_delta; + } + + if (have_first_delta_accumulator) { + average_slave_delta = 0; + for (int i = 0; i < delta_accumulator_size; ++i) { + average_slave_delta += delta_accumulator[i]; + } + average_slave_delta /= delta_accumulator_size; + if (average_slave_delta < 0) { + average_dir = -1; + average_slave_delta = -average_slave_delta; + } else { + average_dir = 1; + } + // cerr << "avgdelta = " << average_slave_delta*average_dir << endl; + } + } + + if (slave_speed != 0.0f) { + + /* slave is running */ + + switch (slave_state) { + case Stopped: + if (_slave->requires_seekahead()) { + slave_wait_end = slave_transport_frame + _current_frame_rate; + locate (slave_wait_end, false, false); + slave_state = Waiting; + starting = true; + + } else { + + slave_state = Running; + + Location* al = _locations.auto_loop_location(); + + if (al && auto_loop && (slave_transport_frame < al->start() || slave_transport_frame > al->end())) { + // cancel looping + request_auto_loop(false); + } + + if (slave_transport_frame != _transport_frame) { + locate (slave_transport_frame, false, false); + } + } + break; + + case Waiting: + break; + + default: + break; + + } + + if (slave_state == Waiting) { + + // cerr << "waiting at " << slave_transport_frame << endl; + + if (slave_transport_frame >= slave_wait_end) { + // cerr << "\tstart at " << _transport_frame << endl; + slave_state = Running; + + bool ok = true; + jack_nframes_t frame_delta = slave_transport_frame - _transport_frame; + + for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) { + if (!(*i)->can_internal_playback_seek (frame_delta)) { + ok = false; + break; + } + } + + if (ok) { + for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.end(); ++i) { + (*i)->internal_playback_seek (frame_delta); + } + _transport_frame += frame_delta; + + } else { + // cerr << "cannot micro-seek\n"; + /* XXX what? */ + } + + memset (delta_accumulator, 0, sizeof (jack_nframes_t) * delta_accumulator_size); + average_slave_delta = 0; + this_delta = 0; + } + } + + if (slave_state == Running && _transport_speed == 0.0f) { + + // cerr << "slave starts transport\n"; + + start_transport (); + } + + } else { + + /* slave has stopped */ + + if (_transport_speed != 0.0f) { + + // cerr << "slave stops transport: " << slave_speed << " frame: " << slave_transport_frame + // << " tf = " << _transport_frame + // << endl; + + if (_slave_type == JACK) { + last_stop_frame = _transport_frame; + } + + stop_transport(); + } + + if (slave_transport_frame != _transport_frame) { + // cerr << "slave stopped, move to " << slave_transport_frame << endl; + force_locate (slave_transport_frame, false); + } + + slave_state = Stopped; + } + + if (slave_state == Running && !Config->get_timecode_source_is_synced()) { + + + if (_transport_speed != 0.0f) { + + /* + note that average_dir is +1 or -1 + */ + + const float adjust_seconds = 1.0f; + float delta; + + //if (average_slave_delta == 0) { + delta = this_delta; + delta *= dir; +// } else { +// delta = average_slave_delta; +// delta *= average_dir; +// } + + float adjusted_speed = slave_speed + + (delta / (adjust_seconds * _current_frame_rate)); + + // cerr << "adjust using " << delta + // << " towards " << adjusted_speed + // << " ratio = " << adjusted_speed / slave_speed + // << " current = " << _transport_speed + // << " slave @ " << slave_speed + // << endl; + + request_transport_speed (adjusted_speed); + +#if 1 + if ((jack_nframes_t) average_slave_delta > _slave->resolution()) { + // cerr << "not locked\n"; + goto silent_motion; + } +#endif + } + } + + if (!starting && !non_realtime_work_pending()) { + /* speed is set, we're locked, and good to go */ + return true; + } + + silent_motion: + + if (slave_speed && _transport_speed) { + + /* something isn't right, but we should move with the master + for now. + */ + + bool need_butler; + + prepare_diskstreams (); + silent_process_routes (nframes, offset); + commit_diskstreams (nframes, need_butler); + + if (need_butler) { + summon_butler (); + } + + jack_nframes_t frames_moved = (long) floor (_transport_speed * nframes); + + if (frames_moved < 0) { + decrement_transport_position (-frames_moved); + } else { + increment_transport_position (frames_moved); + } + + jack_nframes_t stop_limit; + + if (actively_recording()) { + stop_limit = max_frames; + } else { + if (Config->get_stop_at_session_end()) { + stop_limit = current_end_frame(); + } else { + stop_limit = max_frames; + } + } + + maybe_stop (stop_limit); + } + + noroll: + /* don't move at all */ + no_roll (nframes, 0); + return false; +} + +void +Session::process_without_events (jack_nframes_t nframes) +{ + bool session_needs_butler = false; + jack_nframes_t stop_limit; + long frames_moved; + + { + TentativeLockMonitor rm (route_lock, __LINE__, __FILE__); + + if (!rm.locked() || (post_transport_work & (PostTransportLocate|PostTransportStop))) { + no_roll (nframes, 0); + return; + } + + if (!_exporting && _slave) { + if (!follow_slave (nframes, 0)) { + return; + } + } + + if (_transport_speed == 0) { + no_roll (nframes, 0); + return; + } + + if (actively_recording()) { + stop_limit = max_frames; + } else { + if (Config->get_stop_at_session_end()) { + stop_limit = current_end_frame(); + } else { + stop_limit = max_frames; + } + } + + if (maybe_stop (stop_limit)) { + no_roll (nframes, 0); + return; + } + + click (_transport_frame, nframes, 0); + + prepare_diskstreams (); + + frames_moved = (long) floor (_transport_speed * nframes); + + if (process_routes (nframes, 0)) { + no_roll (nframes, 0); + return; + } + + commit_diskstreams (nframes, session_needs_butler); + + if (frames_moved < 0) { + decrement_transport_position (-frames_moved); + } else { + increment_transport_position (frames_moved); + } + + maybe_stop (stop_limit); + check_declick_out (); + + } /* implicit release of route lock */ + + if (session_needs_butler) { + summon_butler (); + } + + if (!_engine.freewheeling() && send_mtc) { + send_midi_time_code_in_another_thread (); + } + + return; +} + +void +Session::process_audition (jack_nframes_t nframes) +{ + TentativeLockMonitor rm (route_lock, __LINE__, __FILE__); + Event* ev; + + if (rm.locked()) { + for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { + if (!(*i)->hidden()) { + (*i)->silence (nframes, 0); + } + } + } + + if (auditioner->play_audition (nframes) > 0) { + summon_butler (); + } + + while (pending_events.read (&ev, 1) == 1) { + merge_event (ev); + } + + /* if we are not in the middle of a state change, + and there are immediate events queued up, + process them. + */ + + while (!non_realtime_work_pending() && !immediate_events.empty()) { + Event *ev = immediate_events.front (); + immediate_events.pop_front (); + process_event (ev); + } + + if (!auditioner->active()) { + process_function = &Session::process_with_events; + } +} + |