ardour/libs/ardour/session_process.cc
David Robillard e0aaed6d65 *** NEW CODING POLICY ***
All #include statements that include a header that is a part of a library
bundled with ardour MUST use quotes, not angle brackets.

Do this:

#include "ardour/types.h"

NOT this:

#include <ardour/types.h>

Rationale:

This is best practice in general, to ensure we include the local version
and not the system version.  That quotes mean "local" (in some sense)
and angle brackets mean "system" (in some sense) is a ubiquitous
convention and IIRC right in the C spec somewhere.

More pragmatically, this is required by (my) waf (stuff) for dependencies
to work correctly.  That is:

!!! FAILURE TO DO THIS CAN RESULT IN BROKEN BUILDS !!!

Failure to comply is punishable by death by torture. :)

P.S. It's not that dramatic in all cases, but this (in combination with some
GCC flags specific to the include type) is the best way I have found to be
absolutely 100% positive the local ones are being used (and we definitely
want to be absolutely 100% positive on that one).


git-svn-id: svn://localhost/ardour2/branches/3.0@4655 d708f5d6-7413-0410-9779-e7cbd77b26cf
2009-02-25 18:26:51 +00:00

965 lines
23 KiB
C++

/*
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)
{
// This is no more the appropriate place to call cycle
// start. cycle_start needs to be called at the Route::roll()
// where the signals which we want to mixdown have been calculated.
//
MIDI::Manager::instance()->cycle_start(nframes);
_silent = false;
if (non_realtime_work_pending()) {
if (!transport_work_requested ()) {
post_transport ();
}
}
(this->*process_function) (nframes);
// the ticker is for sending time information like MidiClock
nframes_t transport_frames = transport_frame();
BBT_Time transport_bbt;
bbt_time(transport_frames, transport_bbt);
SMPTE::Time transport_smpte;
smpte_time(transport_frames, transport_smpte);
tick (transport_frames, transport_bbt, transport_smpte); /* EMIT SIGNAL */
SendFeedback (); /* EMIT SIGNAL */
MIDI::Manager::instance()->cycle_end();
}
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;
}
/// TODO: Figure out what happens to phi and phase, if transport speed momentarily becomes
/// 1.0 eg. during the adjustments of a slave. If that is a bug, then AudioDiskstream::process
/// is very likely broken too
if (_transport_speed == 1.0) {
frames_moved = (long) nframes;
} else {
frames_moved = (long) AudioDiskstream::
calculate_varispeed_playback_distance(nframes, phase, phi, target_phi);
}
end_frame = _transport_frame + (nframes_t)frames_moved;
{
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 */
/* 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)
{
double 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 (_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.
*/
calculate_moving_average_of_slave_delta(dir, this_delta);
}
track_slave_state(slave_speed, slave_transport_frame, this_delta, starting);
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
*/
float delta;
#ifdef USE_MOVING_AVERAGE_OF_SLAVE
if (average_slave_delta == 0) {
delta = this_delta;
delta *= dir;
} else {
delta = average_slave_delta;
delta *= average_dir;
}
#else
delta = this_delta;
delta *= dir;
#endif
float adjusted_speed = slave_speed + (delta / float(_current_frame_rate));
if (_slave->give_slave_full_control_over_transport_speed()) {
request_transport_speed(slave_speed);
} else {
request_transport_speed(adjusted_speed);
#ifdef DEBUG_SLAVES
cerr << "adjust using " << delta
<< " towards " << adjusted_speed
<< " ratio = " << adjusted_speed / slave_speed
<< " current = " << _transport_speed
<< " slave @ " << slave_speed
<< endl;
#endif
}
if (abs(average_slave_delta) > (long) _slave->resolution()) {
cerr << "average slave delta greater than slave resolution, going to silent motion\n";
goto silent_motion;
}
}
}
#ifdef DEBUG_SLAVES
if (slave_speed != 0.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 (!starting && !non_realtime_work_pending()) {
/* speed is set, we're locked, and good to go */
return true;
}
silent_motion:
#ifdef DEBUG_SLAVES
cerr << "reached silent_motion:" <<endl;
#endif
follow_slave_silently(nframes, offset, slave_speed);
noroll:
/* don't move at all */
#ifdef DEBUG_SLAVES
cerr << "reached no_roll:" <<endl;
#endif
no_roll (nframes, 0);
return false;
}
void
Session::calculate_moving_average_of_slave_delta(int dir, nframes_t this_delta)
{
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++] = long(dir) * long(this_delta);
}
if (have_first_delta_accumulator) {
average_slave_delta = 0L;
for (int i = 0; i < delta_accumulator_size; ++i) {
average_slave_delta += delta_accumulator[i];
}
average_slave_delta /= long(delta_accumulator_size);
if (average_slave_delta < 0L) {
average_dir = -1;
average_slave_delta = abs(average_slave_delta);
} else {
average_dir = 1;
}
}
}
void
Session::track_slave_state(
float slave_speed,
nframes_t slave_transport_frame,
nframes_t this_delta,
bool starting)
{
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) {
#ifdef DEBUG_SLAVES
cerr << "waiting at " << slave_transport_frame << endl;
#endif
if (slave_transport_frame >= slave_wait_end) {
#ifdef DEBUG_SLAVES
cerr << "\tstart at " << _transport_frame << endl;
#endif
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 (long) * delta_accumulator_size);
average_slave_delta = 0L;
this_delta = 0;
}
}
if (slave_state == Running && _transport_speed == 0.0f) {
#ifdef DEBUG_SLAVES
cerr << "slave starts transport\n";
#endif
start_transport ();
}
} else { // slave_speed is 0
/* slave has stopped */
if (_transport_speed != 0.0f) {
#ifdef DEBUG_SLAVES
cerr << "slave stops transport: " << slave_speed << " frame: " << slave_transport_frame
<< " tf = " << _transport_frame << endl;
#endif
stop_transport();
}
if (slave_transport_frame != _transport_frame) {
#ifdef DEBUG_SLAVES
cerr << "slave stopped, move to " << slave_transport_frame << endl;
#endif
force_locate (slave_transport_frame, false);
}
slave_state = Stopped;
}
}
void
Session::follow_slave_silently(nframes_t nframes, nframes_t offset, float slave_speed)
{
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);
}
}
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 ();
/// TODO: Figure out what happens to phi and phase, if transport speed momentarily becomes
/// 1.0 eg. during the adjustments of a slave. If that is a bug, then AudioDiskstream::process
/// is very likely broken too
if (_transport_speed == 1.0) {
frames_moved = (long) nframes;
} else {
frames_moved = (long) AudioDiskstream::
calculate_varispeed_playback_distance(nframes, phase, phi, target_phi);
}
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) {
/* generate silence up to the sync point, then
adjust nframes + offset to reflect whatever
is left to do.
*/
no_roll (sync_offset, 0);
nframes -= sync_offset;
offset += sync_offset;
waiting_for_sync_offset = false;
if (nframes == 0) {
return true; // done, nothing left to process
}
} else {
/* sync offset point is not within this process()
cycle, so just generate silence. and don't bother
with any fancy stuff here, just the minimal silence.
*/
g_atomic_int_inc (&processing_prohibited);
no_roll (nframes, 0);
g_atomic_int_dec_and_test (&processing_prohibited);
if (Config->get_locate_while_waiting_for_sync()) {
if (micro_locate (nframes)) {
/* XXX ERROR !!! XXX */
}
}
return true; // done, nothing left to process
}
return false;
}