diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2008-06-02 21:41:35 +0000 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2008-06-02 21:41:35 +0000 |
commit | 449aab3c465bbbf66d221fac3d7ea559f1720357 (patch) | |
tree | 6843cc40c88250a132acac701271f1504cd2df04 /libs/ardour/session_process.cc | |
parent | 9c0d7d72d70082a54f823cd44c0ccda5da64bb6f (diff) |
rollback to 3428, before the mysterious removal of libs/* at 3431/3432
git-svn-id: svn://localhost/ardour2/branches/3.0@3435 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/ardour/session_process.cc')
-rw-r--r-- | libs/ardour/session_process.cc | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc new file mode 100644 index 0000000000..d6890b31ae --- /dev/null +++ b/libs/ardour/session_process.cc @@ -0,0 +1,886 @@ +/* + 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. + +*/ + +#include <cmath> +#include <cerrno> +#include <algorithm> +#include <unistd.h> + +#include <pbd/error.h> + +#include <glibmm/thread.h> + +#include <ardour/ardour.h> +#include <ardour/session.h> +#include <ardour/timestamps.h> +#include <ardour/audio_diskstream.h> +#include <ardour/audioengine.h> +#include <ardour/slave.h> +#include <ardour/auditioner.h> +#include <ardour/cycles.h> +#include <ardour/cycle_timer.h> + +#include <midi++/manager.h> + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using namespace std; + +/** Called by the audio engine when there is work to be done with JACK. + * @param nframes Number of frames to process. + */ +void +Session::process (nframes_t nframes) +{ + MIDI::Manager::instance()->cycle_start(nframes); + + _silent = false; + + if (synced_to_jack() && waiting_to_start) { + if ( _engine.transport_state() == AudioEngine::TransportRolling) { + actually_start_transport (); + } + } + + if (non_realtime_work_pending()) { + if (!transport_work_requested ()) { + post_transport (); + } + } + + (this->*process_function) (nframes); + + MIDI::Manager::instance()->cycle_end(); + + SendFeedback (); /* EMIT SIGNAL */ +} + +void +Session::prepare_diskstreams () +{ + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->prepare (); + } +} + +int +Session::no_roll (nframes_t nframes, nframes_t offset) +{ + nframes_t end_frame = _transport_frame + nframes; // FIXME: varispeed + no_roll ?? + int ret = 0; + bool declick = get_transport_declick_required(); + boost::shared_ptr<RouteList> r = routes.reader (); + + if (_click_io) { + _click_io->silence (nframes, offset); + } + + if (g_atomic_int_get (&processing_prohibited)) { + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->silence (nframes, offset); + } + return 0; + } + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + + if ((*i)->is_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 << string_compose(_("Session: error in no roll for %1"), (*i)->name()) << endmsg; + ret = -1; + break; + } + } + + return ret; +} + +int +Session::process_routes (nframes_t nframes, nframes_t offset) +{ + bool record_active; + int declick = get_transport_declick_required(); + bool rec_monitors = get_rec_monitors_input(); + boost::shared_ptr<RouteList> r = routes.reader (); + + if (transport_sub_state & StopPendingCapture) { + /* force a declick out */ + declick = -1; + } + + record_active = actively_recording(); // || (get_record_enabled() && get_punch_in()); + + const nframes_t start_frame = _transport_frame; + const nframes_t end_frame = _transport_frame + (nframes_t)floor(nframes * _transport_speed); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + + int ret; + + if ((*i)->is_hidden()) { + continue; + } + + (*i)->set_pending_declick (declick); + + if ((ret = (*i)->roll (nframes, start_frame, end_frame, offset, declick, record_active, rec_monitors)) < 0) { + + /* we have to do this here. Route::roll() for an AudioTrack will have called AudioDiskstream::process(), + and the DS will expect AudioDiskstream::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. + */ + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator ids = dsl->begin(); ids != dsl->end(); ++ids) { + (*ids)->recover (); + } + + stop_transport (); + return -1; + } + } + + return 0; +} + +int +Session::silent_process_routes (nframes_t nframes, nframes_t offset) +{ + bool record_active = actively_recording(); + int declick = get_transport_declick_required(); + bool rec_monitors = get_rec_monitors_input(); + boost::shared_ptr<RouteList> r = routes.reader (); + + if (transport_sub_state & StopPendingCapture) { + /* force a declick out */ + declick = -1; + } + + const nframes_t start_frame = _transport_frame; + const nframes_t end_frame = _transport_frame + lrintf(nframes * _transport_speed); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + + int ret; + + if ((*i)->is_hidden()) { + continue; + } + + if ((ret = (*i)->silent_roll (nframes, start_frame, end_frame, offset, record_active, rec_monitors)) < 0) { + + /* we have to do this here. Route::roll() for an AudioTrack will have called AudioDiskstream::process(), + and the DS will expect AudioDiskstream::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. + */ + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator ids = dsl->begin(); ids != dsl->end(); ++ids) { + (*ids)->recover (); + } + + stop_transport (); + return -1; + } + } + + return 0; +} + +void +Session::commit_diskstreams (nframes_t nframes, bool &needs_butler) +{ + int dret; + float pworst = 1.0f; + float cworst = 1.0f; + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + + if ((*i)->hidden()) { + continue; + } + + /* force all diskstreams not handled by a Route to call do their stuff. + Note: the diskstreams that were handled by a route will just return zero + from this call, because they know they were processed. So in fact, this + also runs commit() for every diskstream. + */ + + 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 = g_atomic_int_get (&_playback_load); + uint32_t pminold = g_atomic_int_get (&_playback_load_min); + uint32_t cmin = g_atomic_int_get (&_capture_load); + uint32_t cminold = g_atomic_int_get (&_capture_load_min); + + g_atomic_int_set (&_playback_load, (uint32_t) floor (pworst * 100.0f)); + g_atomic_int_set (&_capture_load, (uint32_t) floor (cworst * 100.0f)); + g_atomic_int_set (&_playback_load_min, min (pmin, pminold)); + g_atomic_int_set (&_capture_load_min, min (cmin, cminold)); + + if (actively_recording()) { + set_dirty(); + } +} + +/** Process callback used when the auditioner is not active */ +void +Session::process_with_events (nframes_t nframes) +{ + Event* ev; + nframes_t this_nframes; + nframes_t end_frame; + nframes_t offset; + bool session_needs_butler = false; + nframes_t stop_limit; + long frames_moved; + + /* make sure the auditioner is silent */ + + if (auditioner) { + auditioner->silence (nframes, 0); + } + + /* handle any pending events */ + + 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); + } + + /* Events caused a transport change, send an MTC Full Frame (SMPTE) message. + * This is sent whether rolling or not, to give slaves an idea of ardour time + * on locates (and allow slow slaves to position and prepare for rolling) + */ + if (_send_smpte_update) { + send_full_time_code(nframes); + } + + if (!process_can_proceed()) { + _silent = true; + return; + } + + if (events.empty() || next_event == events.end()) { + process_without_events (nframes); + return; + } + + end_frame = _transport_frame + (nframes_t)abs(floor(nframes * _transport_speed)); + + { + Event* this_event; + Events::iterator the_next_one; + + if (!process_can_proceed()) { + _silent = true; + return; + } + + if (!_exporting && _slave) { + if (!follow_slave (nframes, 0)) { + return; + } + } + + if (_transport_speed == 0) { + no_roll (nframes, 0); + return; + } + + if (!_exporting) { + send_midi_time_code_for_cycle (nframes); + } + + 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; + + /* yes folks, here it is, the actual loop where we really truly + process some audio */ + while (nframes) { + + this_nframes = nframes; /* real (jack) time relative */ + frames_moved = (long) floor (_transport_speed * nframes); /* transport relative */ + + /* running an event, position transport precisely to its time */ + if (this_event && this_event->action_frame <= end_frame && this_event->action_frame >= _transport_frame) { + /* this isn't quite right for reverse play */ + frames_moved = (long) (this_event->action_frame - _transport_frame); + this_nframes = (nframes_t) abs( floor(frames_moved / _transport_speed) ); + } + + 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; + + 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 */ + end_frame = _transport_frame + (nframes_t) floor (nframes * _transport_speed); + + } + + set_next_event (); + + } /* implicit release of route lock */ + + if (session_needs_butler) + summon_butler (); +} + +void +Session::reset_slave_state () +{ + average_slave_delta = 1800; + delta_accumulator_cnt = 0; + have_first_delta_accumulator = false; + slave_state = Stopped; +} + +bool +Session::transport_locked () const +{ + Slave* sl = _slave; + + if (!locate_pending() && ((Config->get_slave_source() == None) || (sl && sl->ok() && sl->locked()))) { + return true; + } + + return false; +} + +bool +Session::follow_slave (nframes_t nframes, nframes_t offset) +{ + float slave_speed; + nframes_t slave_transport_frame; + nframes_t this_delta; + int dir; + bool starting; + + if (!_slave->ok()) { + stop_transport (); + Config->set_slave_source (None); + 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 (_slave->is_always_synced() || 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 && play_loop && (slave_transport_frame < al->start() || slave_transport_frame > al->end())) { + // cancel looping + request_play_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; + nframes_t frame_delta = slave_transport_frame - _transport_frame; + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->can_internal_playback_seek (frame_delta)) { + ok = false; + break; + } + } + + if (ok) { + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->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 (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 (Config->get_slave_source() == 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 && !_slave->is_always_synced() && !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 ((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 (); + } + + int32_t frames_moved = (int32_t) floor (_transport_speed * nframes); + + if (frames_moved < 0) { + decrement_transport_position (-frames_moved); + } else { + increment_transport_position (frames_moved); + } + + 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 (nframes_t nframes) +{ + bool session_needs_butler = false; + nframes_t stop_limit; + long frames_moved; + nframes_t offset = 0; + + if (!process_can_proceed()) { + _silent = true; + return; + } + + if (!_exporting && _slave) { + if (!follow_slave (nframes, 0)) { + return; + } + } + + if (_transport_speed == 0) { + no_roll (nframes, 0); + return; + } + + if (!_exporting) { + send_midi_time_code_for_cycle (nframes); + } + + 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; + } + + if (maybe_sync_start (nframes, offset)) { + return; + } + + click (_transport_frame, nframes, offset); + + prepare_diskstreams (); + + frames_moved = (long) floor (_transport_speed * nframes); + + if (process_routes (nframes, offset)) { + no_roll (nframes, offset); + 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 (); + + if (session_needs_butler) + summon_butler (); +} + +/** Process callback used when the auditioner is active. + * @param nframes number of frames to process. + */ +void +Session::process_audition (nframes_t nframes) +{ + Event* ev; + boost::shared_ptr<RouteList> r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + if (!(*i)->is_hidden()) { + (*i)->silence (nframes, 0); + } + } + + /* run the auditioner, and if it says we need butler service, ask for it */ + + if (auditioner->play_audition (nframes) > 0) { + summon_butler (); + } + + /* handle pending events */ + + 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()) { + /* auditioner no longer active, so go back to the normal process callback */ + process_function = &Session::process_with_events; + } +} + +bool +Session::maybe_sync_start (nframes_t& nframes, nframes_t& offset) +{ + nframes_t sync_offset; + + if (!waiting_for_sync_offset) { + return false; + } + + if (_engine.get_sync_offset (sync_offset) && sync_offset < nframes) { + + no_roll (sync_offset, 0); + nframes -= sync_offset; + offset += sync_offset; + waiting_for_sync_offset = false; + + if (nframes == 0) { + return true; // done + } + + } else { + no_roll (nframes, 0); + return true; // done + } + + return false; +} + |