merge AudioDiskstream playback code into DiskReader
authorPaul Davis <paul@linuxaudiosystems.com>
Mon, 6 Mar 2017 11:47:46 +0000 (12:47 +0100)
committerPaul Davis <paul@linuxaudiosystems.com>
Mon, 18 Sep 2017 15:40:52 +0000 (11:40 -0400)
libs/ardour/ardour/disk_io.h
libs/ardour/ardour/disk_reader.h
libs/ardour/audio_diskstream.cc
libs/ardour/disk_reader.cc
libs/ardour/wscript

index 4819b632935eab16711b8a1b59d971fe53c7ec74..87a761a7b718ef6db5182d95ecac7a46681b6dac 100644 (file)
@@ -30,131 +30,93 @@ namespace ARDOUR {
 
 class Session;
 class Route;
+class Location;
 
 class LIBARDOUR_API DiskIOProcessor : public Processor
 {
   public:
-       static const std::string state_node_name;
-
-       DiskIOProcessor(Session&, const std::string& name);
-
-       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 { return _configured_input; }
-       ChanCount output_streams() const { return _configured_output; }
-
-       virtual void realtime_handle_transport_stopped () {}
-       virtual void realtime_locate () {}
+       enum Flag {
+               Recordable  = 0x1,
+               Hidden      = 0x2,
+               Destructive = 0x4,
+               NonLayered   = 0x8
+       };
 
-       /* note: derived classes should implement state(), NOT get_state(), to allow
-          us to merge C++ inheritance and XML lack-of-inheritance reasonably
-          smoothly.
-        */
-
-       virtual XMLNode& state (bool full);
-       XMLNode& get_state (void);
-       int set_state (const XMLNode&, int version);
+       static const std::string state_node_name;
 
-       static framecnt_t disk_read_frames() { return disk_read_chunk_frames; }
-       static framecnt_t disk_write_frames() { return disk_write_chunk_frames; }
-       static void set_disk_read_chunk_frames (framecnt_t n) { disk_read_chunk_frames = n; }
-       static void set_disk_write_chunk_frames (framecnt_t n) { disk_write_chunk_frames = n; }
-       static framecnt_t default_disk_read_chunk_frames ();
-       static framecnt_t default_disk_write_chunk_frames ();
+       DiskIOProcessor (Session&, const std::string& name, Flag f);
 
        static void set_buffering_parameters (BufferingPreset bp);
 
-protected:
-       static framecnt_t disk_read_chunk_frames;
-       static framecnt_t disk_write_chunk_frames;
-
-       uint32_t i_am_the_modifier;
+       /** @return A number between 0 and 1, where 0 indicates that the playback buffer
+        *  is dry (ie the disk subsystem could not keep up) and 1 indicates that the
+        *  buffer is full.
+        */
+       virtual float playback_buffer_load() const = 0;
+       virtual float capture_buffer_load() const = 0;
 
-       Track*       _track;
-       ChanCount    _n_channels;
+       void set_flag (Flag f)   { _flags = Flag (_flags | f); }
+       void unset_flag (Flag f) { _flags = Flag (_flags & ~f); }
 
-       double       _visible_speed;
-       double       _actual_speed;
-       /* items needed for speed change logic */
-       bool         _buffer_reallocation_required;
-       bool         _seek_required;
-       bool         _slaved;
-       Location*     loop_location;
-       double        _speed;
-       double        _target_speed;
-       bool          in_set_state;
+       bool           hidden()      const { return _flags & Hidden; }
+       bool           recordable()  const { return _flags & Recordable; }
+       bool           non_layered()  const { return _flags & NonLayered; }
+       bool           reversed()    const { return _actual_speed < 0.0f; }
+       double         speed()       const { return _visible_speed; }
 
-       Glib::Threads::Mutex state_lock;
-       Flag _flags;
-};
+       ChanCount n_channels() { return _n_channels; }
 
-class LIBARDOUR_API DiskReader : public DiskIOProcessor
-{
-  public:
-       DiskReader (Session&, std::string const & name);
-       ~DiskReader ();
+       void non_realtime_set_speed ();
+       bool realtime_set_speed (double sp, bool global);
 
-  private:
-       boost::shared_ptr<Playlist> _playlist;
+       virtual void punch_in()  {}
+       virtual void punch_out() {}
 
-       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;
+       virtual float buffer_load() const = 0;
 
-       double        _speed;
-       double        _target_speed;
+       bool slaved() const      { return _slaved; }
+       void set_slaved(bool yn) { _slaved = yn; }
 
-       /** The next frame position that we should be reading from in our playlist */
-       framepos_t     file_frame;
-       framepos_t     playback_sample;
+       int set_loop (Location *loc);
 
-       PBD::ScopedConnectionList playlist_connections;
-       PBD::ScopedConnection ic_connection;
-};
+       PBD::Signal1<void,Location *> LoopSet;
+       PBD::Signal0<void>            SpeedChanged;
+       PBD::Signal0<void>            ReverseChanged;
 
-class LIBARDOUR_API DiskWriter : public DiskIOProcessor
-{
-  public:
-       DiskWriter (Session&, std::string const & name);
-       ~DiskWriter ();
+       int set_state (const XMLNode&, int version);
 
-  private:
-       std::vector<CaptureInfo*> capture_info;
-       mutable Glib::Threads::Mutex capture_info_lock;
+  protected:
+       friend class Auditioner;
+       virtual int  seek (framepos_t which_sample, bool complete_refill = false) = 0;
 
+  protected:
+       Flag         _flags;
+       uint32_t i_am_the_modifier;
+       ChanCount    _n_channels;
        double       _visible_speed;
        double       _actual_speed;
+       double       _speed;
+       double       _target_speed;
        /* items needed for speed change logic */
        bool         _buffer_reallocation_required;
        bool         _seek_required;
-
-       /** Start of currently running capture in session frames */
-       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;
+       bool         _slaved;
+       Location*     loop_location;
+       bool          in_set_state;
        framecnt_t    wrap_buffer_size;
        framecnt_t    speed_buffer_size;
 
-       std::string   _write_source_name;
+       Glib::Threads::Mutex state_lock;
 
-       PBD::ScopedConnection ic_connection;
-};
+       static bool 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);
 
