diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2017-03-06 12:47:46 +0100 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2017-09-18 11:40:52 -0400 |
commit | c05cfe332868c1aca477aedcecdfb78948e6d559 (patch) | |
tree | 7e758eced492acb32dd40004c41dfb48909afa55 | |
parent | 46366541b1aea2c6441b4273734307ab4c55ce13 (diff) |
merge AudioDiskstream playback code into DiskReader
-rw-r--r-- | libs/ardour/ardour/disk_io.h | 150 | ||||
-rw-r--r-- | libs/ardour/ardour/disk_reader.h | 91 | ||||
-rw-r--r-- | libs/ardour/audio_diskstream.cc | 3 | ||||
-rw-r--r-- | libs/ardour/disk_reader.cc | 1064 | ||||
-rw-r--r-- | libs/ardour/wscript | 3 |
5 files changed, 1199 insertions, 112 deletions
diff --git a/libs/ardour/ardour/disk_io.h b/libs/ardour/ardour/disk_io.h index 4819b63293..87a761a7b7 100644 --- a/libs/ardour/ardour/disk_io.h +++ b/libs/ardour/ardour/disk_io.h @@ -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__ */ diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h index 47dd1d87cc..75300d2b67 100644 --- a/libs/ardour/ardour/disk_reader.h +++ b/libs/ardour/ardour/disk_reader.h @@ -20,7 +20,11 @@ #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 diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index 3e50b17573..7ef5cf1c11 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -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]; } } diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc index 951a1aa632..822142673b 100644 --- a/libs/ardour/disk_reader.cc +++ b/libs/ardour/disk_reader.cc @@ -19,10 +19,15 @@ #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 @@ -70,6 +111,209 @@ DiskReader::set_name (string const & str) } 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&) { playlist_modified (); @@ -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; +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index ee11a3311a..721a22b2a5 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -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', |