OpenShot Library | OpenShotAudio  0.2.2
juce_BufferingAudioSource.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2017 - ROLI Ltd.
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 (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 
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 
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
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 
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 
195  elapsed = (now >= startTime ? now - startTime
196  : (std::numeric_limits<uint32>::max() - startTime) + now);
197  }
198 
199  return false;
200 }
201 
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 
213 {
214  const ScopedLock sl (bufferStartPosLock);
215 
216  nextPlayPos = newPosition;
217  backgroundThread.moveToFrontOfQueue (this);
218 }
219 
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 
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 
310 int BufferingAudioSource::useTimeSlice()
311 {
312  return readNextBufferChunk() ? 1 : 100;
313 }
314 
315 } // namespace juce
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
Changes the buffer's size or number of channels.
int getNumChannels() const noexcept
Returns the number of channels of audio data that this buffer contains.
int getNumSamples() const noexcept
Returns the number of samples allocated in each of the buffer's channels.
void clear() noexcept
Clears all the samples in all channels.
void copyFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples) noexcept
Copies samples from another buffer to this one.
void getNextAudioBlock(const AudioSourceChannelInfo &) override
Implementation of the AudioSource method.
void setNextReadPosition(int64 newPosition) override
Implements the PositionableAudioSource method.
int64 getTotalLength() const override
Implements the PositionableAudioSource method.
~BufferingAudioSource() override
Destructor.
BufferingAudioSource(PositionableAudioSource *source, TimeSliceThread &backgroundThread, bool deleteSourceWhenDeleted, int numberOfSamplesToBuffer, int numberOfChannels=2, bool prefillBufferOnPrepareToPlay=true)
Creates a BufferingAudioSource.
bool isLooping() const override
Implements the PositionableAudioSource method.
void releaseResources() override
Implementation of the AudioSource method.
bool waitForNextAudioBlockReady(const AudioSourceChannelInfo &info, const uint32 timeout)
A useful function to block until the next the buffer info can be filled.
int64 getNextReadPosition() const override
Implements the PositionableAudioSource method.
void prepareToPlay(int samplesPerBlockExpected, double sampleRate) override
Implementation of the AudioSource method.
Automatically locks and unlocks a mutex object.
A type of AudioSource which can be repositioned.
static void JUCE_CALLTYPE sleep(int milliseconds)
Suspends the execution of the current thread until the specified timeout period has elapsed (note tha...
A thread that keeps a list of clients, and calls each one in turn, giving them all a chance to run so...
void removeTimeSliceClient(TimeSliceClient *clientToRemove)
Removes a client from the list.
void addTimeSliceClient(TimeSliceClient *clientToAdd, int millisecondsBeforeStarting=0)
Adds a client to the list.
void moveToFrontOfQueue(TimeSliceClient *clientToMove)
If the given client is waiting in the queue, it will be moved to the front and given a time-slice as ...
static uint32 getMillisecondCounter() noexcept
Returns the number of millisecs since a fixed event (usually system startup).
Definition: juce_Time.cpp:226
bool wait(int timeOutMilliseconds=-1) const
Suspends the calling thread until the event has been signalled.
void signal() const
Wakes up any threads that are currently waiting on this object.
Used by AudioSource::getNextAudioBlock().
int numSamples
The number of samples in the buffer which the callback is expected to fill with data.
void clearActiveBufferRegion() const
Convenient method to clear the buffer if the source is not producing any data.
AudioBuffer< float > * buffer
The destination buffer to fill with audio data.
int startSample
The first sample in the buffer from which the callback is expected to write data.