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.

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