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.

831 lines
27KB

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