diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2017-03-16 17:26:53 +0100 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2017-09-18 11:40:52 -0400 |
commit | 94604c6979be790a072c9d76566250a3aadf6e79 (patch) | |
tree | cd0b071ff702d7b9ebafd194f1521fec5825a5ad /libs | |
parent | f8ef82fceb6dfa8900f29871f706cced7eda55f5 (diff) |
merge almost all audio & midi diskstream code, redistribute between DiskIOProcessor, DiskReader,DiskWriter; compile and link
Diffstat (limited to 'libs')
-rw-r--r-- | libs/ardour/ardour/disk_io.h | 76 | ||||
-rw-r--r-- | libs/ardour/ardour/disk_reader.h | 39 | ||||
-rw-r--r-- | libs/ardour/ardour/disk_writer.h | 74 | ||||
-rw-r--r-- | libs/ardour/disk_io.cc | 260 | ||||
-rw-r--r-- | libs/ardour/disk_reader.cc | 506 | ||||
-rw-r--r-- | libs/ardour/disk_writer.cc | 390 |
6 files changed, 852 insertions, 493 deletions
diff --git a/libs/ardour/ardour/disk_io.h b/libs/ardour/ardour/disk_io.h index d1fa1e7d66..dd775cbe84 100644 --- a/libs/ardour/ardour/disk_io.h +++ b/libs/ardour/ardour/disk_io.h @@ -32,9 +32,16 @@ namespace ARDOUR { -class Session; -class Route; +class AudioFileSource; +class AudioPlaylist; class Location; +class MidiPlaylist; +class Playlist; +class Route; +class Route; +class Session; + +template<typename T> class MidiRingBuffer; class LIBARDOUR_API DiskIOProcessor : public Processor { @@ -50,8 +57,14 @@ class LIBARDOUR_API DiskIOProcessor : public Processor DiskIOProcessor (Session&, const std::string& name, Flag f); + void set_route (boost::shared_ptr<Route>); + static void set_buffering_parameters (BufferingPreset bp); + int set_block_size (pframes_t); + bool configure_io (ChanCount in, ChanCount out); + bool can_support_io_configuration (const ChanCount& in, ChanCount& out); + /** @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. @@ -68,7 +81,7 @@ class LIBARDOUR_API DiskIOProcessor : public Processor bool reversed() const { return _actual_speed < 0.0f; } double speed() const { return _visible_speed; } - ChanCount n_channels() { return _n_channels; } + virtual void non_realtime_locate (framepos_t); void non_realtime_set_speed (); bool realtime_set_speed (double sp, bool global); @@ -94,14 +107,24 @@ class LIBARDOUR_API DiskIOProcessor : public Processor bool need_butler() const { return _need_butler; } + boost::shared_ptr<Playlist> get_playlist (DataType dt) const { return _playlists[dt]; } + boost::shared_ptr<MidiPlaylist> midi_playlist() const; + boost::shared_ptr<AudioPlaylist> audio_playlist() const; + + virtual void playlist_modified () {} + virtual int use_playlist (DataType, boost::shared_ptr<Playlist>); + virtual int use_new_playlist (DataType); + virtual int use_copy_playlist (DataType); + + PBD::Signal1<void,DataType> PlaylistChanged; + 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; + uint32_t i_am_the_modifier; double _visible_speed; double _actual_speed; double _speed; @@ -115,6 +138,9 @@ class LIBARDOUR_API DiskIOProcessor : public Processor framecnt_t wrap_buffer_size; framecnt_t speed_buffer_size; bool _need_butler; + boost::shared_ptr<Route> _route; + + void init (); Glib::Threads::Mutex state_lock; @@ -124,22 +150,24 @@ class LIBARDOUR_API DiskIOProcessor : public Processor framecnt_t& write_chunk_size, framecnt_t& write_buffer_size); - virtual void allocate_temporary_buffers () = 0; + enum TransitionType { + CaptureStart = 0, + CaptureEnd + }; + + struct CaptureTransition { + TransitionType type; + framepos_t capture_val; ///< The start or end file frame position + }; /** Information about one audio channel, playback or capture * (depending on the derived class) */ struct ChannelInfo : public boost::noncopyable { - ChannelInfo (framecnt_t buffer_size, - framecnt_t speed_buffer_size, - framecnt_t wrap_buffer_size); + ChannelInfo (framecnt_t 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. */ @@ -149,7 +177,13 @@ class LIBARDOUR_API DiskIOProcessor : public Processor Sample* scrub_forward_buffer; Sample* scrub_reverse_buffer; - PBD::RingBufferNPT<Sample>::rw_vector read_vector; + PBD::RingBufferNPT<Sample>::rw_vector rw_vector; + + /* used only by capture */ + boost::shared_ptr<AudioFileSource> write_source; + PBD::RingBufferNPT<CaptureTransition> * capture_transition_buf; + // the following are used in the butler thread only + framecnt_t curr_capture_cnt; void resize (framecnt_t); }; @@ -162,6 +196,20 @@ class LIBARDOUR_API DiskIOProcessor : public Processor CubicInterpolation interpolation; + boost::shared_ptr<Playlist> _playlists[DataType::num_types]; + PBD::ScopedConnectionList playlist_connections; + + 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) {} + int find_and_use_playlist (DataType, std::string const &); + + /* The MIDI stuff */ + + MidiRingBuffer<framepos_t>* _midi_buf; + gint _frames_written_to_ringbuffer; + gint _frames_read_from_ringbuffer; + CubicMidiInterpolation midi_interpolation; }; } // namespace ARDOUR diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h index 6a50594dc4..7b076ab23f 100644 --- a/libs/ardour/ardour/disk_reader.h +++ b/libs/ardour/ardour/disk_reader.h @@ -44,12 +44,8 @@ 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*/); - int set_block_size (pframes_t); - bool configure_io (ChanCount in, ChanCount out); - bool can_support_io_configuration (const ChanCount& in, ChanCount& out) = 0; void realtime_handle_transport_stopped (); void realtime_locate (); - void non_realtime_locate (framepos_t); int overwrite_existing_buffers (); void set_pending_overwrite (bool yn); @@ -59,16 +55,6 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor virtual XMLNode& state (bool full); int set_state (const XMLNode&, int version); - boost::shared_ptr<Playlist> get_playlist (DataType dt) const { return _playlists[dt]; } - boost::shared_ptr<MidiPlaylist> midi_playlist() const; - boost::shared_ptr<AudioPlaylist> audio_playlist() const; - - virtual void playlist_modified (); - virtual int use_playlist (DataType, boost::shared_ptr<Playlist>); - virtual int use_new_playlist (DataType); - virtual int use_copy_playlist (DataType); - - PBD::Signal1<void,DataType> PlaylistChanged; PBD::Signal0<void> AlignmentStyleChanged; float buffer_load() const; @@ -93,8 +79,6 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor bool pending_overwrite () const { return _pending_overwrite; } - virtual int find_and_use_playlist (DataType, std::string const &); - // Working buffers for do_refill (butler thread) static void allocate_working_buffers(); static void free_working_buffers(); @@ -104,19 +88,19 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor int can_internal_playback_seek (framecnt_t distance); int seek (framepos_t frame, bool complete_refill = false); - PBD::Signal0<void> Underrun; - - protected: - boost::shared_ptr<Playlist> _playlists[DataType::num_types]; + static PBD::Signal0<void> Underrun; - 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); + void playlist_modified (); + protected: void reset_tracker (); void resolve_tracker (Evoral::EventSink<framepos_t>& buffer, framepos_t time); boost::shared_ptr<MidiBuffer> get_gui_feed_buffer () const; + void playlist_changed (const PBD::PropertyChange&); + int use_playlist (DataType, boost::shared_ptr<Playlist>); + void playlist_ranges_moved (std::list< Evoral::RangeMove<framepos_t> > const &, bool); + private: /** The number of frames by which this diskstream's output should be delayed with respect to the transport frame. This is used for latency compensation. @@ -133,8 +117,6 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor framepos_t playback_sample; MonitorChoice _monitoring_choice; - PBD::ScopedConnectionList playlist_connections; - int _do_refill_with_alloc (bool partial_fill); static framecnt_t _chunk_frames; @@ -142,17 +124,11 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor /* The MIDI stuff */ - MidiRingBuffer<framepos_t>* _midi_buf; - /** A buffer that we use to put newly-arrived MIDI data in for the GUI to read (so that it can update itself). */ MidiBuffer _gui_feed_buffer; mutable Glib::Threads::Mutex _gui_feed_buffer_mutex; - CubicMidiInterpolation midi_interpolation; - gint _frames_written_to_ringbuffer; - gint _frames_read_from_ringbuffer; - int audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, framepos_t& start, framecnt_t cnt, @@ -169,7 +145,6 @@ class LIBARDOUR_API DiskReader : public DiskIOProcessor int internal_playback_seek (framecnt_t distance); frameoffset_t calculate_playback_distance (pframes_t); - void allocate_temporary_buffers(); void get_playback (MidiBuffer& dst, framecnt_t nframes); void flush_playback (framepos_t start, framepos_t end); }; diff --git a/libs/ardour/ardour/disk_writer.h b/libs/ardour/ardour/disk_writer.h index 984a84a938..3eed3c6d99 100644 --- a/libs/ardour/ardour/disk_writer.h +++ b/libs/ardour/ardour/disk_writer.h @@ -21,12 +21,16 @@ #define __ardour_disk_writer_h__ #include <list> +#include <vector> #include "ardour/disk_io.h" namespace ARDOUR { +class AudioFileSource; +class SMFSource; + class LIBARDOUR_API DiskWriter : public DiskIOProcessor { public: @@ -34,18 +38,14 @@ class LIBARDOUR_API DiskWriter : public DiskIOProcessor virtual bool set_write_source_name (const std::string& str); + bool recordable() const { return _flags & Recordable; } + static framecnt_t chunk_frames() { return _chunk_frames; } static framecnt_t default_chunk_frames (); static void set_chunk_frames (framecnt_t n) { _chunk_frames = n; } void run (BufferSet& /*bufs*/, framepos_t /*start_frame*/, framepos_t /*end_frame*/, double speed, pframes_t /*nframes*/, bool /*result_required*/); - void silence (framecnt_t /*nframes*/, framepos_t /*start_frame*/); - bool configure_io (ChanCount in, ChanCount out); - bool can_support_io_configuration (const ChanCount& in, ChanCount& out) = 0; - ChanCount input_streams () const; - ChanCount output_streams() const; - void realtime_handle_transport_stopped (); - void realtime_locate (); + void non_realtime_locate (framepos_t); virtual XMLNode& state (bool full); int set_state (const XMLNode&, int version); @@ -60,6 +60,17 @@ class LIBARDOUR_API DiskWriter : public DiskIOProcessor } } + boost::shared_ptr<AudioFileSource> audio_write_source (uint32_t n=0) { + boost::shared_ptr<ChannelList> c = channels.reader(); + if (n < c->size()) { + return (*c)[n]->write_source; + } + + return boost::shared_ptr<AudioFileSource>(); + } + + boost::shared_ptr<SMFSource> midi_write_source () { return _midi_write_source; } + virtual std::string steal_write_source_name () { return std::string(); } AlignStyle alignment_style() const { return _alignment_style; } @@ -80,9 +91,9 @@ class LIBARDOUR_API DiskWriter : public DiskIOProcessor virtual void set_record_safe (bool yn) = 0; bool destructive() const { return _flags & Destructive; } - virtual int set_destructive (bool /*yn*/) { return -1; } - virtual int set_non_layered (bool /*yn*/) { return -1; } - virtual bool can_become_destructive (bool& /*requires_bounce*/) const { return false; } + int set_destructive (bool yn); + int set_non_layered (bool yn); + bool can_become_destructive (bool& requires_bounce) const; /** @return Start position of currently-running capture (in session frames) */ framepos_t current_capture_start() const { return capture_start_frame; } @@ -98,23 +109,29 @@ class LIBARDOUR_API DiskWriter : public DiskIOProcessor framecnt_t capture_offset() const { return _capture_offset; } virtual void set_capture_offset (); + static PBD::Signal0<void> Overrun; + + PBD::Signal0<void> RecordEnableChanged; + PBD::Signal0<void> RecordSafeChanged; + protected: virtual int do_flush (RunContext context, bool force = false) = 0; - virtual void check_record_status (framepos_t transport_frame, bool can_record); - virtual void prepare_record_status (framepos_t /*capture_start_frame*/) {} - virtual void set_align_style_from_io() {} - virtual void setup_destructive_playlist () {} - virtual void use_destructive_playlist () {} - virtual void prepare_to_stop (framepos_t transport_pos, framepos_t audible_frame); + void get_input_sources (); + void check_record_status (framepos_t transport_frame, bool can_record); + void prepare_record_status (framepos_t /*capture_start_frame*/); + void set_align_style_from_io(); + void setup_destructive_playlist (); + void use_destructive_playlist (); + void prepare_to_stop (framepos_t transport_pos, framepos_t audible_frame); void engage_record_enable (); void disengage_record_enable (); void engage_record_safe (); void disengage_record_safe (); - virtual bool prep_record_enable () = 0; - virtual bool prep_record_disable () = 0; + bool prep_record_enable (); + bool prep_record_disable (); void calculate_record_range ( Evoral::OverlapType ot, framepos_t transport_frame, framecnt_t nframes, @@ -133,16 +150,6 @@ class LIBARDOUR_API DiskWriter : public DiskIOProcessor mutable Glib::Threads::Mutex capture_info_lock; private: - enum TransitionType { - CaptureStart = 0, - CaptureEnd - }; - - struct CaptureTransition { - TransitionType type; - framepos_t capture_val; ///< The start or end file frame position - }; - framecnt_t _input_latency; gint _record_enabled; gint _record_safe; @@ -157,10 +164,19 @@ class LIBARDOUR_API DiskWriter : public DiskIOProcessor AlignStyle _alignment_style; AlignChoice _alignment_choice; std::string _write_source_name; + boost::shared_ptr<SMFSource> _midi_write_source; std::list<boost::shared_ptr<Source> > _last_capture_sources; - + std::vector<boost::shared_ptr<AudioFileSource> > capturing_sources; + static framecnt_t _chunk_frames; + + NoteMode _note_mode; + volatile gint _frames_pending_write; + volatile gint _num_captured_loops; + framepos_t _accumulated_capture_offset; + + void finish_capture (boost::shared_ptr<ChannelList> c); }; } // namespace diff --git a/libs/ardour/disk_io.cc b/libs/ardour/disk_io.cc index 48b58ca3f1..1516933e32 100644 --- a/libs/ardour/disk_io.cc +++ b/libs/ardour/disk_io.cc @@ -20,13 +20,19 @@ #include "pbd/error.h" #include "pbd/i18n.h" +#include "ardour/audioplaylist.h" #include "ardour/butler.h" #include "ardour/disk_io.h" #include "ardour/disk_reader.h" #include "ardour/disk_writer.h" #include "ardour/location.h" +#include "ardour/midi_ring_buffer.h" +#include "ardour/midi_playlist.h" +#include "ardour/playlist.h" +#include "ardour/playlist_factory.h" #include "ardour/rc_configuration.h" #include "ardour/session.h" +#include "ardour/session_playlists.h" using namespace ARDOUR; using namespace PBD; @@ -54,10 +60,19 @@ DiskIOProcessor::DiskIOProcessor (Session& s, string const & str, Flag f) , speed_buffer_size (0) , _need_butler (false) , channels (new ChannelList) + , _midi_buf (0) + , _frames_written_to_ringbuffer (0) + , _frames_read_from_ringbuffer (0) { } void +DiskIOProcessor::init () +{ + set_block_size (_session.get_block_size()); +} + +void DiskIOProcessor::set_buffering_parameters (BufferingPreset bp) { framecnt_t read_chunk_size; @@ -112,6 +127,58 @@ DiskIOProcessor::get_buffering_presets (BufferingPreset bp, return true; } +bool +DiskIOProcessor::can_support_io_configuration (const ChanCount& in, ChanCount& out) +{ + if (in.n_midi() != 0 && in.n_midi() != 1) { + /* we only support zero or 1 MIDI stream */ + return false; + } + + if (in != out) { + /* currently no way to deliver different channels that we receive */ + return false; + } + + return true; +} + +bool +DiskIOProcessor::configure_io (ChanCount in, ChanCount out) +{ + Glib::Threads::Mutex::Lock lm (state_lock); + + RCUWriter<ChannelList> writer (channels); + boost::shared_ptr<ChannelList> c = writer.get_copy(); + + uint32_t n_audio = in.n_audio(); + + if (n_audio > c->size()) { + add_channel_to (c, n_audio - c->size()); + } else if (n_audio < c->size()) { + remove_channel_from (c, c->size() - n_audio); + } + + if (in.n_midi() > 0 && !_midi_buf) { + const size_t size = _session.butler()->midi_diskstream_buffer_size(); + _midi_buf = new MidiRingBuffer<framepos_t>(size); + midi_interpolation.add_channel_to (0,0); + } + + if (speed() != 1.0f || speed() != -1.0f) { + seek ((framepos_t) (_session.transport_frame() * (double) speed())); + } else { + seek (_session.transport_frame()); + } + + return Processor::configure_io (in, out); +} + +int +DiskIOProcessor::set_block_size (pframes_t nframes) +{ + return 0; +} int DiskIOProcessor::set_loop (Location *location) @@ -130,13 +197,23 @@ DiskIOProcessor::set_loop (Location *location) } void +DiskIOProcessor::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); + } +} + +void DiskIOProcessor::non_realtime_set_speed () { if (_buffer_reallocation_required) { Glib::Threads::Mutex::Lock lm (state_lock); - allocate_temporary_buffers (); - _buffer_reallocation_required = false; } @@ -211,16 +288,10 @@ int DiskIOProcessor::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); + c->push_back (new ChannelInfo (_session.butler()->audio_diskstream_playback_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; } @@ -242,8 +313,6 @@ DiskIOProcessor::remove_channel_from (boost::shared_ptr<ChannelList> c, uint32_t interpolation.remove_channel_from (); } - _n_channels.set(DataType::AUDIO, c->size()); - return 0; } @@ -256,3 +325,170 @@ DiskIOProcessor::remove_channel (uint32_t how_many) return remove_channel_from (c, how_many); } +void +DiskIOProcessor::playlist_deleted (boost::weak_ptr<Playlist> wpl) +{ + boost::shared_ptr<Playlist> pl (wpl.lock()); + + if (!pl) { + return; + } + + for (uint32_t n = 0; n < DataType::num_types; ++n) { + if (pl == _playlists[n]) { + + /* this catches an ordering issue with session destruction. playlists + are destroyed before disk readers. we have to invalidate any handles + we have to the playlist. + */ + _playlists[n].reset (); + break; + } + } +} + +boost::shared_ptr<AudioPlaylist> +DiskIOProcessor::audio_playlist () const +{ + return boost::dynamic_pointer_cast<AudioPlaylist> (_playlists[DataType::AUDIO]); +} + +boost::shared_ptr<MidiPlaylist> +DiskIOProcessor::midi_playlist () const +{ + return boost::dynamic_pointer_cast<MidiPlaylist> (_playlists[DataType::MIDI]); +} + +int +DiskIOProcessor::use_playlist (DataType dt, boost::shared_ptr<Playlist> playlist) +{ + if (!playlist) { + return 0; + } + + { + Glib::Threads::Mutex::Lock lm (state_lock); + + if (playlist == _playlists[dt]) { + return 0; + } + + playlist_connections.drop_connections (); + + if (_playlists[dt]) { + _playlists[dt]->release(); + } + + _playlists[dt] = playlist; + playlist->use(); + + playlist->ContentsChanged.connect_same_thread (playlist_connections, boost::bind (&DiskIOProcessor::playlist_modified, this)); + playlist->LayeringChanged.connect_same_thread (playlist_connections, boost::bind (&DiskIOProcessor::playlist_modified, this)); + playlist->DropReferences.connect_same_thread (playlist_connections, boost::bind (&DiskIOProcessor::playlist_deleted, this, boost::weak_ptr<Playlist>(playlist))); + playlist->RangesMoved.connect_same_thread (playlist_connections, boost::bind (&DiskIOProcessor::playlist_ranges_moved, this, _1, _2)); + } + + PlaylistChanged (dt); /* EMIT SIGNAL */ + _session.set_dirty (); + + return 0; +} + +int +DiskIOProcessor::find_and_use_playlist (DataType dt, const string& name) +{ + boost::shared_ptr<Playlist> playlist; + + if ((playlist = _session.playlists->by_name (name)) == 0) { + playlist = PlaylistFactory::create (dt, _session, name); + } + + if (!playlist) { + error << string_compose(_("DiskIOProcessor: \"%1\" isn't an playlist"), name) << endmsg; + return -1; + } + + return use_playlist (dt, playlist); +} + +int +DiskIOProcessor::use_new_playlist (DataType dt) +{ + string newname; + boost::shared_ptr<Playlist> playlist = _playlists[dt]; + + if (playlist) { + newname = Playlist::bump_name (playlist->name(), _session); + } else { + newname = Playlist::bump_name (_name, _session); + } + + playlist = boost::dynamic_pointer_cast<AudioPlaylist> (PlaylistFactory::create (dt, _session, newname, hidden())); + + if (!playlist) { + return -1; + } + + return use_playlist (dt, playlist); +} + +int +DiskIOProcessor::use_copy_playlist (DataType dt) +{ + assert (_playlists[dt]); + + if (_playlists[dt] == 0) { + error << string_compose(_("DiskIOProcessor %1: there is no existing playlist to make a copy of!"), _name) << endmsg; + return -1; + } + + string newname; + boost::shared_ptr<Playlist> playlist; + + newname = Playlist::bump_name (_playlists[dt]->name(), _session); + + if ((playlist = PlaylistFactory::create (_playlists[dt], newname)) == 0) { + return -1; + } + + playlist->reset_shares(); + + return use_playlist (dt, playlist); +} + +DiskIOProcessor::ChannelInfo::ChannelInfo (framecnt_t bufsize) +{ + 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()); + capture_transition_buf = new RingBufferNPT<CaptureTransition> (256); +} + +void +DiskIOProcessor::ChannelInfo::resize (framecnt_t bufsize) +{ + delete buf; + buf = new RingBufferNPT<Sample> (bufsize); + memset (buf->buffer(), 0, sizeof (Sample) * buf->bufsize()); +} + +DiskIOProcessor::ChannelInfo::~ChannelInfo () +{ + delete buf; + buf = 0; + + delete capture_transition_buf; + capture_transition_buf = 0; +} + +void +DiskIOProcessor::set_route (boost::shared_ptr<Route> r) +{ + _route = r; +} diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc index 73d905b4c8..aa26f2644f 100644 --- a/libs/ardour/disk_reader.cc +++ b/libs/ardour/disk_reader.cc @@ -28,6 +28,7 @@ #include "ardour/disk_reader.h" #include "ardour/midi_ring_buffer.h" #include "ardour/midi_playlist.h" +#include "ardour/pannable.h" #include "ardour/playlist.h" #include "ardour/playlist_factory.h" #include "ardour/session.h" @@ -38,6 +39,10 @@ using namespace PBD; using namespace std; ARDOUR::framecnt_t DiskReader::_chunk_frames = default_chunk_frames (); +PBD::Signal0<void> DiskReader::Underrun; +Sample* DiskReader::_mixdown_buffer = 0; +gain_t* DiskReader::_gain_buffer = 0; +framecnt_t DiskReader::midi_readahead = 4096; DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f) : DiskIOProcessor (s, str, f) @@ -50,8 +55,6 @@ DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f) , playback_sample (0) , _monitoring_choice (MonitorDisk) , _gui_feed_buffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)) - , _frames_written_to_ringbuffer (0) - , _frames_read_from_ringbuffer (0) { } @@ -130,6 +133,13 @@ DiskReader::set_roll_delay (ARDOUR::framecnt_t nframes) _roll_delay = nframes; } +XMLNode& +DiskReader::state (bool full) +{ + XMLNode& node (DiskIOProcessor::state (full)); + return node; +} + int DiskReader::set_state (const XMLNode& node, int version) { @@ -158,51 +168,6 @@ DiskReader::set_state (const XMLNode& node, int version) return 0; } -/* Processor interface */ - -bool -DiskReader::configure_io (ChanCount in, ChanCount out) -{ - Glib::Threads::Mutex::Lock lm (state_lock); - - RCUWriter<ChannelList> writer (channels); - boost::shared_ptr<ChannelList> c = writer.get_copy(); - - uint32_t n_audio = in.n_audio(); - - if (n_audio > c->size()) { - add_channel_to (c, n_audio - c->size()); - } else if (n_audio < c->size()) { - remove_channel_from (c, c->size() - n_audio); - } - - if (in.n_midi() > 0 && !_midi_buf) { - const size_t size = _session.butler()->midi_diskstream_buffer_size(); - _midi_buf = new MidiRingBuffer<framepos_t>(size); - midi_interpolation.add_channel_to (0,0); - } - - Processor::configure_io (in, out); - - return true; -} - -bool -DiskReader::can_support_io_configuration (const ChanCount& in, ChanCount& out) -{ - if (in.n_midi() != 0 && in.n_midi() != 1) { - /* we only support zero or 1 MIDI stream */ - return false; - } - - if (in != out) { - /* currently no way to deliver different channels that we receive */ - return false; - } - - return true; -} - void DiskReader::realtime_handle_transport_stopped () { @@ -247,87 +212,6 @@ DiskReader::adjust_buffering () } } -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&) { @@ -343,70 +227,17 @@ DiskReader::playlist_modified () } } -void -DiskReader::playlist_deleted (boost::weak_ptr<Playlist> wpl) -{ - boost::shared_ptr<Playlist> pl (wpl.lock()); - - if (!pl) { - return; - } - - for (uint32_t n = 0; n < DataType::num_types; ++n) { - if (pl == _playlists[n]) { - - /* this catches an ordering issue with session destruction. playlists - are destroyed before disk readers. we have to invalidate any handles - we have to the playlist. - */ - _playlists[n].reset (); - break; - } - } -} - -boost::shared_ptr<AudioPlaylist> -DiskReader::audio_playlist () const -{ - return boost::dynamic_pointer_cast<AudioPlaylist> (_playlists[DataType::AUDIO]); -} - -boost::shared_ptr<MidiPlaylist> -DiskReader::midi_playlist () const -{ - return boost::dynamic_pointer_cast<MidiPlaylist> (_playlists[DataType::MIDI]); -} - int DiskReader::use_playlist (DataType dt, boost::shared_ptr<Playlist> playlist) { - if (!playlist) { - return 0; - } - bool prior_playlist = false; - { - Glib::Threads::Mutex::Lock lm (state_lock); - - if (playlist == _playlists[dt]) { - return 0; - } - - playlist_connections.drop_connections (); - - if (_playlists[dt]) { - _playlists[dt]->release(); - prior_playlist = true; - } - - _playlists[dt] = playlist; - playlist->use(); + if (_playlists[dt]) { + prior_playlist = true; + } - playlist->ContentsChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this)); - playlist->LayeringChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this)); - playlist->DropReferences.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_deleted, this, boost::weak_ptr<Playlist>(playlist))); - playlist->RangesMoved.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_ranges_moved, this, _1, _2)); + if (DiskIOProcessor::use_playlist (dt, playlist)) { + return -1; } /* don't do this if we've already asked for it *or* if we are setting up @@ -419,83 +250,9 @@ DiskReader::use_playlist (DataType dt, boost::shared_ptr<Playlist> playlist) overwrite_queued = true; } - PlaylistChanged (dt); /* EMIT SIGNAL */ - _session.set_dirty (); - return 0; } -int -DiskReader::find_and_use_playlist (DataType dt, const string& name) -{ - boost::shared_ptr<Playlist> playlist; - - if ((playlist = _session.playlists->by_name (name)) == 0) { - playlist = PlaylistFactory::create (dt, _session, name); - } - - if (!playlist) { - error << string_compose(_("DiskReader: \"%1\" isn't an playlist"), name) << endmsg; - return -1; - } - - return use_playlist (dt, playlist); -} - -int -DiskReader::use_new_playlist (DataType dt) -{ - string newname; - boost::shared_ptr<Playlist> playlist = _playlists[dt]; - - if (playlist) { - newname = Playlist::bump_name (playlist->name(), _session); - } else { - newname = Playlist::bump_name (_name, _session); - } - - playlist = boost::dynamic_pointer_cast<AudioPlaylist> (PlaylistFactory::create (dt, _session, newname, hidden())); - - if (!playlist) { - return -1; - } - - return use_playlist (dt, playlist); -} - -int -DiskReader::use_copy_playlist (DataType dt) -{ - assert (_playlists[dt]); - - if (_playlists[dt] == 0) { - error << string_compose(_("DiskReader %1: there is no existing playlist to make a copy of!"), _name) << endmsg; - return -1; - } - - string newname; - boost::shared_ptr<Playlist> playlist; - - newname = Playlist::bump_name (_playlists[dt]->name(), _session); - - if ((playlist = PlaylistFactory::create (_playlists[dt], newname)) == 0) { - return -1; - } - - playlist->reset_shares(); - - return use_playlist (dt, playlist); -} - - -/** 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) @@ -511,133 +268,122 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, return; } - for (chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->current_buffer = 0; - } - const bool need_disk_signal = result_required || _monitoring_choice == MonitorDisk || _monitoring_choice == MonitorCue; - if (need_disk_signal) { - - /* we're doing playback */ + if (fabsf (_actual_speed) != 1.0f) { + midi_interpolation.set_speed (_target_speed); + interpolation.set_speed (_target_speed); + playback_distance = midi_interpolation.distance (nframes); + } else { + playback_distance = nframes; + } - framecnt_t necessary_samples; + if (!need_disk_signal) { - if (_actual_speed != 1.0) { - necessary_samples = (framecnt_t) ceil ((nframes * fabs (_actual_speed))) + 2; - } else { - necessary_samples = nframes; + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + (*chan)->buf->increment_read_ptr (playback_distance); } - for (chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->buf->get_read_vector (&(*chan)->read_vector); - } + return; + } - n = 0; + /* we're doing playback */ - /* 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. - */ + size_t n_buffers = bufs.count().n_audio(); + size_t n_chans = c->size(); + gain_t scaling; - for (chan = c->begin(); chan != c->end(); ++chan, ++n) { + if (n_chans > n_buffers) { + scaling = ((float) n_buffers)/n_chans; + } else { + scaling = 1.0; + } - ChannelInfo* chaninfo (*chan); + for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { - 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]; + AudioBuffer& buf (bufs.get_audio (n%n_buffers)); + Sample* outgoing = buf.data (); - } else { - framecnt_t total = chaninfo->read_vector.len[0] + chaninfo->read_vector.len[1]; + ChannelInfo* chaninfo (*chan); - 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; + chaninfo->buf->get_read_vector (&(*chan)->rw_vector); - } 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. - */ + if (playback_distance <= (framecnt_t) chaninfo->rw_vector.len[0]) { - assert(wrap_buffer_size >= necessary_samples); + if (fabsf (_actual_speed) != 1.0f) { + (void) interpolation.interpolate ( + n, nframes, + chaninfo->rw_vector.buf[0], + outgoing); + } else { + memcpy (outgoing, chaninfo->rw_vector.buf[0], sizeof (Sample) * playback_distance); + } - /* Copy buf[0] from buf */ - memcpy ((char *) chaninfo->wrap_buffer, - chaninfo->read_vector.buf[0], - chaninfo->read_vector.len[0] * sizeof (Sample)); + } else { - /* 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)); + const framecnt_t total = chaninfo->rw_vector.len[0] + chaninfo->rw_vector.len[1]; - chaninfo->current_buffer = chaninfo->wrap_buffer; - } - } - } + if (playback_distance <= total) { - if (_actual_speed != 1.0f && _actual_speed != -1.0f) { + /* We have enough samples, but not in one lump. + */ - interpolation.set_speed (_target_speed); + if (fabsf (_actual_speed) != 1.0f) { + interpolation.interpolate (n, chaninfo->rw_vector.len[0], + chaninfo->rw_vector.buf[0], + outgoing); + outgoing += chaninfo->rw_vector.len[0]; + interpolation.interpolate (n, playback_distance - chaninfo->rw_vector.len[0], + chaninfo->rw_vector.buf[1], + outgoing); + } else { + memcpy (outgoing, + chaninfo->rw_vector.buf[0], + chaninfo->rw_vector.len[0] * sizeof (Sample)); + outgoing += chaninfo->rw_vector.len[0]; + memcpy (outgoing, + chaninfo->rw_vector.buf[1], + (playback_distance - chaninfo->rw_vector.len[0]) * sizeof (Sample)); + } - int channel = 0; - for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) { - ChannelInfo* chaninfo (*chan); + } else { - playback_distance = interpolation.interpolate ( - channel, nframes, chaninfo->current_buffer, chaninfo->speed_buffer); + cerr << _name << " Need " << playback_distance << " 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; - chaninfo->current_buffer = chaninfo->speed_buffer; } - } else { - playback_distance = nframes; } + if (scaling != 1.0f) { + apply_gain_to_buffer (outgoing, nframes, scaling); + } + + chaninfo->buf->increment_read_ptr (playback_distance); _speed = _target_speed; } - if (need_disk_signal) { - - /* 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; + /* MIDI data handling */ - if (n_chans > n_buffers) { - scaling = ((float) n_buffers)/n_chans; - } + if (!_session.declick_out_pending()) { - for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { + /* copy the diskstream data to all output buffers */ - AudioBuffer& buf (bufs.get_audio (n%n_buffers)); - ChannelInfo* chaninfo (*chan); + MidiBuffer& mbuf (bufs.get_midi (0)); + get_playback (mbuf, playback_distance); - 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); - } + /* vari-speed */ + if (_target_speed > 0 && _actual_speed != 1.0f) { + MidiBuffer& mbuf (bufs.get_midi (0)); + for (MidiBuffer::iterator i = mbuf.begin(); i != mbuf.end(); ++i) { + MidiBuffer::TimeType *tme = i.timeptr(); + *tme = (*tme) * nframes / playback_distance; } } - - /* extra buffers will already be silent, so leave them alone */ } _need_butler = false; @@ -648,10 +394,6 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, 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) { if (c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2) { @@ -664,40 +406,6 @@ DiskReader::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, } } - /* MIDI data handling */ - - if (_actual_speed != 1.0f && _target_speed > 0) { - - interpolation.set_speed (_target_speed); - - playback_distance = midi_interpolation.distance (nframes); - - } else { - playback_distance = nframes; - } - - if (need_disk_signal && !_session.declick_out_pending()) { - - /* copy the diskstream data to all output buffers */ - - MidiBuffer& mbuf (bufs.get_midi (0)); - get_playback (mbuf, playback_distance); - - /* leave the audio count alone */ - ChanCount cnt (DataType::MIDI, 1); - cnt.set (DataType::AUDIO, bufs.count().n_audio()); - bufs.set_count (cnt); - - /* vari-speed */ - if (_target_speed > 0 && _actual_speed != 1.0f) { - MidiBuffer& mbuf (bufs.get_midi (0)); - for (MidiBuffer::iterator i = mbuf.begin(); i != mbuf.end(); ++i) { - MidiBuffer::TimeType *tme = i.timeptr(); - *tme = (*tme) * nframes / playback_distance; - } - } - } - /* MIDI butler needed part */ uint32_t frames_read = g_atomic_int_get(const_cast<gint*>(&_frames_read_from_ringbuffer)); @@ -890,18 +598,6 @@ DiskReader::overwrite_existing_buffers () 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) { @@ -1411,8 +1107,7 @@ DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const & return; } -#if 0 - if (!_track || Config->get_automation_follows_regions () == false) { + if (!_route || Config->get_automation_follows_regions () == false) { return; } @@ -1426,7 +1121,7 @@ DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const & } /* move panner automation */ - boost::shared_ptr<Pannable> pannable = _track->pannable(); + boost::shared_ptr<Pannable> pannable = _route->pannable(); Evoral::ControlSet::Controls& c (pannable->controls()); for (Evoral::ControlSet::Controls::iterator ci = c.begin(); ci != c.end(); ++ci) { @@ -1446,8 +1141,7 @@ DiskReader::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const & } } /* move processor automation */ - _track->foreach_processor (boost::bind (&Diskstream::move_processor_automation, this, _1, movements_frames)); -#endif + _route->foreach_processor (boost::bind (&DiskReader::move_processor_automation, this, _1, movements_frames)); } void diff --git a/libs/ardour/disk_writer.cc b/libs/ardour/disk_writer.cc index a436324320..c6f5c92c76 100644 --- a/libs/ardour/disk_writer.cc +++ b/libs/ardour/disk_writer.cc @@ -19,15 +19,21 @@ #include "pbd/i18n.h" +#include "ardour/audioengine.h" +#include "ardour/audio_buffer.h" +#include "ardour/audiofilesource.h" #include "ardour/debug.h" #include "ardour/disk_writer.h" +#include "ardour/port.h" #include "ardour/session.h" +#include "ardour/smf_source.h" using namespace ARDOUR; using namespace PBD; using namespace std; ARDOUR::framecnt_t DiskWriter::_chunk_frames = DiskWriter::default_chunk_frames (); +PBD::Signal0<void> DiskWriter::Overrun; DiskWriter::DiskWriter (Session& s, string const & str, DiskIOProcessor::Flag f) : DiskIOProcessor (s, str, f) @@ -42,6 +48,7 @@ DiskWriter::DiskWriter (Session& s, string const & str, DiskIOProcessor::Flag f) , _alignment_style (ExistingMaterial) , _alignment_choice (Automatic) { + DiskIOProcessor::init (); } framecnt_t @@ -302,6 +309,62 @@ DiskWriter::set_align_style (AlignStyle a, bool force) } void +DiskWriter::set_align_style_from_io () +{ + bool have_physical = false; + + if (_alignment_choice != Automatic) { + return; + } + + if (!_route) { + return; + } + + boost::shared_ptr<IO> input = _route->input (); + + if (input) { + uint32_t n = 0; + vector<string> connections; + boost::shared_ptr<ChannelList> c = channels.reader(); + + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) { + + if ((input->nth (n).get()) && (input->nth (n)->get_connections (connections) == 0)) { + if (AudioEngine::instance()->port_is_physical (connections[0])) { + have_physical = true; + break; + } + } + + connections.clear (); + } + } + +#ifdef MIXBUS + // compensate for latency when bouncing from master or mixbus. + // we need to use "ExistingMaterial" to pick up the master bus' latency + // see also Route::direct_feeds_according_to_reality + IOVector ios; + ios.push_back (_io); + if (_session.master_out() && ios.fed_by (_session.master_out()->output())) { + have_physical = true; + } + for (uint32_t n = 0; n < NUM_MIXBUSES && !have_physical; ++n) { + if (_session.get_mixbus (n) && ios.fed_by (_session.get_mixbus(n)->output())) { + have_physical = true; + } + } +#endif + + if (have_physical) { + set_align_style (ExistingMaterial); + } else { + set_align_style (CaptureTime); + } +} + +void DiskWriter::set_align_choice (AlignChoice a, bool force) { if (record_enabled() && _session.actively_recording()) { @@ -325,6 +388,15 @@ DiskWriter::set_align_choice (AlignChoice a, bool force) } } +XMLNode& +DiskWriter::state (bool full) +{ + XMLNode& node (DiskIOProcessor::state (full)); + node.add_property (X_("capture-alignment"), enum_2_string (_alignment_choice)); + node.add_property (X_("record-safe"), (_record_safe ? X_("yes" : "no"))); + return node; +} + int DiskWriter::set_state (const XMLNode& node, int version) { @@ -347,3 +419,321 @@ DiskWriter::set_state (const XMLNode& node, int version) return 0; } + +void +DiskWriter::non_realtime_locate (framepos_t position) +{ + if (_midi_write_source) { + _midi_write_source->set_timeline_position (position); + } + + DiskIOProcessor::non_realtime_locate (position); +} + + +void +DiskWriter::prepare_record_status(framepos_t capture_start_frame) +{ + if (recordable() && destructive()) { + boost::shared_ptr<ChannelList> c = channels.reader(); + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + + RingBufferNPT<CaptureTransition>::rw_vector transitions; + (*chan)->capture_transition_buf->get_write_vector (&transitions); + + if (transitions.len[0] > 0) { + transitions.buf[0]->type = CaptureStart; + transitions.buf[0]->capture_val = capture_start_frame; + (*chan)->capture_transition_buf->increment_write_ptr(1); + } else { + // bad! + fatal << X_("programming error: capture_transition_buf is full on rec start! inconceivable!") + << endmsg; + } + } + } +} + + +/** 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_playback_buffer in each ChannelInfo to point to data + * that someone can read playback_distance worth of data from. + */ +void +DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, + double speed, pframes_t nframes, bool result_required) + +/* (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 rec_offset = 0; + framecnt_t rec_nframes = 0; + bool can_record = _session.actively_recording (); + + check_record_status (start_frame, can_record); + + if (nframes == 0) { + return; + } + + Glib::Threads::Mutex::Lock sm (state_lock, Glib::Threads::TRY_LOCK); + + if (!sm.locked()) { + return; + } + + // Safeguard against situations where process() goes haywire when autopunching + // and last_recordable_frame < first_recordable_frame + + if (last_recordable_frame < first_recordable_frame) { + last_recordable_frame = max_framepos; + } + + if (record_enabled()) { + + Evoral::OverlapType ot = Evoral::coverage (first_recordable_frame, last_recordable_frame, start_frame, end_frame); + // XXX should this be transport_frame + nframes - 1 ? coverage() expects its parameter ranges to include their end points + // XXX also, first_recordable_frame & last_recordable_frame may both be == max_framepos: coverage() will return OverlapNone in that case. Is thak OK? + calculate_record_range (ot, start_frame, nframes, rec_nframes, rec_offset); + + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: this time record %2 of %3 frames, offset %4\n", _name, rec_nframes, nframes, rec_offset)); + + if (rec_nframes && !was_recording) { + capture_captured = 0; + was_recording = true; + } + } + + if (can_record && !_last_capture_sources.empty()) { + _last_capture_sources.clear (); + } + + if (rec_nframes) { + + const size_t n_buffers = bufs.count().n_audio(); + + for (n = 0; chan != c->end(); ++chan, ++n) { + + ChannelInfo* chaninfo (*chan); + AudioBuffer& buf (bufs.get_audio (n%n_buffers)); + + chaninfo->buf->get_write_vector (&chaninfo->rw_vector); + + if (rec_nframes <= (framecnt_t) chaninfo->rw_vector.len[0]) { + + Sample *incoming = buf.data (rec_offset); + memcpy (chaninfo->rw_vector.buf[0], incoming, sizeof (Sample) * rec_nframes); + + } else { + + framecnt_t total = chaninfo->rw_vector.len[0] + chaninfo->rw_vector.len[1]; + + if (rec_nframes > total) { + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 overrun in %2, rec_nframes = %3 total space = %4\n", + DEBUG_THREAD_SELF, name(), rec_nframes, total)); + Overrun (); + return; + } + + Sample *incoming = buf.data (rec_offset); + framecnt_t first = chaninfo->rw_vector.len[0]; + + memcpy (chaninfo->rw_vector.buf[0], incoming, sizeof (Sample) * first); + memcpy (chaninfo->rw_vector.buf[1], incoming + first, sizeof (Sample) * (rec_nframes - first)); + } + } + + } else { + + if (was_recording) { + finish_capture (c); + } + + } + + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + if (rec_nframes) { + (*chan)->buf->increment_write_ptr (rec_nframes); + } + } + + if (rec_nframes != 0) { + capture_captured += rec_nframes; + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 now captured %2 (by %3)\n", name(), capture_captured, rec_nframes)); + } + + if (!c->empty()) { + if (_slaved) { + if (c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2) { + _need_butler = true; + } + } else { + if (((framecnt_t) c->front()->buf->read_space() >= _chunk_frames)) { + _need_butler = true; + } + } + } +} + +void +DiskWriter::finish_capture (boost::shared_ptr<ChannelList> c) +{ + was_recording = false; + first_recordable_frame = max_framepos; + last_recordable_frame = max_framepos; + + if (capture_captured == 0) { + return; + } + + if (recordable() && destructive()) { + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + + RingBufferNPT<CaptureTransition>::rw_vector transvec; + (*chan)->capture_transition_buf->get_write_vector(&transvec); + + if (transvec.len[0] > 0) { + transvec.buf[0]->type = CaptureEnd; + transvec.buf[0]->capture_val = capture_captured; + (*chan)->capture_transition_buf->increment_write_ptr(1); + } + else { + // bad! + fatal << string_compose (_("programmer error: %1"), X_("capture_transition_buf is full when stopping record! inconceivable!")) << endmsg; + } + } + } + + + CaptureInfo* ci = new CaptureInfo; + + ci->start = capture_start_frame; + ci->frames = capture_captured; + + /* XXX theoretical race condition here. Need atomic exchange ? + However, the circumstances when this is called right + now (either on record-disable or transport_stopped) + mean that no actual race exists. I think ... + We now have a capture_info_lock, but it is only to be used + to synchronize in the transport_stop and the capture info + accessors, so that invalidation will not occur (both non-realtime). + */ + + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("Finish capture, add new CI, %1 + %2\n", ci->start, ci->frames)); + + capture_info.push_back (ci); + capture_captured = 0; + + /* now we've finished a capture, reset first_recordable_frame for next time */ + first_recordable_frame = max_framepos; +} + +void +DiskWriter::set_record_enabled (bool yn) +{ + if (!recordable() || !_session.record_enabling_legal() || record_safe ()) { + return; + } + + /* can't rec-enable in destructive mode if transport is before start */ + + if (destructive() && yn && _session.transport_frame() < _session.current_start_frame()) { + return; + } + + /* yes, i know that this not proof against race conditions, but its + good enough. i think. + */ + + if (record_enabled() != yn) { + if (yn) { + engage_record_enable (); + } else { + disengage_record_enable (); + } + + RecordEnableChanged (); /* EMIT SIGNAL */ + } +} + +void +DiskWriter::set_record_safe (bool yn) +{ + if (!recordable() || !_session.record_enabling_legal() || channels.reader()->empty()) { + return; + } + + /* can't rec-safe in destructive mode if transport is before start ???? + REQUIRES REVIEW */ + + if (destructive() && yn && _session.transport_frame() < _session.current_start_frame()) { + return; + } + + /* yes, i know that this not proof against race conditions, but its + good enough. i think. + */ + + if (record_safe () != yn) { + if (yn) { + engage_record_safe (); + } else { + disengage_record_safe (); + } + + RecordSafeChanged (); /* EMIT SIGNAL */ + } +} + +bool +DiskWriter::prep_record_enable () +{ + if (!recordable() || !_session.record_enabling_legal() || channels.reader()->empty() || record_safe ()) { // REQUIRES REVIEW "|| record_safe ()" + return false; + } + + /* can't rec-enable in destructive mode if transport is before start */ + + if (destructive() && _session.transport_frame() < _session.current_start_frame()) { + return false; + } + + boost::shared_ptr<ChannelList> c = channels.reader(); + + capturing_sources.clear (); + + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + capturing_sources.push_back ((*chan)->write_source); + Source::Lock lock((*chan)->write_source->mutex()); + (*chan)->write_source->mark_streaming_write_started (lock); + } + + return true; +} + +bool +DiskWriter::prep_record_disable () +{ + capturing_sources.clear (); + return true; +} + +float +DiskWriter::buffer_load () const +{ + boost::shared_ptr<ChannelList> c = channels.reader(); + + if (c->empty ()) { + return 1.0; + } + + return (float) ((double) c->front()->buf->write_space()/ + (double) c->front()->buf->bufsize()); +} |