+       virtual void allocate_temporary_buffers () = 0;
+};
 
 } // namespace ARDOUR
 
-#endif /* __ardour_processor_h__ */
+#endif /* __ardour_disk_io_h__ */
index 47dd1d87ccbab62df1faae363d6f27451ff1cb0c..75300d2b67ee9a55f9ccc9907fd286936d22d90f 100644 (file)
 #ifndef __ardour_disk_reader_h__
 #define __ardour_disk_reader_h__
 
+#include "pbd/ringbufferNPT.h"
+#include "pbd/rcu.h"
+
 #include "ardour/disk_io.h"
+#include "ardour/interpolation.h"
 
 namespace ARDOUR
 {
@@ -42,13 +46,14 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor
        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*/);
+       int set_block_size (pframes_t);
        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 ();
+       void non_realtime_locate (framepos_t);
+       int overwrite_existing_buffers ();
+       void set_pending_overwrite (bool yn);
 
        framecnt_t roll_delay() const { return _roll_delay; }
        void set_roll_delay (framecnt_t);
@@ -63,8 +68,8 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor
 
        virtual void playlist_modified ();
        virtual int use_playlist (boost::shared_ptr<Playlist>);
-       virtual int use_new_playlist () = 0;
-       virtual int use_copy_playlist () = 0;
+       virtual int use_new_playlist ();
+       virtual int use_copy_playlist ();
 
        PBD::Signal0<void>            PlaylistChanged;
        PBD::Signal0<void>            AlignmentStyleChanged;
@@ -73,29 +78,48 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor
 
        void move_processor_automation (boost::weak_ptr<Processor>, std::list<Evoral::RangeMove<framepos_t> > const &);
 
+       /* called by the Butler in a non-realtime context */
+
+       int do_refill () {
+               return _do_refill (_mixdown_buffer, _gain_buffer, 0);
+       }
+
        /** 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) {
+       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;
+       virtual int find_and_use_playlist (std::string const &);
 
-  protected:
-       virtual int do_refill () = 0;
+       // Working buffers for do_refill (butler thread)
+       static void allocate_working_buffers();
+       static void free_working_buffers();
+
+       void adjust_buffering ();
+
+       int can_internal_playback_seek (framecnt_t distance);
+       int seek (framepos_t frame, bool complete_refill = false);
 
+       int add_channel (uint32_t how_many);
+       int remove_channel (uint32_t how_many);
+
+       PBD::Signal0<void> Underrun;
+
+  protected:
        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;
 
@@ -113,12 +137,61 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor
        framecnt_t    speed_buffer_size;
        framepos_t     file_frame;
        framepos_t     playback_sample;
+       MonitorChoice   _monitoring_choice;
 
        PBD::ScopedConnectionList playlist_connections;
 
        virtual int _do_refill_with_alloc (bool partial_fill);
 
        static framecnt_t _chunk_frames;
+
+       /** Information about one of our channels */
+       struct ChannelInfo : public boost::noncopyable {
+
+               ChannelInfo (framecnt_t buffer_size,
+                            framecnt_t speed_buffer_size,
+                            framecnt_t wrap_buffer_size);
+               ~ChannelInfo ();
+
+               Sample     *wrap_buffer;
+               Sample     *speed_buffer;
+               Sample     *current_buffer;
+
+               /** A ringbuffer for data to be played back, written to in the
+                   butler thread, read from in the process thread.
+               */
+               PBD::RingBufferNPT<Sample> *buf;
+
+               Sample* scrub_buffer;
+               Sample* scrub_forward_buffer;
+               Sample* scrub_reverse_buffer;
+
+               PBD::RingBufferNPT<Sample>::rw_vector read_vector;
+
+               void resize (framecnt_t);
+       };
+
+       typedef std::vector<ChannelInfo*> ChannelList;
+       SerializedRCUManager<ChannelList> channels;
+
+       CubicInterpolation interpolation;
+
+       int read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
+                 framepos_t& start, framecnt_t cnt,
+                 int channel, bool reversed);
+
+       static Sample* _mixdown_buffer;
+       static gain_t* _gain_buffer;
+
+       int _do_refill (Sample *mixdown_buffer, float *gain_buffer, framecnt_t fill_level);
+
+       int add_channel_to (boost::shared_ptr<ChannelList>, uint32_t how_many);
+       int remove_channel_from (boost::shared_ptr<ChannelList>, uint32_t how_many);
+
+       int internal_playback_seek (framecnt_t distance);
+       frameoffset_t calculate_playback_distance (pframes_t);
+
+       void allocate_temporary_buffers();
 };
 
 } // namespace
index 3e50b175737bece1fc4a3d6f204c2c31b108714b..7ef5cf1c11763b1e516a42094c9bd1781094653a 100644 (file)
@@ -2101,8 +2101,9 @@ AudioDiskstream::set_block_size (pframes_t /*nframes*/)
                boost::shared_ptr<ChannelList> c = channels.reader();
 
                for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
