fix thinko when testing for internal seek with negative distance
[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
123         /* read-thead */
124         void read_flush ()
125         {
126                 SpinLock sl (_reservation_lock);
127                 g_atomic_int_set (&read_idx, g_atomic_int_get (&write_idx));
128                 g_atomic_int_set (&reserved, 0);
129         }
130
131         /* read-thead */
132         guint decrement_read_ptr (guint cnt)
133         {
134                 SpinLock sl (_reservation_lock);
135                 guint r = g_atomic_int_get (&read_idx);
136                 guint res = g_atomic_int_get (&reserved);
137
138                 cnt = std::min (cnt, res);
139
140                 r = (r + size - cnt) & size_mask;
141                 res -= cnt;
142
143                 g_atomic_int_set (&read_idx, r);
144                 g_atomic_int_set (&reserved, res);
145
146                 return cnt;
147         }
148
149         /* read-thead */
150         guint increment_read_ptr (guint cnt)
151         {
152                 cnt = std::min (cnt, read_space ());
153
154                 SpinLock sl (_reservation_lock);
155                 g_atomic_int_set (&read_idx, (g_atomic_int_get (&read_idx) + cnt) & size_mask);
156                 g_atomic_int_set (&reserved, std::min (reservation, g_atomic_int_get (&reserved) + cnt));
157
158                 return cnt;
159         }
160
161         /* read-thead */
162         bool can_seek (int64_t cnt) {
163                 if (cnt > 0) {
164                         return read_space() >= cnt;
165                 }
166                 else if (cnt < 0) {
167                         return g_atomic_int_get (&reserved) >= -cnt;
168                 }
169                 else {
170                         return true;
171                 }
172         }
173
174 private:
175         T *buf;
176         guint reservation;
177         guint size;
178         guint size_mask;
179
180         mutable gint write_idx;
181         mutable gint read_idx;
182         mutable gint reserved;
183
184         /* spinlock will be used to update write_idx and reserved in sync */
185         spinlock_t _reservation_lock;
186         /* reset_lock is used to prevent concurrent reading and reset (seek, transport reversal etc). */
187         Glib::Threads::Mutex _reset_lock;
188 };
189
190 template<class T> /*LIBPBD_API*/ guint
191 PlaybackBuffer<T>::write (T const *src, guint cnt)
192 {
193         guint w = g_atomic_int_get (&write_idx);
194         const guint free_cnt = write_space ();
195
196         if (free_cnt == 0) {
197                 return 0;
198         }
199
200         const guint to_write = cnt > free_cnt ? free_cnt : cnt;
201         const guint cnt2 = w + to_write;
202
203         guint n1, n2;
204         if (cnt2 > size) {
205                 n1 = size - w;
206                 n2 = cnt2 & size_mask;
207         } else {
208                 n1 = to_write;
209                 n2 = 0;
210         }
211
212         memcpy (&buf[w], src, n1 * sizeof (T));
213         w = (w + n1) & size_mask;
214
215         if (n2) {
216                 memcpy (buf, src+n1, n2 * sizeof (T));
217                 w = n2;
218         }
219
220         g_atomic_int_set (&write_idx, w);
221         return to_write;
222 }
223
224 template<class T> /*LIBPBD_API*/ guint
225 PlaybackBuffer<T>::write_zero (guint cnt)
226 {
227         guint w = g_atomic_int_get (&write_idx);
228         const guint free_cnt = write_space ();
229
230         if (free_cnt == 0) {
231                 return 0;
232         }
233
234         const guint to_write = cnt > free_cnt ? free_cnt : cnt;
235         const guint cnt2 = w + to_write;
236
237         guint n1, n2;
238         if (cnt2 > size) {
239                 n1 = size - w;
240                 n2 = cnt2 & size_mask;
241         } else {
242                 n1 = to_write;
243                 n2 = 0;
244         }
245
246         memset (&buf[w], 0, n1 * sizeof (T));
247         w = (w + n1) & size_mask;
248
249         if (n2) {
250                 memset (buf, 0, n2 * sizeof (T));
251                 w = n2;
252         }
253
254         g_atomic_int_set (&write_idx, w);
255         return to_write;
256 }
257
258 template<class T> /*LIBPBD_API*/ guint
259 PlaybackBuffer<T>::read (T *dest, guint cnt, bool commit, guint offset)
260 {
261         Glib::Threads::Mutex::Lock lm (_reset_lock, Glib::Threads::TRY_LOCK);
262         if (!lm.locked ()) {
263                 /* seek, reset in progress */
264                 return 0;
265         }
266
267         guint r = g_atomic_int_get (&read_idx);
268         const guint w = g_atomic_int_get (&write_idx);
269
270         guint free_cnt = (w > r) ? (w - r) : ((w - r + size) & size_mask);
271
272         if (!commit && offset > 0) {
273                 if (offset > free_cnt) {
274                         return 0;
275                 }
276                 free_cnt -= offset;
277                 r = (r + offset) & size_mask;
278         }
279
280         const guint to_read = cnt > free_cnt ? free_cnt : cnt;
281
282         const guint cnt2 = r + to_read;
283
284         guint n1, n2;
285         if (cnt2 > size) {
286                 n1 = size - r;
287                 n2 = cnt2 & size_mask;
288         } else {
289                 n1 = to_read;
290                 n2 = 0;
291         }
292
293         memcpy (dest, &buf[r], n1 * sizeof (T));
294         r = (r + n1) & size_mask;
295
296         if (n2) {
297                 memcpy (dest + n1, buf, n2 * sizeof (T));
298                 r = n2;
299         }
300
301         if (commit) {
302                 SpinLock sl (_reservation_lock);
303                 g_atomic_int_set (&read_idx, r);
304                 g_atomic_int_set (&reserved, std::min (reservation, g_atomic_int_get (&reserved) + to_read));
305         }
306         return to_read;
307 }
308
309 } /* end namespace */
310
311 #endif /* __ringbuffer_h__ */