The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

838 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. struct AudioThumbnail::MinMaxValue
  22. {
  23. MinMaxValue() noexcept
  24. {
  25. values[0] = 0;
  26. values[1] = 0;
  27. }
  28. inline void set (const int8 newMin, const int8 newMax) noexcept
  29. {
  30. values[0] = newMin;
  31. values[1] = newMax;
  32. }
  33. inline int8 getMinValue() const noexcept { return values[0]; }
  34. inline int8 getMaxValue() const noexcept { return values[1]; }
  35. inline void setFloat (Range<float> newRange) noexcept
  36. {
  37. // Workaround for an ndk armeabi compiler bug which crashes on signed saturation
  38. #if JUCE_ANDROID
  39. Range<float> limitedRange (jlimit (-1.0f, 1.0f, newRange.getStart()),
  40. jlimit (-1.0f, 1.0f, newRange.getEnd()));
  41. values[0] = (int8) (limitedRange.getStart() * 127.0f);
  42. values[1] = (int8) (limitedRange.getEnd() * 127.0f);
  43. #else
  44. values[0] = (int8) jlimit (-128, 127, roundFloatToInt (newRange.getStart() * 127.0f));
  45. values[1] = (int8) jlimit (-128, 127, roundFloatToInt (newRange.getEnd() * 127.0f));
  46. #endif
  47. if (values[0] == values[1])
  48. {
  49. if (values[1] == 127)
  50. values[0]--;
  51. else
  52. values[1]++;
  53. }
  54. }
  55. inline bool isNonZero() const noexcept
  56. {
  57. return values[1] > values[0];
  58. }
  59. inline int getPeak() const noexcept
  60. {
  61. return jmax (std::abs ((int) values[0]),
  62. std::abs ((int) values[1]));
  63. }
  64. inline void read (InputStream& input) { input.read (values, 2); }
  65. inline void write (OutputStream& output) { output.write (values, 2); }
  66. private:
  67. int8 values[2];
  68. };
  69. //==============================================================================
  70. class AudioThumbnail::LevelDataSource : public TimeSliceClient
  71. {
  72. public:
  73. LevelDataSource (AudioThumbnail& thumb, AudioFormatReader* newReader, int64 hash)
  74. : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
  75. hashCode (hash), owner (thumb), reader (newReader), lastReaderUseTime (0)
  76. {
  77. }
  78. LevelDataSource (AudioThumbnail& thumb, InputSource* src)
  79. : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
  80. hashCode (src->hashCode()), owner (thumb), source (src), lastReaderUseTime (0)
  81. {
  82. }
  83. ~LevelDataSource()
  84. {
  85. owner.cache.getTimeSliceThread().removeTimeSliceClient (this);
  86. }
  87. enum { timeBeforeDeletingReader = 3000 };
  88. void initialise (int64 samplesFinished)
  89. {
  90. const ScopedLock sl (readerLock);
  91. numSamplesFinished = samplesFinished;
  92. createReader();
  93. if (reader != nullptr)
  94. {
  95. lengthInSamples = reader->lengthInSamples;
  96. numChannels = reader->numChannels;
  97. sampleRate = reader->sampleRate;
  98. if (lengthInSamples <= 0 || isFullyLoaded())
  99. reader = nullptr;
  100. else
  101. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  102. }
  103. }
  104. void getLevels (int64 startSample, int numSamples, Array<Range<float> >& levels)
  105. {
  106. const ScopedLock sl (readerLock);
  107. if (reader == nullptr)
  108. {
  109. createReader();
  110. if (reader != nullptr)
  111. {
  112. lastReaderUseTime = Time::getMillisecondCounter();
  113. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  114. }
  115. }
  116. if (reader != nullptr)
  117. {
  118. if (levels.size() < (int) reader->numChannels)
  119. levels.insertMultiple (0, Range<float>(), (int) reader->numChannels - levels.size());
  120. reader->readMaxLevels (startSample, numSamples, levels.getRawDataPointer(), (int) reader->numChannels);
  121. lastReaderUseTime = Time::getMillisecondCounter();
  122. }
  123. }
  124. void releaseResources()
  125. {
  126. const ScopedLock sl (readerLock);
  127. reader = nullptr;
  128. }
  129. int useTimeSlice() override
  130. {
  131. if (isFullyLoaded())
  132. {
  133. if (reader != nullptr && source != nullptr)
  134. {
  135. if (Time::getMillisecondCounter() > lastReaderUseTime + timeBeforeDeletingReader)
  136. releaseResources();
  137. else
  138. return 200;
  139. }
  140. return -1;
  141. }
  142. bool justFinished = false;
  143. {
  144. const ScopedLock sl (readerLock);
  145. createReader();
  146. if (reader != nullptr)
  147. {
  148. if (! readNextBlock())
  149. return 0;
  150. justFinished = true;
  151. }
  152. }
  153. if (justFinished)
  154. owner.cache.storeThumb (owner, hashCode);
  155. return 200;
  156. }
  157. bool isFullyLoaded() const noexcept
  158. {
  159. return numSamplesFinished >= lengthInSamples;
  160. }
  161. inline int sampleToThumbSample (const int64 originalSample) const noexcept
  162. {
  163. return (int) (originalSample / owner.samplesPerThumbSample);
  164. }
  165. int64 lengthInSamples, numSamplesFinished;
  166. double sampleRate;
  167. unsigned int numChannels;
  168. int64 hashCode;
  169. private:
  170. AudioThumbnail& owner;
  171. ScopedPointer<InputSource> source;
  172. ScopedPointer<AudioFormatReader> reader;
  173. CriticalSection readerLock;
  174. uint32 lastReaderUseTime;
  175. void createReader()
  176. {
  177. if (reader == nullptr && source != nullptr)
  178. if (InputStream* audioFileStream = source->createInputStream())
  179. reader = owner.formatManagerToUse.createReaderFor (audioFileStream);
  180. }
  181. bool readNextBlock()
  182. {
  183. jassert (reader != nullptr);
  184. if (! isFullyLoaded())
  185. {
  186. const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
  187. if (numToDo > 0)
  188. {
  189. int64 startSample = numSamplesFinished;
  190. const int firstThumbIndex = sampleToThumbSample (startSample);
  191. const int lastThumbIndex = sampleToThumbSample (startSample + numToDo);
  192. const int numThumbSamps = lastThumbIndex - firstThumbIndex;
  193. HeapBlock<MinMaxValue> levelData ((size_t) numThumbSamps * numChannels);
  194. HeapBlock<MinMaxValue*> levels (numChannels);
  195. for (int i = 0; i < (int) numChannels; ++i)
  196. levels[i] = levelData + i * numThumbSamps;
  197. HeapBlock<Range<float> > levelsRead (numChannels);
  198. for (int i = 0; i < numThumbSamps; ++i)
  199. {
  200. reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample,
  201. owner.samplesPerThumbSample, levelsRead, (int) numChannels);
  202. for (int j = 0; j < (int) numChannels; ++j)
  203. levels[j][i].setFloat (levelsRead[j]);
  204. }
  205. {
  206. const ScopedUnlock su (readerLock);
  207. owner.setLevels (levels, firstThumbIndex, (int) numChannels, numThumbSamps);
  208. }
  209. numSamplesFinished += numToDo;
  210. lastReaderUseTime = Time::getMillisecondCounter();
  211. }
  212. }
  213. return isFullyLoaded();
  214. }
  215. };
  216. //==============================================================================
  217. class AudioThumbnail::ThumbData
  218. {
  219. public:
  220. ThumbData (const int numThumbSamples)
  221. : peakLevel (-1)
  222. {
  223. ensureSize (numThumbSamples);
  224. }
  225. inline MinMaxValue* getData (const int thumbSampleIndex) noexcept
  226. {
  227. jassert (thumbSampleIndex < data.size());
  228. return data.getRawDataPointer() + thumbSampleIndex;
  229. }
  230. int getSize() const noexcept
  231. {
  232. return data.size();
  233. }
  234. void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
  235. {
  236. if (startSample >= 0)
  237. {
  238. endSample = jmin (endSample, data.size() - 1);
  239. int8 mx = -128;
  240. int8 mn = 127;
  241. while (startSample <= endSample)
  242. {
  243. const MinMaxValue& v = data.getReference (startSample);
  244. if (v.getMinValue() < mn) mn = v.getMinValue();
  245. if (v.getMaxValue() > mx) mx = v.getMaxValue();
  246. ++startSample;
  247. }
  248. if (mn <= mx)
  249. {
  250. result.set (mn, mx);
  251. return;
  252. }
  253. }
  254. result.set (1, 0);
  255. }
  256. void write (const MinMaxValue* const values, const int startIndex, const int numValues)
  257. {
  258. resetPeak();
  259. if (startIndex + numValues > data.size())
  260. ensureSize (startIndex + numValues);
  261. MinMaxValue* const dest = getData (startIndex);
  262. for (int i = 0; i < numValues; ++i)
  263. dest[i] = values[i];
  264. }
  265. void resetPeak() noexcept
  266. {
  267. peakLevel = -1;
  268. }
  269. int getPeak() noexcept
  270. {
  271. if (peakLevel < 0)
  272. {
  273. for (int i = 0; i < data.size(); ++i)
  274. {
  275. const int peak = data[i].getPeak();
  276. if (peak > peakLevel)
  277. peakLevel = peak;
  278. }
  279. }
  280. return peakLevel;
  281. }
  282. private:
  283. Array<MinMaxValue> data;
  284. int peakLevel;
  285. void ensureSize (const int thumbSamples)
  286. {
  287. const int extraNeeded = thumbSamples - data.size();
  288. if (extraNeeded > 0)
  289. data.insertMultiple (-1, MinMaxValue(), extraNeeded);
  290. }
  291. };
  292. //==============================================================================
  293. class AudioThumbnail::CachedWindow
  294. {
  295. public:
  296. CachedWindow()
  297. : cachedStart (0), cachedTimePerPixel (0),
  298. numChannelsCached (0), numSamplesCached (0),
  299. cacheNeedsRefilling (true)
  300. {
  301. }
  302. void invalidate()
  303. {
  304. cacheNeedsRefilling = true;
  305. }
  306. void drawChannel (Graphics& g, const Rectangle<int>& area,
  307. const double startTime, const double endTime,
  308. const int channelNum, const float verticalZoomFactor,
  309. const double rate, const int numChans, const int sampsPerThumbSample,
  310. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  311. {
  312. if (refillCache (area.getWidth(), startTime, endTime, rate,
  313. numChans, sampsPerThumbSample, levelData, chans)
  314. && isPositiveAndBelow (channelNum, numChannelsCached))
  315. {
  316. const Rectangle<int> clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth()))));
  317. if (! clip.isEmpty())
  318. {
  319. const float topY = (float) area.getY();
  320. const float bottomY = (float) area.getBottom();
  321. const float midY = (topY + bottomY) * 0.5f;
  322. const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
  323. const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX());
  324. RectangleList<float> waveform;
  325. waveform.ensureStorageAllocated (clip.getWidth());
  326. float x = (float) clip.getX();
  327. for (int w = clip.getWidth(); --w >= 0;)
  328. {
  329. if (cacheData->isNonZero())
  330. {
  331. const float top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
  332. const float bottom = jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY);
  333. waveform.addWithoutMerging (Rectangle<float> (x, top, 1.0f, bottom - top));
  334. }
  335. x += 1.0f;
  336. ++cacheData;
  337. }
  338. g.fillRectList (waveform);
  339. }
  340. }
  341. }
  342. private:
  343. Array<MinMaxValue> data;
  344. double cachedStart, cachedTimePerPixel;
  345. int numChannelsCached, numSamplesCached;
  346. bool cacheNeedsRefilling;
  347. bool refillCache (const int numSamples, double startTime, const double endTime,
  348. const double rate, const int numChans, const int sampsPerThumbSample,
  349. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  350. {
  351. const double timePerPixel = (endTime - startTime) / numSamples;
  352. if (numSamples <= 0 || timePerPixel <= 0.0 || rate <= 0)
  353. {
  354. invalidate();
  355. return false;
  356. }
  357. if (numSamples == numSamplesCached
  358. && numChannelsCached == numChans
  359. && startTime == cachedStart
  360. && timePerPixel == cachedTimePerPixel
  361. && ! cacheNeedsRefilling)
  362. {
  363. return ! cacheNeedsRefilling;
  364. }
  365. numSamplesCached = numSamples;
  366. numChannelsCached = numChans;
  367. cachedStart = startTime;
  368. cachedTimePerPixel = timePerPixel;
  369. cacheNeedsRefilling = false;
  370. ensureSize (numSamples);
  371. if (timePerPixel * rate <= sampsPerThumbSample && levelData != nullptr)
  372. {
  373. int sample = roundToInt (startTime * rate);
  374. Array<Range<float> > levels;
  375. int i;
  376. for (i = 0; i < numSamples; ++i)
  377. {
  378. const int nextSample = roundToInt ((startTime + timePerPixel) * rate);
  379. if (sample >= 0)
  380. {
  381. if (sample >= levelData->lengthInSamples)
  382. {
  383. for (int chan = 0; chan < numChannelsCached; ++chan)
  384. *getData (chan, i) = MinMaxValue();
  385. }
  386. else
  387. {
  388. levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
  389. const int totalChans = jmin (levels.size(), numChannelsCached);
  390. for (int chan = 0; chan < totalChans; ++chan)
  391. getData (chan, i)->setFloat (levels.getReference (chan));
  392. }
  393. }
  394. startTime += timePerPixel;
  395. sample = nextSample;
  396. }
  397. numSamplesCached = i;
  398. }
  399. else
  400. {
  401. jassert (chans.size() == numChannelsCached);
  402. for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
  403. {
  404. ThumbData* channelData = chans.getUnchecked (channelNum);
  405. MinMaxValue* cacheData = getData (channelNum, 0);
  406. const double timeToThumbSampleFactor = rate / (double) sampsPerThumbSample;
  407. startTime = cachedStart;
  408. int sample = roundToInt (startTime * timeToThumbSampleFactor);
  409. for (int i = numSamples; --i >= 0;)
  410. {
  411. const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
  412. channelData->getMinMax (sample, nextSample, *cacheData);
  413. ++cacheData;
  414. startTime += timePerPixel;
  415. sample = nextSample;
  416. }
  417. }
  418. }
  419. return true;
  420. }
  421. MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
  422. {
  423. jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
  424. return data.getRawDataPointer() + channelNum * numSamplesCached
  425. + cacheIndex;
  426. }
  427. void ensureSize (const int numSamples)
  428. {
  429. const int itemsRequired = numSamples * numChannelsCached;
  430. if (data.size() < itemsRequired)
  431. data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
  432. }
  433. };
  434. //==============================================================================
  435. AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
  436. AudioFormatManager& formatManager,
  437. AudioThumbnailCache& cacheToUse)
  438. : formatManagerToUse (formatManager),
  439. cache (cacheToUse),
  440. window (new CachedWindow()),
  441. samplesPerThumbSample (originalSamplesPerThumbnailSample),
  442. totalSamples (0),
  443. numSamplesFinished (0),
  444. numChannels (0),
  445. sampleRate (0)
  446. {
  447. }
  448. AudioThumbnail::~AudioThumbnail()
  449. {
  450. clear();
  451. }
  452. void AudioThumbnail::clear()
  453. {
  454. source = nullptr;
  455. const ScopedLock sl (lock);
  456. clearChannelData();
  457. }
  458. void AudioThumbnail::clearChannelData()
  459. {
  460. window->invalidate();
  461. channels.clear();
  462. totalSamples = numSamplesFinished = 0;
  463. numChannels = 0;
  464. sampleRate = 0;
  465. sendChangeMessage();
  466. }
  467. void AudioThumbnail::reset (int newNumChannels, double newSampleRate, int64 totalSamplesInSource)
  468. {
  469. clear();
  470. const ScopedLock sl (lock);
  471. numChannels = newNumChannels;
  472. sampleRate = newSampleRate;
  473. totalSamples = totalSamplesInSource;
  474. createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample));
  475. }
  476. void AudioThumbnail::createChannels (const int length)
  477. {
  478. while (channels.size() < numChannels)
  479. channels.add (new ThumbData (length));
  480. }
  481. //==============================================================================
  482. bool AudioThumbnail::loadFrom (InputStream& rawInput)
  483. {
  484. BufferedInputStream input (rawInput, 4096);
  485. if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
  486. return false;
  487. const ScopedLock sl (lock);
  488. clearChannelData();
  489. samplesPerThumbSample = input.readInt();
  490. totalSamples = input.readInt64(); // Total number of source samples.
  491. numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
  492. int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
  493. numChannels = input.readInt(); // Number of audio channels.
  494. sampleRate = input.readInt(); // Source sample rate.
  495. input.skipNextBytes (16); // (reserved)
  496. createChannels (numThumbnailSamples);
  497. for (int i = 0; i < numThumbnailSamples; ++i)
  498. for (int chan = 0; chan < numChannels; ++chan)
  499. channels.getUnchecked(chan)->getData(i)->read (input);
  500. return true;
  501. }
  502. void AudioThumbnail::saveTo (OutputStream& output) const
  503. {
  504. const ScopedLock sl (lock);
  505. const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize();
  506. output.write ("jatm", 4);
  507. output.writeInt (samplesPerThumbSample);
  508. output.writeInt64 (totalSamples);
  509. output.writeInt64 (numSamplesFinished);
  510. output.writeInt (numThumbnailSamples);
  511. output.writeInt (numChannels);
  512. output.writeInt ((int) sampleRate);
  513. output.writeInt64 (0);
  514. output.writeInt64 (0);
  515. for (int i = 0; i < numThumbnailSamples; ++i)
  516. for (int chan = 0; chan < numChannels; ++chan)
  517. channels.getUnchecked(chan)->getData(i)->write (output);
  518. }
  519. //==============================================================================
  520. bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
  521. {
  522. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
  523. numSamplesFinished = 0;
  524. if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
  525. {
  526. source = newSource; // (make sure this isn't done before loadThumb is called)
  527. source->lengthInSamples = totalSamples;
  528. source->sampleRate = sampleRate;
  529. source->numChannels = (unsigned int) numChannels;
  530. source->numSamplesFinished = numSamplesFinished;
  531. }
  532. else
  533. {
  534. source = newSource; // (make sure this isn't done before loadThumb is called)
  535. const ScopedLock sl (lock);
  536. source->initialise (numSamplesFinished);
  537. totalSamples = source->lengthInSamples;
  538. sampleRate = source->sampleRate;
  539. numChannels = (int32) source->numChannels;
  540. createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
  541. }
  542. return sampleRate > 0 && totalSamples > 0;
  543. }
  544. bool AudioThumbnail::setSource (InputSource* const newSource)
  545. {
  546. clear();
  547. return newSource != nullptr && setDataSource (new LevelDataSource (*this, newSource));
  548. }
  549. void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash)
  550. {
  551. clear();
  552. if (newReader != nullptr)
  553. setDataSource (new LevelDataSource (*this, newReader, hash));
  554. }
  555. int64 AudioThumbnail::getHashCode() const
  556. {
  557. return source == nullptr ? 0 : source->hashCode;
  558. }
  559. void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming,
  560. int startOffsetInBuffer, int numSamples)
  561. {
  562. jassert (startSample >= 0
  563. && startOffsetInBuffer >= 0
  564. && startOffsetInBuffer + numSamples <= incoming.getNumSamples());
  565. const int firstThumbIndex = (int) (startSample / samplesPerThumbSample);
  566. const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
  567. const int numToDo = lastThumbIndex - firstThumbIndex;
  568. if (numToDo > 0)
  569. {
  570. const int numChans = jmin (channels.size(), incoming.getNumChannels());
  571. const HeapBlock<MinMaxValue> thumbData ((size_t) (numToDo * numChans));
  572. const HeapBlock<MinMaxValue*> thumbChannels ((size_t) numChans);
  573. for (int chan = 0; chan < numChans; ++chan)
  574. {
  575. const float* const sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
  576. MinMaxValue* const dest = thumbData + numToDo * chan;
  577. thumbChannels [chan] = dest;
  578. for (int i = 0; i < numToDo; ++i)
  579. {
  580. const int start = i * samplesPerThumbSample;
  581. dest[i].setFloat (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start)));
  582. }
  583. }
  584. setLevels (thumbChannels, firstThumbIndex, numChans, numToDo);
  585. }
  586. }
  587. void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
  588. {
  589. const ScopedLock sl (lock);
  590. for (int i = jmin (numChans, channels.size()); --i >= 0;)
  591. channels.getUnchecked(i)->write (values[i], thumbIndex, numValues);
  592. const int64 start = thumbIndex * (int64) samplesPerThumbSample;
  593. const int64 end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
  594. if (numSamplesFinished >= start && end > numSamplesFinished)
  595. numSamplesFinished = end;
  596. totalSamples = jmax (numSamplesFinished, totalSamples);
  597. window->invalidate();
  598. sendChangeMessage();
  599. }
  600. //==============================================================================
  601. int AudioThumbnail::getNumChannels() const noexcept
  602. {
  603. return numChannels;
  604. }
  605. double AudioThumbnail::getTotalLength() const noexcept
  606. {
  607. return sampleRate > 0 ? (totalSamples / sampleRate) : 0;
  608. }
  609. bool AudioThumbnail::isFullyLoaded() const noexcept
  610. {
  611. return numSamplesFinished >= totalSamples - samplesPerThumbSample;
  612. }
  613. double AudioThumbnail::getProportionComplete() const noexcept
  614. {
  615. return jlimit (0.0, 1.0, numSamplesFinished / (double) jmax ((int64) 1, totalSamples));
  616. }
  617. int64 AudioThumbnail::getNumSamplesFinished() const noexcept
  618. {
  619. return numSamplesFinished;
  620. }
  621. float AudioThumbnail::getApproximatePeak() const
  622. {
  623. const ScopedLock sl (lock);
  624. int peak = 0;
  625. for (int i = channels.size(); --i >= 0;)
  626. peak = jmax (peak, channels.getUnchecked(i)->getPeak());
  627. return jlimit (0, 127, peak) / 127.0f;
  628. }
  629. void AudioThumbnail::getApproximateMinMax (const double startTime, const double endTime, const int channelIndex,
  630. float& minValue, float& maxValue) const noexcept
  631. {
  632. const ScopedLock sl (lock);
  633. MinMaxValue result;
  634. const ThumbData* const data = channels [channelIndex];
  635. if (data != nullptr && sampleRate > 0)
  636. {
  637. const int firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
  638. const int lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
  639. data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
  640. }
  641. minValue = result.getMinValue() / 128.0f;
  642. maxValue = result.getMaxValue() / 128.0f;
  643. }
  644. void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
  645. double endTime, int channelNum, float verticalZoomFactor)
  646. {
  647. const ScopedLock sl (lock);
  648. window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
  649. sampleRate, numChannels, samplesPerThumbSample, source, channels);
  650. }
  651. void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
  652. double endTimeSeconds, float verticalZoomFactor)
  653. {
  654. for (int i = 0; i < numChannels; ++i)
  655. {
  656. const int y1 = roundToInt ((i * area.getHeight()) / numChannels);
  657. const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
  658. drawChannel (g, Rectangle<int> (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1),
  659. startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
  660. }
  661. }
  662. } // namespace juce