merge almost all audio & midi diskstream code, redistribute between DiskIOProcessor, DiskReader,DiskWriter; compile and link

This commit is contained in:
Paul Davis 2017-03-16 17:26:53 +01:00
parent 286af12156
commit 38c8aef47c
6 changed files with 861 additions and 502 deletions

View file

@ -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

View file

@ -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;
static PBD::Signal0<void> Underrun;
void playlist_modified ();
protected:
boost::shared_ptr<Playlist> _playlists[DataType::num_types];
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 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);
};

View file

@ -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

View file

@ -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,9 +60,18 @@ 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)
{
@ -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)
@ -129,14 +196,24 @@ DiskIOProcessor::set_loop (Location *location)
return 0;
}
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;
}

View file

@ -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 (_playlists[dt]) {
prior_playlist = true;
}
if (playlist == _playlists[dt]) {
return 0;
}
playlist_connections.drop_connections ();
if (_playlists[dt]) {
_playlists[dt]->release();
prior_playlist = true;
}
_playlists[dt] = playlist;
playlist->use();
playlist->ContentsChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this));
playlist->LayeringChanged.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_modified, this));
playlist->DropReferences.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_deleted, this, boost::weak_ptr<Playlist>(playlist)));
playlist->RangesMoved.connect_same_thread (playlist_connections, boost::bind (&DiskReader::playlist_ranges_moved, this, _1, _2));
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) {
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;
}
/* we're doing playback */
if (!need_disk_signal) {
framecnt_t necessary_samples;
for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
(*chan)->buf->increment_read_ptr (playback_distance);
}
return;
}
/* we're doing playback */
size_t n_buffers = bufs.count().n_audio();
size_t n_chans = c->size();
gain_t scaling;
if (n_chans > n_buffers) {
scaling = ((float) n_buffers)/n_chans;
} else {
scaling = 1.0;
}
for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
AudioBuffer& buf (bufs.get_audio (n%n_buffers));
Sample* outgoing = buf.data ();
ChannelInfo* chaninfo (*chan);
chaninfo->buf->get_read_vector (&(*chan)->rw_vector);
if (playback_distance <= (framecnt_t) chaninfo->rw_vector.len[0]) {
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);
}
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);
}
const framecnt_t total = chaninfo->rw_vector.len[0] + chaninfo->rw_vector.len[1];
n = 0;
if (playback_distance <= total) {
/* 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.
*/
/* We have enough samples, but not in one lump.
*/
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];
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));
}
} 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;
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;
} 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;
}
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) {
/* MIDI data handling */
/* copy data over to buffer set */
if (!_session.declick_out_pending()) {
size_t n_buffers = bufs.count().n_audio();
size_t n_chans = c->size();
gain_t scaling = 1.0f;
/* copy the diskstream data to all output buffers */
if (n_chans > n_buffers) {
scaling = ((float) n_buffers)/n_chans;
}
MidiBuffer& mbuf (bufs.get_midi (0));
get_playback (mbuf, playback_distance);
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);
}
/* 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

View file

@ -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
@ -301,6 +308,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)
{
@ -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());
}