diff --git a/libs/ardour/ardour/disk_io.h b/libs/ardour/ardour/disk_io.h index 0f79930654..8be1ac8601 100644 --- a/libs/ardour/ardour/disk_io.h +++ b/libs/ardour/ardour/disk_io.h @@ -30,6 +30,10 @@ #include "ardour/interpolation.h" #include "ardour/processor.h" +namespace PBD { + template class PlaybackBuffer; +} + namespace ARDOUR { class AudioFileSource; @@ -149,10 +153,10 @@ protected: ChannelInfo (samplecnt_t buffer_size); virtual ~ChannelInfo (); - /** Ringbuffer for data to be played back. + /** A random-access ringbuffer for data to be played back. * written to in the butler thread, read from in the process thread. */ - PBD::RingBufferNPT* rbuf; + PBD::PlaybackBuffer* rbuf; /** A ringbuffer for data to be recorded back, written to in the * process thread, read from in the butler thread. diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h index 47139efd5b..7ccfa0b3d9 100644 --- a/libs/ardour/ardour/disk_reader.h +++ b/libs/ardour/ardour/disk_reader.h @@ -63,7 +63,7 @@ public: /* called by the Butler in a non-realtime context */ int do_refill () { - return refill (_mixdown_buffer, _gain_buffer, 0); + return refill (_sum_buffer, _mixdown_buffer, _gain_buffer, 0); } /** For non-butler contexts (allocates temporary working buffers) @@ -130,7 +130,6 @@ private: with respect to the transport sample. This is used for latency compensation. */ samplepos_t overwrite_sample; - off_t overwrite_offset; bool _pending_overwrite; bool overwrite_queued; IOChange input_change_pending; @@ -144,16 +143,20 @@ private: static samplecnt_t midi_readahead; static bool _no_disk_output; - int audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, + int audio_read (PBD::PlaybackBuffer*, + Sample* sum_buffer, + Sample* mixdown_buffer, + float* gain_buffer, samplepos_t& start, samplecnt_t cnt, int channel, bool reversed); int midi_read (samplepos_t& start, samplecnt_t cnt, bool reversed); + static Sample* _sum_buffer; static Sample* _mixdown_buffer; static gain_t* _gain_buffer; - int refill (Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level); - int refill_audio (Sample *mixdown_buffer, float *gain_buffer, samplecnt_t fill_level); + int refill (Sample* sum_buffer, Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level); + int refill_audio (Sample* sum_buffer, Sample *mixdown_buffer, float *gain_buffer, samplecnt_t fill_level); int refill_midi (); sampleoffset_t calculate_playback_distance (pframes_t); diff --git a/libs/ardour/disk_io.cc b/libs/ardour/disk_io.cc index 097794bb3d..4c7b77335d 100644 --- a/libs/ardour/disk_io.cc +++ b/libs/ardour/disk_io.cc @@ -19,6 +19,7 @@ #include "pbd/debug.h" #include "pbd/error.h" +#include "pbd/playback_buffer.h" #include "ardour/audioplaylist.h" #include "ardour/butler.h" diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc index 41b0dd2dea..5b7b5bde52 100644 --- a/libs/ardour/disk_reader.cc +++ b/libs/ardour/disk_reader.cc @@ -1,26 +1,27 @@ /* - Copyright (C) 2009-2016 Paul Davis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ + * Copyright (C) 2009-2018 Paul Davis + * Copyright (C) 2019 Robin Gareus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ #include #include "pbd/enumwriter.h" #include "pbd/memento_command.h" +#include "pbd/playback_buffer.h" #include "ardour/audioengine.h" #include "ardour/audioplaylist.h" @@ -45,6 +46,7 @@ using namespace std; ARDOUR::samplecnt_t DiskReader::_chunk_samples = default_chunk_samples (); PBD::Signal0 DiskReader::Underrun; +Sample* DiskReader::_sum_buffer = 0; Sample* DiskReader::_mixdown_buffer = 0; gain_t* DiskReader::_gain_buffer = 0; samplecnt_t DiskReader::midi_readahead = 4096; @@ -53,7 +55,6 @@ bool DiskReader::_no_disk_output = false; DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f) : DiskIOProcessor (s, str, f) , overwrite_sample (0) - , overwrite_offset (0) , _pending_overwrite (false) , overwrite_queued (false) , _declick_gain (0) @@ -78,7 +79,7 @@ DiskReader::ReaderChannelInfo::resize (samplecnt_t bufsize) { delete rbuf; /* touch memory to lock it */ - rbuf = new RingBufferNPT (bufsize); + rbuf = new PlaybackBuffer (bufsize); memset (rbuf->buffer(), 0, sizeof (Sample) * rbuf->bufsize()); } @@ -104,6 +105,7 @@ DiskReader::allocate_working_buffers() need to reflect the maximum size we could use, which is 4MB reads, or 2M samples using 16 bit samples. */ + _sum_buffer = new Sample[2*1048576]; _mixdown_buffer = new Sample[2*1048576]; _gain_buffer = new gain_t[2*1048576]; } @@ -111,10 +113,12 @@ DiskReader::allocate_working_buffers() void DiskReader::free_working_buffers() { + delete [] _sum_buffer; delete [] _mixdown_buffer; delete [] _gain_buffer; - _mixdown_buffer = 0; - _gain_buffer = 0; + _sum_buffer = 0; + _mixdown_buffer = 0; + _gain_buffer = 0; } samplecnt_t @@ -184,7 +188,7 @@ DiskReader::buffer_load () const return 1.0; } - PBD::RingBufferNPT* b = c->front()->rbuf; + PBD::PlaybackBuffer* b = c->front()->rbuf; return (float) ((double) b->read_space() / (double) b->bufsize()); } @@ -356,38 +360,16 @@ DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp } } - chaninfo->rbuf->get_read_vector (&(*chan)->rw_vector); - - if (disk_samples_to_consume <= (samplecnt_t) chaninfo->rw_vector.len[0]) { - - if (speed != 0.0) { - memcpy (disk_signal, chaninfo->rw_vector.buf[0], sizeof (Sample) * disk_samples_to_consume); - } - - } else { - - const samplecnt_t total = chaninfo->rw_vector.len[0] + chaninfo->rw_vector.len[1]; - - if (disk_samples_to_consume <= total) { - - if (speed != 0.0) { - memcpy (disk_signal, - chaninfo->rw_vector.buf[0], - chaninfo->rw_vector.len[0] * sizeof (Sample)); - memcpy (disk_signal + chaninfo->rw_vector.len[0], - chaninfo->rw_vector.buf[1], - (disk_samples_to_consume - chaninfo->rw_vector.len[0]) * sizeof (Sample)); - } - - } else { - + if (speed != 0.0) { + assert (disk_signal); + const samplecnt_t total = chaninfo->rbuf->read (disk_signal, disk_samples_to_consume); + if (disk_samples_to_consume > total) { cerr << _name << " Need " << disk_samples_to_consume << " 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; - } } @@ -395,7 +377,7 @@ DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp apply_gain_to_buffer (disk_signal, disk_samples_to_consume, scaling); } - chaninfo->rbuf->increment_read_ptr (disk_samples_to_consume); + //chaninfo->rbuf->increment_read_ptr (disk_samples_to_consume); if (ms & MonitoringInput) { /* mix the disk signal into the input signal (already in bufs) */ @@ -514,8 +496,8 @@ DiskReader::set_pending_overwrite (bool yn) overwrite_sample = playback_sample; boost::shared_ptr c = channels.reader (); - if (!c->empty ()) { - overwrite_offset = c->front()->rbuf->get_read_ptr(); + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + (*chan)->rbuf->read_flush (); } } @@ -531,63 +513,37 @@ DiskReader::overwrite_existing_buffers () DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1 overwriting existing buffers at %2\n", overwrite_sample)); if (!c->empty ()) { - /* AUDIO */ const bool reversed = _session.transport_speed() < 0.0f; /* assume all are the same size */ - samplecnt_t size = c->front()->rbuf->bufsize(); + samplecnt_t size = c->front()->rbuf->write_space (); + boost::scoped_array sum_buffer (new Sample[size]); boost::scoped_array mixdown_buffer (new Sample[size]); boost::scoped_array gain_buffer (new float[size]); /* reduce size so that we can fill the buffer correctly (ringbuffers - can only handle size-1, otherwise they appear to be empty) - */ + * can only handle size-1, otherwise they appear to be empty) + */ size--; uint32_t n=0; - samplepos_t start; for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) { - start = overwrite_sample; - samplecnt_t cnt = size; + samplepos_t start = overwrite_sample; + samplecnt_t to_read = size; - /* to fill the buffer without resetting the playback sample, we need to - do it one or two chunks (normally two). - - |----------------------------------------------------------------------| - - ^ - overwrite_offset - |<- second chunk->||<----------------- first chunk ------------------>| - - */ - - samplecnt_t to_read = size - overwrite_offset; - - if (audio_read ((*chan)->rbuf->buffer() + overwrite_offset, mixdown_buffer.get(), gain_buffer.get(), start, to_read, n, reversed)) { - error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), - id(), size, playback_sample) << endmsg; + cerr << owner()->name() << " over-read: " << to_read << endl; + if (audio_read ((*chan)->rbuf, sum_buffer.get(), mixdown_buffer.get(), gain_buffer.get(), start, to_read, n, reversed)) { + error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), size, overwrite_sample) << endmsg; goto midi; } - - if (cnt > to_read) { - - cnt -= to_read; - - if (audio_read ((*chan)->rbuf->buffer(), mixdown_buffer.get(), gain_buffer.get(), start, cnt, n, reversed)) { - error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), - id(), size, playback_sample) << endmsg; - goto midi; - } - } } ret = 0; - } midi: @@ -722,7 +678,10 @@ void swap_by_ptr (Sample *first, Sample *last) * @param reversed true if we are running backwards, otherwise false. */ int -DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, +DiskReader::audio_read (PBD::PlaybackBuffer*rb, + Sample* sum_buffer, + Sample* mixdown_buffer, + float* gain_buffer, samplepos_t& start, samplecnt_t cnt, int channel, bool reversed) { @@ -730,11 +689,14 @@ DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, bool reloop = false; samplepos_t loop_end = 0; samplepos_t loop_start = 0; - samplecnt_t offset = 0; Location *loc = 0; if (!_playlists[DataType::AUDIO]) { - memset (buf, 0, sizeof (Sample) * cnt); + // TODO optimize use zero-buffer + for (uint32_t z = 0; z < cnt; ++z) { + Sample t = 0; + rb->write (&t, 1); + } return 0; } @@ -792,17 +754,16 @@ DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, break; } - this_read = min(cnt,this_read); + this_read = min (cnt, this_read); - if (audio_playlist()->read (buf+offset, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) { - error << string_compose(_("DiskReader %1: cannot read %2 from playlist at sample %3"), id(), this_read, - start) << endmsg; + if (audio_playlist()->read (sum_buffer, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) { + error << string_compose(_("DiskReader %1: cannot read %2 from playlist at sample %3"), id(), this_read, start) << endmsg; return -1; } if (reversed) { - swap_by_ptr (buf, buf + this_read - 1); + swap_by_ptr (sum_buffer, sum_buffer + this_read - 1); } else { @@ -815,8 +776,11 @@ DiskReader::audio_read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, } } + if (rb->write (sum_buffer, this_read) != this_read) { + cerr << owner()->name() << " Ringbuffer Write overrun" << endl; + } + cnt -= this_read; - offset += this_read; } return 0; @@ -832,10 +796,11 @@ DiskReader::_do_refill_with_alloc (bool partial_fill) */ { + boost::scoped_array sum_buf (new Sample[2*1048576]); boost::scoped_array mix_buf (new Sample[2*1048576]); boost::scoped_array gain_buf (new float[2*1048576]); - int ret = refill_audio (mix_buf.get(), gain_buf.get(), (partial_fill ? _chunk_samples : 0)); + int ret = refill_audio (sum_buf.get(), mix_buf.get(), gain_buf.get(), (partial_fill ? _chunk_samples : 0)); if (ret) { return ret; @@ -846,9 +811,9 @@ DiskReader::_do_refill_with_alloc (bool partial_fill) } int -DiskReader::refill (Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level) +DiskReader::refill (Sample* sum_buffer, Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level) { - int ret = refill_audio (mixdown_buffer, gain_buffer, fill_level); + int ret = refill_audio (sum_buffer, mixdown_buffer, gain_buffer, fill_level); if (ret) { return ret; @@ -868,7 +833,7 @@ DiskReader::refill (Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill */ int -DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level) +DiskReader::refill_audio (Sample* sum_buffer, Sample* mixdown_buffer, float* gain_buffer, samplecnt_t fill_level) { /* do not read from disk while session is marked as Loading, to avoid useless redundant I/O. @@ -879,15 +844,11 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_ } int32_t ret = 0; - samplecnt_t to_read; - RingBufferNPT::rw_vector vector; bool const reversed = _session.transport_speed() < 0.0f; - samplecnt_t total_space; samplecnt_t zero_fill; uint32_t chan_n; ChannelList::iterator i; boost::shared_ptr c = channels.reader(); - samplecnt_t ts; if (c->empty()) { return 0; @@ -896,14 +857,9 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_ assert(mixdown_buffer); assert(gain_buffer); - vector.buf[0] = 0; - vector.len[0] = 0; - vector.buf[1] = 0; - vector.len[1] = 0; + samplecnt_t total_space = c->front()->rbuf->write_space(); - c->front()->rbuf->get_write_vector (&vector); - - if ((total_space = vector.len[0] + vector.len[1]) == 0) { + if (total_space == 0) { DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: no space to refill\n", name())); /* nowhere to write to */ return 0; @@ -949,59 +905,45 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_ if (reversed) { if (ffa == 0) { - /* at start: nothing to do but fill with silence */ - for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) { - ChannelInfo* chan (*i); - chan->rbuf->get_write_vector (&vector); - memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]); - if (vector.len[1]) { - memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]); + // TODO optimize use zero-buffer + samplecnt_t ws = chan->rbuf->write_space (); + for (uint32_t z = 0; z < ws; ++z) { + Sample t = 0; + chan->rbuf->write (&t, 1); } - chan->rbuf->increment_write_ptr (vector.len[0] + vector.len[1]); } return 0; } if (ffa < total_space) { - - /* too close to the start: read what we can, - and then zero fill the rest - */ - + /* too close to the start: read what we can, and then zero fill the rest */ zero_fill = total_space - ffa; total_space = ffa; - } else { - zero_fill = 0; } } else { if (ffa == max_samplepos) { - /* at end: nothing to do but fill with silence */ - for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) { - ChannelInfo* chan (*i); - chan->rbuf->get_write_vector (&vector); - memset (vector.buf[0], 0, sizeof(Sample) * vector.len[0]); - if (vector.len[1]) { - memset (vector.buf[1], 0, sizeof(Sample) * vector.len[1]); + // TODO optimize use zero-buffer + samplecnt_t ws = chan->rbuf->write_space (); + for (uint32_t z = 0; z < ws; ++z) { + Sample t = 0; + chan->rbuf->write (&t, 1); } - chan->rbuf->increment_write_ptr (vector.len[0] + vector.len[1]); } return 0; } if (ffa > max_samplepos - total_space) { - /* to close to the end: read what we can, and zero fill the rest */ - zero_fill = total_space - (max_samplepos - ffa); total_space = max_samplepos - ffa; @@ -1010,15 +952,11 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_ } } - samplepos_t file_sample_tmp = 0; - /* total_space is in samples. We want to optimize read sizes in various sizes using bytes */ - const size_t bits_per_sample = format_data_width (_session.config.get_native_file_data_format()); size_t total_bytes = total_space * bits_per_sample / 8; - /* chunk size range is 256kB to 4MB. Bigger is faster in terms of MB/sec, but bigger chunk size always takes longer - */ + /* chunk size range is 256kB to 4MB. Bigger is faster in terms of MB/sec, but bigger chunk size always takes longer */ size_t byte_size_for_read = max ((size_t) (256 * 1024), min ((size_t) (4 * 1048576), total_bytes)); /* find nearest (lower) multiple of 16384 */ @@ -1026,90 +964,34 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_ byte_size_for_read = (byte_size_for_read / 16384) * 16384; /* now back to samples */ - samplecnt_t samples_to_read = byte_size_for_read / (bits_per_sample / 8); DEBUG_TRACE (DEBUG::DiskIO, string_compose ("%1: will refill %2 channels with %3 samples\n", name(), c->size(), total_space)); - // uint64_t before = g_get_monotonic_time (); - // uint64_t elapsed; + samplepos_t file_sample_tmp = ffa; for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) { - ChannelInfo* chan (*i); - Sample* buf1; - Sample* buf2; - samplecnt_t len1, len2; - - chan->rbuf->get_write_vector (&vector); - - if ((samplecnt_t) vector.len[0] > samples_to_read) { - - /* we're not going to fill the first chunk, so certainly do not bother with the - other part. it won't be connected with the part we do fill, as in: - - .... => writable space - ++++ => readable space - ^^^^ => 1 x disk_read_chunk_samples that would be filled - - |......|+++++++++++++|...............................| - buf1 buf0 - ^^^^^^^^^^^^^^^ - - - So, just pretend that the buf1 part isn't there. - - */ - - vector.buf[1] = 0; - vector.len[1] = 0; - - } - - ts = total_space; file_sample_tmp = ffa; + samplecnt_t ts = total_space; - buf1 = vector.buf[0]; - len1 = vector.len[0]; - buf2 = vector.buf[1]; - len2 = vector.len[1]; - - to_read = min (ts, len1); - to_read = min (to_read, (samplecnt_t) samples_to_read); - + samplecnt_t to_read = min (ts, (samplecnt_t) chan->rbuf->write_space ()); + to_read = min (to_read, samples_to_read); assert (to_read >= 0); - if (to_read) { + cerr << owner()->name() << " to-read: " << to_read << endl; - if (audio_read (buf1, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) { + if (to_read) { + if (audio_read (chan->rbuf, sum_buffer, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) { + error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), to_read, ffa) << endmsg; ret = -1; goto out; } - chan->rbuf->increment_write_ptr (to_read); - ts -= to_read; - } - - to_read = min (ts, len2); - - if (to_read) { - - /* we read all of vector.len[0], but it wasn't the - entire samples_to_read of data, so read some or - all of vector.len[1] as well. - */ - - if (audio_read (buf2, mixdown_buffer, gain_buffer, file_sample_tmp, to_read, chan_n, reversed)) { - ret = -1; - goto out; - } - - chan->rbuf->increment_write_ptr (to_read); } if (zero_fill) { /* XXX: do something */ } - } // elapsed = g_get_monotonic_time () - before; @@ -1120,8 +1002,6 @@ DiskReader::refill_audio (Sample* mixdown_buffer, float* gain_buffer, samplecnt_ ret = ((total_space - samples_to_read) > _chunk_samples); - c->front()->rbuf->get_write_vector (&vector); - out: return ret; } diff --git a/libs/pbd/pbd/playback_buffer.h b/libs/pbd/pbd/playback_buffer.h new file mode 100644 index 0000000000..fc9bd4d6e0 --- /dev/null +++ b/libs/pbd/pbd/playback_buffer.h @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2000 Paul Davis & Benno Senoner + * Copyright (C) 2019 Robin Gareus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef playback_buffer_h +#define playback_buffer_h + +#include +#include + +#include "pbd/libpbd_visibility.h" +#include "pbd/spinlock.h" + +namespace PBD { + +template +class /*LIBPBD_API*/ PlaybackBuffer +{ +public: + PlaybackBuffer (int32_t sz, guint res = 8191) + : reservation (res) + , _writepos_lock () + { + sz += reservation; + + int32_t power_of_two; + for (power_of_two = 1; 1U << power_of_two < sz; ++power_of_two); + size = 1 << power_of_two; + + size_mask = size - 1; + buf = new T[size]; + + read_idx = 0; + reset (0); + } + + virtual ~PlaybackBuffer () { + delete [] buf; + } + + /* non-linear write needs to reset() the buffer and set the + * position that write() will commence at */ + void reset (int64_t start = 0) { + /* writer, when seeking, may block */ + Glib::Threads::Mutex::Lock lm (_reset_lock); + SpinLock sl (_writepos_lock); + write_pos = start; + + g_atomic_int_set (&write_idx, g_atomic_int_get (&read_idx)); + } + + guint write_space () const { + guint w, r; + + w = g_atomic_int_get (&write_idx); + r = g_atomic_int_get (&read_idx); + + guint rv; + + if (w > r) { + rv = (r - w + size) & size_mask; + } else if (w < r) { + rv = (r - w); + } else { + rv = size; + } + /* it may hapen that the read/invalidation-pointer moves backwards + * e.g. after rec-stop, declick fade-out. + * At the same time the butler may already have written data. + * (it's safe as long as the disk-reader does not move backwards by more + * than reservation) + * XXX disk-reading de-click should not move the invalidation-pointer + */ + if (rv > reservation) { + return rv - 1 - reservation; + } + return 0; + } + + guint read_space () const { + guint w, r; + + w = g_atomic_int_get (&write_idx); + r = g_atomic_int_get (&read_idx); + + if (w > r) { + return w - r; + } else { + return (w - r + size) & size_mask; + } + } + + guint read (T *dest, guint cnt, bool commit = true); + guint write (T const * src, guint cnt); + + T *buffer () { return buf; } + guint bufsize () const { return size; } + + guint get_write_idx () const { return g_atomic_int_get (&write_idx); } + guint get_read_idx () const { return g_atomic_int_get (&read_idx); } + + void read_flush () { g_atomic_int_set (&read_idx, g_atomic_int_get (&write_idx)); } + + void increment_read_ptr (guint cnt) { + cnt = std::min (cnt, read_space ()); + g_atomic_int_set (&read_idx, (g_atomic_int_get (&read_idx) + cnt) & size_mask); + } + +protected: + T *buf; + guint reservation; + guint size; + guint size_mask; + + int64_t write_pos; // samplepos_t + + mutable gint write_idx; // corresponds to (write_pos) + mutable gint read_idx; + +private: + /* spinlock will be used to update write_pos and write_idx in sync */ + mutable spinlock_t _writepos_lock; + /* reset_lock is used to prevent concurrent reading and reset (seek, transport reversal etc). */ + Glib::Threads::Mutex _reset_lock; +}; + + +template /*LIBPBD_API*/ guint +PlaybackBuffer::write (T const *src, guint cnt) +{ + guint w = g_atomic_int_get (&write_idx); + const guint free_cnt = write_space (); + + if (free_cnt == 0) { + return 0; + } + + const guint to_write = cnt > free_cnt ? free_cnt : cnt; + const guint cnt2 = w + to_write; + + guint n1, n2; + if (cnt2 > size) { + n1 = size - w; + n2 = cnt2 & size_mask; + } else { + n1 = to_write; + n2 = 0; + } + + memcpy (&buf[w], src, n1 * sizeof (T)); + w = (w + n1) & size_mask; + + if (n2) { + memcpy (buf, src+n1, n2 * sizeof (T)); + w = n2; + } + + { + SpinLock sl (_writepos_lock); + write_pos += to_write; + g_atomic_int_set (&write_idx, w); + } + return to_write; +} + +template /*LIBPBD_API*/ guint +PlaybackBuffer::read (T *dest, guint cnt, bool commit) +{ + Glib::Threads::Mutex::Lock lm (_reset_lock, Glib::Threads::TRY_LOCK); + if (!lm.locked ()) { + /* seek, reset in progress */ + return 0; + } + + guint r = g_atomic_int_get (&read_idx); + const guint w = g_atomic_int_get (&write_idx); + + const guint free_cnt = (w > r) ? (w - r) : ((w - r + size) & size_mask); + const guint to_read = cnt > free_cnt ? free_cnt : cnt; + + const guint cnt2 = r + to_read; + + guint n1, n2; + if (cnt2 > size) { + n1 = size - r; + n2 = cnt2 & size_mask; + } else { + n1 = to_read; + n2 = 0; + } + + memcpy (dest, &buf[r], n1 * sizeof (T)); + r = (r + n1) & size_mask; + + if (n2) { + memcpy (dest + n1, buf, n2 * sizeof (T)); + r = n2; + } + + if (commit) { + /* set read-pointer to position of last read's end */ + g_atomic_int_set (&read_idx, r); + } + return cnt; +} + +} /* end namespace */ + +#endif /* __ringbuffer_h__ */