Ardour  9.2-129-gdf5e1050bd
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 #pragma once
21 
22 #include <atomic>
23 #include <cstdint>
24 #include <cstring>
25 
26 #include "pbd/libpbd_visibility.h"
27 #include "pbd/mutex.h"
28 #include "pbd/spinlock.h"
29 
30 namespace PBD {
31 
32 template<class T>
33 class /*LIBPBD_API*/ PlaybackBuffer
34 {
35 public:
36  static size_t power_of_two_size (size_t sz) {
37  int32_t power_of_two;
38  for (power_of_two = 1; 1U << power_of_two < sz; ++power_of_two);
39  return 1U << power_of_two;
40  }
41 
42  PlaybackBuffer (size_t sz, size_t res = 8191)
43  : reservation (res)
44  {
45  sz += reservation;
46  size = power_of_two_size (sz);
47  size_mask = size - 1;
48  buf = new T[size];
49 
50  read_idx.store (0);
51  reset ();
52  }
53 
54  virtual ~PlaybackBuffer () {
55  delete [] buf;
56  }
57 
58  /* init (mlock) */
59  T *buffer () { return buf; }
60  /* init (mlock) */
61  size_t bufsize () const { return size; }
62 
63  /* write-thread */
64  void reset () {
65  /* writer, when seeking, may block */
68  read_idx.store (0);
69  write_idx.store (0);
70  reserved.store (0);
71  }
72 
73  /* called from rt (reader) thread for new buffers */
74  void align_to (PlaybackBuffer const& other) {
76  read_idx.store (other.read_idx.load());
77  write_idx.store (other.write_idx.load());
78  reserved.store (other.reserved.load());
79  memset (buf, 0, size * sizeof (T));
80  }
81 
82  /* write-thread */
83  size_t write_space () const {
84  size_t w, r;
85 
86  w = write_idx.load ();
87  r = read_idx.load ();
88 
89  size_t rv;
90 
91  if (w > r) {
92  rv = ((r + size) - w) & size_mask;
93  } else if (w < r) {
94  rv = (r - w);
95  } else {
96  rv = size;
97  }
98  /* it may hapen that the read/invalidation-pointer moves backwards
99  * e.g. after rec-stop, declick fade-out.
100  * At the same time the butler may already have written data.
101  * (it's safe as long as the disk-reader does not move backwards by more
102  * than reservation)
103  * XXX disk-reading de-click should not move the invalidation-pointer
104  */
105  if (rv > reservation) {
106  return rv - 1 - reservation;
107  }
108  return 0;
109  }
110 
111  /* read-thread */
112  size_t read_space () const {
113  size_t w, r;
114 
115  w = write_idx.load ();
116  r = read_idx.load ();
117 
118  if (w > r) {
119  return w - r;
120  } else {
121  return ((w + size) - r) & size_mask;
122  }
123  }
124 
125  /* write thread */
126  size_t overwritable_at (size_t r) const {
127  size_t w;
128 
129  w = write_idx.load ();
130 
131  if (w > r) {
132  return w - r;
133  }
134  return (w - r + size) & size_mask;
135  }
136 
137  /* read-thead */
138  size_t read (T *dest, size_t cnt, bool commit = true, size_t offset = 0);
139 
140  /* write-thead */
141  size_t write (T const * src, size_t cnt);
142  /* write-thead */
143  size_t write_zero (size_t cnt);
144  /* read-thead */
145  size_t increment_write_ptr (size_t cnt)
146  {
147  cnt = std::min (cnt, write_space ());
148  write_idx.store ((write_idx.load () + cnt) & size_mask);
149  return cnt;
150  }
151 
152  /* read-thead */
153  size_t decrement_read_ptr (size_t cnt)
154  {
156  size_t r = read_idx.load ();
157  size_t res = reserved.load ();
158 
159  cnt = std::min (cnt, res);
160 
161  r = (r + size - cnt) & size_mask;
162  res -= cnt;
163 
164  read_idx.store (r);
165  reserved.store (res);
166 
167  return cnt;
168  }
169 
170  /* read-thead */
171  size_t increment_read_ptr (size_t cnt)
172  {
173  cnt = std::min (cnt, read_space ());
174 
176  read_idx.store ((read_idx.load () + cnt) & size_mask);
177  reserved.store (std::min (reservation, reserved.load () + cnt));
178 
179  return cnt;
180  }
181 
182  /* read-thead */
183  bool can_seek (int64_t cnt) {
184  if (cnt > 0) {
185  return read_space() >= (size_t) cnt;
186  } else if (cnt < 0) {
187  return reserved.load () >= (size_t) -cnt;
188  } else {
189  return true;
190  }
191  }
192 
193  size_t read_ptr() const { return read_idx.load (); }
194  size_t write_ptr() const { return write_idx.load (); }
195  size_t reserved_size() const { return reserved.load (); }
196  size_t reservation_size() const { return reservation; }
197 
198 private:
199  T *buf;
200  const size_t reservation;
201  size_t size;
202  size_t size_mask;
203 
204  mutable std::atomic<size_t> write_idx;
205  mutable std::atomic<size_t> read_idx;
206  mutable std::atomic<size_t> reserved;
207 
208  /* spinlock will be used to update write_idx and reserved in sync */
210  /* reset_lock is used to prevent concurrent reading and reset (seek, transport reversal etc). */
212 };
213 
214 template<class T> /*LIBPBD_API*/ size_t
215 PlaybackBuffer<T>::write (T const *src, size_t cnt)
216 {
217  size_t w = write_idx.load ();
218  const size_t free_cnt = write_space ();
219 
220  if (free_cnt == 0) {
221  return 0;
222  }
223 
224  const size_t to_write = cnt > free_cnt ? free_cnt : cnt;
225  const size_t cnt2 = w + to_write;
226 
227  size_t n1, n2;
228  if (cnt2 > size) {
229  n1 = size - w;
230  n2 = cnt2 & size_mask;
231  } else {
232  n1 = to_write;
233  n2 = 0;
234  }
235 
236  memcpy (&buf[w], src, n1 * sizeof (T));
237  w = (w + n1) & size_mask;
238 
239  if (n2) {
240  memcpy (buf, src+n1, n2 * sizeof (T));
241  w = n2;
242  }
243 
244  write_idx.store (w);
245  return to_write;
246 }
247 
248 template<class T> /*LIBPBD_API*/ size_t
250 {
251  size_t w = write_idx.load ();
252  const size_t free_cnt = write_space ();
253 
254  if (free_cnt == 0) {
255  return 0;
256  }
257 
258  const size_t to_write = cnt > free_cnt ? free_cnt : cnt;
259  const size_t cnt2 = w + to_write;
260 
261  size_t n1, n2;
262  if (cnt2 > size) {
263  n1 = size - w;
264  n2 = cnt2 & size_mask;
265  } else {
266  n1 = to_write;
267  n2 = 0;
268  }
269 
270  memset (&buf[w], 0, n1 * sizeof (T));
271  w = (w + n1) & size_mask;
272 
273  if (n2) {
274  memset (buf, 0, n2 * sizeof (T));
275  w = n2;
276  }
277 
278  write_idx.store (w);
279  return to_write;
280 }
281 
282 template<class T> /*LIBPBD_API*/ size_t
283 PlaybackBuffer<T>::read (T *dest, size_t cnt, bool commit, size_t offset)
284 {
285  PBD::Mutex::Lock lm (_reset_lock, PBD::Mutex::TryLock);
286  if (!lm.locked ()) {
287  /* seek, reset in progress */
288  return 0;
289  }
290 
291  size_t r = read_idx.load ();
292  const size_t w = write_idx.load ();
293 
294  size_t free_cnt = (w > r) ? (w - r) : ((w - r + size) & size_mask);
295 
296  if (!commit && offset > 0) {
297  if (offset > free_cnt) {
298  return 0;
299  }
300  free_cnt -= offset;
301  r = (r + offset) & size_mask;
302  }
303 
304  const size_t to_read = cnt > free_cnt ? free_cnt : cnt;
305 
306  const size_t cnt2 = r + to_read;
307 
308  size_t n1, n2;
309  if (cnt2 > size) {
310  n1 = size - r;
311  n2 = cnt2 & size_mask;
312  } else {
313  n1 = to_read;
314  n2 = 0;
315  }
316 
317  memcpy (dest, &buf[r], n1 * sizeof (T));
318  r = (r + n1) & size_mask;
319 
320  if (n2) {
321  memcpy (dest + n1, buf, n2 * sizeof (T));
322  r = n2;
323  }
324 
325  if (commit) {
326  SpinLock sl (_reservation_lock);
327  read_idx.store (r);
328  reserved.store (std::min (reservation, reserved.load () + to_read));
329  }
330  return to_read;
331 }
332 
333 } /* end namespace */
334 
bool locked() const
Definition: mutex.h:94
@ TryLock
Definition: mutex.h:40
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)
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