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.

833 lines
27KB

  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. struct AudioThumbnail::MinMaxValue
  20. {
  21. MinMaxValue() noexcept
  22. {
  23. values[0] = 0;
  24. values[1] = 0;
  25. }
  26. inline void set (const int8 newMin, const int8 newMax) noexcept
  27. {
  28. values[0] = newMin;
  29. values[1] = newMax;
  30. }
  31. inline int8 getMinValue() const noexcept { return values[0]; }
  32. inline int8 getMaxValue() const noexcept { return values[1]; }
  33. inline void setFloat (Range<float> newRange) noexcept
  34. {
  35. // Workaround for an ndk armeabi compiler bug which crashes on signed saturation
  36. #if JUCE_ANDROID
  37. Range<float> limitedRange (jlimit (-1.0f, 1.0f, newRange.getStart()),
  38. jlimit (-1.0f, 1.0f, newRange.getEnd()));
  39. values[0] = (int8) (limitedRange.getStart() * 127.0f);
  40. values[1] = (int8) (limitedRange.getEnd() * 127.0f);
  41. #else
  42. values[0] = (int8) jlimit (-128, 127, roundFloatToInt (newRange.getStart() * 127.0f));
  43. values[1] = (int8) jlimit (-128, 127, roundFloatToInt (newRange.getEnd() * 127.0f));
  44. #endif
  45. if (values[0] == values[1])
  46. {
  47. if (values[1] == 127)
  48. values[0]--;
  49. else
  50. values[1]++;
  51. }
  52. }
  53. inline bool isNonZero() const noexcept
  54. {
  55. return values[1] > values[0];
  56. }
  57. inline int getPeak() const noexcept
  58. {
  59. return jmax (std::abs ((int) values[0]),
  60. std::abs ((int) values[1]));
  61. }
  62. inline void read (InputStream& input) { input.read (values, 2); }
  63. inline void write (OutputStream& output) { output.write (values, 2); }
  64. private:
  65. int8 values[2];
  66. };
  67. //==============================================================================
  68. class AudioThumbnail::LevelDataSource : public TimeSliceClient
  69. {
  70. public:
  71. LevelDataSource (AudioThumbnail& thumb, AudioFormatReader* newReader, int64 hash)
  72. : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
  73. hashCode (hash), owner (thumb), reader (newReader), lastReaderUseTime (0)
  74. {
  75. }
  76. LevelDataSource (AudioThumbnail& thumb, InputSource* src)
  77. : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
  78. hashCode (src->hashCode()), owner (thumb), source (src), lastReaderUseTime (0)
  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 = nullptr;
  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, Range<float>(), (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 = nullptr;
  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, numSamplesFinished;
  164. double sampleRate;
  165. unsigned int numChannels;
  166. int64 hashCode;
  167. private:
  168. AudioThumbnail& owner;
  169. ScopedPointer<InputSource> source;
  170. ScopedPointer<AudioFormatReader> reader;
  171. CriticalSection readerLock;
  172. uint32 lastReaderUseTime;
  173. void createReader()
  174. {
  175. if (reader == nullptr && source != nullptr)
  176. if (InputStream* audioFileStream = source->createInputStream())
  177. reader = owner.formatManagerToUse.createReaderFor (audioFileStream);
  178. }
  179. bool readNextBlock()
  180. {
  181. jassert (reader != nullptr);
  182. if (! isFullyLoaded())
  183. {
  184. const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
  185. if (numToDo > 0)
  186. {
  187. int64 startSample = numSamplesFinished;
  188. const int firstThumbIndex = sampleToThumbSample (startSample);
  189. const int lastThumbIndex = sampleToThumbSample (startSample + numToDo);
  190. const int numThumbSamps = lastThumbIndex - firstThumbIndex;
  191. HeapBlock<MinMaxValue> levelData ((size_t) 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 (const int numThumbSamples)
  219. : peakLevel (-1)
  220. {
  221. ensureSize (numThumbSamples);
  222. }
  223. inline MinMaxValue* getData (const int thumbSampleIndex) noexcept
  224. {
  225. jassert (thumbSampleIndex < data.size());
  226. return data.getRawDataPointer() + thumbSampleIndex;
  227. }
  228. int getSize() const noexcept
  229. {
  230. return data.size();
  231. }
  232. void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
  233. {
  234. if (startSample >= 0)
  235. {
  236. endSample = jmin (endSample, data.size() - 1);
  237. int8 mx = -128;
  238. int8 mn = 127;
  239. while (startSample <= endSample)
  240. {
  241. const MinMaxValue& v = data.getReference (startSample);
  242. if (v.getMinValue() < mn) mn = v.getMinValue();
  243. if (v.getMaxValue() > mx) mx = v.getMaxValue();
  244. ++startSample;
  245. }
  246. if (mn <= mx)
  247. {
  248. result.set (mn, mx);
  249. return;
  250. }
  251. }
  252. result.set (1, 0);
  253. }
  254. void write (const MinMaxValue* const values, const int startIndex, const int numValues)
  255. {
  256. resetPeak();
  257. if (startIndex + numValues > data.size())
  258. ensureSize (startIndex + numValues);
  259. MinMaxValue* const dest = getData (startIndex);
  260. for (int i = 0; i < numValues; ++i)
  261. dest[i] = values[i];
  262. }
  263. void resetPeak() noexcept
  264. {
  265. peakLevel = -1;
  266. }
  267. int getPeak() noexcept
  268. {
  269. if (peakLevel < 0)
  270. {
  271. for (int i = 0; i < data.size(); ++i)
  272. {
  273. const int peak = data[i].getPeak();
  274. if (peak > peakLevel)
  275. peakLevel = peak;
  276. }
  277. }
  278. return peakLevel;
  279. }
  280. private:
  281. Array<MinMaxValue> data;
  282. int peakLevel;
  283. void ensureSize (const int thumbSamples)
  284. {
  285. const int extraNeeded = thumbSamples - data.size();
  286. if (extraNeeded > 0)
  287. data.insertMultiple (-1, MinMaxValue(), extraNeeded);
  288. }
  289. };
  290. //==============================================================================
  291. class AudioThumbnail::CachedWindow
  292. {
  293. public:
  294. CachedWindow()
  295. : cachedStart (0), cachedTimePerPixel (0),
  296. numChannelsCached (0), numSamplesCached (0),
  297. cacheNeedsRefilling (true)
  298. {
  299. }
  300. void invalidate()
  301. {
  302. cacheNeedsRefilling = true;
  303. }
  304. void drawChannel (Graphics& g, const Rectangle<int>& area,
  305. const double startTime, const double endTime,
  306. const int channelNum, const float verticalZoomFactor,
  307. const double rate, const int numChans, const int sampsPerThumbSample,
  308. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  309. {
  310. if (refillCache (area.getWidth(), startTime, endTime, rate,
  311. numChans, sampsPerThumbSample, levelData, chans)
  312. && isPositiveAndBelow (channelNum, numChannelsCached))
  313. {
  314. const Rectangle<int> clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth()))));
  315. if (! clip.isEmpty())
  316. {
  317. const float topY = (float) area.getY();
  318. const float bottomY = (float) area.getBottom();
  319. const float midY = (topY + bottomY) * 0.5f;
  320. const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
  321. const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX());
  322. RectangleList<float> waveform;
  323. waveform.ensureStorageAllocated (clip.getWidth());
  324. float x = (float) clip.getX();
  325. for (int w = clip.getWidth(); --w >= 0;)
  326. {
  327. if (cacheData->isNonZero())
  328. {
  329. const float top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
  330. const float bottom = jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY);
  331. waveform.addWithoutMerging (Rectangle<float> (x, top, 1.0f, bottom - top));
  332. }
  333. x += 1.0f;
  334. ++cacheData;
  335. }
  336. g.fillRectList (waveform);
  337. }
  338. }
  339. }
  340. private:
  341. Array<MinMaxValue> data;
  342. double cachedStart, cachedTimePerPixel;
  343. int numChannelsCached, numSamplesCached;
  344. bool cacheNeedsRefilling;
  345. bool refillCache (const int numSamples, double startTime, const double endTime,
  346. const double rate, const int numChans, const int sampsPerThumbSample,
  347. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  348. {
  349. const double timePerPixel = (endTime - startTime) / numSamples;
  350. if (numSamples <= 0 || timePerPixel <= 0.0 || rate <= 0)
  351. {
  352. invalidate();
  353. return false;
  354. }
  355. if (numSamples == numSamplesCached
  356. && numChannelsCached == numChans
  357. && startTime == cachedStart
  358. && timePerPixel == cachedTimePerPixel
  359. && ! cacheNeedsRefilling)
  360. {
  361. return ! cacheNeedsRefilling;
  362. }
  363. numSamplesCached = numSamples;
  364. numChannelsCached = numChans;
  365. cachedStart = startTime;
  366. cachedTimePerPixel = timePerPixel;
  367. cacheNeedsRefilling = false;
  368. ensureSize (numSamples);
  369. if (timePerPixel * rate <= sampsPerThumbSample && levelData != nullptr)
  370. {
  371. int sample = roundToInt (startTime * rate);
  372. Array<Range<float> > levels;
  373. int i;
  374. for (i = 0; i < numSamples; ++i)
  375. {
  376. const int nextSample = roundToInt ((startTime + timePerPixel) * rate);
  377. if (sample >= 0)
  378. {
  379. if (sample >= levelData->lengthInSamples)
  380. {
  381. for (int chan = 0; chan < numChannelsCached; ++chan)
  382. *getData (chan, i) = MinMaxValue();
  383. }
  384. else
  385. {
  386. levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
  387. const int totalChans = jmin (levels.size(), numChannelsCached);
  388. for (int chan = 0; chan < totalChans; ++chan)
  389. getData (chan, i)->setFloat (levels.getReference (chan));
  390. }
  391. }
  392. startTime += timePerPixel;
  393. sample = nextSample;
  394. }
  395. numSamplesCached = i;
  396. }
  397. else
  398. {
  399. jassert (chans.size() == numChannelsCached);
  400. for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
  401. {
  402. ThumbData* channelData = chans.getUnchecked (channelNum);
  403. MinMaxValue* cacheData = getData (channelNum, 0);
  404. const double timeToThumbSampleFactor = rate / (double) sampsPerThumbSample;
  405. startTime = cachedStart;
  406. int sample = roundToInt (startTime * timeToThumbSampleFactor);
  407. for (int i = numSamples; --i >= 0;)
  408. {
  409. const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
  410. channelData->getMinMax (sample, nextSample, *cacheData);
  411. ++cacheData;
  412. startTime += timePerPixel;
  413. sample = nextSample;
  414. }
  415. }
  416. }
  417. return true;
  418. }
  419. MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
  420. {
  421. jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
  422. return data.getRawDataPointer() + channelNum * numSamplesCached
  423. + cacheIndex;
  424. }
  425. void ensureSize (const int numSamples)
  426. {
  427. const int itemsRequired = numSamples * numChannelsCached;
  428. if (data.size() < itemsRequired)
  429. data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
  430. }
  431. };
  432. //==============================================================================
  433. AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
  434. AudioFormatManager& formatManager,
  435. AudioThumbnailCache& cacheToUse)
  436. : formatManagerToUse (formatManager),
  437. cache (cacheToUse),
  438. window (new CachedWindow()),
  439. samplesPerThumbSample (originalSamplesPerThumbnailSample),
  440. totalSamples (0),
  441. numSamplesFinished (0),
  442. numChannels (0),
  443. sampleRate (0)
  444. {
  445. }
  446. AudioThumbnail::~AudioThumbnail()
  447. {
  448. clear();
  449. }
  450. void AudioThumbnail::clear()
  451. {
  452. source = nullptr;
  453. const ScopedLock sl (lock);
  454. clearChannelData();
  455. }
  456. void AudioThumbnail::clearChannelData()
  457. {
  458. window->invalidate();
  459. channels.clear();
  460. totalSamples = numSamplesFinished = 0;
  461. numChannels = 0;
  462. sampleRate = 0;
  463. sendChangeMessage();
  464. }
  465. void AudioThumbnail::reset (int newNumChannels, double newSampleRate, int64 totalSamplesInSource)
  466. {
  467. clear();
  468. const ScopedLock sl (lock);
  469. numChannels = newNumChannels;
  470. sampleRate = newSampleRate;
  471. totalSamples = totalSamplesInSource;
  472. createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample));
  473. }
  474. void AudioThumbnail::createChannels (const int length)
  475. {
  476. while (channels.size() < numChannels)
  477. channels.add (new ThumbData (length));
  478. }
  479. //==============================================================================
  480. bool AudioThumbnail::loadFrom (InputStream& rawInput)
  481. {
  482. BufferedInputStream input (rawInput, 4096);
  483. if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
  484. return false;
  485. const ScopedLock sl (lock);
  486. clearChannelData();
  487. samplesPerThumbSample = input.readInt();
  488. totalSamples = input.readInt64(); // Total number of source samples.
  489. numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
  490. int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
  491. numChannels = input.readInt(); // Number of audio channels.
  492. sampleRate = input.readInt(); // Source sample rate.
  493. input.skipNextBytes (16); // (reserved)
  494. createChannels (numThumbnailSamples);
  495. for (int i = 0; i < numThumbnailSamples; ++i)
  496. for (int chan = 0; chan < numChannels; ++chan)
  497. channels.getUnchecked(chan)->getData(i)->read (input);
  498. return true;
  499. }
  500. void AudioThumbnail::saveTo (OutputStream& output) const
  501. {
  502. const ScopedLock sl (lock);
  503. const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize();
  504. output.write ("jatm", 4);
  505. output.writeInt (samplesPerThumbSample);
  506. output.writeInt64 (totalSamples);
  507. output.writeInt64 (numSamplesFinished);
  508. output.writeInt (numThumbnailSamples);
  509. output.writeInt (numChannels);
  510. output.writeInt ((int) sampleRate);
  511. output.writeInt64 (0);
  512. output.writeInt64 (0);
  513. for (int i = 0; i < numThumbnailSamples; ++i)
  514. for (int chan = 0; chan < numChannels; ++chan)
  515. channels.getUnchecked(chan)->getData(i)->write (output);
  516. }
  517. //==============================================================================
  518. bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
  519. {
  520. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
  521. numSamplesFinished = 0;
  522. if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
  523. {
  524. source = newSource; // (make sure this isn't done before loadThumb is called)
  525. source->lengthInSamples = totalSamples;
  526. source->sampleRate = sampleRate;
  527. source->numChannels = (unsigned int) numChannels;
  528. source->numSamplesFinished = numSamplesFinished;
  529. }
  530. else
  531. {
  532. source = newSource; // (make sure this isn't done before loadThumb is called)
  533. const ScopedLock sl (lock);
  534. source->initialise (numSamplesFinished);
  535. totalSamples = source->lengthInSamples;
  536. sampleRate = source->sampleRate;
  537. numChannels = (int32) source->numChannels;
  538. createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
  539. }
  540. return sampleRate > 0 && totalSamples > 0;
  541. }
  542. bool AudioThumbnail::setSource (InputSource* const newSource)
  543. {
  544. clear();
  545. return newSource != nullptr && setDataSource (new LevelDataSource (*this, newSource));
  546. }
  547. void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash)
  548. {
  549. clear();
  550. if (newReader != nullptr)
  551. setDataSource (new LevelDataSource (*this, newReader, hash));
  552. }
  553. int64 AudioThumbnail::getHashCode() const
  554. {
  555. return source == nullptr ? 0 : source->hashCode;
  556. }
  557. void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming,
  558. int startOffsetInBuffer, int numSamples)
  559. {
  560. jassert (startSample >= 0
  561. && startOffsetInBuffer >= 0
  562. && startOffsetInBuffer + numSamples <= incoming.getNumSamples());
  563. const int firstThumbIndex = (int) (startSample / samplesPerThumbSample);
  564. const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
  565. const int numToDo = lastThumbIndex - firstThumbIndex;
  566. if (numToDo > 0)
  567. {
  568. const int numChans = jmin (channels.size(), incoming.getNumChannels());
  569. const HeapBlock<MinMaxValue> thumbData ((size_t) (numToDo * numChans));
  570. const HeapBlock<MinMaxValue*> thumbChannels ((size_t) numChans);
  571. for (int chan = 0; chan < numChans; ++chan)
  572. {
  573. const float* const sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
  574. MinMaxValue* const dest = thumbData + numToDo * chan;
  575. thumbChannels [chan] = dest;
  576. for (int i = 0; i < numToDo; ++i)
  577. {
  578. const int start = i * samplesPerThumbSample;
  579. dest[i].setFloat (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start)));
  580. }
  581. }
  582. setLevels (thumbChannels, firstThumbIndex, numChans, numToDo);
  583. }
  584. }
  585. void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
  586. {
  587. const ScopedLock sl (lock);
  588. for (int i = jmin (numChans, channels.size()); --i >= 0;)
  589. channels.getUnchecked(i)->write (values[i], thumbIndex, numValues);
  590. const int64 start = thumbIndex * (int64) samplesPerThumbSample;
  591. const int64 end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
  592. if (numSamplesFinished >= start && end > numSamplesFinished)
  593. numSamplesFinished = end;
  594. totalSamples = jmax (numSamplesFinished, totalSamples);
  595. window->invalidate();
  596. sendChangeMessage();
  597. }
  598. //==============================================================================
  599. int AudioThumbnail::getNumChannels() const noexcept
  600. {
  601. return numChannels;
  602. }
  603. double AudioThumbnail::getTotalLength() const noexcept
  604. {
  605. return sampleRate > 0 ? (totalSamples / sampleRate) : 0;
  606. }
  607. bool AudioThumbnail::isFullyLoaded() const noexcept
  608. {
  609. return numSamplesFinished >= totalSamples - samplesPerThumbSample;
  610. }
  611. double AudioThumbnail::getProportionComplete() const noexcept
  612. {
  613. return jlimit (0.0, 1.0, numSamplesFinished / (double) jmax ((int64) 1, totalSamples));
  614. }
  615. int64 AudioThumbnail::getNumSamplesFinished() const noexcept
  616. {
  617. return numSamplesFinished;
  618. }
  619. float AudioThumbnail::getApproximatePeak() const
  620. {
  621. const ScopedLock sl (lock);
  622. int peak = 0;
  623. for (int i = channels.size(); --i >= 0;)
  624. peak = jmax (peak, channels.getUnchecked(i)->getPeak());
  625. return jlimit (0, 127, peak) / 127.0f;
  626. }
  627. void AudioThumbnail::getApproximateMinMax (const double startTime, const double endTime, const int channelIndex,
  628. float& minValue, float& maxValue) const noexcept
  629. {
  630. const ScopedLock sl (lock);
  631. MinMaxValue result;
  632. const ThumbData* const data = channels [channelIndex];
  633. if (data != nullptr && sampleRate > 0)
  634. {
  635. const int firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
  636. const int lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
  637. data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
  638. }
  639. minValue = result.getMinValue() / 128.0f;
  640. maxValue = result.getMaxValue() / 128.0f;
  641. }
  642. void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
  643. double endTime, int channelNum, float verticalZoomFactor)
  644. {
  645. const ScopedLock sl (lock);
  646. window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
  647. sampleRate, numChannels, samplesPerThumbSample, source, channels);
  648. }
  649. void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
  650. double endTimeSeconds, float verticalZoomFactor)
  651. {
  652. for (int i = 0; i < numChannels; ++i)
  653. {
  654. const int y1 = roundToInt ((i * area.getHeight()) / numChannels);
  655. const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
  656. drawChannel (g, Rectangle<int> (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1),
  657. startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
  658. }
  659. }