fc9bd4d6e0dfc4217a08c4185f498c6a237081a4
[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
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (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
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 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 <glibmm.h>
25
26 #include "pbd/libpbd_visibility.h"
27 #include "pbd/spinlock.h"
28
29 namespace PBD {
30
31 template<class T>
32 class /*LIBPBD_API*/ PlaybackBuffer
33 {
34 public:
35         PlaybackBuffer (int32_t sz, guint res = 8191)
36         : reservation (res)
37         , _writepos_lock ()
38         {
39                 sz += reservation;
40
41                 int32_t power_of_two;
42                 for (power_of_two = 1; 1U << power_of_two < sz; ++power_of_two);
43                 size = 1 << power_of_two;
44
45                 size_mask = size - 1;
46                 buf = new T[size];
47
48                 read_idx = 0;
49                 reset (0);
50         }
51
52         virtual ~PlaybackBuffer () {
53                 delete [] buf;
54         }
55
56         /* non-linear write needs to reset() the buffer and set the
57          * position that write() will commence at */
58         void reset (int64_t start = 0) {
59                 /* writer, when seeking, may block */
60                 Glib::Threads::Mutex::Lock lm (_reset_lock);
61                 SpinLock sl (_writepos_lock);
62                 write_pos = start;
63
64                 g_atomic_int_set (&write_idx, g_atomic_int_get (&read_idx));
65         }
66
67         guint write_space () const {
68                 guint w, r;
69
70                 w = g_atomic_int_get (&write_idx);
71                 r = g_atomic_int_get (&read_idx);
72
73                 guint rv;
74
75                 if (w > r) {
76                         rv = (r - w + size) & size_mask;
77                 } else if (w < r) {
78                         rv = (r - w);
79                 } else {
80                         rv = size;
81                 }
82                 /* it may hapen that the read/invalidation-pointer moves backwards
83                  * e.g. after rec-stop, declick fade-out.
84                  * At the same time the butler may already have written data.
85                  * (it's safe as long as the disk-reader does not move backwards by more
86                  * than reservation)
87                  * XXX disk-reading de-click should not move the invalidation-pointer
88                  */
89                 if (rv > reservation) {
90                         return rv - 1 - reservation;
91                 }
92                 return 0;
93         }
94
95         guint read_space () const {
96                 guint w, r;
97
98                 w = g_atomic_int_get (&write_idx);
99                 r = g_atomic_int_get (&read_idx);
100
101                 if (w > r) {
102                         return w - r;
103                 } else {
104                         return (w - r + size) & size_mask;
105                 }
106         }
107
108         guint read  (T *dest, guint cnt, bool commit = true);
109         guint write (T const * src,  guint cnt);
110
111         T *buffer () { return buf; }
112         guint bufsize () const { return size; }
113
114         guint get_write_idx () const { return g_atomic_int_get (&write_idx); }
115         guint get_read_idx () const { return g_atomic_int_get (&read_idx); }
116
117         void read_flush () { g_atomic_int_set (&read_idx, g_atomic_int_get (&write_idx)); }
118
119         void increment_read_ptr (guint cnt) {
120                 cnt = std::min (cnt, read_space ());
121                 g_atomic_int_set (&read_idx, (g_atomic_int_get (&read_idx) + cnt) & size_mask);
122         }
123
124 protected:
125         T *buf;
126         guint reservation;
127         guint size;
128         guint size_mask;
129
130         int64_t write_pos; // samplepos_t
131
132         mutable gint write_idx; // corresponds to (write_pos)
133         mutable gint read_idx;
134
135 private:
136         /* spinlock will be used to update write_pos and write_idx in sync */
137         mutable spinlock_t   _writepos_lock;
138         /* reset_lock is used to prevent concurrent reading and reset (seek, transport reversal etc). */
139         Glib::Threads::Mutex _reset_lock;
140 };
141
142
143 template<class T> /*LIBPBD_API*/ guint
144 PlaybackBuffer<T>::write (T const *src, guint cnt)
145 {
146         guint w = g_atomic_int_get (&write_idx);
147         const guint free_cnt = write_space ();
148
149         if (free_cnt == 0) {
150                 return 0;
151         }
152
153         const guint to_write = cnt > free_cnt ? free_cnt : cnt;
154         const guint cnt2 = w + to_write;
155
156         guint n1, n2;
157         if (cnt2 > size) {
158                 n1 = size - w;
159                 n2 = cnt2 & size_mask;
160         } else {
161                 n1 = to_write;
162                 n2 = 0;
163         }
164
165         memcpy (&buf[w], src, n1 * sizeof (T));
166         w = (w + n1) & size_mask;
167
168         if (n2) {
169                 memcpy (buf, src+n1, n2 * sizeof (T));
170                 w = n2;
171         }
172
173         {
174                 SpinLock sl (_writepos_lock);
175                 write_pos += to_write;
176                 g_atomic_int_set (&write_idx, w);
177         }
178         return to_write;
179 }
180
181 template<class T> /*LIBPBD_API*/ guint
182 PlaybackBuffer<T>::read (T *dest, guint cnt, bool commit)
183 {
184         Glib::Threads::Mutex::Lock lm (_reset_lock, Glib::Threads::TRY_LOCK);
185         if (!lm.locked ()) {
186                 /* seek, reset in progress */
187                 return 0;
188         }
189
190         guint r = g_atomic_int_get (&read_idx);
191         const guint w = g_atomic_int_get (&write_idx);
192
193         const guint free_cnt = (w > r) ? (w - r) : ((w - r + size) & size_mask);
194         const guint to_read = cnt > free_cnt ? free_cnt : cnt;
195
196         const guint cnt2 = r + to_read;
197
198         guint n1, n2;
199         if (cnt2 > size) {
200                 n1 = size - r;
201                 n2 = cnt2 & size_mask;
202         } else {
203                 n1 = to_read;
204                 n2 = 0;
205         }
206
207         memcpy (dest, &buf[r], n1 * sizeof (T));
208         r = (r + n1) & size_mask;
209
210         if (n2) {
211                 memcpy (dest + n1, buf, n2 * sizeof (T));
212                 r = n2;
213         }
214
215         if (commit) {
216                 /* set read-pointer to position of last read's end */
217                 g_atomic_int_set (&read_idx, r);
218         }
219         return cnt;
220 }
221
222 } /* end namespace */
223
224 #endif /* __ringbuffer_h__ */