OpenShot Audio Library | OpenShotAudio  0.6.0
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 
23 namespace 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 
45 {
47 }
48 
49 //==============================================================================
50 void 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 
221 Range<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 
231 bool 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 
313 void 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 
324 int 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)
int64 getNextReadPosition() const override
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
Definition: juce_Time.cpp:241
bool wait(double timeOutMilliseconds=-1.0) const
AudioBuffer< float > * buffer