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