summaryrefslogtreecommitdiff
path: root/libs/ardour
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2017-03-03 16:14:07 +0100
committerPaul Davis <paul@linuxaudiosystems.com>2017-09-18 11:40:52 -0400
commit46366541b1aea2c6441b4273734307ab4c55ce13 (patch)
tree6de8d49ec5937d9b4a0386905dbf6d271ec47617 /libs/ardour
parent36046cccc1fd40b1d98e7740db8fb398b3928285 (diff)
crawling towards the APIs for separate disk i/o
Diffstat (limited to 'libs/ardour')
-rw-r--r--libs/ardour/ardour/disk_reader.h126
-rw-r--r--libs/ardour/ardour/disk_writer.h168
-rw-r--r--libs/ardour/disk_io.cc205
-rw-r--r--libs/ardour/disk_reader.cc177
-rw-r--r--libs/ardour/disk_writer.cc349
5 files changed, 1025 insertions, 0 deletions
diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h
new file mode 100644
index 0000000000..47dd1d87cc
--- /dev/null
+++ b/libs/ardour/ardour/disk_reader.h
@@ -0,0 +1,126 @@
+/*
+ Copyright (C) 2009-2016 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.
+
+*/
+
+#ifndef __ardour_disk_reader_h__
+#define __ardour_disk_reader_h__
+
+#include "ardour/disk_io.h"
+
+namespace ARDOUR
+{
+
+class Playlist;
+class AudioPlaylist;
+class MidiPlaylist;
+
+class LIBARDOUR_API DiskReader : public DiskIOProcessor
+{
+ public:
+ DiskReader (Session&, std::string const & name, DiskIOProcessor::Flag f = DiskIOProcessor::Flag (0));
+ ~DiskReader ();
+
+ bool set_name (std::string const & str);
+
+ static framecnt_t chunk_frames() { return _chunk_frames; }
+ static framecnt_t default_chunk_frames ();
+ static void set_chunk_frames (framecnt_t n) { _chunk_frames = n; }
+
+ void run (BufferSet& /*bufs*/, framepos_t /*start_frame*/, framepos_t /*end_frame*/, double speed, pframes_t /*nframes*/, bool /*result_required*/);
+ void silence (framecnt_t /*nframes*/, framepos_t /*start_frame*/);
+ bool configure_io (ChanCount in, ChanCount out);
+ bool can_support_io_configuration (const ChanCount& in, ChanCount& out) = 0;
+ ChanCount input_streams () const;
+ ChanCount output_streams() const;
+ void realtime_handle_transport_stopped ();
+ void realtime_locate ();
+
+ framecnt_t roll_delay() const { return _roll_delay; }
+ void set_roll_delay (framecnt_t);
+
+ virtual XMLNode& state (bool full);
+ int set_state (const XMLNode&, int version);
+
+ boost::shared_ptr<Playlist> playlist();
+ boost::shared_ptr<Playlist> get_playlist (DataType);
+ boost::shared_ptr<MidiPlaylist> midi_playlist();
+ boost::shared_ptr<AudioPlaylist> audio_playlist();
+
+ virtual void playlist_modified ();
+ virtual int use_playlist (boost::shared_ptr<Playlist>);
+ virtual int use_new_playlist () = 0;
+ virtual int use_copy_playlist () = 0;
+
+ PBD::Signal0<void> PlaylistChanged;
+ PBD::Signal0<void> AlignmentStyleChanged;
+
+ float buffer_load() const;
+
+ void move_processor_automation (boost::weak_ptr<Processor>, std::list<Evoral::RangeMove<framepos_t> > const &);
+
+ /** For non-butler contexts (allocates temporary working buffers)
+ *
+ * This accessible method has a default argument; derived classes
+ * must inherit the virtual method that we call which does NOT
+ * have a default argument, to avoid complications with inheritance
+ */
+ int do_refill_with_alloc(bool partial_fill = true) {
+ return _do_refill_with_alloc (partial_fill);
+ }
+
+ bool pending_overwrite () const { return _pending_overwrite; }
+
+ virtual int find_and_use_playlist (std::string const &) = 0;
+
+ protected:
+ virtual int do_refill () = 0;
+
+ boost::shared_ptr<Playlist> _playlist;
+
+ virtual void playlist_changed (const PBD::PropertyChange&);
+ virtual void playlist_deleted (boost::weak_ptr<Playlist>);
+ virtual void playlist_ranges_moved (std::list< Evoral::RangeMove<framepos_t> > const &, bool);
+
+ private:
+ typedef std::map<DataType,boost::shared_ptr<Playlist> > Playlists;
+
+ /** The number of frames by which this diskstream's output should be delayed
+ with respect to the transport frame. This is used for latency compensation.
+ */
+ framecnt_t _roll_delay;
+ Playlists _playlists;
+ framepos_t overwrite_frame;
+ off_t overwrite_offset;
+ bool _pending_overwrite;
+ bool overwrite_queued;
+ IOChange input_change_pending;
+ framecnt_t wrap_buffer_size;
+ framecnt_t speed_buffer_size;
+ framepos_t file_frame;
+ framepos_t playback_sample;
+
+ PBD::ScopedConnectionList playlist_connections;
+
+ virtual int _do_refill_with_alloc (bool partial_fill);
+
+ static framecnt_t _chunk_frames;
+};
+
+} // namespace
+
+#endif /* __ardour_disk_reader_h__ */
diff --git a/libs/ardour/ardour/disk_writer.h b/libs/ardour/ardour/disk_writer.h
new file mode 100644
index 0000000000..984a84a938
--- /dev/null
+++ b/libs/ardour/ardour/disk_writer.h
@@ -0,0 +1,168 @@
+/*
+ Copyright (C) 2009-2016 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.
+
+*/
+
+#ifndef __ardour_disk_writer_h__
+#define __ardour_disk_writer_h__
+
+#include <list>
+
+#include "ardour/disk_io.h"
+
+namespace ARDOUR
+{
+
+class LIBARDOUR_API DiskWriter : public DiskIOProcessor
+{
+ public:
+ DiskWriter (Session&, std::string const & name, DiskIOProcessor::Flag f = DiskIOProcessor::Flag (0));
+
+ virtual bool set_write_source_name (const std::string& str);
+
+ static framecnt_t chunk_frames() { return _chunk_frames; }
+ static framecnt_t default_chunk_frames ();
+ static void set_chunk_frames (framecnt_t n) { _chunk_frames = n; }
+
+ void run (BufferSet& /*bufs*/, framepos_t /*start_frame*/, framepos_t /*end_frame*/, double speed, pframes_t /*nframes*/, bool /*result_required*/);
+ void silence (framecnt_t /*nframes*/, framepos_t /*start_frame*/);
+ bool configure_io (ChanCount in, ChanCount out);
+ bool can_support_io_configuration (const ChanCount& in, ChanCount& out) = 0;
+ ChanCount input_streams () const;
+ ChanCount output_streams() const;
+ void realtime_handle_transport_stopped ();
+ void realtime_locate ();
+
+ virtual XMLNode& state (bool full);
+ int set_state (const XMLNode&, int version);
+
+ virtual int use_new_write_source (uint32_t n=0) = 0;
+
+ std::string write_source_name () const {
+ if (_write_source_name.empty()) {
+ return name();
+ } else {
+ return _write_source_name;
+ }
+ }
+
+ virtual std::string steal_write_source_name () { return std::string(); }
+
+ AlignStyle alignment_style() const { return _alignment_style; }
+ AlignChoice alignment_choice() const { return _alignment_choice; }
+ void set_align_style (AlignStyle, bool force=false);
+ void set_align_choice (AlignChoice a, bool force=false);
+
+ PBD::Signal0<void> AlignmentStyleChanged;
+
+ void set_input_latency (framecnt_t);
+ framecnt_t input_latency () const { return _input_latency; }
+
+ std::list<boost::shared_ptr<Source> >& last_capture_sources () { return _last_capture_sources; }
+
+ bool record_enabled() const { return g_atomic_int_get (const_cast<gint*>(&_record_enabled)); }
+ bool record_safe () const { return g_atomic_int_get (const_cast<gint*>(&_record_safe)); }
+ virtual void set_record_enabled (bool yn) = 0;
+ virtual void set_record_safe (bool yn) = 0;
+
+ bool destructive() const { return _flags & Destructive; }
+ virtual int set_destructive (bool /*yn*/) { return -1; }
+ virtual int set_non_layered (bool /*yn*/) { return -1; }
+ virtual bool can_become_destructive (bool& /*requires_bounce*/) const { return false; }
+
+ /** @return Start position of currently-running capture (in session frames) */
+ framepos_t current_capture_start() const { return capture_start_frame; }
+ framepos_t current_capture_end() const { return capture_start_frame + capture_captured; }
+ framepos_t get_capture_start_frame (uint32_t n = 0) const;
+ framecnt_t get_captured_frames (uint32_t n = 0) const;
+
+ float buffer_load() const;
+
+ virtual void request_input_monitoring (bool) {}
+ virtual void ensure_input_monitoring (bool) {}
+
+ framecnt_t capture_offset() const { return _capture_offset; }
+ virtual void set_capture_offset ();
+
+ protected:
+ virtual int do_flush (RunContext context, bool force = false) = 0;
+
+ virtual void check_record_status (framepos_t transport_frame, bool can_record);
+ virtual void prepare_record_status (framepos_t /*capture_start_frame*/) {}
+ virtual void set_align_style_from_io() {}
+ virtual void setup_destructive_playlist () {}
+ virtual void use_destructive_playlist () {}
+ virtual void prepare_to_stop (framepos_t transport_pos, framepos_t audible_frame);
+
+ void engage_record_enable ();
+ void disengage_record_enable ();
+ void engage_record_safe ();
+ void disengage_record_safe ();
+
+ virtual bool prep_record_enable () = 0;
+ virtual bool prep_record_disable () = 0;
+
+ void calculate_record_range (
+ Evoral::OverlapType ot, framepos_t transport_frame, framecnt_t nframes,
+ framecnt_t& rec_nframes, framecnt_t& rec_offset
+ );
+
+ static framecnt_t disk_read_chunk_frames;
+ static framecnt_t disk_write_chunk_frames;
+
+ struct CaptureInfo {
+ framepos_t start;
+ framecnt_t frames;
+ };
+
+ std::vector<CaptureInfo*> capture_info;
+ mutable Glib::Threads::Mutex capture_info_lock;
+
+ private:
+ enum TransitionType {
+ CaptureStart = 0,
+ CaptureEnd
+ };
+
+ struct CaptureTransition {
+ TransitionType type;
+ framepos_t capture_val; ///< The start or end file frame position
+ };
+
+ framecnt_t _input_latency;
+ gint _record_enabled;
+ gint _record_safe;
+ framepos_t capture_start_frame;
+ framecnt_t capture_captured;
+ bool was_recording;
+ framecnt_t adjust_capture_position;
+ framecnt_t _capture_offset;
+ framepos_t first_recordable_frame;
+ framepos_t last_recordable_frame;
+ int last_possibly_recording;
+ AlignStyle _alignment_style;
+ AlignChoice _alignment_choice;
+ std::string _write_source_name;
+
+ std::list<boost::shared_ptr<Source> > _last_capture_sources;
+
+ static framecnt_t _chunk_frames;
+};
+
+} // namespace
+
+#endif /* __ardour_disk_writer_h__ */
diff --git a/libs/ardour/disk_io.cc b/libs/ardour/disk_io.cc
new file mode 100644
index 0000000000..f75178a5bd
--- /dev/null
+++ b/libs/ardour/disk_io.cc
@@ -0,0 +1,205 @@
+/*
+ Copyright (C) 2009-2016 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 "pbd/error.h"
+#include "pbd/i18n.h"
+
+#include "ardour/rc_configuration.h"
+#include "ardour/disk_io.h"
+#include "ardour/disk_reader.h"
+#include "ardour/disk_writer.h"
+#include "ardour/location.h"
+#include "ardour/session.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace std;
+
+const string DiskIOProcessor::state_node_name = X_("DiskIOProcessor");
+
+// PBD::Signal0<void> DiskIOProcessor::DiskOverrun;
+// PBD::Signal0<void> DiskIOProcessor::DiskUnderrun;
+
+DiskIOProcessor::DiskIOProcessor (Session& s, string const & str, Flag f)
+ : Processor (s, str)
+ , _flags (f)
+ , i_am_the_modifier (false)
+ , _visible_speed (0.0)
+ , _actual_speed (0.0)
+ , _speed (0.0)
+ , _target_speed (0.0)
+ , _buffer_reallocation_required (false)
+ , _seek_required (false)
+ , _slaved (false)
+ , loop_location (0)
+ , in_set_state (false)
+ , wrap_buffer_size (0)
+ , speed_buffer_size (0)
+{
+}
+
+void
+DiskIOProcessor::set_buffering_parameters (BufferingPreset bp)
+{
+ framecnt_t read_chunk_size;
+ framecnt_t read_buffer_size;
+ framecnt_t write_chunk_size;
+ framecnt_t write_buffer_size;
+
+ if (!get_buffering_presets (bp, read_chunk_size, read_buffer_size, write_chunk_size, write_buffer_size)) {
+ return;
+ }
+
+ DiskReader::set_chunk_frames (read_chunk_size);
+ DiskWriter::set_chunk_frames (write_chunk_size);
+
+ Config->set_audio_capture_buffer_seconds (write_buffer_size);
+ Config->set_audio_playback_buffer_seconds (read_buffer_size);
+}
+
+bool
+DiskIOProcessor::get_buffering_presets (BufferingPreset bp,
+ framecnt_t& read_chunk_size,
+ framecnt_t& read_buffer_size,
+ framecnt_t& write_chunk_size,
+ framecnt_t& write_buffer_size)
+{
+ switch (bp) {
+ case Small:
+ read_chunk_size = 65536; /* samples */
+ write_chunk_size = 65536; /* samples */
+ read_buffer_size = 5; /* seconds */
+ write_buffer_size = 5; /* seconds */
+ break;
+
+ case Medium:
+ read_chunk_size = 262144; /* samples */
+ write_chunk_size = 131072; /* samples */
+ read_buffer_size = 10; /* seconds */
+ write_buffer_size = 10; /* seconds */
+ break;
+
+ case Large:
+ read_chunk_size = 524288; /* samples */
+ write_chunk_size = 131072; /* samples */
+ read_buffer_size = 20; /* seconds */
+ write_buffer_size = 20; /* seconds */
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+
+int
+DiskIOProcessor::set_loop (Location *location)
+{
+ if (location) {
+ if (location->start() >= location->end()) {
+ error << string_compose(_("Location \"%1\" not valid for track loop (start >= end)"), location->name()) << endl;
+ return -1;
+ }
+ }
+
+ loop_location = location;
+
+ LoopSet (location); /* EMIT SIGNAL */
+ return 0;
+}
+
+void
+DiskIOProcessor::non_realtime_set_speed ()
+{
+ if (_buffer_reallocation_required)
+ {
+ Glib::Threads::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);
+ }
+
+ _seek_required = false;
+ }
+}
+
+bool
+DiskIOProcessor::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) {
+
+ framecnt_t required_wrap_size = (framecnt_t) ceil (_session.get_block_size() *
+ fabs (new_speed)) + 2;
+
+ if (required_wrap_size > wrap_buffer_size) {
+ _buffer_reallocation_required = true;
+ }
+
+ _actual_speed = new_speed;
+ _target_speed = fabs(_actual_speed);
+ }
+
+ if (changed) {
+ if (!global) {
+ _seek_required = true;
+ }
+ SpeedChanged (); /* EMIT SIGNAL */
+ }
+
+ return _buffer_reallocation_required || _seek_required;
+}
+
+int
+DiskIOProcessor::set_state (const XMLNode& node, int version)
+{
+ XMLProperty const * prop;
+
+ Processor::set_state (node, version);
+
+ if ((prop = node.property ("flags")) != 0) {
+ _flags = Flag (string_2_enum (prop->value(), _flags));
+ }
+
+ if ((prop = node.property ("speed")) != 0) {
+ double sp = atof (prop->value().c_str());
+
+ if (realtime_set_speed (sp, false)) {
+ non_realtime_set_speed ();
+ }
+ }
+ return 0;
+}
diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc
new file mode 100644
index 0000000000..951a1aa632
--- /dev/null
+++ b/libs/ardour/disk_reader.cc
@@ -0,0 +1,177 @@
+/*
+ Copyright (C) 2009-2016 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 "pbd/i18n.h"
+
+#include "ardour/debug.h"
+#include "ardour/disk_reader.h"
+#include "ardour/playlist.h"
+#include "ardour/session.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace std;
+
+ARDOUR::framecnt_t DiskReader::_chunk_frames = default_chunk_frames ();
+
+DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
+ : DiskIOProcessor (s, str, f)
+ , _roll_delay (0)
+ , overwrite_frame (0)
+ , overwrite_offset (0)
+ , _pending_overwrite (false)
+ , overwrite_queued (false)
+ , file_frame (0)
+ , playback_sample (0)
+{
+}
+
+DiskReader::~DiskReader ()
+{
+ DEBUG_TRACE (DEBUG::Destruction, string_compose ("DiskReader %1 deleted\n", _name));
+
+ if (_playlist) {
+ _playlist->release ();
+ }
+}
+
+framecnt_t
+DiskReader::default_chunk_frames()
+{
+ return 65536;
+}
+
+bool
+DiskReader::set_name (string const & str)
+{
+ if (_name != str) {
+ assert (_playlist);
+ _playlist->set_name (str);
+ SessionObject::set_name(str);
+ }
+
+ return true;
+}
+
+void
+DiskReader::playlist_changed (const PropertyChange&)
+{
+ playlist_modified ();
+}
+
+void
+DiskReader::playlist_modified ()
+{
+ if (!i_am_the_modifier && !overwrite_queued) {
+ // !!!! _session.request_overwrite_buffer (this);
+ overwrite_queued = true;
+ }
+}
+
+void
+DiskReader::playlist_deleted (boost::weak_ptr<Playlist> wpl)
+{
+ boost::shared_ptr<Playlist> pl (wpl.lock());
+
+ if (pl == _playlist) {
+
+ /* this catches an ordering issue with session destruction. playlists
+ are destroyed before disk readers. we have to invalidate any handles
+ we have to the playlist.
+ */
+
+ if (_playlist) {
+ _playlist.reset ();
+ }
+ }
+}
+
+int
+DiskReader::use_playlist (boost::shared_ptr<Playlist> playlist)
+{
+ if (!playlist) {
+ return 0;
+ }
+
+ bool prior_playlist = false;
+
+ {
+ Glib::Threads::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();
+
+ _playlist->ContentsChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this));
+ _playlist->LayeringChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this));
+ _playlist->DropReferences.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_deleted, this, boost::weak_ptr<Playlist>(_playlist)));
+ _playlist->RangesMoved.connect_same_thread (playlist_connections, boost::bind (&DiskReader::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 (this);
+ overwrite_queued = true;
+ }
+
+ PlaylistChanged (); /* EMIT SIGNAL */
+ _session.set_dirty ();
+
+ return 0;
+}
+
+void
+DiskReader::set_roll_delay (ARDOUR::framecnt_t nframes)
+{
+ _roll_delay = nframes;
+}
+
+int
+DiskReader::set_state (const XMLNode& node, int version)
+{
+ XMLProperty const * prop;
+
+ if (DiskIOProcessor::set_state (node, version)) {
+ return -1;
+ }
+
+ if ((prop = node.property ("playlist")) == 0) {
+ return -1;
+ }
+
+ if (find_and_use_playlist (prop->value())) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libs/ardour/disk_writer.cc b/libs/ardour/disk_writer.cc
new file mode 100644
index 0000000000..a436324320
--- /dev/null
+++ b/libs/ardour/disk_writer.cc
@@ -0,0 +1,349 @@
+/*
+ Copyright (C) 2009-2016 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 "pbd/i18n.h"
+
+#include "ardour/debug.h"
+#include "ardour/disk_writer.h"
+#include "ardour/session.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace std;
+
+ARDOUR::framecnt_t DiskWriter::_chunk_frames = DiskWriter::default_chunk_frames ();
+
+DiskWriter::DiskWriter (Session& s, string const & str, DiskIOProcessor::Flag f)
+ : DiskIOProcessor (s, str, f)
+ , capture_start_frame (0)
+ , capture_captured (0)
+ , was_recording (false)
+ , adjust_capture_position (0)
+ , _capture_offset (0)
+ , first_recordable_frame (max_framepos)
+ , last_recordable_frame (max_framepos)
+ , last_possibly_recording (0)
+ , _alignment_style (ExistingMaterial)
+ , _alignment_choice (Automatic)
+{
+}
+
+framecnt_t
+DiskWriter::default_chunk_frames ()
+{
+ return 65536;
+}
+
+bool
+DiskWriter::set_write_source_name (string const & str)
+{
+ _write_source_name = str;
+ return true;
+}
+
+void
+DiskWriter::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) | ((int)record_enabled() << 1) | (int)can_record;
+ change = possibly_recording ^ last_possibly_recording;
+
+ if (possibly_recording == last_possibly_recording) {
+ return;
+ }
+
+ const framecnt_t existing_material_offset = _session.worst_playback_latency();
+
+ if (possibly_recording == fully_rec_enabled) {
+
+ if (last_possibly_recording == fully_rec_enabled) {
+ return;
+ }
+
+ capture_start_frame = _session.transport_frame();
+ first_recordable_frame = capture_start_frame + _capture_offset;
+ last_recordable_frame = max_framepos;
+
+ DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: @ %7 (%9) FRF = %2 CSF = %4 CO = %5, EMO = %6 RD = %8 WOL %10 WTL %11\n",
+ name(), first_recordable_frame, last_recordable_frame, capture_start_frame,
+ _capture_offset,
+ existing_material_offset,
+ transport_frame,
+ _session.transport_frame(),
+ _session.worst_output_latency(),
+ _session.worst_track_latency()));
+
+
+ if (_alignment_style == ExistingMaterial) {
+ first_recordable_frame += existing_material_offset;
+ DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tshift FRF by EMO %1\n",
+ first_recordable_frame));
+ }
+
+ 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(). We
+ * had to set it there because we likely rolled past the stopping point to declick out,
+ * and then backed up.
+ */
+
+ } else {
+ /* punch out */
+
+ last_recordable_frame = _session.transport_frame() + _capture_offset;
+
+ if (_alignment_style == ExistingMaterial) {
+ last_recordable_frame += existing_material_offset;
+ }
+ }
+ }
+ }
+
+ last_possibly_recording = possibly_recording;
+}
+
+void
+DiskWriter::calculate_record_range (Evoral::OverlapType ot, framepos_t transport_frame, framecnt_t nframes,
+ framecnt_t & rec_nframes, framecnt_t & rec_offset)
+{
+ switch (ot) {
+ case Evoral::OverlapNone:
+ rec_nframes = 0;
+ break;
+
+ case Evoral::OverlapInternal:
+ /* ---------- recrange
+ * |---| transrange
+ */
+ rec_nframes = nframes;
+ rec_offset = 0;
+ break;
+
+ case Evoral::OverlapStart:
+ /* |--------| recrange
+ * -----| transrange
+ */
+ rec_nframes = transport_frame + nframes - first_recordable_frame;
+ if (rec_nframes) {
+ rec_offset = first_recordable_frame - transport_frame;
+ }
+ break;
+
+ case Evoral::OverlapEnd:
+ /* |--------| recrange
+ * |-------- transrange
+ */
+ rec_nframes = last_recordable_frame - transport_frame;
+ rec_offset = 0;
+ break;
+
+ case Evoral::OverlapExternal:
+ /* |--------| recrange
+ * -------------- transrange
+ */
+ rec_nframes = last_recordable_frame - first_recordable_frame;
+ rec_offset = first_recordable_frame - transport_frame;
+ break;
+ }
+
+ DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 rec? %2 @ %3 (for %4) FRF %5 LRF %6 : rf %7 @ %8\n",
+ _name, enum_2_string (ot), transport_frame, nframes,
+ first_recordable_frame, last_recordable_frame, rec_nframes, rec_offset));
+}
+
+void
+DiskWriter::prepare_to_stop (framepos_t transport_frame, framepos_t audible_frame)
+{
+ switch (_alignment_style) {
+ case ExistingMaterial:
+ last_recordable_frame = transport_frame + _capture_offset;
+ DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose("%1: prepare to stop sets last recordable frame to %2 + %3 = %4\n", _name, transport_frame, _capture_offset, last_recordable_frame));
+ break;
+
+ case CaptureTime:
+ last_recordable_frame = audible_frame; // note that capture_offset is zero
+ /* we may already have captured audio before the last_recordable_frame (audible frame),
+ so deal with this.
+ */
+ if (last_recordable_frame > capture_start_frame) {
+ capture_captured = min (capture_captured, last_recordable_frame - capture_start_frame);
+ }
+ DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose("%1: prepare to stop sets last recordable frame to audible frame @ %2\n", _name, audible_frame));
+ break;
+ }
+
+}
+
+void
+DiskWriter::engage_record_enable ()
+{
+ g_atomic_int_set (&_record_enabled, 1);
+}
+
+void
+DiskWriter::disengage_record_enable ()
+{
+ g_atomic_int_set (&_record_enabled, 0);
+}
+
+void
+DiskWriter::engage_record_safe ()
+{
+ g_atomic_int_set (&_record_safe, 1);
+}
+
+void
+DiskWriter::disengage_record_safe ()
+{
+ g_atomic_int_set (&_record_safe, 0);
+}
+
+/** Get the start position (in session frames) of the nth capture in the current pass */
+ARDOUR::framepos_t
+DiskWriter::get_capture_start_frame (uint32_t n) const
+{
+ Glib::Threads::Mutex::Lock lm (capture_info_lock);
+
+ if (capture_info.size() > n) {
+ /* this is a completed capture */
+ return capture_info[n]->start;
+ } else {
+ /* this is the currently in-progress capture */
+ return capture_start_frame;
+ }
+}
+
+ARDOUR::framecnt_t
+DiskWriter::get_captured_frames (uint32_t n) const
+{
+ Glib::Threads::Mutex::Lock lm (capture_info_lock);
+
+ if (capture_info.size() > n) {
+ /* this is a completed capture */
+ return capture_info[n]->frames;
+ } else {
+ /* this is the currently in-progress capture */
+ return capture_captured;
+ }
+}
+
+void
+DiskWriter::set_input_latency (framecnt_t l)
+{
+ _input_latency = l;
+}
+
+void
+DiskWriter::set_capture_offset ()
+{
+ switch (_alignment_style) {
+ case ExistingMaterial:
+ _capture_offset = _input_latency;
+ break;
+
+ case CaptureTime:
+ default:
+ _capture_offset = 0;
+ break;
+ }
+
+ DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: using IO latency, capture offset set to %2 with style = %3\n", name(), _capture_offset, enum_2_string (_alignment_style)));
+}
+
+
+void
+DiskWriter::set_align_style (AlignStyle a, bool force)
+{
+ if (record_enabled() && _session.actively_recording()) {
+ return;
+ }
+
+ if ((a != _alignment_style) || force) {
+ _alignment_style = a;
+ set_capture_offset ();
+ AlignmentStyleChanged ();
+ }
+}
+
+void
+DiskWriter::set_align_choice (AlignChoice a, bool force)
+{
+ if (record_enabled() && _session.actively_recording()) {
+ return;
+ }
+
+ if ((a != _alignment_choice) || force) {
+ _alignment_choice = a;
+
+ switch (_alignment_choice) {
+ case Automatic:
+ set_align_style_from_io ();
+ break;
+ case UseExistingMaterial:
+ set_align_style (ExistingMaterial);
+ break;
+ case UseCaptureTime:
+ set_align_style (CaptureTime);
+ break;
+ }
+ }
+}
+
+int
+DiskWriter::set_state (const XMLNode& node, int version)
+{
+ XMLProperty const * prop;
+
+ if (DiskIOProcessor::set_state (node, version)) {
+ return -1;
+ }
+
+ if ((prop = node.property (X_("capture-alignment"))) != 0) {
+ set_align_choice (AlignChoice (string_2_enum (prop->value(), _alignment_choice)), true);
+ } else {
+ set_align_choice (Automatic, true);
+ }
+
+
+ if ((prop = node.property ("record-safe")) != 0) {
+ _record_safe = PBD::string_is_affirmative (prop->value()) ? 1 : 0;
+ }
+
+ return 0;
+}