X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fdiskstream.cc;h=df9d1a9b4df5c0dc84f747f1f93ab963c84c1b9f;hb=6be56d78b199452e5dba726f2d7aaab2777d2b49;hp=050f23497b6e4e07612d1c96cbf8f9c5f045781b;hpb=b7f3a6350783ffda019c22f74dd67e7d619b387b;p=ardour.git diff --git a/libs/ardour/diskstream.cc b/libs/ardour/diskstream.cc index 050f23497b..df9d1a9b4d 100644 --- a/libs/ardour/diskstream.cc +++ b/libs/ardour/diskstream.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2000-2003 Paul Davis + Copyright (C) 2000-2006 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,10 +15,10 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id: diskstream.cc 567 2006-06-07 14:54:12Z trutkin $ */ #include +#include #include #include #include @@ -31,22 +31,30 @@ #include #include -#include -#include + +#include "pbd/error.h" +#include "pbd/basename.h" #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "pbd/xml++.h" +#include "pbd/memento_command.h" + +#include "ardour/ardour.h" +#include "ardour/audioengine.h" +#include "ardour/debug.h" +#include "ardour/diskstream.h" +#include "ardour/utils.h" +#include "ardour/configuration.h" +#include "ardour/audiofilesource.h" +#include "ardour/send.h" +#include "ardour/pannable.h" +#include "ardour/panner_shell.h" +#include "ardour/playlist.h" +#include "ardour/cycle_timer.h" +#include "ardour/region.h" +#include "ardour/panner.h" +#include "ardour/session.h" +#include "ardour/io.h" +#include "ardour/track.h" #include "i18n.h" #include @@ -55,132 +63,161 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -jack_nframes_t Diskstream::disk_io_chunk_frames; +/* XXX This goes uninitialized when there is no ~/.config/ardour3 directory. + * I can't figure out why, so this will do for now (just stole the + * default from configuration_vars.h). 0 is not a good value for + * allocating buffer sizes.. + */ +ARDOUR::framecnt_t Diskstream::disk_io_chunk_frames = 1024 * 256; -sigc::signal Diskstream::DiskstreamCreated; -//sigc::signal*> Diskstream::DeleteSources; -sigc::signal Diskstream::DiskOverrun; -sigc::signal Diskstream::DiskUnderrun; +PBD::Signal0 Diskstream::DiskOverrun; +PBD::Signal0 Diskstream::DiskUnderrun; Diskstream::Diskstream (Session &sess, const string &name, Flag flag) - : _name (name) - , _session (sess) -{ -#if 0 - /* prevent any write sources from being created */ + : SessionObject(sess, name) + , i_am_the_modifier (0) + , _track (0) + , _record_enabled (0) + , _visible_speed (1.0f) + , _actual_speed (1.0f) + , _buffer_reallocation_required (false) + , _seek_required (false) + , force_refill (false) + , capture_start_frame (0) + , capture_captured (0) + , was_recording (false) + , adjust_capture_position (0) + , _capture_offset (0) + , _roll_delay (0) + , first_recordable_frame (max_framepos) + , last_recordable_frame (max_framepos) + , last_possibly_recording (0) + , _alignment_style (ExistingMaterial) + , _scrubbing (false) + , _slaved (false) + , loop_location (0) + , overwrite_frame (0) + , overwrite_offset (0) + , _pending_overwrite (false) + , overwrite_queued (false) + , wrap_buffer_size (0) + , speed_buffer_size (0) + , _speed (1.0) + , _target_speed (_speed) + , file_frame (0) + , playback_sample (0) + , playback_distance (0) + , _read_data_count (0) + , _write_data_count (0) + , in_set_state (false) + , _persistent_alignment_style (ExistingMaterial) + , first_input_change (true) + , _flags (flag) - in_set_state = true; - - init (flag); - //use_new_playlist (); +{ +} - in_set_state = false; - DiskstreamCreated (this); /* EMIT SIGNAL */ -#endif +Diskstream::Diskstream (Session& sess, const XMLNode& /*node*/) + : SessionObject(sess, "unnamed diskstream") + , i_am_the_modifier (0) + , _track (0) + , _record_enabled (0) + , _visible_speed (1.0f) + , _actual_speed (1.0f) + , _buffer_reallocation_required (false) + , _seek_required (false) + , force_refill (false) + , capture_start_frame (0) + , capture_captured (0) + , was_recording (false) + , adjust_capture_position (0) + , _capture_offset (0) + , _roll_delay (0) + , first_recordable_frame (max_framepos) + , last_recordable_frame (max_framepos) + , last_possibly_recording (0) + , _alignment_style (ExistingMaterial) + , _scrubbing (false) + , _slaved (false) + , loop_location (0) + , overwrite_frame (0) + , overwrite_offset (0) + , _pending_overwrite (false) + , overwrite_queued (false) + , wrap_buffer_size (0) + , speed_buffer_size (0) + , _speed (1.0) + , _target_speed (_speed) + , file_frame (0) + , playback_sample (0) + , playback_distance (0) + , _read_data_count (0) + , _write_data_count (0) + , in_set_state (false) + , _persistent_alignment_style (ExistingMaterial) + , first_input_change (true) + , _flags (Recordable) +{ } - -Diskstream::Diskstream (Session& sess, const XMLNode& node) - : _session (sess) - + +Diskstream::~Diskstream () { -#if 0 - in_set_state = true; - init (Recordable); - - /*if (set_state (node)) { - in_set_state = false; - throw failed_constructor(); - }*/ - - in_set_state = false; - - //if (destructive()) { - // use_destructive_playlist (); - //} - DiskstreamCreated (this); /* EMIT SIGNAL */ -#endif + DEBUG_TRACE (DEBUG::Destruction, string_compose ("Diskstream %1 deleted\n", _name)); + + if (_playlist) { + _playlist->release (); + } } void -Diskstream::init (Flag f) +Diskstream::set_track (Track* t) { - _id = new_id(); - _refcnt = 0; - _flags = f; - _io = 0; - _alignment_style = ExistingMaterial; - _persistent_alignment_style = ExistingMaterial; - first_input_change = true; - i_am_the_modifier = 0; - g_atomic_int_set (&_record_enabled, 0); - was_recording = false; - capture_start_frame = 0; - capture_captured = 0; - _visible_speed = 1.0f; - _actual_speed = 1.0f; - _buffer_reallocation_required = false; - _seek_required = false; - first_recordable_frame = max_frames; - last_recordable_frame = max_frames; - _roll_delay = 0; - _capture_offset = 0; - _processed = false; - _slaved = false; - adjust_capture_position = 0; - last_possibly_recording = 0; - loop_location = 0; - wrap_buffer_size = 0; - speed_buffer_size = 0; - last_phase = 0; - phi = (uint64_t) (0x1000000); - file_frame = 0; - playback_sample = 0; - playback_distance = 0; - _read_data_count = 0; - _write_data_count = 0; - deprecated_io_node = 0; - - /* there are no channels at this point, so these - two calls just get speed_buffer_size and wrap_buffer - size setup without duplicating their code. - */ + _track = t; + _io = _track->input(); - //set_block_size (_session.get_block_size()); - //allocate_temporary_buffers (); + ic_connection.disconnect(); + _io->changed.connect_same_thread (ic_connection, boost::bind (&Diskstream::handle_input_change, this, _1, _2)); - pending_overwrite = false; - overwrite_frame = 0; - overwrite_queued = false; - input_change_pending = NoChange; + input_change_pending = IOChange::ConfigurationChanged; + non_realtime_input_change (); + set_align_style_from_io (); - //add_channel (); - _n_channels = 0;//1; + _track->Destroyed.connect_same_thread (*this, boost::bind (&Diskstream::route_going_away, this)); } -Diskstream::~Diskstream () +void +Diskstream::handle_input_change (IOChange change, void * /*src*/) { - // Taken by child.. assure lock? - //Glib::Mutex::Lock lm (state_lock); - - //if (_playlist) { - // _playlist->unref (); - //} + Glib::Mutex::Lock lm (state_lock); - //for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) { - // destroy_channel((*chan)); - //} - - //channels.clear(); + if (change.type & IOChange::ConfigurationChanged) { + if (!(input_change_pending.type & change.type)) { + input_change_pending.type = IOChange::Type (input_change_pending.type | change.type); + _session.request_input_change_handling (); + } + } } void -Diskstream::handle_input_change (IOChange change, void *src) +Diskstream::non_realtime_set_speed () { - Glib::Mutex::Lock lm (state_lock); + if (_buffer_reallocation_required) + { + Glib::Mutex::Lock lm (state_lock); + allocate_temporary_buffers (); + + _buffer_reallocation_required = false; + } + + if (_seek_required) { + if (speed() != 1.0f || speed() != -1.0f) { + seek ((framepos_t) (_session.transport_frame() * (double) speed()), true); + } + else { + seek (_session.transport_frame(), true); + } - if (!(input_change_pending & change)) { - input_change_pending = IOChange (input_change_pending|change); - _session.request_input_change_handling (); + _seek_required = false; } } @@ -189,49 +226,35 @@ Diskstream::realtime_set_speed (double sp, bool global) { bool changed = false; double new_speed = sp * _session.transport_speed(); - + if (_visible_speed != sp) { _visible_speed = sp; changed = true; } - + if (new_speed != _actual_speed) { - - jack_nframes_t required_wrap_size = (jack_nframes_t) floor (_session.get_block_size() * - fabs (new_speed)) + 1; - + + framecnt_t required_wrap_size = (framecnt_t) floor (_session.get_block_size() * + fabs (new_speed)) + 1; + if (required_wrap_size > wrap_buffer_size) { _buffer_reallocation_required = true; } - + _actual_speed = new_speed; - phi = (uint64_t) (0x1000000 * fabs(_actual_speed)); + _target_speed = fabs(_actual_speed); } if (changed) { if (!global) { _seek_required = true; } - speed_changed (); /* EMIT SIGNAL */ + SpeedChanged (); /* EMIT SIGNAL */ } return _buffer_reallocation_required || _seek_required; } -void -Diskstream::prepare () -{ - _processed = false; - playback_distance = 0; -} - -void -Diskstream::recover () -{ - state_lock.unlock(); - _processed = false; -} - void Diskstream::set_capture_offset () { @@ -240,7 +263,7 @@ Diskstream::set_capture_offset () return; } - _capture_offset = _io->input_latency(); + _capture_offset = _io->latency(); } void @@ -250,7 +273,6 @@ Diskstream::set_align_style (AlignStyle a) return; } - if (a != _alignment_style) { _alignment_style = a; AlignmentStyleChanged (); @@ -269,53 +291,98 @@ Diskstream::set_loop (Location *location) loop_location = location; - LoopSet (location); /* EMIT SIGNAL */ + LoopSet (location); /* EMIT SIGNAL */ return 0; } -jack_nframes_t -Diskstream::get_capture_start_frame (uint32_t n) +/** Get the start position (in session frames) of the nth capture in the current pass */ +ARDOUR::framepos_t +Diskstream::get_capture_start_frame (uint32_t n) const { Glib::Mutex::Lock lm (capture_info_lock); if (capture_info.size() > n) { + /* this is a completed capture */ return capture_info[n]->start; - } - else { + } else { + /* this is the currently in-progress capture */ return capture_start_frame; } } -jack_nframes_t -Diskstream::get_captured_frames (uint32_t n) +ARDOUR::framecnt_t +Diskstream::get_captured_frames (uint32_t n) const { Glib::Mutex::Lock lm (capture_info_lock); if (capture_info.size() > n) { + /* this is a completed capture */ return capture_info[n]->frames; - } - else { + } else { + /* this is the currently in-progress capture */ return capture_captured; } } void -Diskstream::set_roll_delay (jack_nframes_t nframes) +Diskstream::set_roll_delay (ARDOUR::framecnt_t nframes) { _roll_delay = nframes; } -void -Diskstream::set_speed (double sp) +int +Diskstream::use_playlist (boost::shared_ptr playlist) { - _session.request_diskstream_speed (*this, sp); + if (!playlist) { + return 0; + } + + bool prior_playlist = false; + + { + Glib::Mutex::Lock lm (state_lock); + + if (playlist == _playlist) { + return 0; + } + + playlist_connections.drop_connections (); + + if (_playlist) { + _playlist->release(); + prior_playlist = true; + } + + _playlist = playlist; + _playlist->use(); + + if (!in_set_state && recordable()) { + reset_write_sources (false); + } + + _playlist->ContentsChanged.connect_same_thread (playlist_connections, boost::bind (&Diskstream::playlist_modified, this)); + _playlist->DropReferences.connect_same_thread (playlist_connections, boost::bind (&Diskstream::playlist_deleted, this, boost::weak_ptr(_playlist))); + _playlist->RangesMoved.connect_same_thread (playlist_connections, boost::bind (&Diskstream::playlist_ranges_moved, this, _1, _2)); + } + + /* don't do this if we've already asked for it *or* if we are setting up + the diskstream for the very first time - the input changed handling will + take care of the buffer refill. + */ + + if (!overwrite_queued && prior_playlist) { + _session.request_overwrite_buffer (_track); + overwrite_queued = true; + } + + PlaylistChanged (); /* EMIT SIGNAL */ + _session.set_dirty (); - /* to force a rebuffering at the right place */ - playlist_modified(); + return 0; } void -Diskstream::playlist_changed (Change ignored) +Diskstream::playlist_changed (const PropertyChange&) { playlist_modified (); } @@ -324,39 +391,300 @@ void Diskstream::playlist_modified () { if (!i_am_the_modifier && !overwrite_queued) { - _session.request_overwrite_buffer (this); + _session.request_overwrite_buffer (_track); overwrite_queued = true; - } + } } -int -Diskstream::set_name (string str, void *src) +void +Diskstream::playlist_deleted (boost::weak_ptr wpl) +{ + boost::shared_ptr pl (wpl.lock()); + + if (pl == _playlist) { + + /* this catches an ordering issue with session destruction. playlists + are destroyed before diskstreams. we have to invalidate any handles + we have to the playlist. + */ + + if (_playlist) { + _playlist.reset (); + } + } +} + +bool +Diskstream::set_name (const string& str) { - if (str != _name) { + if (_name != str) { assert(playlist()); playlist()->set_name (str); - _name = str; - + + SessionObject::set_name(str); + if (!in_set_state && recordable()) { /* rename existing capture files so that they have the correct name */ return rename_write_sources (); } else { - return -1; + return false; } } - return 0; + return true; } void -Diskstream::set_destructive (bool yn) +Diskstream::playlist_ranges_moved (list< Evoral::RangeMove > const & movements_frames, bool from_undo) { - if (yn != destructive()) { - reset_write_sources (true, true); - if (yn) { - _flags |= Destructive; - } else { - _flags &= ~Destructive; + /* If we're coming from an undo, it will have handled + automation undo (it must, since automation-follows-regions + can lose automation data). Hence we can do nothing here. + */ + + if (from_undo) { + return; + } + + if (!_track || Config->get_automation_follows_regions () == false) { + return; + } + + list< Evoral::RangeMove > movements; + + for (list< Evoral::RangeMove >::const_iterator i = movements_frames.begin(); + i != movements_frames.end(); + ++i) { + + movements.push_back(Evoral::RangeMove(i->from, i->length, i->to)); + } + + /* move panner automation */ + boost::shared_ptr pannable = _track->pannable(); + Evoral::ControlSet::Controls& c (pannable->controls()); + + for (Evoral::ControlSet::Controls::iterator ci = c.begin(); ci != c.end(); ++ci) { + boost::shared_ptr ac = boost::dynamic_pointer_cast(ci->second); + if (!ac) { + continue; + } + boost::shared_ptr alist = ac->alist(); + + XMLNode & before = alist->get_state (); + bool const things_moved = alist->move_ranges (movements); + if (things_moved) { + _session.add_command (new MementoCommand ( + *alist.get(), &before, &alist->get_state ())); + } + } + + /* move processor automation */ + _track->foreach_processor (boost::bind (&Diskstream::move_processor_automation, this, _1, movements_frames)); +} + +void +Diskstream::move_processor_automation (boost::weak_ptr p, list< Evoral::RangeMove > const & movements_frames) +{ + boost::shared_ptr processor (p.lock ()); + if (!processor) { + return; + } + + list< Evoral::RangeMove > movements; + for (list< Evoral::RangeMove >::const_iterator i = movements_frames.begin(); i != movements_frames.end(); ++i) { + movements.push_back(Evoral::RangeMove(i->from, i->length, i->to)); + } + + set const a = processor->what_can_be_automated (); + + for (set::iterator i = a.begin (); i != a.end (); ++i) { + boost::shared_ptr al = processor->automation_control(*i)->alist(); + XMLNode & before = al->get_state (); + bool const things_moved = al->move_ranges (movements); + if (things_moved) { + _session.add_command ( + new MementoCommand ( + *al.get(), &before, &al->get_state () + ) + ); } } } + +void +Diskstream::check_record_status (framepos_t transport_frame, bool can_record) +{ + int possibly_recording; + int rolling; + int change; + const int transport_rolling = 0x4; + const int track_rec_enabled = 0x2; + const int global_rec_enabled = 0x1; + const int fully_rec_enabled = (transport_rolling|track_rec_enabled|global_rec_enabled); + + /* merge together the 3 factors that affect record status, and compute + what has changed. + */ + + rolling = _session.transport_speed() != 0.0f; + possibly_recording = (rolling << 2) | (record_enabled() << 1) | can_record; + change = possibly_recording ^ last_possibly_recording; + + if (possibly_recording == last_possibly_recording) { + return; + } + if (possibly_recording == fully_rec_enabled) { + + if (last_possibly_recording == fully_rec_enabled) { + return; + } + + /* we transitioned to recording. lets see if its transport based or a punch */ + + first_recordable_frame = transport_frame + _capture_offset; + last_recordable_frame = max_framepos; + capture_start_frame = transport_frame; + + if (change & transport_rolling) { + + /* transport-change (started rolling) */ + + if (_alignment_style == ExistingMaterial) { + + /* there are two delays happening: + + 1) inbound, represented by _capture_offset + 2) outbound, represented by _session.worst_output_latency() + + the first sample to record occurs when the larger of these + two has elapsed, since they occur in parallel. + + since we've already added _capture_offset, just add the + difference if _session.worst_output_latency() is larger. + */ + + if (_capture_offset < _session.worst_output_latency()) { + first_recordable_frame += (_session.worst_output_latency() - _capture_offset); + } + } else { + first_recordable_frame += _roll_delay; + } + + } else { + + /* punch in */ + + if (_alignment_style == ExistingMaterial) { + + /* There are two kinds of punch: + + manual punch in happens at the correct transport frame + because the user hit a button. but to get alignment correct + we have to back up the position of the new region to the + appropriate spot given the roll delay. + + autopunch toggles recording at the precise + transport frame, and then the DS waits + to start recording for a time that depends + on the output latency. + + XXX: BUT THIS CODE DOESN'T DIFFERENTIATE !!! + + */ + + if (_capture_offset < _session.worst_output_latency()) { + /* see comment in ExistingMaterial block above */ + first_recordable_frame += (_session.worst_output_latency() - _capture_offset); + } + + } else { + capture_start_frame -= _roll_delay; + } + } + + prepare_record_status (capture_start_frame); + + } else { + + if (last_possibly_recording == fully_rec_enabled) { + + /* we were recording last time */ + + if (change & transport_rolling) { + /* transport-change (stopped rolling): last_recordable_frame was set in ::prepare_to_stop() */ + + } else { + /* punch out */ + + last_recordable_frame = transport_frame + _capture_offset; + + if (_alignment_style == ExistingMaterial) { + if (_session.worst_output_latency() > _capture_offset) { + last_recordable_frame += (_session.worst_output_latency() - _capture_offset); + } + } else { + last_recordable_frame += _roll_delay; + } + } + } + } + + last_possibly_recording = possibly_recording; +} + +void +Diskstream::route_going_away () +{ + _io.reset (); +} + +void +Diskstream::calculate_record_range(OverlapType ot, framepos_t transport_frame, framecnt_t nframes, + framecnt_t & rec_nframes, framecnt_t & rec_offset) +{ + switch (ot) { + case OverlapNone: + rec_nframes = 0; + break; + + case OverlapInternal: + /* ---------- recrange + |---| transrange + */ + rec_nframes = nframes; + rec_offset = 0; + break; + + case OverlapStart: + /* |--------| recrange + -----| transrange + */ + rec_nframes = transport_frame + nframes - first_recordable_frame; + if (rec_nframes) { + rec_offset = first_recordable_frame - transport_frame; + } + break; + + case OverlapEnd: + /* |--------| recrange + |-------- transrange + */ + rec_nframes = last_recordable_frame - transport_frame; + rec_offset = 0; + break; + + case OverlapExternal: + /* |--------| recrange + -------------- transrange + */ + rec_nframes = last_recordable_frame - first_recordable_frame; + rec_offset = first_recordable_frame - transport_frame; + break; + } +} + +void +Diskstream::prepare_to_stop (framepos_t pos) +{ + last_recordable_frame = pos + _capture_offset; +}