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.

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