OpenShot Audio Library | OpenShotAudio 0.4.0
Loading...
Searching...
No Matches
juce_BufferingAudioSource.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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
23namespace juce
24{
25
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
48
49//==============================================================================
50void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate)
51{
52 auto bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer);
53
54 if (! approximatelyEqual (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 const ScopedLock sl (bufferRangeLock);
69
70 bufferValidStart = 0;
71 bufferValidEnd = 0;
72
73 backgroundThread.addTimeSliceClient (this);
74
75 do
76 {
77 const ScopedUnlock ul (bufferRangeLock);
78
79 backgroundThread.moveToFrontOfQueue (this);
80 Thread::sleep (5);
81 }
82 while (prefillBuffer
83 && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2)));
84 }
85}
86
88{
89 isPrepared = false;
90 backgroundThread.removeTimeSliceClient (this);
91
92 buffer.setSize (numberOfChannels, 0);
93
94 // MSVC2017 seems to need this if statement to not generate a warning during linking.
95 // As source is set in the constructor, there is no way that source could
96 // ever equal this, but it seems to make MSVC2017 happy.
97 if (source != this)
98 source->releaseResources();
99}
100
102{
103 const auto bufferRange = getValidBufferRange (info.numSamples);
104
105 if (bufferRange.isEmpty())
106 {
107 // total cache miss
109 return;
110 }
111
112 const auto validStart = bufferRange.getStart();
113 const auto validEnd = bufferRange.getEnd();
114
115 const ScopedLock sl (callbackLock);
116
117 if (validStart > 0)
118 info.buffer->clear (info.startSample, validStart); // partial cache miss at start
119
120 if (validEnd < info.numSamples)
121 info.buffer->clear (info.startSample + validEnd,
122 info.numSamples - validEnd); // partial cache miss at end
123
124 if (validStart < validEnd)
125 {
126 for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
127 {
128 jassert (buffer.getNumSamples() > 0);
129
130 const auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
131 const auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());
132
133 if (startBufferIndex < endBufferIndex)
134 {
135 info.buffer->copyFrom (chan, info.startSample + validStart,
136 buffer,
137 chan, startBufferIndex,
138 validEnd - validStart);
139 }
140 else
141 {
142 const auto initialSize = buffer.getNumSamples() - startBufferIndex;
143
144 info.buffer->copyFrom (chan, info.startSample + validStart,
145 buffer,
146 chan, startBufferIndex,
147 initialSize);
148
149 info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
150 buffer,
151 chan, 0,
152 (validEnd - validStart) - initialSize);
153 }
154 }
155 }
156
157 nextPlayPos += info.numSamples;
158}
159
161{
162 if (source == nullptr || source->getTotalLength() <= 0)
163 return false;
164
165 if ((nextPlayPos + info.numSamples < 0)
166 || (! isLooping() && nextPlayPos > getTotalLength()))
167 return true;
168
169 const auto startTime = Time::getMillisecondCounter();
170 auto now = startTime;
171
172 auto elapsed = (now >= startTime ? now - startTime
173 : (std::numeric_limits<uint32>::max() - startTime) + now);
174
175 while (elapsed <= timeout)
176 {
177 const auto bufferRange = getValidBufferRange (info.numSamples);
178
179 const auto validStart = bufferRange.getStart();
180 const auto validEnd = bufferRange.getEnd();
181
182 if (validStart <= 0
183 && validStart < validEnd
184 && validEnd >= info.numSamples)
185 {
186 return true;
187 }
188
189 if (elapsed < timeout
190 && ! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed)))
191 {
192 return false;
193 }
194
196 elapsed = (now >= startTime ? now - startTime
197 : (std::numeric_limits<uint32>::max() - startTime) + now);
198 }
199
200 return false;
201}
202
204{
205 jassert (source->getTotalLength() > 0);
206 const auto pos = nextPlayPos.load();
207
208 return (source->isLooping() && nextPlayPos > 0)
209 ? pos % source->getTotalLength()
210 : pos;
211}
212
214{
215 const ScopedLock sl (bufferRangeLock);
216
217 nextPlayPos = newPosition;
218 backgroundThread.moveToFrontOfQueue (this);
219}
220
221Range<int> BufferingAudioSource::getValidBufferRange (int numSamples) const
222{
223 const ScopedLock sl (bufferRangeLock);
224
225 const auto pos = nextPlayPos.load();
226
227 return { (int) (jlimit (bufferValidStart, bufferValidEnd, pos) - pos),
228 (int) (jlimit (bufferValidStart, bufferValidEnd, pos + numSamples) - pos) };
229}
230
231bool BufferingAudioSource::readNextBufferChunk()
232{
233 int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd;
234
235 {
236 const ScopedLock sl (bufferRangeLock);
237
238 if (wasSourceLooping != isLooping())
239 {
240 wasSourceLooping = isLooping();
241 bufferValidStart = 0;
242 bufferValidEnd = 0;
243 }
244
245 newBVS = jmax ((int64) 0, nextPlayPos.load());
246 newBVE = newBVS + buffer.getNumSamples() - 4;
247 sectionToReadStart = 0;
248 sectionToReadEnd = 0;
249
250 constexpr int maxChunkSize = 2048;
251
252 if (newBVS < bufferValidStart || newBVS >= bufferValidEnd)
253 {
254 newBVE = jmin (newBVE, newBVS + maxChunkSize);
255
256 sectionToReadStart = newBVS;
257 sectionToReadEnd = newBVE;
258
259 bufferValidStart = 0;
260 bufferValidEnd = 0;
261 }
262 else if (std::abs ((int) (newBVS - bufferValidStart)) > 512
263 || std::abs ((int) (newBVE - bufferValidEnd)) > 512)
264 {
265 newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize);
266
267 sectionToReadStart = bufferValidEnd;
268 sectionToReadEnd = newBVE;
269
270 bufferValidStart = newBVS;
271 bufferValidEnd = jmin (bufferValidEnd, newBVE);
272 }
273 }
274
275 if (sectionToReadStart == sectionToReadEnd)
276 return false;
277
278 jassert (buffer.getNumSamples() > 0);
279
280 const auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
281 const auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());
282
283 if (bufferIndexStart < bufferIndexEnd)
284 {
285 readBufferSection (sectionToReadStart,
286 (int) (sectionToReadEnd - sectionToReadStart),
287 bufferIndexStart);
288 }
289 else
290 {
291 const auto initialSize = buffer.getNumSamples() - bufferIndexStart;
292
293 readBufferSection (sectionToReadStart,
294 initialSize,
295 bufferIndexStart);
296
297 readBufferSection (sectionToReadStart + initialSize,
298 (int) (sectionToReadEnd - sectionToReadStart) - initialSize,
299 0);
300 }
301
302 {
303 const ScopedLock sl2 (bufferRangeLock);
304
305 bufferValidStart = newBVS;
306 bufferValidEnd = newBVE;
307 }
308
309 bufferReadyEvent.signal();
310 return true;
311}
312
313void BufferingAudioSource::readBufferSection (int64 start, int length, int bufferOffset)
314{
315 if (source->getNextReadPosition() != start)
316 source->setNextReadPosition (start);
317
318 AudioSourceChannelInfo info (&buffer, bufferOffset, length);
319
320 const ScopedLock sl (callbackLock);
321 source->getNextAudioBlock (info);
322}
323
324int BufferingAudioSource::useTimeSlice()
325{
326 return readNextBufferChunk() ? 1 : 100;
327}
328
329} // namespace juce
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
int getNumChannels() const noexcept
int getNumSamples() const noexcept
void copyFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples) noexcept
void getNextAudioBlock(const AudioSourceChannelInfo &) override
void setNextReadPosition(int64 newPosition) override
int64 getTotalLength() const override
BufferingAudioSource(PositionableAudioSource *source, TimeSliceThread &backgroundThread, bool deleteSourceWhenDeleted, int numberOfSamplesToBuffer, int numberOfChannels=2, bool prefillBufferOnPrepareToPlay=true)
bool waitForNextAudioBlockReady(const AudioSourceChannelInfo &info, uint32 timeout)
void prepareToPlay(int samplesPerBlockExpected, double sampleRate) override
static void JUCE_CALLTYPE sleep(int milliseconds)
void removeTimeSliceClient(TimeSliceClient *clientToRemove)
void addTimeSliceClient(TimeSliceClient *clientToAdd, int millisecondsBeforeStarting=0)
void moveToFrontOfQueue(TimeSliceClient *clientToMove)
static uint32 getMillisecondCounter() noexcept
bool wait(double timeOutMilliseconds=-1.0) const
AudioBuffer< float > * buffer