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.

803 lines
26KB

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