JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_AudioThumbnail.cpp
Go to the documentation of this file.
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{
31 MinMaxValue() noexcept
32 {
33 values[0] = 0;
34 values[1] = 0;
35 }
36
37 inline void set (const int8 newMin, const int8 newMax) noexcept
38 {
39 values[0] = newMin;
40 values[1] = newMax;
41 }
42
43 inline int8 getMinValue() const noexcept { return values[0]; }
44 inline int8 getMaxValue() const noexcept { return values[1]; }
45
46 inline void setFloat (Range<float> newRange) noexcept
47 {
48 // Workaround for an ndk armeabi compiler bug which crashes on signed saturation
49 #if JUCE_ANDROID
50 Range<float> limitedRange (jlimit (-1.0f, 1.0f, newRange.getStart()),
51 jlimit (-1.0f, 1.0f, newRange.getEnd()));
52 values[0] = (int8) (limitedRange.getStart() * 127.0f);
53 values[1] = (int8) (limitedRange.getEnd() * 127.0f);
54 #else
55 values[0] = (int8) jlimit (-128, 127, roundToInt (newRange.getStart() * 127.0f));
56 values[1] = (int8) jlimit (-128, 127, roundToInt (newRange.getEnd() * 127.0f));
57 #endif
58
59 if (values[0] == values[1])
60 {
61 if (values[1] == 127)
62 values[0]--;
63 else
64 values[1]++;
65 }
66 }
67
68 inline bool isNonZero() const noexcept
69 {
70 return values[1] > values[0];
71 }
72
73 inline int getPeak() const noexcept
74 {
75 return jmax (std::abs ((int) values[0]),
76 std::abs ((int) values[1]));
77 }
78
79 inline void read (InputStream& input) { input.read (values, 2); }
80 inline void write (OutputStream& output) { output.write (values, 2); }
81
82private:
83 int8 values[2];
84};
85
86
87//==============================================================================
88template <typename T>
90{
91public:
92 AudioBufferReader (const AudioBuffer<T>* bufferIn, double rate)
93 : AudioFormatReader (nullptr, "AudioBuffer"), buffer (bufferIn)
94 {
95 sampleRate = rate;
96 bitsPerSample = 32;
98 numChannels = (unsigned int) buffer->getNumChannels();
99 usesFloatingPointData = std::is_floating_point_v<T>;
100 }
101
102 bool readSamples (int* const* destChannels,
103 int numDestChannels,
106 int numSamples) override
107 {
110
111 const auto numAvailableSamples = (int) ((int64) buffer->getNumSamples() - startSampleInFile);
112 const auto numSamplesToCopy = std::clamp (numAvailableSamples, 0, numSamples);
113
114 if (numSamplesToCopy == 0)
115 return true;
116
117 for (int i = 0; i < numDestChannels; ++i)
118 {
119 if (void* targetChannel = destChannels[i])
120 {
121 const auto dest = DestType (targetChannel) + startOffsetInDestBuffer;
122
123 if (i < buffer->getNumChannels())
125 else
126 dest.clearSamples (numSamples);
127 }
128 }
129
130 return true;
131 }
132
133private:
134 using SourceNumericalType =
136 std::conditional_t<std::is_same_v<T, float>, AudioData::Float32, void>>;
137
138 using DestinationNumericalType = std::conditional_t<std::is_floating_point_v<T>, AudioData::Float32, AudioData::Int32>;
139
142
143 const AudioBuffer<T>* buffer;
144};
145
146//==============================================================================
148{
149public:
151 : hashCode (hash), owner (thumb), reader (newReader)
152 {
153 }
154
156 : hashCode (src->hashCode()), owner (thumb), source (src)
157 {
158 }
159
160 ~LevelDataSource() override
161 {
162 owner.cache.getTimeSliceThread().removeTimeSliceClient (this);
163 }
164
165 enum { timeBeforeDeletingReader = 3000 };
166
167 void initialise (int64 samplesFinished)
168 {
169 const ScopedLock sl (readerLock);
170
171 numSamplesFinished = samplesFinished;
172
173 createReader();
174
175 if (reader != nullptr)
176 {
177 lengthInSamples = reader->lengthInSamples;
178 numChannels = reader->numChannels;
179 sampleRate = reader->sampleRate;
180
181 if (lengthInSamples <= 0 || isFullyLoaded())
182 reader.reset();
183 else
184 owner.cache.getTimeSliceThread().addTimeSliceClient (this);
185 }
186 }
187
188 void getLevels (int64 startSample, int numSamples, Array<Range<float>>& levels)
189 {
190 const ScopedLock sl (readerLock);
191
192 if (reader == nullptr)
193 {
194 createReader();
195
196 if (reader != nullptr)
197 {
198 lastReaderUseTime = Time::getMillisecondCounter();
199 owner.cache.getTimeSliceThread().addTimeSliceClient (this);
200 }
201 }
202
203 if (reader != nullptr)
204 {
205 if (levels.size() < (int) reader->numChannels)
206 levels.insertMultiple (0, {}, (int) reader->numChannels - levels.size());
207
208 reader->readMaxLevels (startSample, numSamples, levels.getRawDataPointer(), (int) reader->numChannels);
209
210 lastReaderUseTime = Time::getMillisecondCounter();
211 }
212 }
213
214 void releaseResources()
215 {
216 const ScopedLock sl (readerLock);
217 reader.reset();
218 }
219
220 int useTimeSlice() override
221 {
222 if (isFullyLoaded())
223 {
224 if (reader != nullptr && source != nullptr)
225 {
226 if (Time::getMillisecondCounter() > lastReaderUseTime + timeBeforeDeletingReader)
227 releaseResources();
228 else
229 return 200;
230 }
231
232 return -1;
233 }
234
235 bool justFinished = false;
236
237 {
238 const ScopedLock sl (readerLock);
239 createReader();
240
241 if (reader != nullptr)
242 {
243 if (! readNextBlock())
244 return 0;
245
246 justFinished = true;
247 }
248 }
249
250 if (justFinished)
251 owner.cache.storeThumb (owner, hashCode);
252
253 return 200;
254 }
255
256 bool isFullyLoaded() const noexcept
257 {
258 return numSamplesFinished >= lengthInSamples;
259 }
260
261 inline int sampleToThumbSample (const int64 originalSample) const noexcept
262 {
263 return (int) (originalSample / owner.samplesPerThumbSample);
264 }
265
266 int64 lengthInSamples = 0, numSamplesFinished = 0;
267 double sampleRate = 0;
268 unsigned int numChannels = 0;
269 int64 hashCode = 0;
270
271private:
272 AudioThumbnail& owner;
275 CriticalSection readerLock;
276 std::atomic<uint32> lastReaderUseTime { 0 };
277
278 void createReader()
279 {
280 if (reader == nullptr && source != nullptr)
281 if (auto* audioFileStream = source->createInputStream())
282 reader.reset (owner.formatManagerToUse.createReaderFor (std::unique_ptr<InputStream> (audioFileStream)));
283 }
284
285 bool readNextBlock()
286 {
287 jassert (reader != nullptr);
288
289 if (! isFullyLoaded())
290 {
291 auto numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
292
293 if (numToDo > 0)
294 {
295 auto startSample = numSamplesFinished;
296
297 auto firstThumbIndex = sampleToThumbSample (startSample);
298 auto lastThumbIndex = sampleToThumbSample (startSample + numToDo);
300
301 HeapBlock<MinMaxValue> levelData ((unsigned int) numThumbSamps * numChannels);
302 HeapBlock<MinMaxValue*> levels (numChannels);
303
304 for (int i = 0; i < (int) numChannels; ++i)
305 levels[i] = levelData + i * numThumbSamps;
306
307 HeapBlock<Range<float>> levelsRead (numChannels);
308
309 for (int i = 0; i < numThumbSamps; ++i)
310 {
311 reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample,
312 owner.samplesPerThumbSample, levelsRead, (int) numChannels);
313
314 for (int j = 0; j < (int) numChannels; ++j)
315 levels[j][i].setFloat (levelsRead[j]);
316 }
317
318 {
319 const ScopedUnlock su (readerLock);
320 owner.setLevels (levels, firstThumbIndex, (int) numChannels, numThumbSamps);
321 }
322
323 numSamplesFinished += numToDo;
324 lastReaderUseTime = Time::getMillisecondCounter();
325 }
326 }
327
328 return isFullyLoaded();
329 }
330};
331
332//==============================================================================
334{
335public:
337 {
338 ensureSize (numThumbSamples);
339 }
340
341 inline MinMaxValue* getData (int thumbSampleIndex) noexcept
342 {
343 jassert (thumbSampleIndex < data.size());
344 return data.getRawDataPointer() + thumbSampleIndex;
345 }
346
347 int getSize() const noexcept
348 {
349 return data.size();
350 }
351
352 void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
353 {
354 if (startSample >= 0)
355 {
356 endSample = jmin (endSample, data.size() - 1);
357
358 int8 mx = -128;
359 int8 mn = 127;
360
361 while (startSample <= endSample)
362 {
363 auto& v = data.getReference (startSample);
364
365 if (v.getMinValue() < mn) mn = v.getMinValue();
366 if (v.getMaxValue() > mx) mx = v.getMaxValue();
367
368 ++startSample;
369 }
370
371 if (mn <= mx)
372 {
373 result.set (mn, mx);
374 return;
375 }
376 }
377
378 result.set (1, 0);
379 }
380
381 void write (const MinMaxValue* values, int startIndex, int numValues)
382 {
383 resetPeak();
384
385 if (startIndex + numValues > data.size())
386 ensureSize (startIndex + numValues);
387
388 auto* dest = getData (startIndex);
389
390 for (int i = 0; i < numValues; ++i)
391 dest[i] = values[i];
392 }
393
394 void resetPeak() noexcept
395 {
396 peakLevel = -1;
397 }
398
399 int getPeak() noexcept
400 {
401 if (peakLevel < 0)
402 {
403 for (auto& s : data)
404 {
405 auto peak = s.getPeak();
406
407 if (peak > peakLevel)
408 peakLevel = peak;
409 }
410 }
411
412 return peakLevel;
413 }
414
415private:
417 int peakLevel = -1;
418
419 void ensureSize (int thumbSamples)
420 {
421 auto extraNeeded = thumbSamples - data.size();
422
423 if (extraNeeded > 0)
424 data.insertMultiple (-1, MinMaxValue(), extraNeeded);
425 }
426};
427
428//==============================================================================
430{
431public:
432 CachedWindow() {}
433
434 void invalidate()
435 {
436 cacheNeedsRefilling = true;
437 }
438
439 void drawChannel (Graphics& g, const Rectangle<int>& area,
440 const double startTime, const double endTime,
441 const int channelNum, const float verticalZoomFactor,
442 const double rate, const int numChans, const int sampsPerThumbSample,
444 {
445 if (refillCache (area.getWidth(), startTime, endTime, rate,
447 && isPositiveAndBelow (channelNum, numChannelsCached))
448 {
449 auto clip = g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())));
450
451 if (! clip.isEmpty())
452 {
453 auto topY = (float) area.getY();
454 auto bottomY = (float) area.getBottom();
455 auto midY = (topY + bottomY) * 0.5f;
456 auto vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
457
458 auto* cacheData = getData (channelNum, clip.getX() - area.getX());
459
461 waveform.ensureStorageAllocated (clip.getWidth());
462
463 auto x = (float) clip.getX();
464
465 for (int w = clip.getWidth(); --w >= 0;)
466 {
467 if (cacheData->isNonZero())
468 {
469 auto top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
470 auto bottom = jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY);
471
472 waveform.addWithoutMerging (Rectangle<float> (x, top, 1.0f, bottom - top));
473 }
474
475 x += 1.0f;
476 ++cacheData;
477 }
478
480 }
481 }
482 }
483
484private:
486 double cachedStart = 0, cachedTimePerPixel = 0;
487 int numChannelsCached = 0, numSamplesCached = 0;
488 bool cacheNeedsRefilling = true;
489
490 bool refillCache (int numSamples, double startTime, double endTime,
491 double rate, int numChans, int sampsPerThumbSample,
493 {
494 auto timePerPixel = (endTime - startTime) / numSamples;
495
496 if (numSamples <= 0 || timePerPixel <= 0.0 || rate <= 0)
497 {
498 invalidate();
499 return false;
500 }
501
502 if (numSamples == numSamplesCached
503 && numChannelsCached == numChans
504 && approximatelyEqual (startTime, cachedStart)
505 && approximatelyEqual (timePerPixel, cachedTimePerPixel)
506 && ! cacheNeedsRefilling)
507 {
508 return ! cacheNeedsRefilling;
509 }
510
511 numSamplesCached = numSamples;
512 numChannelsCached = numChans;
513 cachedStart = startTime;
514 cachedTimePerPixel = timePerPixel;
515 cacheNeedsRefilling = false;
516
517 ensureSize (numSamples);
518
519 if (timePerPixel * rate <= sampsPerThumbSample && levelData != nullptr)
520 {
521 auto sample = roundToInt (startTime * rate);
522 Array<Range<float>> levels;
523
524 int i;
525 for (i = 0; i < numSamples; ++i)
526 {
527 auto nextSample = roundToInt ((startTime + timePerPixel) * rate);
528
529 if (sample >= 0)
530 {
531 if (sample >= levelData->lengthInSamples)
532 {
533 for (int chan = 0; chan < numChannelsCached; ++chan)
534 *getData (chan, i) = MinMaxValue();
535 }
536 else
537 {
538 levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
539
540 auto totalChans = jmin (levels.size(), numChannelsCached);
541
542 for (int chan = 0; chan < totalChans; ++chan)
543 getData (chan, i)->setFloat (levels.getReference (chan));
544 }
545 }
546
547 startTime += timePerPixel;
548 sample = nextSample;
549 }
550
551 numSamplesCached = i;
552 }
553 else
554 {
555 jassert (chans.size() == numChannelsCached);
556
557 for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
558 {
559 ThumbData* channelData = chans.getUnchecked (channelNum);
560 MinMaxValue* cacheData = getData (channelNum, 0);
561
563
564 startTime = cachedStart;
565 auto sample = roundToInt (startTime * timeToThumbSampleFactor);
566
567 for (int i = numSamples; --i >= 0;)
568 {
569 auto nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
570
571 channelData->getMinMax (sample, nextSample, *cacheData);
572
573 ++cacheData;
574 startTime += timePerPixel;
575 sample = nextSample;
576 }
577 }
578 }
579
580 return true;
581 }
582
583 MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
584 {
585 jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
586
587 return data.getRawDataPointer() + channelNum * numSamplesCached
588 + cacheIndex;
589 }
590
591 void ensureSize (const int numSamples)
592 {
593 auto itemsRequired = numSamples * numChannelsCached;
594
595 if (data.size() < itemsRequired)
596 data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
597 }
598};
599
600//==============================================================================
602 AudioFormatManager& formatManager,
604 : formatManagerToUse (formatManager),
605 cache (cacheToUse),
606 window (new CachedWindow()),
607 samplesPerThumbSample (originalSamplesPerThumbnailSample)
608{
609}
610
615
617{
618 source.reset();
619 const ScopedLock sl (lock);
620 clearChannelData();
621}
622
623void AudioThumbnail::clearChannelData()
624{
625 window->invalidate();
626 channels.clear();
627 totalSamples = numSamplesFinished = 0;
628 numChannels = 0;
629 sampleRate = 0;
630
632}
633
635{
636 clear();
637
638 const ScopedLock sl (lock);
639 numChannels = newNumChannels;
640 sampleRate = newSampleRate;
641 totalSamples = totalSamplesInSource;
642
643 createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample));
644}
645
646void AudioThumbnail::createChannels (const int length)
647{
648 while (channels.size() < numChannels)
649 channels.add (new ThumbData (length));
650}
651
652//==============================================================================
654{
655 BufferedInputStream input (rawInput, 4096);
656
657 if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
658 return false;
659
660 const ScopedLock sl (lock);
661 clearChannelData();
662
663 samplesPerThumbSample = input.readInt();
664 totalSamples = input.readInt64(); // Total number of source samples.
665 numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
666 int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
667 numChannels = input.readInt(); // Number of audio channels.
668 sampleRate = input.readInt(); // Source sample rate.
669 input.skipNextBytes (16); // (reserved)
670
671 createChannels (numThumbnailSamples);
672
673 for (int i = 0; i < numThumbnailSamples; ++i)
674 for (int chan = 0; chan < numChannels; ++chan)
675 channels.getUnchecked (chan)->getData (i)->read (input);
676
677 return true;
678}
679
681{
682 const ScopedLock sl (lock);
683
684 const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked (0)->getSize();
685
686 output.write ("jatm", 4);
687 output.writeInt (samplesPerThumbSample);
688 output.writeInt64 (totalSamples);
689 output.writeInt64 (numSamplesFinished);
691 output.writeInt (numChannels);
692 output.writeInt ((int) sampleRate);
693 output.writeInt64 (0);
694 output.writeInt64 (0);
695
696 for (int i = 0; i < numThumbnailSamples; ++i)
697 for (int chan = 0; chan < numChannels; ++chan)
698 channels.getUnchecked (chan)->getData (i)->write (output);
699}
700
701//==============================================================================
702bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
703{
705
706 numSamplesFinished = 0;
707 auto wasSuccessful = [&] { return sampleRate > 0 && totalSamples > 0; };
708
709 if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
710 {
711 source.reset (newSource); // (make sure this isn't done before loadThumb is called)
712
713 source->lengthInSamples = totalSamples;
714 source->sampleRate = sampleRate;
715 source->numChannels = (unsigned int) numChannels;
716 source->numSamplesFinished = numSamplesFinished;
717
718 return wasSuccessful();
719 }
720
721 source.reset (newSource);
722
723 const ScopedLock sl (lock);
724 source->initialise (numSamplesFinished);
725
726 totalSamples = source->lengthInSamples;
727 sampleRate = source->sampleRate;
728 numChannels = (int32) source->numChannels;
729
730 createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
731
732 return wasSuccessful();
733}
734
736{
737 clear();
738
739 return newSource != nullptr && setDataSource (new LevelDataSource (*this, newSource));
740}
741
743{
744 clear();
745
746 if (newReader != nullptr)
747 setDataSource (new LevelDataSource (*this, newReader, hash));
748}
749
751{
752 setReader (new AudioBufferReader<float> (newSource, rate), hash);
753}
754
756{
757 setReader (new AudioBufferReader<int> (newSource, rate), hash);
758}
759
761{
762 return source == nullptr ? 0 : source->hashCode;
763}
764
766 int startOffsetInBuffer, int numSamples)
767{
768 jassert (startSample >= 0
769 && startOffsetInBuffer >= 0
770 && startOffsetInBuffer + numSamples <= incoming.getNumSamples());
771
772 auto firstThumbIndex = (int) (startSample / samplesPerThumbSample);
773 auto lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
775
776 if (numToDo > 0)
777 {
778 auto numChans = jmin (channels.size(), incoming.getNumChannels());
779
782
783 for (int chan = 0; chan < numChans; ++chan)
784 {
785 auto* sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
786 auto* dest = thumbData + numToDo * chan;
787 thumbChannels [chan] = dest;
788
789 for (int i = 0; i < numToDo; ++i)
790 {
791 auto start = i * samplesPerThumbSample;
792 dest[i].setFloat (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start)));
793 }
794 }
795
797 }
798}
799
800void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
801{
802 const ScopedLock sl (lock);
803
804 for (int i = jmin (numChans, channels.size()); --i >= 0;)
805 channels.getUnchecked (i)->write (values[i], thumbIndex, numValues);
806
807 auto start = thumbIndex * (int64) samplesPerThumbSample;
808 auto end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
809
810 if (numSamplesFinished >= start && end > numSamplesFinished)
811 numSamplesFinished = end;
812
813 totalSamples = jmax (numSamplesFinished, totalSamples);
814 window->invalidate();
816}
817
818//==============================================================================
820{
821 const ScopedLock sl (lock);
822 return numChannels;
823}
824
826{
827 const ScopedLock sl (lock);
828 return sampleRate > 0 ? ((double) totalSamples / sampleRate) : 0.0;
829}
830
832{
833 const ScopedLock sl (lock);
834 return numSamplesFinished >= totalSamples - samplesPerThumbSample;
835}
836
838{
839 const ScopedLock sl (lock);
840 return jlimit (0.0, 1.0, (double) numSamplesFinished / (double) jmax ((int64) 1, totalSamples));
841}
842
844{
845 const ScopedLock sl (lock);
846 return numSamplesFinished;
847}
848
850{
851 const ScopedLock sl (lock);
852 int peak = 0;
853
854 for (auto* c : channels)
855 peak = jmax (peak, c->getPeak());
856
857 return (float) jlimit (0, 127, peak) / 127.0f;
858}
859
860void AudioThumbnail::getApproximateMinMax (double startTime, double endTime, int channelIndex,
861 float& minValue, float& maxValue) const noexcept
862{
863 const ScopedLock sl (lock);
864 MinMaxValue result;
865 auto* data = channels [channelIndex];
866
867 if (data != nullptr && sampleRate > 0)
868 {
869 auto firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
870 auto lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
871
872 data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
873 }
874
875 minValue = result.getMinValue() / 128.0f;
876 maxValue = result.getMaxValue() / 128.0f;
877}
878
879void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
880 double endTime, int channelNum, float verticalZoomFactor)
881{
882 const ScopedLock sl (lock);
883
884 window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
885 sampleRate, numChannels, samplesPerThumbSample, source.get(), channels);
886}
887
890{
891 for (int i = 0; i < numChannels; ++i)
892 {
893 auto y1 = roundToInt ((i * area.getHeight()) / numChannels);
894 auto y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
895
896 drawChannel (g, { area.getX(), area.getY() + y1, area.getWidth(), y2 - y1 },
898 }
899}
900
901} // namespace juce
T clamp(T... args)
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:215
ElementType & getReference(int index) noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:267
bool readSamples(int *const *destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
A multi-channel buffer containing floating point audio samples.
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.
const Type * getReadPointer(int channelNumber) const noexcept
Returns a pointer to an array of read-only samples in one of the buffer's channels.
Used as a template parameter for AudioData::Pointer.
void convertSamples(Pointer source, int numSamples) const noexcept
Writes a stream of samples into this pointer from another pointer.
A class for keeping a list of available audio formats, and for deciding which one to use to open a gi...
Reads samples from an audio file stream.
bool usesFloatingPointData
Indicates whether the data is floating-point or fixed.
static void clearSamplesBeyondAvailableLength(int *const *destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int &numSamples, int64 fileLengthInSamples)
Used by AudioFormatReader subclasses to clear any parts of the data blocks that lie beyond the end of...
int64 lengthInSamples
The total number of samples in the audio stream.
double sampleRate
The sample-rate of the stream.
unsigned int bitsPerSample
The number of bits per sample, e.g.
unsigned int numChannels
The total number of channels in the audio stream.
An instance of this class is used to manage multiple AudioThumbnail objects.
TimeSliceThread & getTimeSliceThread() noexcept
Returns the thread that client thumbnails can use.
void storeThumb(const AudioThumbnailBase &thumb, int64 hashCode)
Stores the cacheable data from the specified thumb in this cache.
bool loadThumb(AudioThumbnailBase &thumb, int64 hashCode)
Reloads the specified thumb if this cache contains the appropriate stored data.
int useTimeSlice() override
Called back by a TimeSliceThread.
Makes it easy to quickly draw scaled views of the waveform shape of an audio file.
void drawChannels(Graphics &g, const Rectangle< int > &area, double startTimeSeconds, double endTimeSeconds, float verticalZoomFactor) override
Draws the waveforms for all channels in the thumbnail.
~AudioThumbnail() override
Destructor.
void getApproximateMinMax(double startTime, double endTime, int channelIndex, float &minValue, float &maxValue) const noexcept override
Reads the approximate min and max levels from a section of the thumbnail.
double getTotalLength() const noexcept override
Returns the length of the audio file, in seconds.
float getApproximatePeak() const override
Returns the highest level in the thumbnail.
int64 getNumSamplesFinished() const noexcept override
Returns the number of samples that have been set in the thumbnail.
void clear() override
Clears and resets the thumbnail.
AudioThumbnail(int sourceSamplesPerThumbnailSample, AudioFormatManager &formatManagerToUse, AudioThumbnailCache &cacheToUse)
Creates an audio thumbnail.
double getProportionComplete() const noexcept
Returns a value between 0 and 1 to indicate the progress towards loading the entire file.
void saveTo(OutputStream &output) const override
Saves the low res thumbnail data to an output stream.
bool loadFrom(InputStream &input) override
Reloads the low res thumbnail data from an input stream.
int64 getHashCode() const override
Returns the hash code that was set by setSource() or setReader().
void reset(int numChannels, double sampleRate, int64 totalSamplesInSource=0) override
Resets the thumbnail, ready for adding data with the specified format.
int getNumChannels() const noexcept override
Returns the number of channels in the file.
void drawChannel(Graphics &g, const Rectangle< int > &area, double startTimeSeconds, double endTimeSeconds, int channelNum, float verticalZoomFactor) override
Draws the waveform for a channel.
void addBlock(int64 sampleNumberInSource, const AudioBuffer< float > &newData, int startOffsetInBuffer, int numSamples) override
Adds a block of level data to the thumbnail.
void setReader(AudioFormatReader *newReader, int64 hashCode) override
Gives the thumbnail an AudioFormatReader to use directly.
bool setSource(InputSource *newSource) override
Specifies the file or stream that contains the audio file.
bool isFullyLoaded() const noexcept override
Returns true if the low res preview is fully generated.
Wraps another input stream, and reads from it using an intermediate buffer.
void sendChangeMessage()
Causes an asynchronous change message to be sent to all the registered listeners.
Automatically locks and unlocks a mutex object.
A graphics context, used for drawing a component or image.
void fillRectList(const RectangleList< float > &rectangles) const
Fills a set of rectangles using the current colour or brush.
Rectangle< int > getClipBounds() const
Returns the position of the bounding box for the current clipping region.
Very simple container class to hold a pointer to some data on the heap.
A lightweight object that can create a stream to read some kind of resource.
virtual int64 hashCode() const =0
Returns a hash code that uniquely represents this item.
The base class for streams that read data.
virtual int64 readInt64()
Reads eight bytes from the stream as a little-endian 64-bit value.
virtual void skipNextBytes(int64 numBytesToSkip)
Reads and discards a number of bytes from the stream.
virtual int read(void *destBuffer, int maxBytesToRead)=0
Reads some data from the stream into a memory buffer.
virtual int readInt()
Reads four bytes from the stream as a little-endian 32-bit value.
virtual char readByte()
Reads a byte from the stream.
The base class for streams that write data to some kind of destination.
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
Writes a block of data to the stream.
virtual bool writeInt64(int64 value)
Writes a 64-bit integer to the stream in a little-endian byte order.
virtual bool writeInt(int value)
Writes a 32-bit integer to the stream in a little-endian byte order.
An array designed for holding objects.
A general-purpose range object, that simply represents any linear range with a start and end point.
Definition juce_Range.h:40
Maintains a set of rectangles as a complex region.
void ensureStorageAllocated(int minNumRectangles)
Increases the internal storage to hold a minimum number of rectangles.
Manages a rectangle and allows geometric operations to be performed on it.
ValueType getX() const noexcept
Returns the x coordinate of the rectangle's left-hand-side.
Rectangle getIntersection(Rectangle other) const noexcept
Returns the region that is the overlap between this and another rectangle.
ValueType getBottom() const noexcept
Returns the y coordinate of the rectangle's bottom edge.
ValueType getWidth() const noexcept
Returns the width of the rectangle.
ValueType getY() const noexcept
Returns the y coordinate of the rectangle's top edge.
Rectangle withWidth(ValueType newWidth) const noexcept
Returns a rectangle which has the same position and height as this one, but with a different width.
ValueType getHeight() const noexcept
Returns the height of the rectangle.
Used by the TimeSliceThread class.
void removeTimeSliceClient(TimeSliceClient *clientToRemove)
Removes a client from the list.
void addTimeSliceClient(TimeSliceClient *clientToAdd, int millisecondsBeforeStarting=0)
Adds a client to the list.
static uint32 getMillisecondCounter() noexcept
Returns the number of millisecs since a fixed event (usually system startup).
T get(T... args)
#define JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
This macro is used to catch unsafe use of functions which expect to only be called on the message thr...
#define jassert(expression)
Platform-independent assertion macro.
typedef int
typedef float
JUCE Namespace.
CriticalSection::ScopedLockType ScopedLock
Automatically locks and unlocks a CriticalSection object.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
signed int int32
A platform-independent 32-bit signed integer type.
signed char int8
A platform-independent 8-bit signed integer type.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
CriticalSection::ScopedUnlockType ScopedUnlock
Automatically unlocks and re-locks a CriticalSection object.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
long long int64
A platform-independent 64-bit integer type.
T reset(T... args)
y1