-                       if ((*chan)->speed_buffer)
+                       if ((*chan)->speed_buffer) {
                                delete [] (*chan)->speed_buffer;
+                       }
                        (*chan)->speed_buffer = new Sample[speed_buffer_size];
                }
        }
index 951a1aa632e543e7e3370fa33975a706da663d58..822142673b3bf9add46c4520b8c29e6f0cfc55fb 100644 (file)
 
 #include "pbd/i18n.h"
 
+#include "ardour/audioplaylist.h"
+#include "ardour/audio_buffer.h"
+#include "ardour/butler.h"
 #include "ardour/debug.h"
 #include "ardour/disk_reader.h"
 #include "ardour/playlist.h"
+#include "ardour/playlist_factory.h"
 #include "ardour/session.h"
+#include "ardour/session_playlists.h"
 
 using namespace ARDOUR;
 using namespace PBD;
@@ -39,6 +44,8 @@ DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
         , overwrite_queued (false)
         , file_frame (0)
         , playback_sample (0)
+       , _monitoring_choice (MonitorDisk)
+       , channels (new ChannelList)
 {
 }
 
@@ -49,6 +56,40 @@ DiskReader::~DiskReader ()
        if (_playlist) {
                _playlist->release ();
        }
+
+       {
+               RCUWriter<ChannelList> writer (channels);
+               boost::shared_ptr<ChannelList> c = writer.get_copy();
+
+               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+                       delete *chan;
+               }
+
+               c->clear();
+       }
+
+       channels.flush ();
+}
+
+void
+DiskReader::allocate_working_buffers()
+{
+       /* with varifill buffer refilling, we compute the read size in bytes (to optimize
+          for disk i/o bandwidth) and then convert back into samples. These buffers
+          need to reflect the maximum size we could use, which is 4MB reads, or 2M samples
+          using 16 bit samples.
+       */
+       _mixdown_buffer       = new Sample[2*1048576];
+       _gain_buffer          = new gain_t[2*1048576];
+}
+
+void
+DiskReader::free_working_buffers()
+{
+       delete [] _mixdown_buffer;
+       delete [] _gain_buffer;
+       _mixdown_buffer       = 0;
+       _gain_buffer          = 0;
 }
 
 framecnt_t
@@ -69,6 +110,209 @@ DiskReader::set_name (string const & str)
        return true;
 }
 
+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;
+}
+
+/* Processor interface */
+
+bool
+DiskReader::configure_io (ChanCount in, ChanCount out)
+{
+       return true;
+}
+
+bool
+DiskReader::can_support_io_configuration (const ChanCount& in, ChanCount& out)
+{
+       return true;
+}
+
+void
+DiskReader::realtime_handle_transport_stopped ()
+{
+}
+
+void
+DiskReader::realtime_locate ()
+{
+}
+
+int
+DiskReader::add_channel_to (boost::shared_ptr<ChannelList> c, uint32_t how_many)
+{
+       while (how_many--) {
+               c->push_back (new ChannelInfo(
+                                     _session.butler()->audio_diskstream_playback_buffer_size(),
+                                     speed_buffer_size, wrap_buffer_size));
+               interpolation.add_channel_to (
+                       _session.butler()->audio_diskstream_playback_buffer_size(),
+                       speed_buffer_size);
+       }
+
+       _n_channels.set (DataType::AUDIO, c->size());
+
+       return 0;
+}
+
+int
+DiskReader::add_channel (uint32_t how_many)
+{
+       RCUWriter<ChannelList> writer (channels);
+       boost::shared_ptr<ChannelList> c = writer.get_copy();
+
+       return add_channel_to (c, how_many);
+}
+
+int
+DiskReader::remove_channel_from (boost::shared_ptr<ChannelList> c, uint32_t how_many)
+{
+       while (how_many-- && !c->empty()) {
+               delete c->back();
+               c->pop_back();
+               interpolation.remove_channel_from ();
+       }
+
+       _n_channels.set(DataType::AUDIO, c->size());
+
+       return 0;
+}
+
+int
+DiskReader::remove_channel (uint32_t how_many)
+{
+       RCUWriter<ChannelList> writer (channels);
+       boost::shared_ptr<ChannelList> c = writer.get_copy();
+
+       return remove_channel_from (c, how_many);
+}
+
+float
+DiskReader::buffer_load () const
+{
+       boost::shared_ptr<ChannelList> c = channels.reader();
+
+       if (c->empty ()) {
+               return 1.0;
+       }
+
+       return (float) ((double) c->front()->buf->read_space()/
+                          (double) c->front()->buf->bufsize());
+}
+
+void
+DiskReader::adjust_buffering ()
+{
+       boost::shared_ptr<ChannelList> c = channels.reader();
+
+       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+               (*chan)->resize (_session.butler()->audio_diskstream_playback_buffer_size());
+       }
+}
+
+DiskReader::ChannelInfo::ChannelInfo (framecnt_t bufsize, framecnt_t speed_size, framecnt_t wrap_size)
+{
+       current_buffer = 0;
+
+       speed_buffer = new Sample[speed_size];
+       wrap_buffer = new Sample[wrap_size];
+
+       buf = new RingBufferNPT<Sample> (bufsize);
+
+       /* touch the ringbuffer buffer, which will cause
+          them to be mapped into locked physical RAM if
+          we're running with mlockall(). this doesn't do
+          much if we're not.
+       */
+
+       memset (buf->buffer(), 0, sizeof (Sample) * buf->bufsize());
+}
+
+void
+DiskReader::ChannelInfo::resize (framecnt_t bufsize)
+{
+       delete buf;
+       buf = new RingBufferNPT<Sample> (bufsize);
+       memset (buf->buffer(), 0, sizeof (Sample) * buf->bufsize());
+}
+
+DiskReader::ChannelInfo::~ChannelInfo ()
+{
+       delete [] speed_buffer;
+       speed_buffer = 0;
+
+       delete [] wrap_buffer;
+       wrap_buffer = 0;
+
+       delete buf;
+       buf = 0;
+}
+
+int
+DiskReader::set_block_size (pframes_t /*nframes*/)
+{
+       if (_session.get_block_size() > speed_buffer_size) {
+               speed_buffer_size = _session.get_block_size();
+               boost::shared_ptr<ChannelList> c = channels.reader();
+
+               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+                       delete [] (*chan)->speed_buffer;
+                       (*chan)->speed_buffer = new Sample[speed_buffer_size];
+               }
+       }
+       allocate_temporary_buffers ();
+       return 0;
+}
+
+void
+DiskReader::allocate_temporary_buffers ()
+{
+       /* make sure the wrap buffer is at least large enough to deal
+          with the speeds up to 1.2, to allow for micro-variation
+          when slaving to MTC, Timecode etc.
+       */
+
+       double const sp = max (fabs (_actual_speed), 1.2);
+       framecnt_t required_wrap_size = (framecnt_t) ceil (_session.get_block_size() * sp) + 2;
+
+       if (required_wrap_size > wrap_buffer_size) {
+
+               boost::shared_ptr<ChannelList> c = channels.reader();
+
+               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+                       if ((*chan)->wrap_buffer) {
+                               delete [] (*chan)->wrap_buffer;
+                       }
+                       (*chan)->wrap_buffer = new Sample[required_wrap_size];
+               }
+
+               wrap_buffer_size = required_wrap_size;
+       }
+}
+
+
 void
 DiskReader::playlist_changed (const PropertyChange&)
 {
@@ -150,28 +394,832 @@ DiskReader::use_playlist (boost::shared_ptr<Playlist> playlist)
        return 0;
 }
 
