1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21 */
22
23 namespace juce
24 {
25
BufferingAudioSource(PositionableAudioSource * s,TimeSliceThread & thread,bool deleteSourceWhenDeleted,int bufferSizeSamples,int numChannels,bool prefillBufferOnPrepareToPlay)26 BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s,
27 TimeSliceThread& thread,
28 bool deleteSourceWhenDeleted,
29 int bufferSizeSamples,
30 int numChannels,
31 bool prefillBufferOnPrepareToPlay)
32 : source (s, deleteSourceWhenDeleted),
33 backgroundThread (thread),
34 numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)),
35 numberOfChannels (numChannels),
36 prefillBuffer (prefillBufferOnPrepareToPlay)
37 {
38 jassert (source != nullptr);
39
40 jassert (numberOfSamplesToBuffer > 1024); // not much point using this class if you're
41 // not using a larger buffer..
42 }
43
~BufferingAudioSource()44 BufferingAudioSource::~BufferingAudioSource()
45 {
46 releaseResources();
47 }
48
49 //==============================================================================
prepareToPlay(int samplesPerBlockExpected,double newSampleRate)50 void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate)
51 {
52 auto bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer);
53
54 if (newSampleRate != sampleRate
55 || bufferSizeNeeded != buffer.getNumSamples()
56 || ! isPrepared)
57 {
58 backgroundThread.removeTimeSliceClient (this);
59
60 isPrepared = true;
61 sampleRate = newSampleRate;
62
63 source->prepareToPlay (samplesPerBlockExpected, newSampleRate);
64
65 buffer.setSize (numberOfChannels, bufferSizeNeeded);
66 buffer.clear();
67
68 bufferValidStart = 0;
69 bufferValidEnd = 0;
70
71 backgroundThread.addTimeSliceClient (this);
72
73 do
74 {
75 backgroundThread.moveToFrontOfQueue (this);
76 Thread::sleep (5);
77 }
78 while (prefillBuffer
79 && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2)));
80 }
81 }
82
releaseResources()83 void BufferingAudioSource::releaseResources()
84 {
85 isPrepared = false;
86 backgroundThread.removeTimeSliceClient (this);
87
88 buffer.setSize (numberOfChannels, 0);
89
90 // MSVC2015 seems to need this if statement to not generate a warning during linking.
91 // As source is set in the constructor, there is no way that source could
92 // ever equal this, but it seems to make MSVC2015 happy.
93 if (source != this)
94 source->releaseResources();
95 }
96
getNextAudioBlock(const AudioSourceChannelInfo & info)97 void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
98 {
99 const ScopedLock sl (bufferStartPosLock);
100
101 auto start = bufferValidStart.load();
102 auto end = bufferValidEnd.load();
103 auto pos = nextPlayPos.load();
104
105 auto validStart = (int) (jlimit (start, end, pos) - pos);
106 auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos);
107
108 if (validStart == validEnd)
109 {
110 // total cache miss
111 info.clearActiveBufferRegion();
112 }
113 else
114 {
115 if (validStart > 0)
116 info.buffer->clear (info.startSample, validStart); // partial cache miss at start
117
118 if (validEnd < info.numSamples)
119 info.buffer->clear (info.startSample + validEnd,
120 info.numSamples - validEnd); // partial cache miss at end
121
122 if (validStart < validEnd)
123 {
124 for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
125 {
126 jassert (buffer.getNumSamples() > 0);
127 auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
128 auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());
129
130 if (startBufferIndex < endBufferIndex)
131 {
132 info.buffer->copyFrom (chan, info.startSample + validStart,
133 buffer,
134 chan, startBufferIndex,
135 validEnd - validStart);
136 }
137 else
138 {
139 auto initialSize = buffer.getNumSamples() - startBufferIndex;
140
141 info.buffer->copyFrom (chan, info.startSample + validStart,
142 buffer,
143 chan, startBufferIndex,
144 initialSize);
145
146 info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
147 buffer,
148 chan, 0,
149 (validEnd - validStart) - initialSize);
150 }
151 }
152 }
153
154 nextPlayPos += info.numSamples;
155 }
156 }
157
waitForNextAudioBlockReady(const AudioSourceChannelInfo & info,uint32 timeout)158 bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout)
159 {
160 if (!source || source->getTotalLength() <= 0)
161 return false;
162
163 if (nextPlayPos + info.numSamples < 0)
164 return true;
165
166 if (! isLooping() && nextPlayPos > getTotalLength())
167 return true;
168
169 auto now = Time::getMillisecondCounter();
170 auto startTime = now;
171
172 auto elapsed = (now >= startTime ? now - startTime
173 : (std::numeric_limits<uint32>::max() - startTime) + now);
174
175 while (elapsed <= timeout)
176 {
177 {
178 const ScopedLock sl (bufferStartPosLock);
179
180 auto start = bufferValidStart.load();
181 auto end = bufferValidEnd.load();
182 auto pos = nextPlayPos.load();
183
184 auto validStart = static_cast<int> (jlimit (start, end, pos) - pos);
185 auto validEnd = static_cast<int> (jlimit (start, end, pos + info.numSamples) - pos);
186
187 if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples)
188 return true;
189 }
190
191 if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed))))
192 return false;
193
194 now = Time::getMillisecondCounter();
195 elapsed = (now >= startTime ? now - startTime
196 : (std::numeric_limits<uint32>::max() - startTime) + now);
197 }
198
199 return false;
200 }
201
getNextReadPosition() const202 int64 BufferingAudioSource::getNextReadPosition() const
203 {
204 jassert (source->getTotalLength() > 0);
205 auto pos = nextPlayPos.load();
206
207 return (source->isLooping() && nextPlayPos > 0)
208 ? pos % source->getTotalLength()
209 : pos;
210 }
211
setNextReadPosition(int64 newPosition)212 void BufferingAudioSource::setNextReadPosition (int64 newPosition)
213 {
214 const ScopedLock sl (bufferStartPosLock);
215
216 nextPlayPos = newPosition;
217 backgroundThread.moveToFrontOfQueue (this);
218 }
219
readNextBufferChunk()220 bool BufferingAudioSource::readNextBufferChunk()
221 {
222 int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd;
223
224 {
225 const ScopedLock sl (bufferStartPosLock);
226
227 if (wasSourceLooping != isLooping())
228 {
229 wasSourceLooping = isLooping();
230 bufferValidStart = 0;
231 bufferValidEnd = 0;
232 }
233
234 newBVS = jmax ((int64) 0, nextPlayPos.load());
235 newBVE = newBVS + buffer.getNumSamples() - 4;
236 sectionToReadStart = 0;
237 sectionToReadEnd = 0;
238
239 const int maxChunkSize = 2048;
240
241 if (newBVS < bufferValidStart || newBVS >= bufferValidEnd)
242 {
243 newBVE = jmin (newBVE, newBVS + maxChunkSize);
244
245 sectionToReadStart = newBVS;
246 sectionToReadEnd = newBVE;
247
248 bufferValidStart = 0;
249 bufferValidEnd = 0;
250 }
251 else if (std::abs ((int) (newBVS - bufferValidStart)) > 512
252 || std::abs ((int) (newBVE - bufferValidEnd)) > 512)
253 {
254 newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize);
255
256 sectionToReadStart = bufferValidEnd;
257 sectionToReadEnd = newBVE;
258
259 bufferValidStart = newBVS;
260 bufferValidEnd = jmin (bufferValidEnd.load(), newBVE);
261 }
262 }
263
264 if (sectionToReadStart == sectionToReadEnd)
265 return false;
266
267 jassert (buffer.getNumSamples() > 0);
268 auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
269 auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());
270
271 if (bufferIndexStart < bufferIndexEnd)
272 {
273 readBufferSection (sectionToReadStart,
274 (int) (sectionToReadEnd - sectionToReadStart),
275 bufferIndexStart);
276 }
277 else
278 {
279 auto initialSize = buffer.getNumSamples() - bufferIndexStart;
280
281 readBufferSection (sectionToReadStart,
282 initialSize,
283 bufferIndexStart);
284
285 readBufferSection (sectionToReadStart + initialSize,
286 (int) (sectionToReadEnd - sectionToReadStart) - initialSize,
287 0);
288 }
289
290 {
291 const ScopedLock sl2 (bufferStartPosLock);
292
293 bufferValidStart = newBVS;
294 bufferValidEnd = newBVE;
295 }
296
297 bufferReadyEvent.signal();
298 return true;
299 }
300
readBufferSection(int64 start,int length,int bufferOffset)301 void BufferingAudioSource::readBufferSection (int64 start, int length, int bufferOffset)
302 {
303 if (source->getNextReadPosition() != start)
304 source->setNextReadPosition (start);
305
306 AudioSourceChannelInfo info (&buffer, bufferOffset, length);
307 source->getNextAudioBlock (info);
308 }
309
useTimeSlice()310 int BufferingAudioSource::useTimeSlice()
311 {
312 return readNextBufferChunk() ? 1 : 100;
313 }
314
315 } // namespace juce
316