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