-void
-DiskReader::set_roll_delay (ARDOUR::framecnt_t nframes)
+int
+DiskReader::find_and_use_playlist (const string& name)
 {
-       _roll_delay = nframes;
+       boost::shared_ptr<AudioPlaylist> playlist;
+
+       if ((playlist = boost::dynamic_pointer_cast<AudioPlaylist> (_session.playlists->by_name (name))) == 0) {
+               playlist = boost::dynamic_pointer_cast<AudioPlaylist> (PlaylistFactory::create (DataType::AUDIO, _session, name));
+       }
+
+       if (!playlist) {
+               error << string_compose(_("DiskReader: Playlist \"%1\" isn't an audio playlist"), name) << endmsg;
+               return -1;
+       }
+
+       return use_playlist (playlist);
 }
 
 int
-DiskReader::set_state (const XMLNode& node, int version)
+DiskReader::use_new_playlist ()
 {
-       XMLProperty const * prop;
+       string newname;
+       boost::shared_ptr<AudioPlaylist> playlist;
 
-       if (DiskIOProcessor::set_state (node, version)) {
+       if (_playlist) {
+               newname = Playlist::bump_name (_playlist->name(), _session);
+       } else {
+               newname = Playlist::bump_name (_name, _session);
+       }
+
+       if ((playlist = boost::dynamic_pointer_cast<AudioPlaylist> (PlaylistFactory::create (DataType::AUDIO, _session, newname, hidden()))) != 0) {
+
+               return use_playlist (playlist);
+
+       } else {
                return -1;
        }
+}
 
-       if ((prop = node.property ("playlist")) == 0) {
+int
+DiskReader::use_copy_playlist ()
+{
+       assert(audio_playlist());
+
+       if (_playlist == 0) {
+               error << string_compose(_("DiskReader %1: there is no existing playlist to make a copy of!"), _name) << endmsg;
                return -1;
        }
 
-       if (find_and_use_playlist (prop->value())) {
+       string newname;
+       boost::shared_ptr<AudioPlaylist> playlist;
+
+       newname = Playlist::bump_name (_playlist->name(), _session);
+
+       if ((playlist = boost::dynamic_pointer_cast<AudioPlaylist>(PlaylistFactory::create (audio_playlist(), newname))) != 0) {
+               playlist->reset_shares();
+               return use_playlist (playlist);
+       } else {
                return -1;
        }
+}
+
+
+/** Do some record stuff [not described in this comment!]
+ *
+ *  Also:
+ *    - Setup playback_distance with the nframes, or nframes adjusted
+ *      for current varispeed, if appropriate.
+ *    - Setup current_buffer in each ChannelInfo to point to data
+ *      that someone can read playback_distance worth of data from.
+ */
+void
+DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame,
+                 double speed, pframes_t nframes, bool result_required)
+/*
+       int
+       DiskReader::process (BufferSet& bufs, framepos_t transport_frame, pframes_t nframes,
+       framecnt_t& playback_distance, bool need_disk_signal)
+*/
+{
+       uint32_t n;
+       boost::shared_ptr<ChannelList> c = channels.reader();
+       ChannelList::iterator chan;
+       framecnt_t playback_distance = 0;
+
+       Glib::Threads::Mutex::Lock sm (state_lock, Glib::Threads::TRY_LOCK);
+
+       if (!sm.locked()) {
+               return;
+       }
+
+       for (chan = c->begin(); chan != c->end(); ++chan) {
+               (*chan)->current_buffer = 0;
+       }
+
+       if (result_required || _monitoring_choice == MonitorDisk || _monitoring_choice == MonitorCue) {
+
+               /* we're doing playback */
+
+               framecnt_t necessary_samples;
+
+               if (_actual_speed != 1.0) {
+                       necessary_samples = (framecnt_t) ceil ((nframes * fabs (_actual_speed))) + 2;
+               } else {
+                       necessary_samples = nframes;
+               }
+
+               for (chan = c->begin(); chan != c->end(); ++chan) {
+                       (*chan)->buf->get_read_vector (&(*chan)->read_vector);
+               }
+
+               n = 0;
+
+               /* Setup current_buffer in each ChannelInfo to point to data that someone
+                  can read necessary_samples (== nframes at a transport speed of 1) worth of data
+                  from right now.
+               */
+
+               for (chan = c->begin(); chan != c->end(); ++chan, ++n) {
+
+                       ChannelInfo* chaninfo (*chan);
+
+                       if (necessary_samples <= (framecnt_t) chaninfo->read_vector.len[0]) {
+                               /* There are enough samples in the first part of the ringbuffer */
+                               chaninfo->current_buffer = chaninfo->read_vector.buf[0];
+
+                       } else {
+                               framecnt_t total = chaninfo->read_vector.len[0] + chaninfo->read_vector.len[1];
+
+                               if (necessary_samples > total) {
+                                       cerr << _name << " Need " << necessary_samples << " total = " << total << endl;
+                                       cerr << "underrun for " << _name << endl;
+                                        DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 underrun in %2, total space = %3\n",
+                                                                                    DEBUG_THREAD_SELF, name(), total));
+                                       Underrun ();
+                                       return;
+
+                               } else {
+
+                                       /* We have enough samples, but not in one lump.  Coalesce the two parts
+                                          into one in wrap_buffer in our ChannelInfo, and specify that
+                                          as our current_buffer.
+                                       */
+
+                                       assert(wrap_buffer_size >= necessary_samples);
+
+                                       /* Copy buf[0] from buf */
+                                       memcpy ((char *) chaninfo->wrap_buffer,
+                                                       chaninfo->read_vector.buf[0],
+                                                       chaninfo->read_vector.len[0] * sizeof (Sample));
+
+                                       /* Copy buf[1] from buf */
+                                       memcpy (chaninfo->wrap_buffer + chaninfo->read_vector.len[0],
+                                                       chaninfo->read_vector.buf[1],
+                                                       (necessary_samples - chaninfo->read_vector.len[0])
+                                                                       * sizeof (Sample));
+
+                                       chaninfo->current_buffer = chaninfo->wrap_buffer;
+                               }
+                       }
+               }
+
+               if (_actual_speed != 1.0f && _actual_speed != -1.0f) {
+
+                       interpolation.set_speed (_target_speed);
+
+                       int channel = 0;
+                       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) {
+                               ChannelInfo* chaninfo (*chan);
+
+                               playback_distance = interpolation.interpolate (
+                                       channel, nframes, chaninfo->current_buffer, chaninfo->speed_buffer);
+
+                               chaninfo->current_buffer = chaninfo->speed_buffer;
+                       }
+
+               } else {
+                       playback_distance = nframes;
+               }
+
+               _speed = _target_speed;
+       }
+
+       if (result_required || _monitoring_choice == MonitorDisk || _monitoring_choice == MonitorCue) {
+
+               /* copy data over to buffer set */
+
+               size_t n_buffers = bufs.count().n_audio();
+               size_t n_chans = c->size();
+               gain_t scaling = 1.0f;
+
+               if (n_chans > n_buffers) {
+                       scaling = ((float) n_buffers)/n_chans;
+               }
+
+               for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
+
+                       AudioBuffer& buf (bufs.get_audio (n%n_buffers));
+                       ChannelInfo* chaninfo (*chan);
+
+                       if (n < n_chans) {
+                               if (scaling != 1.0f) {
+                                       buf.read_from_with_gain (chaninfo->current_buffer, nframes, scaling);
+                               } else {
+                                       buf.read_from (chaninfo->current_buffer, nframes);
+                               }
+                       } else {
+                               if (scaling != 1.0f) {
+                                       buf.accumulate_with_gain_from (chaninfo->current_buffer, nframes, scaling);
+                               } else {
+                                       buf.accumulate_from (chaninfo->current_buffer, nframes);
+                               }
+                       }
+               }
+
+               /* leave the MIDI count alone */
+               ChanCount cnt (DataType::AUDIO, n_chans);
+               cnt.set (DataType::MIDI, bufs.count().n_midi());
+               bufs.set_count (cnt);
+
+               /* extra buffers will already be silent, so leave them alone */
+       }
+
+       bool need_butler = false;
+
+       if (_actual_speed < 0.0) {
+               playback_sample -= playback_distance;
+       } else {
+               playback_sample += playback_distance;
+       }
+
+       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+               (*chan)->buf->increment_read_ptr (playback_distance);
+       }
+
+       if (!c->empty()) {
+               if (_slaved) {
+                       need_butler = c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2;
+               } else {
+                       need_butler = (framecnt_t) c->front()->buf->write_space() >= _chunk_frames;
+               }
+       }
+
+       //return need_butler;
+}
+
+frameoffset_t
+DiskReader::calculate_playback_distance (pframes_t nframes)
+{
+       frameoffset_t playback_distance = nframes;
+
+       if (_actual_speed != 1.0f && _actual_speed != -1.0f) {
+               interpolation.set_speed (_target_speed);
+               boost::shared_ptr<ChannelList> c = channels.reader();
+               int channel = 0;
+               for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) {
+                       playback_distance = interpolation.interpolate (channel, nframes, NULL, NULL);
+               }
+       } else {
+               playback_distance = nframes;
+       }
+
+       if (_actual_speed < 0.0) {
+               return -playback_distance;
+       } else {
+               return playback_distance;
+       }
+}
+
+void
+DiskReader::set_pending_overwrite (bool yn)
+{
+       /* called from audio thread, so we can use the read ptr and playback sample as we wish */
+
+       _pending_overwrite = yn;
+
+       overwrite_frame = playback_sample;
+
+       boost::shared_ptr<ChannelList> c = channels.reader ();
+       if (!c->empty ()) {
+               overwrite_offset = c->front()->buf->get_read_ptr();
+       }
+}
+
+int
+DiskReader::overwrite_existing_buffers ()
+{
+       boost::shared_ptr<ChannelList> c = channels.reader();
+       if (c->empty ()) {
+               _pending_overwrite = false;
+               return 0;
+       }
+
+       Sample* mixdown_buffer;
+       float* gain_buffer;
+       int ret = -1;
+       bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
+
+       overwrite_queued = false;
+
+       /* assume all are the same size */
+       framecnt_t size = c->front()->buf->bufsize();
+
+       mixdown_buffer = new Sample[size];
+       gain_buffer = new float[size];
+
+       /* reduce size so that we can fill the buffer correctly (ringbuffers
+          can only handle size-1, otherwise they appear to be empty)
+       */
+       size--;
+
+       uint32_t n=0;
+       framepos_t start;
+
+       for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) {
+
+               start = overwrite_frame;
+               framecnt_t cnt = size;
+
+               /* to fill the buffer without resetting the playback sample, we need to
+                  do it one or two chunks (normally two).
+
+                  |----------------------------------------------------------------------|
+
+                                      ^
+                                      overwrite_offset
+                   |<- second chunk->||<----------------- first chunk ------------------>|
+
+               */
+
+               framecnt_t to_read = size - overwrite_offset;
+
+               if (read ((*chan)->buf->buffer() + overwrite_offset, mixdown_buffer, gain_buffer, start, to_read, n, reversed)) {
+                       error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at frame %3"),
+                                               id(), size, playback_sample) << endmsg;
+                       goto out;
+               }
+
+               if (cnt > to_read) {
+
+                       cnt -= to_read;
+
+                       if (read ((*chan)->buf->buffer(), mixdown_buffer, gain_buffer, start, cnt, n, reversed)) {
+                               error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at frame %3"),
+                                                       id(), size, playback_sample) << endmsg;
+                               goto out;
+                       }
+               }
+       }
+
+       ret = 0;
+
+  out:
+       _pending_overwrite = false;
+       delete [] gain_buffer;
+       delete [] mixdown_buffer;
+       return ret;
+}
+
+void
+DiskReader::non_realtime_locate (framepos_t location)
+{
+       /* now refill channel buffers */
+
+       if (speed() != 1.0f || speed() != -1.0f) {
+               seek ((framepos_t) (location * (double) speed()), true);
+       } else {
+               seek (location, true);
+       }
+}
+
+int
+DiskReader::seek (framepos_t frame, bool complete_refill)
+{
+       uint32_t n;
+       int ret = -1;
+       ChannelList::iterator chan;
+       boost::shared_ptr<ChannelList> c = channels.reader();
+
+       Glib::Threads::Mutex::Lock lm (state_lock);
+
+       for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
+               (*chan)->buf->reset ();
+       }
+
+       playback_sample = frame;
+       file_frame = frame;
+
+       if (complete_refill) {
+               /* call _do_refill() to refill the entire buffer, using
+                  the largest reads possible.
+               */
+               while ((ret = do_refill_with_alloc (false)) > 0) ;
+       } else {
+               /* call _do_refill() to refill just one chunk, and then
+                  return.
+               */
+               ret = do_refill_with_alloc (true);
+       }
+
+       return ret;
+}
+
+int
+DiskReader::can_internal_playback_seek (framecnt_t distance)
+{
+       ChannelList::iterator chan;
+       boost::shared_ptr<ChannelList> c = channels.reader();
+
+       for (chan = c->begin(); chan != c->end(); ++chan) {
+               if ((*chan)->buf->read_space() < (size_t) distance) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+int
+DiskReader::internal_playback_seek (framecnt_t distance)
+{
+       ChannelList::iterator chan;
+       boost::shared_ptr<ChannelList> c = channels.reader();
+
+       for (chan = c->begin(); chan != c->end(); ++chan) {
+               (*chan)->buf->increment_read_ptr (::llabs(distance));
+       }
+
+       playback_sample += distance;
+
+       return 0;
+}
+
+static
+void swap_by_ptr (Sample *first, Sample *last)
+{
+       while (first < last) {
+               Sample tmp = *first;
+               *first++ = *last;
+               *last-- = tmp;
+       }
+}
+
+/** Read some data for 1 channel from our playlist into a buffer.
+ *  @param buf Buffer to write to.
+ *  @param start Session frame to start reading from; updated to where we end up
+ *         after the read.
+ *  @param cnt Count of samples to read.
+ *  @param reversed true if we are running backwards, otherwise false.
+ */
+int
+DiskReader::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer,
+                  framepos_t& start, framecnt_t cnt,
+                  int channel, bool reversed)
+{
+       framecnt_t this_read = 0;
+       bool reloop = false;
+       framepos_t loop_end = 0;
+       framepos_t loop_start = 0;
+       framecnt_t offset = 0;
+       Location *loc = 0;
+
+       /* XXX we don't currently play loops in reverse. not sure why */
+
+       if (!reversed) {
+
+               framecnt_t loop_length = 0;
+
+               /* Make the use of a Location atomic for this read operation.
+
+                  Note: Locations don't get deleted, so all we care about
+                  when I say "atomic" is that we are always pointing to
+                  the same one and using a start/length values obtained
+                  just once.
+               */
+
+               if ((loc = loop_location) != 0) {
+                       loop_start = loc->start();
+                       loop_end = loc->end();
+                       loop_length = loop_end - loop_start;
+               }
+
+               /* if we are looping, ensure that the first frame we read is at the correct
+                  position within the loop.
+               */
+
+               if (loc && start >= loop_end) {
+                       start = loop_start + ((start - loop_start) % loop_length);
+               }
+
+       }
+
+       if (reversed) {
+               start -= cnt;
+       }
+
+       /* We need this while loop in case we hit a loop boundary, in which case our read from
+          the playlist must be split into more than one section.
+       */
+
+       while (cnt) {
+
+               /* take any loop into account. we can't read past the end of the loop. */
+
+               if (loc && (loop_end - start < cnt)) {
+                       this_read = loop_end - start;
+                       reloop = true;
+               } else {
+                       reloop = false;
+                       this_read = cnt;
+               }
+
+               if (this_read == 0) {
+                       break;
+               }
+
+               this_read = min(cnt,this_read);
+
+               if (audio_playlist()->read (buf+offset, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) {
+                       error << string_compose(_("DiskReader %1: cannot read %2 from playlist at frame %3"), id(), this_read,
+                                        start) << endmsg;
+                       return -1;
+               }
+
+               if (reversed) {
+
+                       swap_by_ptr (buf, buf + this_read - 1);
+
+               } else {
+
+                       /* if we read to the end of the loop, go back to the beginning */
+
+                       if (reloop) {
+                               start = loop_start;
+                       } else {
+                               start += this_read;
+                       }
+               }
+
+               cnt -= this_read;
+               offset += this_read;
+       }
 
        return 0;
 }
+
+int
+DiskReader::_do_refill_with_alloc (bool partial_fill)
+{
+       /* We limit disk reads to at most 4MB chunks, which with floating point
+          samples would be 1M samples. But we might use 16 or 14 bit samples,
+          in which case 4MB is more samples than that. Therefore size this for
+          the smallest sample value .. 4MB = 2M samples (16 bit).
+       */
+
+       Sample* mix_buf  = new Sample[2*1048576];
+       float*  gain_buf = new float[2*1048576];
+
+       int ret = _do_refill (mix_buf, gain_buf, (partial_fill ? _chunk_frames : 0));
+
+       delete [] mix_buf;
+       delete [] gain_buf;
+
+       return ret;
+}
+
+/** Get some more data from disk and put it in our channels' bufs,
+ *  if there is suitable space in them.
+ *
+ * If fill_level is non-zero, then we will refill the buffer so that there is
+ * still at least fill_level samples of space left to be filled. This is used
+ * after locates so that we do not need to wait to fill the entire buffer.
+ *
+ */
+
+int
+DiskReader::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t fill_level)
+{
+       if (_session.state_of_the_state() & Session::Loading) {
+               return 0;
+       }
+
+       int32_t ret = 0;
+       framecnt_t to_read;
+       RingBufferNPT<Sample>::rw_vector vector;
+       bool const reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
+       framecnt_t total_space;
+       framecnt_t zero_fill;
+       uint32_t chan_n;
+       ChannelList::iterator i;
+       boost::shared_ptr<ChannelList> c = channels.reader();
+       framecnt_t ts;
+
+       /* do not read from disk while session is marked as Loading, to avoid
+          useless redundant I/O.
+       */
+
+       if (c->empty()) {
+               return 0;
+       }
+
+       assert(mixdown_buffer);
+       assert(gain_buffer);
+
+       vector.buf[0] = 0;
+       vector.len[0] = 0;
+       vector.buf[1] = 0;
+       vector.len[1] = 0;
+
+       c->front()->buf->get_write_vector (&vector);
+
+       if ((total_space = vector.len[0] + vector.len[1]) == 0) {
+               /* nowhere to write to */
+               return 0;
+       }
+
+       if (fill_level) {
+               if (fill_level < total_space) {
+                       total_space -= fill_level;
+               } else {
+                       /* we can't do anything with it */
+                       fill_level = 0;
+               }
+       }
+
+       /* if we're running close to normal speed and there isn't enough
+          space to do disk_read_chunk_frames of I/O, then don't bother.
+
+          at higher speeds, just do it because the sync between butler
+          and audio thread may not be good enough.
+
+          Note: it is a design assumption that disk_read_chunk_frames is smaller
+          than the playback buffer size, so this check should never trip when
+          the playback buffer is empty.
+       */
+
+       if ((total_space < _chunk_frames) && fabs (_actual_speed) < 2.0f) {
+               return 0;
+       }
+
+       /* when slaved, don't try to get too close to the read pointer. this
+          leaves space for the buffer reversal to have something useful to
+          work with.
+       */
+
+       if (_slaved && total_space < (framecnt_t) (c->front()->buf->bufsize() / 2)) {
+               return 0;
+       }
+
+       if (reversed) {
+
+               if (file_frame == 0) {
+
+                       /* at start: nothing to do but fill with silence */
+
+                       for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
+
+                               ChannelInfo* chan (*i);
+                               chan->buf->get_write_vector (&vector);
+                               memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
+                               if (vector.len[1]) {
+                                       memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
+                               }
+                               chan->buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+                       }
+                       return 0;
+               }
+
+               if (file_frame < total_space) {
+
+                       /* too close to the start: read what we can,
+                          and then zero fill the rest
+                       */
+
+                       zero_fill = total_space - file_frame;
+                       total_space = file_frame;
+
+               } else {
+
+                       zero_fill = 0;
+               }
+
+       } else {
+
+               if (file_frame == max_framepos) {
+
+                       /* at end: nothing to do but fill with silence */
+
+                       for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
+
+                               ChannelInfo* chan (*i);
+                               chan->buf->get_write_vector (&vector);
+                               memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]);
+                               if (vector.len[1]) {
+                                       memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]);
+                               }
+                               chan->buf->increment_write_ptr (vector.len[0] + vector.len[1]);
+                       }
+                       return 0;
+               }
+
+               if (file_frame > max_framepos - total_space) {
+
+                       /* to close to the end: read what we can, and zero fill the rest */
+
+                       zero_fill = total_space - (max_framepos - file_frame);
+                       total_space = max_framepos - file_frame;
+
+               } else {
+                       zero_fill = 0;
+               }
+       }
+
+       framepos_t file_frame_tmp = 0;
+
+       /* total_space is in samples. We want to optimize read sizes in various sizes using bytes */
+
+       const size_t bits_per_sample = format_data_width (_session.config.get_native_file_data_format());
+       size_t total_bytes = total_space * bits_per_sample / 8;
+
+       /* chunk size range is 256kB to 4MB. Bigger is faster in terms of MB/sec, but bigger chunk size always takes longer
+        */
+       size_t byte_size_for_read = max ((size_t) (256 * 1024), min ((size_t) (4 * 1048576), total_bytes));
+
+       /* find nearest (lower) multiple of 16384 */
+
+       byte_size_for_read = (byte_size_for_read / 16384) * 16384;
+
+       /* now back to samples */
+
+       framecnt_t samples_to_read = byte_size_for_read / (bits_per_sample / 8);
+
+       //cerr << name() << " will read " << byte_size_for_read << " out of total bytes " << total_bytes << " in buffer of "
+       // << c->front()->buf->bufsize() * bits_per_sample / 8 << " bps = " << bits_per_sample << endl;
+       // cerr << name () << " read samples = " << samples_to_read << " out of total space " << total_space << " in buffer of " << c->front()->buf->bufsize() << " samples\n";
+
+       // uint64_t before = g_get_monotonic_time ();
+       // uint64_t elapsed;
+
+       for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) {
+
+               ChannelInfo* chan (*i);
+               Sample* buf1;
+               Sample* buf2;
+               framecnt_t len1, len2;
+
+               chan->buf->get_write_vector (&vector);
+
+               if ((framecnt_t) vector.len[0] > samples_to_read) {
+
+                       /* we're not going to fill the first chunk, so certainly do not bother with the
+                          other part. it won't be connected with the part we do fill, as in:
+
+                          .... => writable space
+                          ++++ => readable space
+                          ^^^^ => 1 x disk_read_chunk_frames that would be filled
+
+                          |......|+++++++++++++|...............................|
+                          buf1                buf0
+                                               ^^^^^^^^^^^^^^^
+
+
+                          So, just pretend that the buf1 part isn't there.
+
+                       */
+
+                       vector.buf[1] = 0;
+                       vector.len[1] = 0;
+
+               }
+
+               ts = total_space;
+               file_frame_tmp = file_frame;
+
+               buf1 = vector.buf[0];
+               len1 = vector.len[0];
+               buf2 = vector.buf[1];
+               len2 = vector.len[1];
+
+               to_read = min (ts, len1);
+               to_read = min (to_read, (framecnt_t) samples_to_read);
+
+               assert (to_read >= 0);
+
+               if (to_read) {
+
+                       if (read (buf1, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan_n, reversed)) {
+                               ret = -1;
+                               goto out;
+                       }
+
+                       chan->buf->increment_write_ptr (to_read);
+                       ts -= to_read;
+               }
+
+               to_read = min (ts, len2);
+
+               if (to_read) {
+
+                       /* we read all of vector.len[0], but it wasn't the
+                          entire samples_to_read of data, so read some or
+                          all of vector.len[1] as well.
+                       */
+
+                       if (read (buf2, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan_n, reversed)) {
+                               ret = -1;
+                               goto out;
+                       }
+
+                       chan->buf->increment_write_ptr (to_read);
+               }
+
+               if (zero_fill) {
+                       /* XXX: do something */
+               }
+
+       }
+
+       // elapsed = g_get_monotonic_time () - before;
+       // cerr << "\tbandwidth = " << (byte_size_for_read / 1048576.0) / (elapsed/1000000.0) << "MB/sec\n";
+
+       file_frame = file_frame_tmp;
+       assert (file_frame >= 0);
+
+       ret = ((total_space - samples_to_read) > _chunk_frames);
+
+       c->front()->buf->get_write_vector (&vector);
+
+  out:
+       return ret;
+}
index ee11a3311a3b0c7d2891784d09097f03f38e918e..721a22b2a5f1831bc1ee7f173b2b8f52ba04bd3c 100644 (file)
@@ -67,6 +67,9 @@ libardour_sources = [
         'delayline.cc',
         'delivery.cc',
         'directory_names.cc',
+        'disk_io.cc',
+        'disk_reader.cc',
+        'disk_writer.cc',
         'diskstream.cc',
         'dsp_filter.cc',
         'ebur128_analysis.cc',