13
0

Towards a new disk-reader ringbuffer

This is mainly a NO-OP, introducing a new PlaybackBuffer type
and preparing for its use.

At this point in time, the buffer is just a power-of-two sized
ringbuffer and the disk-reader's read-logic is still unchanged.

Eventually the read and write sample position that are currently
private to the disk-reader can be migrated to be owned by the buffer.
Also Diskreader::read() positions can be matched to read-position ..
+/- buffer reservation and de-click can read w/o committing the read.
This commit is contained in:
Robin Gareus 2019-02-05 20:51:33 +01:00
parent 3cc3074dc7
commit 007c4ffdc0
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
5 changed files with 325 additions and 213 deletions

View File

@ -30,6 +30,10 @@
#include "ardour/interpolation.h"
#include "ardour/processor.h"
namespace PBD {
template<class T> 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<Sample>* rbuf;
PBD::PlaybackBuffer<Sample>* rbuf;
/** A ringbuffer for data to be recorded back, written to in the
* process thread, read from in the butler thread.

View File

@ -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>*,
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);

View File

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

View File

@ -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 <robin@gareus.org>
*
* 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 <boost/smart_ptr/scoped_array.hpp>
#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<void> 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<Sample> (bufsize);
rbuf = new PlaybackBuffer<Sample> (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<Sample>* b = c->front()->rbuf;
PBD::PlaybackBuffer<Sample>* 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<ChannelList> 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<Sample> sum_buffer (new Sample[size]);
boost::scoped_array<Sample> mixdown_buffer (new Sample[size]);
boost::scoped_array<float> 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<Sample>*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<Sample> sum_buf (new Sample[2*1048576]);
boost::scoped_array<Sample> mix_buf (new Sample[2*1048576]);
boost::scoped_array<float> 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<Sample>::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<ChannelList> 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;
}

View File

@ -0,0 +1,224 @@
/*
* Copyright (C) 2000 Paul Davis & Benno Senoner
* Copyright (C) 2019 Robin Gareus <robin@gareus.org>
*
* 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 <cstring>
#include <glibmm.h>
#include "pbd/libpbd_visibility.h"
#include "pbd/spinlock.h"
namespace PBD {
template<class T>
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<class T> /*LIBPBD_API*/ guint
PlaybackBuffer<T>::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<class T> /*LIBPBD_API*/ guint
PlaybackBuffer<T>::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__ */