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.

827 lines
26KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. struct AudioThumbnail::MinMaxValue
  22. {
  23. MinMaxValue() noexcept
  24. {
  25. values[0] = 0;
  26. values[1] = 0;
  27. }
  28. inline void set (const int8 newMin, const int8 newMax) noexcept
  29. {
  30. values[0] = newMin;
  31. values[1] = newMax;
  32. }
  33. inline int8 getMinValue() const noexcept { return values[0]; }
  34. inline int8 getMaxValue() const noexcept { return values[1]; }
  35. inline void setFloat (Range<float> newRange) noexcept
  36. {
  37. // Workaround for an ndk armeabi compiler bug which crashes on signed saturation
  38. #if JUCE_ANDROID
  39. Range<float> limitedRange (jlimit (-1.0f, 1.0f, newRange.getStart()),
  40. jlimit (-1.0f, 1.0f, newRange.getEnd()));
  41. values[0] = (int8) (limitedRange.getStart() * 127.0f);
  42. values[1] = (int8) (limitedRange.getEnd() * 127.0f);
  43. #else
  44. values[0] = (int8) jlimit (-128, 127, roundToInt (newRange.getStart() * 127.0f));
  45. values[1] = (int8) jlimit (-128, 127, roundToInt (newRange.getEnd() * 127.0f));
  46. #endif
  47. if (values[0] == values[1])
  48. {
  49. if (values[1] == 127)
  50. values[0]--;
  51. else
  52. values[1]++;
  53. }
  54. }
  55. inline bool isNonZero() const noexcept
  56. {
  57. return values[1] > values[0];
  58. }
  59. inline int getPeak() const noexcept
  60. {
  61. return jmax (std::abs ((int) values[0]),
  62. std::abs ((int) values[1]));
  63. }
  64. inline void read (InputStream& input) { input.read (values, 2); }
  65. inline void write (OutputStream& output) { output.write (values, 2); }
  66. private:
  67. int8 values[2];
  68. };
  69. //==============================================================================
  70. class AudioThumbnail::LevelDataSource : public TimeSliceClient
  71. {
  72. public:
  73. LevelDataSource (AudioThumbnail& thumb, AudioFormatReader* newReader, int64 hash)
  74. : hashCode (hash), owner (thumb), reader (newReader)
  75. {
  76. }
  77. LevelDataSource (AudioThumbnail& thumb, InputSource* src)
  78. : hashCode (src->hashCode()), owner (thumb), source (src)
  79. {
  80. }
  81. ~LevelDataSource()
  82. {
  83. owner.cache.getTimeSliceThread().removeTimeSliceClient (this);
  84. }
  85. enum { timeBeforeDeletingReader = 3000 };
  86. void initialise (int64 samplesFinished)
  87. {
  88. const ScopedLock sl (readerLock);
  89. numSamplesFinished = samplesFinished;
  90. createReader();
  91. if (reader != nullptr)
  92. {
  93. lengthInSamples = reader->lengthInSamples;
  94. numChannels = reader->numChannels;
  95. sampleRate = reader->sampleRate;
  96. if (lengthInSamples <= 0 || isFullyLoaded())
  97. reader.reset();
  98. else
  99. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  100. }
  101. }
  102. void getLevels (int64 startSample, int numSamples, Array<Range<float>>& levels)
  103. {
  104. const ScopedLock sl (readerLock);
  105. if (reader == nullptr)
  106. {
  107. createReader();
  108. if (reader != nullptr)
  109. {
  110. lastReaderUseTime = Time::getMillisecondCounter();
  111. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  112. }
  113. }
  114. if (reader != nullptr)
  115. {
  116. if (levels.size() < (int) reader->numChannels)
  117. levels.insertMultiple (0, {}, (int) reader->numChannels - levels.size());
  118. reader->readMaxLevels (startSample, numSamples, levels.getRawDataPointer(), (int) reader->numChannels);
  119. lastReaderUseTime = Time::getMillisecondCounter();
  120. }
  121. }
  122. void releaseResources()
  123. {
  124. const ScopedLock sl (readerLock);
  125. reader.reset();
  126. }
  127. int useTimeSlice() override
  128. {
  129. if (isFullyLoaded())
  130. {
  131. if (reader != nullptr && source != nullptr)
  132. {
  133. if (Time::getMillisecondCounter() > lastReaderUseTime + timeBeforeDeletingReader)
  134. releaseResources();
  135. else
  136. return 200;
  137. }
  138. return -1;
  139. }
  140. bool justFinished = false;
  141. {
  142. const ScopedLock sl (readerLock);
  143. createReader();
  144. if (reader != nullptr)
  145. {
  146. if (! readNextBlock())
  147. return 0;
  148. justFinished = true;
  149. }
  150. }
  151. if (justFinished)
  152. owner.cache.storeThumb (owner, hashCode);
  153. return 200;
  154. }
  155. bool isFullyLoaded() const noexcept
  156. {
  157. return numSamplesFinished >= lengthInSamples;
  158. }
  159. inline int sampleToThumbSample (const int64 originalSample) const noexcept
  160. {
  161. return (int) (originalSample / owner.samplesPerThumbSample);
  162. }
  163. int64 lengthInSamples = 0, numSamplesFinished = 0;
  164. double sampleRate = 0;
  165. unsigned int numChannels = 0;
  166. int64 hashCode = 0;
  167. private:
  168. AudioThumbnail& owner;
  169. std::unique_ptr<InputSource> source;
  170. std::unique_ptr<AudioFormatReader> reader;
  171. CriticalSection readerLock;
  172. uint32 lastReaderUseTime = 0;
  173. void createReader()
  174. {
  175. if (reader == nullptr && source != nullptr)
  176. if (auto* audioFileStream = source->createInputStream())
  177. reader.reset (owner.formatManagerToUse.createReaderFor (audioFileStream));
  178. }
  179. bool readNextBlock()
  180. {
  181. jassert (reader != nullptr);
  182. if (! isFullyLoaded())
  183. {
  184. auto numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
  185. if (numToDo > 0)
  186. {
  187. auto startSample = numSamplesFinished;
  188. auto firstThumbIndex = sampleToThumbSample (startSample);
  189. auto lastThumbIndex = sampleToThumbSample (startSample + numToDo);
  190. auto numThumbSamps = lastThumbIndex - firstThumbIndex;
  191. HeapBlock<MinMaxValue> levelData ((unsigned int) numThumbSamps * numChannels);
  192. HeapBlock<MinMaxValue*> levels (numChannels);
  193. for (int i = 0; i < (int) numChannels; ++i)
  194. levels[i] = levelData + i * numThumbSamps;
  195. HeapBlock<Range<float>> levelsRead (numChannels);
  196. for (int i = 0; i < numThumbSamps; ++i)
  197. {
  198. reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample,
  199. owner.samplesPerThumbSample, levelsRead, (int) numChannels);
  200. for (int j = 0; j < (int) numChannels; ++j)
  201. levels[j][i].setFloat (levelsRead[j]);
  202. }
  203. {
  204. const ScopedUnlock su (readerLock);
  205. owner.setLevels (levels, firstThumbIndex, (int) numChannels, numThumbSamps);
  206. }
  207. numSamplesFinished += numToDo;
  208. lastReaderUseTime = Time::getMillisecondCounter();
  209. }
  210. }
  211. return isFullyLoaded();
  212. }
  213. };
  214. //==============================================================================
  215. class AudioThumbnail::ThumbData
  216. {
  217. public:
  218. ThumbData (int numThumbSamples)
  219. {
  220. ensureSize (numThumbSamples);
  221. }
  222. inline MinMaxValue* getData (int thumbSampleIndex) noexcept
  223. {
  224. jassert (thumbSampleIndex < data.size());
  225. return data.getRawDataPointer() + thumbSampleIndex;
  226. }
  227. int getSize() const noexcept
  228. {
  229. return data.size();
  230. }
  231. void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
  232. {
  233. if (startSample >= 0)
  234. {
  235. endSample = jmin (endSample, data.size() - 1);
  236. int8 mx = -128;
  237. int8 mn = 127;
  238. while (startSample <= endSample)
  239. {
  240. auto& v = data.getReference (startSample);
  241. if (v.getMinValue() < mn) mn = v.getMinValue();
  242. if (v.getMaxValue() > mx) mx = v.getMaxValue();
  243. ++startSample;
  244. }
  245. if (mn <= mx)
  246. {
  247. result.set (mn, mx);
  248. return;
  249. }
  250. }
  251. result.set (1, 0);
  252. }
  253. void write (const MinMaxValue* values, int startIndex, int numValues)
  254. {
  255. resetPeak();
  256. if (startIndex + numValues > data.size())
  257. ensureSize (startIndex + numValues);
  258. auto* dest = getData (startIndex);
  259. for (int i = 0; i < numValues; ++i)
  260. dest[i] = values[i];
  261. }
  262. void resetPeak() noexcept
  263. {
  264. peakLevel = -1;
  265. }
  266. int getPeak() noexcept
  267. {
  268. if (peakLevel < 0)
  269. {
  270. for (auto& s : data)
  271. {
  272. auto peak = s.getPeak();
  273. if (peak > peakLevel)
  274. peakLevel = peak;
  275. }
  276. }
  277. return peakLevel;
  278. }
  279. private:
  280. Array<MinMaxValue> data;
  281. int peakLevel = -1;
  282. void ensureSize (int thumbSamples)
  283. {
  284. auto extraNeeded = thumbSamples - data.size();
  285. if (extraNeeded > 0)
  286. data.insertMultiple (-1, MinMaxValue(), extraNeeded);
  287. }
  288. };
  289. //==============================================================================
  290. class AudioThumbnail::CachedWindow
  291. {
  292. public:
  293. CachedWindow() {}
  294. void invalidate()
  295. {
  296. cacheNeedsRefilling = true;
  297. }
  298. void drawChannel (Graphics& g, const Rectangle<int>& area,
  299. const double startTime, const double endTime,
  300. const int channelNum, const float verticalZoomFactor,
  301. const double rate, const int numChans, const int sampsPerThumbSample,
  302. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  303. {
  304. if (refillCache (area.getWidth(), startTime, endTime, rate,
  305. numChans, sampsPerThumbSample, levelData, chans)
  306. && isPositiveAndBelow (channelNum, numChannelsCached))
  307. {
  308. auto clip = g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())));
  309. if (! clip.isEmpty())
  310. {
  311. auto topY = (float) area.getY();
  312. auto bottomY = (float) area.getBottom();
  313. auto midY = (topY + bottomY) * 0.5f;
  314. auto vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
  315. auto* cacheData = getData (channelNum, clip.getX() - area.getX());
  316. RectangleList<float> waveform;
  317. waveform.ensureStorageAllocated (clip.getWidth());
  318. auto x = (float) clip.getX();
  319. for (int w = clip.getWidth(); --w >= 0;)
  320. {
  321. if (cacheData->isNonZero())
  322. {
  323. auto top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
  324. auto bottom = jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY);
  325. waveform.addWithoutMerging (Rectangle<float> (x, top, 1.0f, bottom - top));
  326. }
  327. x += 1.0f;
  328. ++cacheData;
  329. }
  330. g.fillRectList (waveform);
  331. }
  332. }
  333. }
  334. private:
  335. Array<MinMaxValue> data;
  336. double cachedStart = 0, cachedTimePerPixel = 0;
  337. int numChannelsCached = 0, numSamplesCached = 0;
  338. bool cacheNeedsRefilling = true;
  339. bool refillCache (int numSamples, double startTime, double endTime,
  340. double rate, int numChans, int sampsPerThumbSample,
  341. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  342. {
  343. auto timePerPixel = (endTime - startTime) / numSamples;
  344. if (numSamples <= 0 || timePerPixel <= 0.0 || rate <= 0)
  345. {
  346. invalidate();
  347. return false;
  348. }
  349. if (numSamples == numSamplesCached
  350. && numChannelsCached == numChans
  351. && startTime == cachedStart
  352. && timePerPixel == cachedTimePerPixel
  353. && ! cacheNeedsRefilling)
  354. {
  355. return ! cacheNeedsRefilling;
  356. }
  357. numSamplesCached = numSamples;
  358. numChannelsCached = numChans;
  359. cachedStart = startTime;
  360. cachedTimePerPixel = timePerPixel;
  361. cacheNeedsRefilling = false;
  362. ensureSize (numSamples);
  363. if (timePerPixel * rate <= sampsPerThumbSample && levelData != nullptr)
  364. {
  365. auto sample = roundToInt (startTime * rate);
  366. Array<Range<float>> levels;
  367. int i;
  368. for (i = 0; i < numSamples; ++i)
  369. {
  370. auto nextSample = roundToInt ((startTime + timePerPixel) * rate);
  371. if (sample >= 0)
  372. {
  373. if (sample >= levelData->lengthInSamples)
  374. {
  375. for (int chan = 0; chan < numChannelsCached; ++chan)
  376. *getData (chan, i) = MinMaxValue();
  377. }
  378. else
  379. {
  380. levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
  381. auto totalChans = jmin (levels.size(), numChannelsCached);
  382. for (int chan = 0; chan < totalChans; ++chan)
  383. getData (chan, i)->setFloat (levels.getReference (chan));
  384. }
  385. }
  386. startTime += timePerPixel;
  387. sample = nextSample;
  388. }
  389. numSamplesCached = i;
  390. }
  391. else
  392. {
  393. jassert (chans.size() == numChannelsCached);
  394. for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
  395. {
  396. ThumbData* channelData = chans.getUnchecked (channelNum);
  397. MinMaxValue* cacheData = getData (channelNum, 0);
  398. auto timeToThumbSampleFactor = rate / (double) sampsPerThumbSample;
  399. startTime = cachedStart;
  400. auto sample = roundToInt (startTime * timeToThumbSampleFactor);
  401. for (int i = numSamples; --i >= 0;)
  402. {
  403. auto nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
  404. channelData->getMinMax (sample, nextSample, *cacheData);
  405. ++cacheData;
  406. startTime += timePerPixel;
  407. sample = nextSample;
  408. }
  409. }
  410. }
  411. return true;
  412. }
  413. MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
  414. {
  415. jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
  416. return data.getRawDataPointer() + channelNum * numSamplesCached
  417. + cacheIndex;
  418. }
  419. void ensureSize (const int numSamples)
  420. {
  421. auto itemsRequired = numSamples * numChannelsCached;
  422. if (data.size() < itemsRequired)
  423. data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
  424. }
  425. };
  426. //==============================================================================
  427. AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
  428. AudioFormatManager& formatManager,
  429. AudioThumbnailCache& cacheToUse)
  430. : formatManagerToUse (formatManager),
  431. cache (cacheToUse),
  432. window (new CachedWindow()),
  433. samplesPerThumbSample (originalSamplesPerThumbnailSample)
  434. {
  435. }
  436. AudioThumbnail::~AudioThumbnail()
  437. {
  438. clear();
  439. }
  440. void AudioThumbnail::clear()
  441. {
  442. source.reset();
  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.reset (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.reset (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 (int64 startSample, const AudioBuffer<float>& incoming,
  548. int startOffsetInBuffer, int numSamples)
  549. {
  550. jassert (startSample >= 0
  551. && startOffsetInBuffer >= 0
  552. && startOffsetInBuffer + numSamples <= incoming.getNumSamples());
  553. auto firstThumbIndex = (int) (startSample / samplesPerThumbSample);
  554. auto lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
  555. auto numToDo = lastThumbIndex - firstThumbIndex;
  556. if (numToDo > 0)
  557. {
  558. auto numChans = jmin (channels.size(), incoming.getNumChannels());
  559. const HeapBlock<MinMaxValue> thumbData (numToDo * numChans);
  560. const HeapBlock<MinMaxValue*> thumbChannels (numChans);
  561. for (int chan = 0; chan < numChans; ++chan)
  562. {
  563. auto* sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
  564. auto* dest = thumbData + numToDo * chan;
  565. thumbChannels [chan] = dest;
  566. for (int i = 0; i < numToDo; ++i)
  567. {
  568. auto start = i * samplesPerThumbSample;
  569. dest[i].setFloat (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start)));
  570. }
  571. }
  572. setLevels (thumbChannels, firstThumbIndex, numChans, numToDo);
  573. }
  574. }
  575. void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
  576. {
  577. const ScopedLock sl (lock);
  578. for (int i = jmin (numChans, channels.size()); --i >= 0;)
  579. channels.getUnchecked(i)->write (values[i], thumbIndex, numValues);
  580. auto start = thumbIndex * (int64) samplesPerThumbSample;
  581. auto end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
  582. if (numSamplesFinished >= start && end > numSamplesFinished)
  583. numSamplesFinished = end;
  584. totalSamples = jmax (numSamplesFinished, totalSamples);
  585. window->invalidate();
  586. sendChangeMessage();
  587. }
  588. //==============================================================================
  589. int AudioThumbnail::getNumChannels() const noexcept
  590. {
  591. return numChannels;
  592. }
  593. double AudioThumbnail::getTotalLength() const noexcept
  594. {
  595. return sampleRate > 0 ? (totalSamples / sampleRate) : 0;
  596. }
  597. bool AudioThumbnail::isFullyLoaded() const noexcept
  598. {
  599. return numSamplesFinished >= totalSamples - samplesPerThumbSample;
  600. }
  601. double AudioThumbnail::getProportionComplete() const noexcept
  602. {
  603. return jlimit (0.0, 1.0, numSamplesFinished / (double) jmax ((int64) 1, totalSamples));
  604. }
  605. int64 AudioThumbnail::getNumSamplesFinished() const noexcept
  606. {
  607. return numSamplesFinished;
  608. }
  609. float AudioThumbnail::getApproximatePeak() const
  610. {
  611. const ScopedLock sl (lock);
  612. int peak = 0;
  613. for (auto* c : channels)
  614. peak = jmax (peak, c->getPeak());
  615. return jlimit (0, 127, peak) / 127.0f;
  616. }
  617. void AudioThumbnail::getApproximateMinMax (double startTime, double endTime, int channelIndex,
  618. float& minValue, float& maxValue) const noexcept
  619. {
  620. const ScopedLock sl (lock);
  621. MinMaxValue result;
  622. auto* data = channels [channelIndex];
  623. if (data != nullptr && sampleRate > 0)
  624. {
  625. auto firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
  626. auto lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
  627. data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
  628. }
  629. minValue = result.getMinValue() / 128.0f;
  630. maxValue = result.getMaxValue() / 128.0f;
  631. }
  632. void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
  633. double endTime, int channelNum, float verticalZoomFactor)
  634. {
  635. const ScopedLock sl (lock);
  636. window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
  637. sampleRate, numChannels, samplesPerThumbSample, source.get(), channels);
  638. }
  639. void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
  640. double endTimeSeconds, float verticalZoomFactor)
  641. {
  642. for (int i = 0; i < numChannels; ++i)
  643. {
  644. auto y1 = roundToInt ((i * area.getHeight()) / numChannels);
  645. auto y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
  646. drawChannel (g, { area.getX(), area.getY() + y1, area.getWidth(), y2 - y1 },
  647. startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
  648. }
  649. }
  650. } // namespace juce