OpenShot Audio Library | OpenShotAudio 0.4.0
Loading...
Searching...
No Matches
juce_BufferingAudioFormatReader.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 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
30 TimeSliceThread& timeSliceThread,
31 int samplesToBuffer)
32 : AudioFormatReader (nullptr, sourceReader->getFormatName()),
33 source (sourceReader), thread (timeSliceThread),
34 numBlocks (1 + (samplesToBuffer / samplesPerBlock))
35{
36 sampleRate = source->sampleRate;
37 lengthInSamples = source->lengthInSamples;
38 numChannels = source->numChannels;
39 metadataValues = source->metadataValues;
40 bitsPerSample = 32;
42
43 timeSliceThread.addTimeSliceClient (this);
44}
45
46BufferingAudioReader::~BufferingAudioReader()
47{
48 thread.removeTimeSliceClient (this);
49}
50
51void BufferingAudioReader::setReadTimeout (int timeoutMilliseconds) noexcept
52{
53 timeoutMs = timeoutMilliseconds;
54}
55
56bool BufferingAudioReader::readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
57 int64 startSampleInFile, int numSamples)
58{
59 auto startTime = Time::getMillisecondCounter();
60 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
61 startSampleInFile, numSamples, lengthInSamples);
62
63 const ScopedLock sl (lock);
64 nextReadPosition = startSampleInFile;
65
66 bool allSamplesRead = true;
67
68 while (numSamples > 0)
69 {
70 if (auto block = getBlockContaining (startSampleInFile))
71 {
72 auto offset = (int) (startSampleInFile - block->range.getStart());
73 auto numToDo = jmin (numSamples, (int) (block->range.getEnd() - startSampleInFile));
74
75 for (int j = 0; j < numDestChannels; ++j)
76 {
77 if (auto* dest = (float*) destSamples[j])
78 {
79 dest += startOffsetInDestBuffer;
80
81 if (j < (int) numChannels)
82 FloatVectorOperations::copy (dest, block->buffer.getReadPointer (j, offset), numToDo);
83 else
84 FloatVectorOperations::clear (dest, numToDo);
85 }
86 }
87
88 startOffsetInDestBuffer += numToDo;
89 startSampleInFile += numToDo;
90 numSamples -= numToDo;
91
92 allSamplesRead = allSamplesRead && block->allSamplesRead;
93 }
94 else
95 {
96 if (timeoutMs >= 0 && Time::getMillisecondCounter() >= startTime + (uint32) timeoutMs)
97 {
98 for (int j = 0; j < numDestChannels; ++j)
99 if (auto* dest = (float*) destSamples[j])
100 FloatVectorOperations::clear (dest + startOffsetInDestBuffer, numSamples);
101
102 allSamplesRead = false;
103 break;
104 }
105 else
106 {
107 ScopedUnlock ul (lock);
109 }
110 }
111 }
112
113 return allSamplesRead;
114}
115
116BufferingAudioReader::BufferedBlock::BufferedBlock (AudioFormatReader& reader, int64 pos, int numSamples)
117 : range (pos, pos + numSamples),
118 buffer ((int) reader.numChannels, numSamples),
119 allSamplesRead (reader.read (&buffer, 0, numSamples, pos, true, true))
120{
121}
122
123BufferingAudioReader::BufferedBlock* BufferingAudioReader::getBlockContaining (int64 pos) const noexcept
124{
125 for (auto* b : blocks)
126 if (b->range.contains (pos))
127 return b;
128
129 return nullptr;
130}
131
133{
134 return readNextBufferChunk() ? 1 : 100;
135}
136
137bool BufferingAudioReader::readNextBufferChunk()
138{
139 auto pos = (nextReadPosition.load() / samplesPerBlock) * samplesPerBlock;
140 auto endPos = jmin (lengthInSamples, pos + numBlocks * samplesPerBlock);
141
142 OwnedArray<BufferedBlock> newBlocks;
143
144 for (int i = blocks.size(); --i >= 0;)
145 if (blocks.getUnchecked (i)->range.intersects (Range<int64> (pos, endPos)))
146 newBlocks.add (blocks.getUnchecked (i));
147
148 if (newBlocks.size() == numBlocks)
149 {
150 newBlocks.clear (false);
151 return false;
152 }
153
154 for (auto p = pos; p < endPos; p += samplesPerBlock)
155 {
156 if (getBlockContaining (p) == nullptr)
157 {
158 newBlocks.add (new BufferedBlock (*source, p, samplesPerBlock));
159 break; // just do one block
160 }
161 }
162
163 {
164 const ScopedLock sl (lock);
165 newBlocks.swapWith (blocks);
166 }
167
168 for (int i = blocks.size(); --i >= 0;)
169 newBlocks.removeObject (blocks.getUnchecked (i), false);
170
171 return true;
172}
173
174
175//==============================================================================
176//==============================================================================
177#if JUCE_UNIT_TESTS
178
179static bool isSilent (const AudioBuffer<float>& b)
180{
181 for (int channel = 0; channel < b.getNumChannels(); ++channel)
182 if (b.findMinMax (channel, 0, b.getNumSamples()) != Range<float>{})
183 return false;
184
185 return true;
186}
187
188struct TestAudioFormatReader : public AudioFormatReader
189{
190 explicit TestAudioFormatReader (const AudioBuffer<float>* b)
191 : AudioFormatReader (nullptr, {}),
192 buffer (b)
193 {
194 jassert (buffer != nullptr);
195 sampleRate = 44100.0f;
196 bitsPerSample = 32;
197 usesFloatingPointData = true;
198 lengthInSamples = buffer->getNumSamples();
199 numChannels = (unsigned int) buffer->getNumChannels();
200 }
201
202 bool readSamples (int* const* destChannels, int numDestChannels, int startOffsetInDestBuffer,
203 int64 startSampleInFile, int numSamples) override
204 {
205 clearSamplesBeyondAvailableLength (destChannels, numDestChannels, startOffsetInDestBuffer,
206 startSampleInFile, numSamples, lengthInSamples);
207
208 if (numSamples <= 0)
209 return true;
210
211 for (int j = 0; j < numDestChannels; ++j)
212 {
213 static_assert (sizeof (int) == sizeof (float),
214 "Int and float size must match in order for pointer arithmetic to work correctly");
215
216 if (auto* dest = reinterpret_cast<float*> (destChannels[j]))
217 {
218 dest += startOffsetInDestBuffer;
219
220 if (j < (int) numChannels)
221 FloatVectorOperations::copy (dest, buffer->getReadPointer (j, (int) startSampleInFile), numSamples);
222 else
223 FloatVectorOperations::clear (dest, numSamples);
224 }
225 }
226
227 return true;
228 }
229
230 const AudioBuffer<float>* buffer;
231};
232
233static AudioBuffer<float> generateTestBuffer (Random& random, int bufferSize)
234{
235 AudioBuffer<float> buffer { 2, bufferSize };
236
237 for (int channel = 0; channel < buffer.getNumChannels(); ++channel)
238 for (int sample = 0; sample < buffer.getNumSamples(); ++sample)
239 buffer.setSample (channel, sample, random.nextFloat());
240
241 return buffer;
242}
243
244class BufferingAudioReaderTests final : public UnitTest
245{
246public:
247 BufferingAudioReaderTests() : UnitTest ("BufferingAudioReader", UnitTestCategories::audio) {}
248
249 void runTest() override
250 {
251 TimeSliceThread thread ("TestBackgroundThread");
252 thread.startThread (Thread::Priority::normal);
253
254 beginTest ("Reading samples from a blocked reader should produce silence");
255 {
256 struct BlockingReader final : public TestAudioFormatReader
257 {
258 explicit BlockingReader (const AudioBuffer<float>* b)
259 : TestAudioFormatReader (b)
260 {
261 }
262
263 bool readSamples (int* const* destChannels,
264 int numDestChannels,
265 int startOffsetInDestBuffer,
266 int64 startSampleInFile,
267 int numSamples) override
268 {
269 unblock.wait();
270 return TestAudioFormatReader::readSamples (destChannels, numDestChannels, startOffsetInDestBuffer, startSampleInFile, numSamples);
271 }
272
273 WaitableEvent unblock;
274 };
275
276 Random random { getRandom() };
277 constexpr auto bufferSize = 1024;
278
279 const auto source = generateTestBuffer (random, bufferSize);
280 expect (! isSilent (source));
281
282 auto* blockingReader = new BlockingReader (&source);
283 BufferingAudioReader reader (blockingReader, thread, bufferSize);
284
285 auto destination = generateTestBuffer (random, bufferSize);
286 expect (! isSilent (destination));
287
288 read (reader, destination);
289 expect (isSilent (destination));
290
291 blockingReader->unblock.signal();
292 }
293
294 beginTest ("Reading samples from a reader should produce the same samples as its source");
295 {
296 Random random { getRandom() };
297
298 for (auto i = 4; i < 18; ++i)
299 {
300 const auto bufferSize = 1 << i;
301 const auto source = generateTestBuffer (random, bufferSize);
302 expect (! isSilent (source));
303
304 BufferingAudioReader reader (new TestAudioFormatReader (&source), thread, bufferSize);
305 reader.setReadTimeout (-1);
306
307 auto destination = generateTestBuffer (random, bufferSize);
308 expect (! isSilent (destination));
309 expect (source != destination);
310
311 read (reader, destination);
312 expect (source == destination);
313 }
314 }
315 }
316
317private:
318 void read (BufferingAudioReader& reader, AudioBuffer<float>& readBuffer)
319 {
320 constexpr int blockSize = 1024;
321
322 const auto numSamples = readBuffer.getNumSamples();
323 int readPos = 0;
324
325 for (;;)
326 {
327 reader.read (&readBuffer, readPos, jmin (blockSize, numSamples - readPos), readPos, true, true);
328
329 readPos += blockSize;
330
331 if (readPos >= numSamples)
332 break;
333 }
334 }
335};
336
337static BufferingAudioReaderTests bufferingAudioReaderTests;
338
339#endif
340
341} // namespace juce
void setSample(int destChannel, int destSample, Type newValue) noexcept
const String & getFormatName() const noexcept
static void clearSamplesBeyondAvailableLength(int *const *destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int &numSamples, int64 fileLengthInSamples)
AudioFormatReader(InputStream *sourceStream, const String &formatName)
BufferingAudioReader(AudioFormatReader *sourceReader, TimeSliceThread &timeSliceThread, int samplesToBuffer)
void setReadTimeout(int timeoutMilliseconds) noexcept
bool readSamples(int *const *destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
static void JUCE_CALLTYPE yield()
virtual int useTimeSlice()=0
void removeTimeSliceClient(TimeSliceClient *clientToRemove)
void addTimeSliceClient(TimeSliceClient *clientToAdd, int millisecondsBeforeStarting=0)
static uint32 getMillisecondCounter() noexcept