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.

832 lines
26KB

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