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.

820 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. struct AudioThumbnail::MinMaxValue
  16. {
  17. MinMaxValue() noexcept
  18. {
  19. values[0] = 0;
  20. values[1] = 0;
  21. }
  22. inline void set (const int8 newMin, const int8 newMax) noexcept
  23. {
  24. values[0] = newMin;
  25. values[1] = newMax;
  26. }
  27. inline int8 getMinValue() const noexcept { return values[0]; }
  28. inline int8 getMaxValue() const noexcept { return values[1]; }
  29. inline void setFloat (Range<float> newRange) noexcept
  30. {
  31. // Workaround for an ndk armeabi compiler bug which crashes on signed saturation
  32. #if JUCE_ANDROID
  33. Range<float> limitedRange (jlimit (-1.0f, 1.0f, newRange.getStart()),
  34. jlimit (-1.0f, 1.0f, newRange.getEnd()));
  35. values[0] = (int8) (limitedRange.getStart() * 127.0f);
  36. values[1] = (int8) (limitedRange.getEnd() * 127.0f);
  37. #else
  38. values[0] = (int8) jlimit (-128, 127, roundToInt (newRange.getStart() * 127.0f));
  39. values[1] = (int8) jlimit (-128, 127, roundToInt (newRange.getEnd() * 127.0f));
  40. #endif
  41. if (values[0] == values[1])
  42. {
  43. if (values[1] == 127)
  44. values[0]--;
  45. else
  46. values[1]++;
  47. }
  48. }
  49. inline bool isNonZero() const noexcept
  50. {
  51. return values[1] > values[0];
  52. }
  53. inline int getPeak() const noexcept
  54. {
  55. return jmax (std::abs ((int) values[0]),
  56. std::abs ((int) values[1]));
  57. }
  58. inline void read (InputStream& input) { input.read (values, 2); }
  59. inline void write (OutputStream& output) { output.write (values, 2); }
  60. private:
  61. int8 values[2];
  62. };
  63. //==============================================================================
  64. class AudioThumbnail::LevelDataSource : public TimeSliceClient
  65. {
  66. public:
  67. LevelDataSource (AudioThumbnail& thumb, AudioFormatReader* newReader, int64 hash)
  68. : hashCode (hash), owner (thumb), reader (newReader)
  69. {
  70. }
  71. LevelDataSource (AudioThumbnail& thumb, InputSource* src)
  72. : hashCode (src->hashCode()), owner (thumb), source (src)
  73. {
  74. }
  75. ~LevelDataSource() override
  76. {
  77. owner.cache.getTimeSliceThread().removeTimeSliceClient (this);
  78. }
  79. enum { timeBeforeDeletingReader = 3000 };
  80. void initialise (int64 samplesFinished)
  81. {
  82. const ScopedLock sl (readerLock);
  83. numSamplesFinished = samplesFinished;
  84. createReader();
  85. if (reader != nullptr)
  86. {
  87. lengthInSamples = reader->lengthInSamples;
  88. numChannels = reader->numChannels;
  89. sampleRate = reader->sampleRate;
  90. if (lengthInSamples <= 0 || isFullyLoaded())
  91. reader.reset();
  92. else
  93. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  94. }
  95. }
  96. void getLevels (int64 startSample, int numSamples, Array<Range<float>>& levels)
  97. {
  98. const ScopedLock sl (readerLock);
  99. if (reader == nullptr)
  100. {
  101. createReader();
  102. if (reader != nullptr)
  103. {
  104. lastReaderUseTime = Time::getMillisecondCounter();
  105. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  106. }
  107. }
  108. if (reader != nullptr)
  109. {
  110. if (levels.size() < (int) reader->numChannels)
  111. levels.insertMultiple (0, {}, (int) reader->numChannels - levels.size());
  112. reader->readMaxLevels (startSample, numSamples, levels.getRawDataPointer(), (int) reader->numChannels);
  113. lastReaderUseTime = Time::getMillisecondCounter();
  114. }
  115. }
  116. void releaseResources()
  117. {
  118. const ScopedLock sl (readerLock);
  119. reader.reset();
  120. }
  121. int useTimeSlice() override
  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 = 0, numSamplesFinished = 0;
  158. double sampleRate = 0;
  159. unsigned int numChannels = 0;
  160. int64 hashCode = 0;
  161. private:
  162. AudioThumbnail& owner;
  163. std::unique_ptr<InputSource> source;
  164. std::unique_ptr<AudioFormatReader> reader;
  165. CriticalSection readerLock;
  166. std::atomic<uint32> lastReaderUseTime { 0 };
  167. void createReader()
  168. {
  169. if (reader == nullptr && source != nullptr)
  170. if (auto* audioFileStream = source->createInputStream())
  171. reader.reset (owner.formatManagerToUse.createReaderFor (std::unique_ptr<InputStream> (audioFileStream)));
  172. }
  173. bool readNextBlock()
  174. {
  175. jassert (reader != nullptr);
  176. if (! isFullyLoaded())
  177. {
  178. auto numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
  179. if (numToDo > 0)
  180. {
  181. auto startSample = numSamplesFinished;
  182. auto firstThumbIndex = sampleToThumbSample (startSample);
  183. auto lastThumbIndex = sampleToThumbSample (startSample + numToDo);
  184. auto numThumbSamps = lastThumbIndex - firstThumbIndex;
  185. HeapBlock<MinMaxValue> levelData ((unsigned int) numThumbSamps * numChannels);
  186. HeapBlock<MinMaxValue*> levels (numChannels);
  187. for (int i = 0; i < (int) numChannels; ++i)
  188. levels[i] = levelData + i * numThumbSamps;
  189. HeapBlock<Range<float>> levelsRead (numChannels);
  190. for (int i = 0; i < numThumbSamps; ++i)
  191. {
  192. reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample,
  193. owner.samplesPerThumbSample, levelsRead, (int) numChannels);
  194. for (int j = 0; j < (int) numChannels; ++j)
  195. levels[j][i].setFloat (levelsRead[j]);
  196. }
  197. {
  198. const ScopedUnlock su (readerLock);
  199. owner.setLevels (levels, firstThumbIndex, (int) numChannels, numThumbSamps);
  200. }
  201. numSamplesFinished += numToDo;
  202. lastReaderUseTime = Time::getMillisecondCounter();
  203. }
  204. }
  205. return isFullyLoaded();
  206. }
  207. };
  208. //==============================================================================
  209. class AudioThumbnail::ThumbData
  210. {
  211. public:
  212. ThumbData (int numThumbSamples)
  213. {
  214. ensureSize (numThumbSamples);
  215. }
  216. inline MinMaxValue* getData (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. int8 mx = -128;
  231. int8 mn = 127;
  232. while (startSample <= endSample)
  233. {
  234. auto& 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* values, int startIndex, int numValues)
  248. {
  249. resetPeak();
  250. if (startIndex + numValues > data.size())
  251. ensureSize (startIndex + numValues);
  252. auto* dest = getData (startIndex);
  253. for (int i = 0; i < numValues; ++i)
  254. dest[i] = values[i];
  255. }
  256. void resetPeak() noexcept
  257. {
  258. peakLevel = -1;
  259. }
  260. int getPeak() noexcept
  261. {
  262. if (peakLevel < 0)
  263. {
  264. for (auto& s : data)
  265. {
  266. auto peak = s.getPeak();
  267. if (peak > peakLevel)
  268. peakLevel = peak;
  269. }
  270. }
  271. return peakLevel;
  272. }
  273. private:
  274. Array<MinMaxValue> data;
  275. int peakLevel = -1;
  276. void ensureSize (int thumbSamples)
  277. {
  278. auto 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. void invalidate()
  289. {
  290. cacheNeedsRefilling = true;
  291. }
  292. void drawChannel (Graphics& g, const Rectangle<int>& area,
  293. const double startTime, const double endTime,
  294. const int channelNum, const float verticalZoomFactor,
  295. const double rate, const int numChans, const int sampsPerThumbSample,
  296. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  297. {
  298. if (refillCache (area.getWidth(), startTime, endTime, rate,
  299. numChans, sampsPerThumbSample, levelData, chans)
  300. && isPositiveAndBelow (channelNum, numChannelsCached))
  301. {
  302. auto clip = g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())));
  303. if (! clip.isEmpty())
  304. {
  305. auto topY = (float) area.getY();
  306. auto bottomY = (float) area.getBottom();
  307. auto midY = (topY + bottomY) * 0.5f;
  308. auto vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
  309. auto* cacheData = getData (channelNum, clip.getX() - area.getX());
  310. RectangleList<float> waveform;
  311. waveform.ensureStorageAllocated (clip.getWidth());
  312. auto x = (float) clip.getX();
  313. for (int w = clip.getWidth(); --w >= 0;)
  314. {
  315. if (cacheData->isNonZero())
  316. {
  317. auto top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
  318. auto 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 = 0, cachedTimePerPixel = 0;
  331. int numChannelsCached = 0, numSamplesCached = 0;
  332. bool cacheNeedsRefilling = true;
  333. bool refillCache (int numSamples, double startTime, double endTime,
  334. double rate, int numChans, int sampsPerThumbSample,
  335. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  336. {
  337. auto 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. auto sample = roundToInt (startTime * rate);
  360. Array<Range<float>> levels;
  361. int i;
  362. for (i = 0; i < numSamples; ++i)
  363. {
  364. auto nextSample = roundToInt ((startTime + timePerPixel) * rate);
  365. if (sample >= 0)
  366. {
  367. if (sample >= levelData->lengthInSamples)
  368. {
  369. for (int chan = 0; chan < numChannelsCached; ++chan)
  370. *getData (chan, i) = MinMaxValue();
  371. }
  372. else
  373. {
  374. levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
  375. auto totalChans = jmin (levels.size(), numChannelsCached);
  376. for (int chan = 0; chan < totalChans; ++chan)
  377. getData (chan, i)->setFloat (levels.getReference (chan));
  378. }
  379. }
  380. startTime += timePerPixel;
  381. sample = nextSample;
  382. }
  383. numSamplesCached = i;
  384. }
  385. else
  386. {
  387. jassert (chans.size() == numChannelsCached);
  388. for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
  389. {
  390. ThumbData* channelData = chans.getUnchecked (channelNum);
  391. MinMaxValue* cacheData = getData (channelNum, 0);
  392. auto timeToThumbSampleFactor = rate / (double) sampsPerThumbSample;
  393. startTime = cachedStart;
  394. auto sample = roundToInt (startTime * timeToThumbSampleFactor);
  395. for (int i = numSamples; --i >= 0;)
  396. {
  397. auto nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
  398. channelData->getMinMax (sample, nextSample, *cacheData);
  399. ++cacheData;
  400. startTime += timePerPixel;
  401. sample = nextSample;
  402. }
  403. }
  404. }
  405. return true;
  406. }
  407. MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
  408. {
  409. jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
  410. return data.getRawDataPointer() + channelNum * numSamplesCached
  411. + cacheIndex;
  412. }
  413. void ensureSize (const int numSamples)
  414. {
  415. auto itemsRequired = numSamples * numChannelsCached;
  416. if (data.size() < itemsRequired)
  417. data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
  418. }
  419. };
  420. //==============================================================================
  421. AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
  422. AudioFormatManager& formatManager,
  423. AudioThumbnailCache& cacheToUse)
  424. : formatManagerToUse (formatManager),
  425. cache (cacheToUse),
  426. window (new CachedWindow()),
  427. samplesPerThumbSample (originalSamplesPerThumbnailSample)
  428. {
  429. }
  430. AudioThumbnail::~AudioThumbnail()
  431. {
  432. clear();
  433. }
  434. void AudioThumbnail::clear()
  435. {
  436. source.reset();
  437. const ScopedLock sl (lock);
  438. clearChannelData();
  439. }
  440. void AudioThumbnail::clearChannelData()
  441. {
  442. window->invalidate();
  443. channels.clear();
  444. totalSamples = numSamplesFinished = 0;
  445. numChannels = 0;
  446. sampleRate = 0;
  447. sendChangeMessage();
  448. }
  449. void AudioThumbnail::reset (int newNumChannels, double newSampleRate, int64 totalSamplesInSource)
  450. {
  451. clear();
  452. const ScopedLock sl (lock);
  453. numChannels = newNumChannels;
  454. sampleRate = newSampleRate;
  455. totalSamples = totalSamplesInSource;
  456. createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample));
  457. }
  458. void AudioThumbnail::createChannels (const int length)
  459. {
  460. while (channels.size() < numChannels)
  461. channels.add (new ThumbData (length));
  462. }
  463. //==============================================================================
  464. bool AudioThumbnail::loadFrom (InputStream& rawInput)
  465. {
  466. BufferedInputStream input (rawInput, 4096);
  467. if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
  468. return false;
  469. const ScopedLock sl (lock);
  470. clearChannelData();
  471. samplesPerThumbSample = input.readInt();
  472. totalSamples = input.readInt64(); // Total number of source samples.
  473. numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
  474. int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
  475. numChannels = input.readInt(); // Number of audio channels.
  476. sampleRate = input.readInt(); // Source sample rate.
  477. input.skipNextBytes (16); // (reserved)
  478. createChannels (numThumbnailSamples);
  479. for (int i = 0; i < numThumbnailSamples; ++i)
  480. for (int chan = 0; chan < numChannels; ++chan)
  481. channels.getUnchecked(chan)->getData(i)->read (input);
  482. return true;
  483. }
  484. void AudioThumbnail::saveTo (OutputStream& output) const
  485. {
  486. const ScopedLock sl (lock);
  487. const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize();
  488. output.write ("jatm", 4);
  489. output.writeInt (samplesPerThumbSample);
  490. output.writeInt64 (totalSamples);
  491. output.writeInt64 (numSamplesFinished);
  492. output.writeInt (numThumbnailSamples);
  493. output.writeInt (numChannels);
  494. output.writeInt ((int) sampleRate);
  495. output.writeInt64 (0);
  496. output.writeInt64 (0);
  497. for (int i = 0; i < numThumbnailSamples; ++i)
  498. for (int chan = 0; chan < numChannels; ++chan)
  499. channels.getUnchecked(chan)->getData(i)->write (output);
  500. }
  501. //==============================================================================
  502. bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
  503. {
  504. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  505. numSamplesFinished = 0;
  506. auto wasSuccessful = [&] { return sampleRate > 0 && totalSamples > 0; };
  507. if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
  508. {
  509. source.reset (newSource); // (make sure this isn't done before loadThumb is called)
  510. source->lengthInSamples = totalSamples;
  511. source->sampleRate = sampleRate;
  512. source->numChannels = (unsigned int) numChannels;
  513. source->numSamplesFinished = numSamplesFinished;
  514. return wasSuccessful();
  515. }
  516. source.reset (newSource);
  517. const ScopedLock sl (lock);
  518. source->initialise (numSamplesFinished);
  519. totalSamples = source->lengthInSamples;
  520. sampleRate = source->sampleRate;
  521. numChannels = (int32) source->numChannels;
  522. createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
  523. return wasSuccessful();
  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 (int64 startSample, const AudioBuffer<float>& incoming,
  541. int startOffsetInBuffer, int numSamples)
  542. {
  543. jassert (startSample >= 0
  544. && startOffsetInBuffer >= 0
  545. && startOffsetInBuffer + numSamples <= incoming.getNumSamples());
  546. auto firstThumbIndex = (int) (startSample / samplesPerThumbSample);
  547. auto lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
  548. auto numToDo = lastThumbIndex - firstThumbIndex;
  549. if (numToDo > 0)
  550. {
  551. auto numChans = jmin (channels.size(), incoming.getNumChannels());
  552. const HeapBlock<MinMaxValue> thumbData (numToDo * numChans);
  553. const HeapBlock<MinMaxValue*> thumbChannels (numChans);
  554. for (int chan = 0; chan < numChans; ++chan)
  555. {
  556. auto* sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
  557. auto* dest = thumbData + numToDo * chan;
  558. thumbChannels [chan] = dest;
  559. for (int i = 0; i < numToDo; ++i)
  560. {
  561. auto start = i * samplesPerThumbSample;
  562. dest[i].setFloat (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start)));
  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. auto start = thumbIndex * (int64) samplesPerThumbSample;
  574. auto end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
  575. if (numSamplesFinished >= start && end > numSamplesFinished)
  576. numSamplesFinished = end;
  577. totalSamples = jmax (numSamplesFinished, totalSamples.load());
  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.load()));
  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 (auto* c : channels)
  607. peak = jmax (peak, c->getPeak());
  608. return jlimit (0, 127, peak) / 127.0f;
  609. }
  610. void AudioThumbnail::getApproximateMinMax (double startTime, double endTime, int channelIndex,
  611. float& minValue, float& maxValue) const noexcept
  612. {
  613. const ScopedLock sl (lock);
  614. MinMaxValue result;
  615. auto* data = channels [channelIndex];
  616. if (data != nullptr && sampleRate > 0)
  617. {
  618. auto firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
  619. auto 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.get(), 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. auto y1 = roundToInt ((i * area.getHeight()) / numChannels);
  638. auto y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
  639. drawChannel (g, { area.getX(), area.getY() + y1, area.getWidth(), y2 - y1 },
  640. startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
  641. }
  642. }
  643. } // namespace juce