Add TAL-Reverb-II plugin to test
[juce-lv2.git] / juce / source / src / audio / audio_file_formats / juce_AudioThumbnail.cpp
blobbe1bd9f36b0ced1aef6394098a2a5bfbee8e4d52
1 /*
2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../core/juce_StandardHeader.h"
28 BEGIN_JUCE_NAMESPACE
30 #include "juce_AudioThumbnail.h"
31 #include "juce_AudioThumbnailCache.h"
32 #include "../../events/juce_MessageManager.h"
33 #include "../../io/streams/juce_BufferedInputStream.h"
36 //==============================================================================
37 struct AudioThumbnail::MinMaxValue
39 MinMaxValue() noexcept
41 values[0] = 0;
42 values[1] = 0;
45 inline void set (const char newMin, const char newMax) noexcept
47 values[0] = newMin;
48 values[1] = newMax;
51 inline char getMinValue() const noexcept { return values[0]; }
52 inline char getMaxValue() const noexcept { return values[1]; }
54 inline void setFloat (const float newMin, const float newMax) noexcept
56 values[0] = (char) jlimit (-128, 127, roundFloatToInt (newMin * 127.0f));
57 values[1] = (char) jlimit (-128, 127, roundFloatToInt (newMax * 127.0f));
59 if (values[0] == values[1])
61 if (values[1] == 127)
62 values[0]--;
63 else
64 values[1]++;
68 inline bool isNonZero() const noexcept
70 return values[1] > values[0];
73 inline int getPeak() const noexcept
75 return jmax (std::abs ((int) values[0]),
76 std::abs ((int) values[1]));
79 inline void read (InputStream& input) { input.read (values, 2); }
80 inline void write (OutputStream& output) { output.write (values, 2); }
82 private:
83 char values[2];
86 //==============================================================================
87 class AudioThumbnail::LevelDataSource : public TimeSliceClient
89 public:
90 LevelDataSource (AudioThumbnail& owner_, AudioFormatReader* newReader, int64 hash)
91 : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
92 hashCode (hash), owner (owner_), reader (newReader)
96 LevelDataSource (AudioThumbnail& owner_, InputSource* source_)
97 : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
98 hashCode (source_->hashCode()), owner (owner_), source (source_)
102 ~LevelDataSource()
104 owner.cache.removeTimeSliceClient (this);
107 enum { timeBeforeDeletingReader = 1000 };
109 void initialise (int64 numSamplesFinished_)
111 const ScopedLock sl (readerLock);
113 numSamplesFinished = numSamplesFinished_;
115 createReader();
117 if (reader != nullptr)
119 lengthInSamples = reader->lengthInSamples;
120 numChannels = reader->numChannels;
121 sampleRate = reader->sampleRate;
123 if (lengthInSamples <= 0 || isFullyLoaded())
124 reader = nullptr;
125 else
126 owner.cache.addTimeSliceClient (this);
130 void getLevels (int64 startSample, int numSamples, Array<float>& levels)
132 const ScopedLock sl (readerLock);
134 if (reader == nullptr)
136 createReader();
138 if (reader != nullptr)
139 owner.cache.addTimeSliceClient (this);
142 if (reader != nullptr)
144 float l[4] = { 0 };
145 reader->readMaxLevels (startSample, numSamples, l[0], l[1], l[2], l[3]);
147 levels.clearQuick();
148 levels.addArray ((const float*) l, 4);
152 void releaseResources()
154 const ScopedLock sl (readerLock);
155 reader = nullptr;
158 int useTimeSlice()
160 if (isFullyLoaded())
162 if (reader != nullptr && source != nullptr)
163 releaseResources();
165 return -1;
168 bool justFinished = false;
171 const ScopedLock sl (readerLock);
173 createReader();
175 if (reader != nullptr)
177 if (! readNextBlock())
178 return 0;
180 justFinished = true;
184 if (justFinished)
185 owner.cache.storeThumb (owner, hashCode);
187 return timeBeforeDeletingReader;
190 bool isFullyLoaded() const noexcept
192 return numSamplesFinished >= lengthInSamples;
195 inline int sampleToThumbSample (const int64 originalSample) const noexcept
197 return (int) (originalSample / owner.samplesPerThumbSample);
200 int64 lengthInSamples, numSamplesFinished;
201 double sampleRate;
202 int numChannels;
203 int64 hashCode;
205 private:
206 AudioThumbnail& owner;
207 ScopedPointer <InputSource> source;
208 ScopedPointer <AudioFormatReader> reader;
209 CriticalSection readerLock;
211 void createReader()
213 if (reader == nullptr && source != nullptr)
215 InputStream* audioFileStream = source->createInputStream();
217 if (audioFileStream != nullptr)
218 reader = owner.formatManagerToUse.createReaderFor (audioFileStream);
222 bool readNextBlock()
224 jassert (reader != nullptr);
226 if (! isFullyLoaded())
228 const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
230 if (numToDo > 0)
232 int64 startSample = numSamplesFinished;
234 const int firstThumbIndex = sampleToThumbSample (startSample);
235 const int lastThumbIndex = sampleToThumbSample (startSample + numToDo);
236 const int numThumbSamps = lastThumbIndex - firstThumbIndex;
238 HeapBlock<MinMaxValue> levelData (numThumbSamps * 2);
239 MinMaxValue* levels[2] = { levelData, levelData + numThumbSamps };
241 for (int i = 0; i < numThumbSamps; ++i)
243 float lowestLeft, highestLeft, lowestRight, highestRight;
245 reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample, owner.samplesPerThumbSample,
246 lowestLeft, highestLeft, lowestRight, highestRight);
248 levels[0][i].setFloat (lowestLeft, highestLeft);
249 levels[1][i].setFloat (lowestRight, highestRight);
253 const ScopedUnlock su (readerLock);
254 owner.setLevels (levels, firstThumbIndex, 2, numThumbSamps);
257 numSamplesFinished += numToDo;
261 return isFullyLoaded();
265 //==============================================================================
266 class AudioThumbnail::ThumbData
268 public:
269 ThumbData (const int numThumbSamples)
270 : peakLevel (-1)
272 ensureSize (numThumbSamples);
275 inline MinMaxValue* getData (const int thumbSampleIndex) noexcept
277 jassert (thumbSampleIndex < data.size());
278 return data.getRawDataPointer() + thumbSampleIndex;
281 int getSize() const noexcept
283 return data.size();
286 void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
288 if (startSample >= 0)
290 endSample = jmin (endSample, data.size() - 1);
292 char mx = -128;
293 char mn = 127;
295 while (startSample <= endSample)
297 const MinMaxValue& v = data.getReference (startSample);
299 if (v.getMinValue() < mn) mn = v.getMinValue();
300 if (v.getMaxValue() > mx) mx = v.getMaxValue();
302 ++startSample;
305 if (mn <= mx)
307 result.set (mn, mx);
308 return;
312 result.set (1, 0);
315 void write (const MinMaxValue* const source, const int startIndex, const int numValues)
317 resetPeak();
319 if (startIndex + numValues > data.size())
320 ensureSize (startIndex + numValues);
322 MinMaxValue* const dest = getData (startIndex);
324 for (int i = 0; i < numValues; ++i)
325 dest[i] = source[i];
328 void resetPeak() noexcept
330 peakLevel = -1;
333 int getPeak() noexcept
335 if (peakLevel < 0)
337 for (int i = 0; i < data.size(); ++i)
339 const int peak = data[i].getPeak();
340 if (peak > peakLevel)
341 peakLevel = peak;
345 return peakLevel;
348 private:
349 Array <MinMaxValue> data;
350 int peakLevel;
352 void ensureSize (const int thumbSamples)
354 const int extraNeeded = thumbSamples - data.size();
355 if (extraNeeded > 0)
356 data.insertMultiple (-1, MinMaxValue(), extraNeeded);
360 //==============================================================================
361 class AudioThumbnail::CachedWindow
363 public:
364 CachedWindow()
365 : cachedStart (0), cachedTimePerPixel (0),
366 numChannelsCached (0), numSamplesCached (0),
367 cacheNeedsRefilling (true)
371 void invalidate()
373 cacheNeedsRefilling = true;
376 void drawChannel (Graphics& g, const Rectangle<int>& area,
377 const double startTime, const double endTime,
378 const int channelNum, const float verticalZoomFactor,
379 const double sampleRate, const int numChannels, const int samplesPerThumbSample,
380 LevelDataSource* levelData, const OwnedArray<ThumbData>& channels)
382 refillCache (area.getWidth(), startTime, endTime, sampleRate,
383 numChannels, samplesPerThumbSample, levelData, channels);
385 if (isPositiveAndBelow (channelNum, numChannelsCached))
387 const Rectangle<int> clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth()))));
389 if (! clip.isEmpty())
391 const float topY = (float) area.getY();
392 const float bottomY = (float) area.getBottom();
393 const float midY = (topY + bottomY) * 0.5f;
394 const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
396 const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX());
398 int x = clip.getX();
399 for (int w = clip.getWidth(); --w >= 0;)
401 if (cacheData->isNonZero())
402 g.drawVerticalLine (x, jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY),
403 jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY));
405 ++x;
406 ++cacheData;
412 private:
413 Array <MinMaxValue> data;
414 double cachedStart, cachedTimePerPixel;
415 int numChannelsCached, numSamplesCached;
416 bool cacheNeedsRefilling;
418 void refillCache (const int numSamples, double startTime, const double endTime,
419 const double sampleRate, const int numChannels, const int samplesPerThumbSample,
420 LevelDataSource* levelData, const OwnedArray<ThumbData>& channels)
422 const double timePerPixel = (endTime - startTime) / numSamples;
424 if (numSamples <= 0 || timePerPixel <= 0.0 || sampleRate <= 0)
426 invalidate();
427 return;
430 if (numSamples == numSamplesCached
431 && numChannelsCached == numChannels
432 && startTime == cachedStart
433 && timePerPixel == cachedTimePerPixel
434 && ! cacheNeedsRefilling)
436 return;
439 numSamplesCached = numSamples;
440 numChannelsCached = numChannels;
441 cachedStart = startTime;
442 cachedTimePerPixel = timePerPixel;
443 cacheNeedsRefilling = false;
445 ensureSize (numSamples);
447 if (timePerPixel * sampleRate <= samplesPerThumbSample && levelData != nullptr)
449 int sample = roundToInt (startTime * sampleRate);
450 Array<float> levels;
452 int i;
453 for (i = 0; i < numSamples; ++i)
455 const int nextSample = roundToInt ((startTime + timePerPixel) * sampleRate);
457 if (sample >= 0)
459 if (sample >= levelData->lengthInSamples)
460 break;
462 levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
464 const int numChans = jmin (levels.size() / 2, numChannelsCached);
466 for (int chan = 0; chan < numChans; ++chan)
467 getData (chan, i)->setFloat (levels.getUnchecked (chan * 2),
468 levels.getUnchecked (chan * 2 + 1));
471 startTime += timePerPixel;
472 sample = nextSample;
475 numSamplesCached = i;
477 else
479 jassert (channels.size() == numChannelsCached);
481 for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
483 ThumbData* channelData = channels.getUnchecked (channelNum);
484 MinMaxValue* cacheData = getData (channelNum, 0);
486 const double timeToThumbSampleFactor = sampleRate / (double) samplesPerThumbSample;
488 startTime = cachedStart;
489 int sample = roundToInt (startTime * timeToThumbSampleFactor);
491 for (int i = numSamples; --i >= 0;)
493 const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
495 channelData->getMinMax (sample, nextSample, *cacheData);
497 ++cacheData;
498 startTime += timePerPixel;
499 sample = nextSample;
505 MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
507 jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
509 return data.getRawDataPointer() + channelNum * numSamplesCached
510 + cacheIndex;
513 void ensureSize (const int numSamples)
515 const int itemsRequired = numSamples * numChannelsCached;
517 if (data.size() < itemsRequired)
518 data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
522 //==============================================================================
523 AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
524 AudioFormatManager& formatManagerToUse_,
525 AudioThumbnailCache& cacheToUse)
526 : formatManagerToUse (formatManagerToUse_),
527 cache (cacheToUse),
528 window (new CachedWindow()),
529 samplesPerThumbSample (originalSamplesPerThumbnailSample),
530 totalSamples (0),
531 numChannels (0),
532 sampleRate (0)
536 AudioThumbnail::~AudioThumbnail()
538 clear();
541 void AudioThumbnail::clear()
543 source = nullptr;
544 clearChannelData();
547 void AudioThumbnail::clearChannelData()
549 const ScopedLock sl (lock);
550 window->invalidate();
551 channels.clear();
552 totalSamples = numSamplesFinished = 0;
553 numChannels = 0;
554 sampleRate = 0;
556 sendChangeMessage();
559 void AudioThumbnail::reset (int newNumChannels, double newSampleRate, int64 totalSamplesInSource)
561 clear();
563 numChannels = newNumChannels;
564 sampleRate = newSampleRate;
565 totalSamples = totalSamplesInSource;
567 createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample));
570 void AudioThumbnail::createChannels (const int length)
572 while (channels.size() < numChannels)
573 channels.add (new ThumbData (length));
576 //==============================================================================
577 void AudioThumbnail::loadFrom (InputStream& rawInput)
579 clearChannelData();
581 BufferedInputStream input (rawInput, 4096);
583 if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
584 return;
586 samplesPerThumbSample = input.readInt();
587 totalSamples = input.readInt64(); // Total number of source samples.
588 numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
589 int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
590 numChannels = input.readInt(); // Number of audio channels.
591 sampleRate = input.readInt(); // Source sample rate.
592 input.skipNextBytes (16); // (reserved)
594 createChannels (numThumbnailSamples);
596 for (int i = 0; i < numThumbnailSamples; ++i)
597 for (int chan = 0; chan < numChannels; ++chan)
598 channels.getUnchecked(chan)->getData(i)->read (input);
601 void AudioThumbnail::saveTo (OutputStream& output) const
603 const ScopedLock sl (lock);
605 const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize();
607 output.write ("jatm", 4);
608 output.writeInt (samplesPerThumbSample);
609 output.writeInt64 (totalSamples);
610 output.writeInt64 (numSamplesFinished);
611 output.writeInt (numThumbnailSamples);
612 output.writeInt (numChannels);
613 output.writeInt ((int) sampleRate);
614 output.writeInt64 (0);
615 output.writeInt64 (0);
617 for (int i = 0; i < numThumbnailSamples; ++i)
618 for (int chan = 0; chan < numChannels; ++chan)
619 channels.getUnchecked(chan)->getData(i)->write (output);
622 //==============================================================================
623 bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
625 jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
627 numSamplesFinished = 0;
629 if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
631 source = newSource; // (make sure this isn't done before loadThumb is called)
633 source->lengthInSamples = totalSamples;
634 source->sampleRate = sampleRate;
635 source->numChannels = numChannels;
636 source->numSamplesFinished = numSamplesFinished;
638 else
640 source = newSource; // (make sure this isn't done before loadThumb is called)
642 const ScopedLock sl (lock);
643 source->initialise (numSamplesFinished);
645 totalSamples = source->lengthInSamples;
646 sampleRate = source->sampleRate;
647 numChannels = source->numChannels;
649 createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
652 return sampleRate > 0 && totalSamples > 0;
655 bool AudioThumbnail::setSource (InputSource* const newSource)
657 clear();
659 return newSource != nullptr && setDataSource (new LevelDataSource (*this, newSource));
662 void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash)
664 clear();
666 if (newReader != nullptr)
667 setDataSource (new LevelDataSource (*this, newReader, hash));
670 int64 AudioThumbnail::getHashCode() const
672 return source == nullptr ? 0 : source->hashCode;
675 void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming,
676 int startOffsetInBuffer, int numSamples)
678 jassert (startSample >= 0);
680 const int firstThumbIndex = (int) (startSample / samplesPerThumbSample);
681 const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
682 const int numToDo = lastThumbIndex - firstThumbIndex;
684 if (numToDo > 0)
686 const int numChans = jmin (channels.size(), incoming.getNumChannels());
688 const HeapBlock<MinMaxValue> thumbData (numToDo * numChans);
689 const HeapBlock<MinMaxValue*> thumbChannels (numChans);
691 for (int chan = 0; chan < numChans; ++chan)
693 const float* const sourceData = incoming.getSampleData (chan, startOffsetInBuffer);
694 MinMaxValue* const dest = thumbData + numToDo * chan;
695 thumbChannels [chan] = dest;
697 for (int i = 0; i < numToDo; ++i)
699 float low, high;
700 const int start = i * samplesPerThumbSample;
701 findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start), low, high);
702 dest[i].setFloat (low, high);
706 setLevels (thumbChannels, firstThumbIndex, numChans, numToDo);
710 void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
712 const ScopedLock sl (lock);
714 for (int i = jmin (numChans, channels.size()); --i >= 0;)
715 channels.getUnchecked(i)->write (values[i], thumbIndex, numValues);
717 const int64 start = thumbIndex * (int64) samplesPerThumbSample;
718 const int64 end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
720 if (numSamplesFinished >= start && end > numSamplesFinished)
721 numSamplesFinished = end;
723 totalSamples = jmax (numSamplesFinished, totalSamples);
724 window->invalidate();
725 sendChangeMessage();
728 //==============================================================================
729 int AudioThumbnail::getNumChannels() const noexcept
731 return numChannels;
734 double AudioThumbnail::getTotalLength() const noexcept
736 return sampleRate > 0 ? (totalSamples / sampleRate) : 0;
739 bool AudioThumbnail::isFullyLoaded() const noexcept
741 return numSamplesFinished >= totalSamples - samplesPerThumbSample;
744 int64 AudioThumbnail::getNumSamplesFinished() const noexcept
746 return numSamplesFinished;
749 float AudioThumbnail::getApproximatePeak() const
751 int peak = 0;
753 for (int i = channels.size(); --i >= 0;)
754 peak = jmax (peak, channels.getUnchecked(i)->getPeak());
756 return jlimit (0, 127, peak) / 127.0f;
759 void AudioThumbnail::getApproximateMinMax (const double startTime, const double endTime, const int channelIndex,
760 float& minValue, float& maxValue) const noexcept
762 MinMaxValue result;
763 const ThumbData* const data = channels [channelIndex];
765 if (data != nullptr && sampleRate > 0)
767 const int firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
768 const int lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
770 data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
773 minValue = result.getMinValue() / 128.0f;
774 maxValue = result.getMaxValue() / 128.0f;
777 void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
778 double endTime, int channelNum, float verticalZoomFactor)
780 const ScopedLock sl (lock);
782 window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
783 sampleRate, numChannels, samplesPerThumbSample, source, channels);
786 void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
787 double endTimeSeconds, float verticalZoomFactor)
789 for (int i = 0; i < numChannels; ++i)
791 const int y1 = roundToInt ((i * area.getHeight()) / numChannels);
792 const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
794 drawChannel (g, Rectangle<int> (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1),
795 startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
800 END_JUCE_NAMESPACE