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.

821 lines
26KB

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