revert to single buffer for disk playback, and 5.x-style overwrite
[ardour.git] / libs / pbd / pbd / playback_buffer.h
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 <cstring>
24 #include <stdint.h>
25 #include <glibmm.h>
26
27 #include "pbd/libpbd_visibility.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 guint power_of_two_size (guint 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 (guint sz, guint 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                 g_atomic_int_set (&read_idx, 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         guint bufsize () const { return size; }
62
63         /* write-thread */
64         void reset () {
65                 /* writer, when seeking, may block */
66                 Glib::Threads::Mutex::Lock lm (_reset_lock);
67                 SpinLock sl (_reservation_lock);
68                 g_atomic_int_set (&write_idx, g_atomic_int_get (&read_idx));
69                 g_atomic_int_set (&reserved, 0);
70         }
71
72         /* write-thread */
73         guint write_space () const {
74                 guint w, r;
75
76                 w = g_atomic_int_get (&write_idx);
77                 r = g_atomic_int_get (&read_idx);
78
79                 guint rv;
80
81                 if (w > r) {
82                         rv = (r - w + size) & size_mask;
83                 } else if (w < r) {
84                         rv = (r - w);
85                 } else {
86                         rv = size;
87                 }
88                 /* it may hapen that the read/invalidation-pointer moves backwards
89                  * e.g. after rec-stop, declick fade-out.
90                  * At the same time the butler may already have written data.
91                  * (it's safe as long as the disk-reader does not move backwards by more
92                  * than reservation)
93                  * XXX disk-reading de-click should not move the invalidation-pointer
94                  */
95                 if (rv > reservation) {
96                         return rv - 1 - reservation;
97                 }
98                 return 0;
99         }
100
101         /* read-thread */
102         guint read_space () const {
103                 guint w, r;
104
105                 w = g_atomic_int_get (&write_idx);
106                 r = g_atomic_int_get (&read_idx);
107
108                 if (w > r) {
109                         return w - r;
110                 } else {
111                         return (w - r + size) & size_mask;
112                 }
113         }
114
115         /* read-thead */
116         guint read (T *dest, guint cnt, bool commit = true, guint offset = 0);
117
118         /* write-thead */
119         guint write (T const * src, guint cnt);
120         /* write-thead */
121         guint write_zero (guint cnt);
122         /* read-thead */
123         guint increment_write_ptr (guint cnt)
124         {
125                 cnt = std::min (cnt, write_space ());
126                 g_atomic_int_set (&write_idx, (g_atomic_int_get (&write_idx) + cnt) & size_mask);
127                 return cnt;
128         }
129
130         /* read-thead */
131         void read_flush ()
132         {
133                 SpinLock sl (_reservation_lock);
134                 g_atomic_int_set (&read_idx, g_atomic_int_get (&write_idx));
135                 g_atomic_int_set (&reserved, 0);
136         }
137
138         /* read-thead */
139         guint decrement_read_ptr (guint cnt)
140         {
141                 SpinLock sl (_reservation_lock);
142                 guint r = g_atomic_int_get (&read_idx);
143                 guint res = g_atomic_int_get (&reserved);
144
145                 cnt = std::min (cnt, res);
146
147                 r = (r + size - cnt) & size_mask;
148                 res -= cnt;
149
150                 g_atomic_int_set (&read_idx, r);
151                 g_atomic_int_set (&reserved, res);
152
153                 return cnt;
154         }
155
156         /* read-thead */
157         guint increment_read_ptr (guint cnt)
158         {
159                 cnt = std::min (cnt, read_space ());
160
161                 SpinLock sl (_reservation_lock);
162                 g_atomic_int_set (&read_idx, (g_atomic_int_get (&read_idx) + cnt) & size_mask);
163                 g_atomic_int_set (&reserved, std::min (reservation, g_atomic_int_get (&reserved) + cnt));
164
165                 return cnt;
166         }
167
168         /* read-thead */
169         bool can_seek (int64_t cnt) {
170                 if (cnt > 0) {
171                         return read_space() >= cnt;
172                 }
173                 else if (cnt < 0) {
174                         return g_atomic_int_get (&reserved) >= -cnt;
175                 }
176                 else {
177                         return true;
178                 }
179         }
180
181         guint read_ptr() const { return read_idx; }
182         guint reserved_size() const { return reserved; }
183
184 private:
185         T *buf;
186         guint reservation;
187         guint size;
188         guint size_mask;
189
190         mutable gint write_idx;
191         mutable gint read_idx;
192         mutable gint reserved;
193
194         /* spinlock will be used to update write_idx and reserved in sync */
195         spinlock_t _reservation_lock;
196         /* reset_lock is used to prevent concurrent reading and reset (seek, transport reversal etc). */
197         Glib::Threads::Mutex _reset_lock;
198 };
199
200 template<class T> /*LIBPBD_API*/ guint
201 PlaybackBuffer<T>::write (T const *src, guint cnt)
202 {
203         guint w = g_atomic_int_get (&write_idx);
204         const guint free_cnt = write_space ();
205
206         if (free_cnt == 0) {
207                 return 0;
208         }
209
210         const guint to_write = cnt > free_cnt ? free_cnt : cnt;
211         const guint cnt2 = w + to_write;
212
213         guint n1, n2;
214         if (cnt2 > size) {
215                 n1 = size - w;
216                 n2 = cnt2 & size_mask;
217         } else {
218                 n1 = to_write;
219                 n2 = 0;
220         }
221
222         memcpy (&buf[w], src, n1 * sizeof (T));
223         w = (w + n1) & size_mask;
224
225         if (n2) {
226                 memcpy (buf, src+n1, n2 * sizeof (T));
227                 w = n2;
228         }
229
230         g_atomic_int_set (&write_idx, w);
231         return to_write;
232 }
233
234 template<class T> /*LIBPBD_API*/ guint
235 PlaybackBuffer<T>::write_zero (guint cnt)
236 {
237         guint w = g_atomic_int_get (&write_idx);
238         const guint free_cnt = write_space ();
239
240         if (free_cnt == 0) {
241                 return 0;
242         }
243
244         const guint to_write = cnt > free_cnt ? free_cnt : cnt;
245         const guint cnt2 = w + to_write;
246
247         guint n1, n2;
248         if (cnt2 > size) {
249                 n1 = size - w;
250                 n2 = cnt2 & size_mask;
251         } else {
252                 n1 = to_write;
253                 n2 = 0;
254         }
255
256         memset (&buf[w], 0, n1 * sizeof (T));
257         w = (w + n1) & size_mask;
258
259         if (n2) {
260                 memset (buf, 0, n2 * sizeof (T));
261                 w = n2;
262         }
263
264         g_atomic_int_set (&write_idx, w);
265         return to_write;
266 }
267
268 template<class T> /*LIBPBD_API*/ guint
269 PlaybackBuffer<T>::read (T *dest, guint cnt, bool commit, guint offset)
270 {
271         Glib::Threads::Mutex::Lock lm (_reset_lock, Glib::Threads::TRY_LOCK);
272         if (!lm.locked ()) {
273                 /* seek, reset in progress */
274                 return 0;
275         }
276
277         guint r = g_atomic_int_get (&read_idx);
278         const guint w = g_atomic_int_get (&write_idx);
279
280         guint free_cnt = (w > r) ? (w - r) : ((w - r + size) & size_mask);
281
282         if (!commit && offset > 0) {
283                 if (offset > free_cnt) {
284                         return 0;
285                 }
286                 free_cnt -= offset;
287                 r = (r + offset) & size_mask;
288         }
289
290         const guint to_read = cnt > free_cnt ? free_cnt : cnt;
291
292         const guint cnt2 = r + to_read;
293
294         guint n1, n2;
295         if (cnt2 > size) {
296                 n1 = size - r;
297                 n2 = cnt2 & size_mask;
298         } else {
299                 n1 = to_read;
300                 n2 = 0;
301         }
302
303         memcpy (dest, &buf[r], n1 * sizeof (T));
304         r = (r + n1) & size_mask;
305
306         if (n2) {
307                 memcpy (dest + n1, buf, n2 * sizeof (T));
308                 r = n2;
309         }
310
311         if (commit) {
312                 SpinLock sl (_reservation_lock);
313                 g_atomic_int_set (&read_idx, r);
314                 g_atomic_int_set (&reserved, std::min (reservation, g_atomic_int_get (&reserved) + to_read));
315         }
316         return to_read;
317 }
318
319 } /* end namespace */
320
321 #endif /* __ringbuffer_h__ */