Ardour  8.12
playback_buffer.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2000 Paul Davis & Benno Senoner
3  * Copyright (C) 2019 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #ifndef playback_buffer_h
21 #define playback_buffer_h
22 
23 #include <atomic>
24 #include <cstdint>
25 #include <cstring>
26 
27 #include <glibmm.h>
28 
29 #include "pbd/libpbd_visibility.h"
30 #include "pbd/spinlock.h"
31 
32 namespace PBD {
33 
34 template<class T>
35 class /*LIBPBD_API*/ PlaybackBuffer
36 {
37 public:
38  static size_t power_of_two_size (size_t sz) {
39  int32_t power_of_two;
40  for (power_of_two = 1; 1U << power_of_two < sz; ++power_of_two);
41  return 1U << power_of_two;
42  }
43 
44  PlaybackBuffer (size_t sz, size_t res = 8191)
45  : reservation (res)
46  {
47  sz += reservation;
48  size = power_of_two_size (sz);
49  size_mask = size - 1;
50  buf = new T[size];
51 
52  read_idx.store (0);
53  reset ();
54  }
55 
56  virtual ~PlaybackBuffer () {
57  delete [] buf;
58  }
59 
60  /* init (mlock) */
61  T *buffer () { return buf; }
62  /* init (mlock) */
63  size_t bufsize () const { return size; }
64 
65  /* write-thread */
66  void reset () {
67  /* writer, when seeking, may block */
70  read_idx.store (0);
71  write_idx.store (0);
72  reserved.store (0);
73  }
74 
75  /* called from rt (reader) thread for new buffers */
76  void align_to (PlaybackBuffer const& other) {
78  read_idx.store (other.read_idx.load());
79  write_idx.store (other.write_idx.load());
80  reserved.store (other.reserved.load());
81  memset (buf, 0, size * sizeof (T));
82  }
83 
84  /* write-thread */
85  size_t write_space () const {
86  size_t w, r;
87 
88  w = write_idx.load ();
89  r = read_idx.load ();
90 
91  size_t rv;
92 
93  if (w > r) {
94  rv = ((r + size) - w) & size_mask;
95  } else if (w < r) {
96  rv = (r - w);
97  } else {
98  rv = size;
99  }
100  /* it may hapen that the read/invalidation-pointer moves backwards
101  * e.g. after rec-stop, declick fade-out.
102  * At the same time the butler may already have written data.
103  * (it's safe as long as the disk-reader does not move backwards by more
104  * than reservation)
105  * XXX disk-reading de-click should not move the invalidation-pointer
106  */
107  if (rv > reservation) {
108  return rv - 1 - reservation;
109  }
110  return 0;
111  }
112 
113  /* read-thread */
114  size_t read_space () const {
115  size_t w, r;
116 
117  w = write_idx.load ();
118  r = read_idx.load ();
119 
120  if (w > r) {
121  return w - r;
122  } else {
123  return ((w + size) - r) & size_mask;
124  }
125  }
126 
127  /* write thread */
128  size_t overwritable_at (size_t r) const {
129  size_t w;
130 
131  w = write_idx.load ();
132 
133  if (w > r) {
134  return w - r;
135  }
136  return (w - r + size) & size_mask;
137  }
138 
139  /* read-thead */
140  size_t read (T *dest, size_t cnt, bool commit = true, size_t offset = 0);
141 
142  /* write-thead */
143  size_t write (T const * src, size_t cnt);
144  /* write-thead */
145  size_t write_zero (size_t cnt);
146  /* read-thead */
147  size_t increment_write_ptr (size_t cnt)
148  {
149  cnt = std::min (cnt, write_space ());
150  write_idx.store ((write_idx.load () + cnt) & size_mask);
151  return cnt;
152  }
153 
154  /* read-thead */
155  size_t decrement_read_ptr (size_t cnt)
156  {
158  size_t r = read_idx.load ();
159  size_t res = reserved.load ();
160 
161  cnt = std::min (cnt, res);
162 
163  r = (r + size - cnt) & size_mask;
164  res -= cnt;
165 
166  read_idx.store (r);
167  reserved.store (res);
168 
169  return cnt;
170  }
171 
172  /* read-thead */
173  size_t increment_read_ptr (size_t cnt)
174  {
175  cnt = std::min (cnt, read_space ());
176 
178  read_idx.store ((read_idx.load () + cnt) & size_mask);
179  reserved.store (std::min (reservation, reserved.load () + cnt));
180 
181  return cnt;
182  }
183 
184  /* read-thead */
185  bool can_seek (int64_t cnt) {
186  if (cnt > 0) {
187  return read_space() >= (size_t) cnt;
188  } else if (cnt < 0) {
189  return reserved.load () >= (size_t) -cnt;
190  } else {
191  return true;
192  }
193  }
194 
195  size_t read_ptr() const { return read_idx.load (); }
196  size_t write_ptr() const { return write_idx.load (); }
197  size_t reserved_size() const { return reserved.load (); }
198  size_t reservation_size() const { return reservation; }
199 
200 private:
201  T *buf;
202  const size_t reservation;
203  size_t size;
204  size_t size_mask;
205 
206  mutable std::atomic<size_t> write_idx;
207  mutable std::atomic<size_t> read_idx;
208  mutable std::atomic<size_t> reserved;
209 
210  /* spinlock will be used to update write_idx and reserved in sync */
212  /* reset_lock is used to prevent concurrent reading and reset (seek, transport reversal etc). */
213  Glib::Threads::Mutex _reset_lock;
214 };
215 
216 template<class T> /*LIBPBD_API*/ size_t
217 PlaybackBuffer<T>::write (T const *src, size_t cnt)
218 {
219  size_t w = write_idx.load ();
220  const size_t free_cnt = write_space ();
221 
222  if (free_cnt == 0) {
223  return 0;
224  }
225 
226  const size_t to_write = cnt > free_cnt ? free_cnt : cnt;
227  const size_t cnt2 = w + to_write;
228 
229  size_t n1, n2;
230  if (cnt2 > size) {
231  n1 = size - w;
232  n2 = cnt2 & size_mask;
233  } else {
234  n1 = to_write;
235  n2 = 0;
236  }
237 
238  memcpy (&buf[w], src, n1 * sizeof (T));
239  w = (w + n1) & size_mask;
240 
241  if (n2) {
242  memcpy (buf, src+n1, n2 * sizeof (T));
243  w = n2;
244  }
245 
246  write_idx.store (w);
247  return to_write;
248 }
249 
250 template<class T> /*LIBPBD_API*/ size_t
252 {
253  size_t w = write_idx.load ();
254  const size_t free_cnt = write_space ();
255 
256  if (free_cnt == 0) {
257  return 0;
258  }
259 
260  const size_t to_write = cnt > free_cnt ? free_cnt : cnt;
261  const size_t cnt2 = w + to_write;
262 
263  size_t n1, n2;
264  if (cnt2 > size) {
265  n1 = size - w;
266  n2 = cnt2 & size_mask;
267  } else {
268  n1 = to_write;
269  n2 = 0;
270  }
271 
272  memset (&buf[w], 0, n1 * sizeof (T));
273  w = (w + n1) & size_mask;
274 
275  if (n2) {
276  memset (buf, 0, n2 * sizeof (T));
277  w = n2;
278  }
279 
280  write_idx.store (w);
281  return to_write;
282 }
283 
284 template<class T> /*LIBPBD_API*/ size_t
285 PlaybackBuffer<T>::read (T *dest, size_t cnt, bool commit, size_t offset)
286 {
287  Glib::Threads::Mutex::Lock lm (_reset_lock, Glib::Threads::TRY_LOCK);
288  if (!lm.locked ()) {
289  /* seek, reset in progress */
290  return 0;
291  }
292 
293  size_t r = read_idx.load ();
294  const size_t w = write_idx.load ();
295 
296  size_t free_cnt = (w > r) ? (w - r) : ((w - r + size) & size_mask);
297 
298  if (!commit && offset > 0) {
299  if (offset > free_cnt) {
300  return 0;
301  }
302  free_cnt -= offset;
303  r = (r + offset) & size_mask;
304  }
305 
306  const size_t to_read = cnt > free_cnt ? free_cnt : cnt;
307 
308  const size_t cnt2 = r + to_read;
309 
310  size_t n1, n2;
311  if (cnt2 > size) {
312  n1 = size - r;
313  n2 = cnt2 & size_mask;
314  } else {
315  n1 = to_read;
316  n2 = 0;
317  }
318 
319  memcpy (dest, &buf[r], n1 * sizeof (T));
320  r = (r + n1) & size_mask;
321 
322  if (n2) {
323  memcpy (dest + n1, buf, n2 * sizeof (T));
324  r = n2;
325  }
326 
327  if (commit) {
328  SpinLock sl (_reservation_lock);
329  read_idx.store (r);
330  reserved.store (std::min (reservation, reserved.load () + to_read));
331  }
332  return to_read;
333 }
334 
335 } /* end namespace */
336 
337 #endif /* __ringbuffer_h__ */
spinlock_t _reservation_lock
bool can_seek(int64_t cnt)
size_t write_ptr() const
size_t reservation_size() const
static size_t power_of_two_size(size_t sz)
Glib::Threads::Mutex _reset_lock
size_t decrement_read_ptr(size_t cnt)
std::atomic< size_t > read_idx
std::atomic< size_t > write_idx
size_t read(T *dest, size_t cnt, bool commit=true, size_t offset=0)
size_t read_space() const
size_t bufsize() const
size_t increment_read_ptr(size_t cnt)
std::atomic< size_t > reserved
PlaybackBuffer(size_t sz, size_t res=8191)
size_t write(T const *src, size_t cnt)
size_t read_ptr() const
const size_t reservation
size_t write_zero(size_t cnt)
size_t reserved_size() const
size_t write_space() const
void align_to(PlaybackBuffer const &other)
size_t overwritable_at(size_t r) const
size_t increment_write_ptr(size_t cnt)
void memset(float *data, const float val, const uint32_t n_samples)
Definition: axis_view.h:42