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.

902 lines
29KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. template <typename T>
  70. class AudioBufferReader : public AudioFormatReader
  71. {
  72. public:
  73. AudioBufferReader (const AudioBuffer<T>* bufferIn, double rate)
  74. : AudioFormatReader (nullptr, "AudioBuffer"), buffer (bufferIn)
  75. {
  76. sampleRate = rate;
  77. bitsPerSample = 32;
  78. lengthInSamples = buffer->getNumSamples();
  79. numChannels = (unsigned int) buffer->getNumChannels();
  80. usesFloatingPointData = std::is_floating_point_v<T>;
  81. }
  82. bool readSamples (int* const* destChannels,
  83. int numDestChannels,
  84. int startOffsetInDestBuffer,
  85. int64 startSampleInFile,
  86. int numSamples) override
  87. {
  88. clearSamplesBeyondAvailableLength (destChannels, numDestChannels, startOffsetInDestBuffer,
  89. startSampleInFile, numSamples, lengthInSamples);
  90. const auto numAvailableSamples = (int) ((int64) buffer->getNumSamples() - startSampleInFile);
  91. const auto numSamplesToCopy = std::clamp (numAvailableSamples, 0, numSamples);
  92. if (numSamplesToCopy == 0)
  93. return true;
  94. for (int i = 0; i < numDestChannels; ++i)
  95. {
  96. if (void* targetChannel = destChannels[i])
  97. {
  98. const auto dest = DestType (targetChannel) + startOffsetInDestBuffer;
  99. if (i < buffer->getNumChannels())
  100. dest.convertSamples (SourceType (buffer->getReadPointer (i) + startSampleInFile), numSamplesToCopy);
  101. else
  102. dest.clearSamples (numSamples);
  103. }
  104. }
  105. return true;
  106. }
  107. private:
  108. using SourceNumericalType =
  109. std::conditional_t<std::is_same_v<T, int>, AudioData::Int32,
  110. std::conditional_t<std::is_same_v<T, float>, AudioData::Float32, void>>;
  111. using DestinationNumericalType = std::conditional_t<std::is_floating_point_v<T>, AudioData::Float32, AudioData::Int32>;
  112. using DestType = AudioData::Pointer<DestinationNumericalType, AudioData::LittleEndian, AudioData::NonInterleaved, AudioData::NonConst>;
  113. using SourceType = AudioData::Pointer<SourceNumericalType, AudioData::LittleEndian, AudioData::NonInterleaved, AudioData::Const>;
  114. const AudioBuffer<T>* buffer;
  115. };
  116. //==============================================================================
  117. class AudioThumbnail::LevelDataSource : public TimeSliceClient
  118. {
  119. public:
  120. LevelDataSource (AudioThumbnail& thumb, AudioFormatReader* newReader, int64 hash)
  121. : hashCode (hash), owner (thumb), reader (newReader)
  122. {
  123. }
  124. LevelDataSource (AudioThumbnail& thumb, InputSource* src)
  125. : hashCode (src->hashCode()), owner (thumb), source (src)
  126. {
  127. }
  128. ~LevelDataSource() override
  129. {
  130. owner.cache.getTimeSliceThread().removeTimeSliceClient (this);
  131. }
  132. enum { timeBeforeDeletingReader = 3000 };
  133. void initialise (int64 samplesFinished)
  134. {
  135. const ScopedLock sl (readerLock);
  136. numSamplesFinished = samplesFinished;
  137. createReader();
  138. if (reader != nullptr)
  139. {
  140. lengthInSamples = reader->lengthInSamples;
  141. numChannels = reader->numChannels;
  142. sampleRate = reader->sampleRate;
  143. if (lengthInSamples <= 0 || isFullyLoaded())
  144. reader.reset();
  145. else
  146. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  147. }
  148. }
  149. void getLevels (int64 startSample, int numSamples, Array<Range<float>>& levels)
  150. {
  151. const ScopedLock sl (readerLock);
  152. if (reader == nullptr)
  153. {
  154. createReader();
  155. if (reader != nullptr)
  156. {
  157. lastReaderUseTime = Time::getMillisecondCounter();
  158. owner.cache.getTimeSliceThread().addTimeSliceClient (this);
  159. }
  160. }
  161. if (reader != nullptr)
  162. {
  163. if (levels.size() < (int) reader->numChannels)
  164. levels.insertMultiple (0, {}, (int) reader->numChannels - levels.size());
  165. reader->readMaxLevels (startSample, numSamples, levels.getRawDataPointer(), (int) reader->numChannels);
  166. lastReaderUseTime = Time::getMillisecondCounter();
  167. }
  168. }
  169. void releaseResources()
  170. {
  171. const ScopedLock sl (readerLock);
  172. reader.reset();
  173. }
  174. int useTimeSlice() override
  175. {
  176. if (isFullyLoaded())
  177. {
  178. if (reader != nullptr && source != nullptr)
  179. {
  180. if (Time::getMillisecondCounter() > lastReaderUseTime + timeBeforeDeletingReader)
  181. releaseResources();
  182. else
  183. return 200;
  184. }
  185. return -1;
  186. }
  187. bool justFinished = false;
  188. {
  189. const ScopedLock sl (readerLock);
  190. createReader();
  191. if (reader != nullptr)
  192. {
  193. if (! readNextBlock())
  194. return 0;
  195. justFinished = true;
  196. }
  197. }
  198. if (justFinished)
  199. owner.cache.storeThumb (owner, hashCode);
  200. return 200;
  201. }
  202. bool isFullyLoaded() const noexcept
  203. {
  204. return numSamplesFinished >= lengthInSamples;
  205. }
  206. inline int sampleToThumbSample (const int64 originalSample) const noexcept
  207. {
  208. return (int) (originalSample / owner.samplesPerThumbSample);
  209. }
  210. int64 lengthInSamples = 0, numSamplesFinished = 0;
  211. double sampleRate = 0;
  212. unsigned int numChannels = 0;
  213. int64 hashCode = 0;
  214. private:
  215. AudioThumbnail& owner;
  216. std::unique_ptr<InputSource> source;
  217. std::unique_ptr<AudioFormatReader> reader;
  218. CriticalSection readerLock;
  219. std::atomic<uint32> lastReaderUseTime { 0 };
  220. void createReader()
  221. {
  222. if (reader == nullptr && source != nullptr)
  223. if (auto* audioFileStream = source->createInputStream())
  224. reader.reset (owner.formatManagerToUse.createReaderFor (std::unique_ptr<InputStream> (audioFileStream)));
  225. }
  226. bool readNextBlock()
  227. {
  228. jassert (reader != nullptr);
  229. if (! isFullyLoaded())
  230. {
  231. auto numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
  232. if (numToDo > 0)
  233. {
  234. auto startSample = numSamplesFinished;
  235. auto firstThumbIndex = sampleToThumbSample (startSample);
  236. auto lastThumbIndex = sampleToThumbSample (startSample + numToDo);
  237. auto numThumbSamps = lastThumbIndex - firstThumbIndex;
  238. HeapBlock<MinMaxValue> levelData ((unsigned int) numThumbSamps * numChannels);
  239. HeapBlock<MinMaxValue*> levels (numChannels);
  240. for (int i = 0; i < (int) numChannels; ++i)
  241. levels[i] = levelData + i * numThumbSamps;
  242. HeapBlock<Range<float>> levelsRead (numChannels);
  243. for (int i = 0; i < numThumbSamps; ++i)
  244. {
  245. reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample,
  246. owner.samplesPerThumbSample, levelsRead, (int) numChannels);
  247. for (int j = 0; j < (int) numChannels; ++j)
  248. levels[j][i].setFloat (levelsRead[j]);
  249. }
  250. {
  251. const ScopedUnlock su (readerLock);
  252. owner.setLevels (levels, firstThumbIndex, (int) numChannels, numThumbSamps);
  253. }
  254. numSamplesFinished += numToDo;
  255. lastReaderUseTime = Time::getMillisecondCounter();
  256. }
  257. }
  258. return isFullyLoaded();
  259. }
  260. };
  261. //==============================================================================
  262. class AudioThumbnail::ThumbData
  263. {
  264. public:
  265. ThumbData (int numThumbSamples)
  266. {
  267. ensureSize (numThumbSamples);
  268. }
  269. inline MinMaxValue* getData (int thumbSampleIndex) noexcept
  270. {
  271. jassert (thumbSampleIndex < data.size());
  272. return data.getRawDataPointer() + thumbSampleIndex;
  273. }
  274. int getSize() const noexcept
  275. {
  276. return data.size();
  277. }
  278. void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
  279. {
  280. if (startSample >= 0)
  281. {
  282. endSample = jmin (endSample, data.size() - 1);
  283. int8 mx = -128;
  284. int8 mn = 127;
  285. while (startSample <= endSample)
  286. {
  287. auto& v = data.getReference (startSample);
  288. if (v.getMinValue() < mn) mn = v.getMinValue();
  289. if (v.getMaxValue() > mx) mx = v.getMaxValue();
  290. ++startSample;
  291. }
  292. if (mn <= mx)
  293. {
  294. result.set (mn, mx);
  295. return;
  296. }
  297. }
  298. result.set (1, 0);
  299. }
  300. void write (const MinMaxValue* values, int startIndex, int numValues)
  301. {
  302. resetPeak();
  303. if (startIndex + numValues > data.size())
  304. ensureSize (startIndex + numValues);
  305. auto* dest = getData (startIndex);
  306. for (int i = 0; i < numValues; ++i)
  307. dest[i] = values[i];
  308. }
  309. void resetPeak() noexcept
  310. {
  311. peakLevel = -1;
  312. }
  313. int getPeak() noexcept
  314. {
  315. if (peakLevel < 0)
  316. {
  317. for (auto& s : data)
  318. {
  319. auto peak = s.getPeak();
  320. if (peak > peakLevel)
  321. peakLevel = peak;
  322. }
  323. }
  324. return peakLevel;
  325. }
  326. private:
  327. Array<MinMaxValue> data;
  328. int peakLevel = -1;
  329. void ensureSize (int thumbSamples)
  330. {
  331. auto extraNeeded = thumbSamples - data.size();
  332. if (extraNeeded > 0)
  333. data.insertMultiple (-1, MinMaxValue(), extraNeeded);
  334. }
  335. };
  336. //==============================================================================
  337. class AudioThumbnail::CachedWindow
  338. {
  339. public:
  340. CachedWindow() {}
  341. void invalidate()
  342. {
  343. cacheNeedsRefilling = true;
  344. }
  345. void drawChannel (Graphics& g, const Rectangle<int>& area,
  346. const double startTime, const double endTime,
  347. const int channelNum, const float verticalZoomFactor,
  348. const double rate, const int numChans, const int sampsPerThumbSample,
  349. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  350. {
  351. if (refillCache (area.getWidth(), startTime, endTime, rate,
  352. numChans, sampsPerThumbSample, levelData, chans)
  353. && isPositiveAndBelow (channelNum, numChannelsCached))
  354. {
  355. auto clip = g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())));
  356. if (! clip.isEmpty())
  357. {
  358. auto topY = (float) area.getY();
  359. auto bottomY = (float) area.getBottom();
  360. auto midY = (topY + bottomY) * 0.5f;
  361. auto vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
  362. auto* cacheData = getData (channelNum, clip.getX() - area.getX());
  363. RectangleList<float> waveform;
  364. waveform.ensureStorageAllocated (clip.getWidth());
  365. auto x = (float) clip.getX();
  366. for (int w = clip.getWidth(); --w >= 0;)
  367. {
  368. if (cacheData->isNonZero())
  369. {
  370. auto top = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
  371. auto bottom = jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY);
  372. waveform.addWithoutMerging (Rectangle<float> (x, top, 1.0f, bottom - top));
  373. }
  374. x += 1.0f;
  375. ++cacheData;
  376. }
  377. g.fillRectList (waveform);
  378. }
  379. }
  380. }
  381. private:
  382. Array<MinMaxValue> data;
  383. double cachedStart = 0, cachedTimePerPixel = 0;
  384. int numChannelsCached = 0, numSamplesCached = 0;
  385. bool cacheNeedsRefilling = true;
  386. bool refillCache (int numSamples, double startTime, double endTime,
  387. double rate, int numChans, int sampsPerThumbSample,
  388. LevelDataSource* levelData, const OwnedArray<ThumbData>& chans)
  389. {
  390. auto timePerPixel = (endTime - startTime) / numSamples;
  391. if (numSamples <= 0 || timePerPixel <= 0.0 || rate <= 0)
  392. {
  393. invalidate();
  394. return false;
  395. }
  396. if (numSamples == numSamplesCached
  397. && numChannelsCached == numChans
  398. && startTime == cachedStart
  399. && timePerPixel == cachedTimePerPixel
  400. && ! cacheNeedsRefilling)
  401. {
  402. return ! cacheNeedsRefilling;
  403. }
  404. numSamplesCached = numSamples;
  405. numChannelsCached = numChans;
  406. cachedStart = startTime;
  407. cachedTimePerPixel = timePerPixel;
  408. cacheNeedsRefilling = false;
  409. ensureSize (numSamples);
  410. if (timePerPixel * rate <= sampsPerThumbSample && levelData != nullptr)
  411. {
  412. auto sample = roundToInt (startTime * rate);
  413. Array<Range<float>> levels;
  414. int i;
  415. for (i = 0; i < numSamples; ++i)
  416. {
  417. auto nextSample = roundToInt ((startTime + timePerPixel) * rate);
  418. if (sample >= 0)
  419. {
  420. if (sample >= levelData->lengthInSamples)
  421. {
  422. for (int chan = 0; chan < numChannelsCached; ++chan)
  423. *getData (chan, i) = MinMaxValue();
  424. }
  425. else
  426. {
  427. levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
  428. auto totalChans = jmin (levels.size(), numChannelsCached);
  429. for (int chan = 0; chan < totalChans; ++chan)
  430. getData (chan, i)->setFloat (levels.getReference (chan));
  431. }
  432. }
  433. startTime += timePerPixel;
  434. sample = nextSample;
  435. }
  436. numSamplesCached = i;
  437. }
  438. else
  439. {
  440. jassert (chans.size() == numChannelsCached);
  441. for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
  442. {
  443. ThumbData* channelData = chans.getUnchecked (channelNum);
  444. MinMaxValue* cacheData = getData (channelNum, 0);
  445. auto timeToThumbSampleFactor = rate / (double) sampsPerThumbSample;
  446. startTime = cachedStart;
  447. auto sample = roundToInt (startTime * timeToThumbSampleFactor);
  448. for (int i = numSamples; --i >= 0;)
  449. {
  450. auto nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
  451. channelData->getMinMax (sample, nextSample, *cacheData);
  452. ++cacheData;
  453. startTime += timePerPixel;
  454. sample = nextSample;
  455. }
  456. }
  457. }
  458. return true;
  459. }
  460. MinMaxValue* getData (const int channelNum, const int cacheIndex) noexcept
  461. {
  462. jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
  463. return data.getRawDataPointer() + channelNum * numSamplesCached
  464. + cacheIndex;
  465. }
  466. void ensureSize (const int numSamples)
  467. {
  468. auto itemsRequired = numSamples * numChannelsCached;
  469. if (data.size() < itemsRequired)
  470. data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
  471. }
  472. };
  473. //==============================================================================
  474. AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
  475. AudioFormatManager& formatManager,
  476. AudioThumbnailCache& cacheToUse)
  477. : formatManagerToUse (formatManager),
  478. cache (cacheToUse),
  479. window (new CachedWindow()),
  480. samplesPerThumbSample (originalSamplesPerThumbnailSample)
  481. {
  482. }
  483. AudioThumbnail::~AudioThumbnail()
  484. {
  485. clear();
  486. }
  487. void AudioThumbnail::clear()
  488. {
  489. source.reset();
  490. const ScopedLock sl (lock);
  491. clearChannelData();
  492. }
  493. void AudioThumbnail::clearChannelData()
  494. {
  495. window->invalidate();
  496. channels.clear();
  497. totalSamples = numSamplesFinished = 0;
  498. numChannels = 0;
  499. sampleRate = 0;
  500. sendChangeMessage();
  501. }
  502. void AudioThumbnail::reset (int newNumChannels, double newSampleRate, int64 totalSamplesInSource)
  503. {
  504. clear();
  505. const ScopedLock sl (lock);
  506. numChannels = newNumChannels;
  507. sampleRate = newSampleRate;
  508. totalSamples = totalSamplesInSource;
  509. createChannels (1 + (int) (totalSamplesInSource / samplesPerThumbSample));
  510. }
  511. void AudioThumbnail::createChannels (const int length)
  512. {
  513. while (channels.size() < numChannels)
  514. channels.add (new ThumbData (length));
  515. }
  516. //==============================================================================
  517. bool AudioThumbnail::loadFrom (InputStream& rawInput)
  518. {
  519. BufferedInputStream input (rawInput, 4096);
  520. if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
  521. return false;
  522. const ScopedLock sl (lock);
  523. clearChannelData();
  524. samplesPerThumbSample = input.readInt();
  525. totalSamples = input.readInt64(); // Total number of source samples.
  526. numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
  527. int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
  528. numChannels = input.readInt(); // Number of audio channels.
  529. sampleRate = input.readInt(); // Source sample rate.
  530. input.skipNextBytes (16); // (reserved)
  531. createChannels (numThumbnailSamples);
  532. for (int i = 0; i < numThumbnailSamples; ++i)
  533. for (int chan = 0; chan < numChannels; ++chan)
  534. channels.getUnchecked(chan)->getData(i)->read (input);
  535. return true;
  536. }
  537. void AudioThumbnail::saveTo (OutputStream& output) const
  538. {
  539. const ScopedLock sl (lock);
  540. const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize();
  541. output.write ("jatm", 4);
  542. output.writeInt (samplesPerThumbSample);
  543. output.writeInt64 (totalSamples);
  544. output.writeInt64 (numSamplesFinished);
  545. output.writeInt (numThumbnailSamples);
  546. output.writeInt (numChannels);
  547. output.writeInt ((int) sampleRate);
  548. output.writeInt64 (0);
  549. output.writeInt64 (0);
  550. for (int i = 0; i < numThumbnailSamples; ++i)
  551. for (int chan = 0; chan < numChannels; ++chan)
  552. channels.getUnchecked(chan)->getData(i)->write (output);
  553. }
  554. //==============================================================================
  555. bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
  556. {
  557. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  558. numSamplesFinished = 0;
  559. auto wasSuccessful = [&] { return sampleRate > 0 && totalSamples > 0; };
  560. if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
  561. {
  562. source.reset (newSource); // (make sure this isn't done before loadThumb is called)
  563. source->lengthInSamples = totalSamples;
  564. source->sampleRate = sampleRate;
  565. source->numChannels = (unsigned int) numChannels;
  566. source->numSamplesFinished = numSamplesFinished;
  567. return wasSuccessful();
  568. }
  569. source.reset (newSource);
  570. const ScopedLock sl (lock);
  571. source->initialise (numSamplesFinished);
  572. totalSamples = source->lengthInSamples;
  573. sampleRate = source->sampleRate;
  574. numChannels = (int32) source->numChannels;
  575. createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
  576. return wasSuccessful();
  577. }
  578. bool AudioThumbnail::setSource (InputSource* const newSource)
  579. {
  580. clear();
  581. return newSource != nullptr && setDataSource (new LevelDataSource (*this, newSource));
  582. }
  583. void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash)
  584. {
  585. clear();
  586. if (newReader != nullptr)
  587. setDataSource (new LevelDataSource (*this, newReader, hash));
  588. }
  589. void AudioThumbnail::setSource (const AudioBuffer<float>* newSource, double rate, int64 hash)
  590. {
  591. setReader (new AudioBufferReader<float> (newSource, rate), hash);
  592. }
  593. void AudioThumbnail::setSource (const AudioBuffer<int>* newSource, double rate, int64 hash)
  594. {
  595. setReader (new AudioBufferReader<int> (newSource, rate), hash);
  596. }
  597. int64 AudioThumbnail::getHashCode() const
  598. {
  599. return source == nullptr ? 0 : source->hashCode;
  600. }
  601. void AudioThumbnail::addBlock (int64 startSample, const AudioBuffer<float>& incoming,
  602. int startOffsetInBuffer, int numSamples)
  603. {
  604. jassert (startSample >= 0
  605. && startOffsetInBuffer >= 0
  606. && startOffsetInBuffer + numSamples <= incoming.getNumSamples());
  607. auto firstThumbIndex = (int) (startSample / samplesPerThumbSample);
  608. auto lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
  609. auto numToDo = lastThumbIndex - firstThumbIndex;
  610. if (numToDo > 0)
  611. {
  612. auto numChans = jmin (channels.size(), incoming.getNumChannels());
  613. const HeapBlock<MinMaxValue> thumbData (numToDo * numChans);
  614. const HeapBlock<MinMaxValue*> thumbChannels (numChans);
  615. for (int chan = 0; chan < numChans; ++chan)
  616. {
  617. auto* sourceData = incoming.getReadPointer (chan, startOffsetInBuffer);
  618. auto* dest = thumbData + numToDo * chan;
  619. thumbChannels [chan] = dest;
  620. for (int i = 0; i < numToDo; ++i)
  621. {
  622. auto start = i * samplesPerThumbSample;
  623. dest[i].setFloat (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start)));
  624. }
  625. }
  626. setLevels (thumbChannels, firstThumbIndex, numChans, numToDo);
  627. }
  628. }
  629. void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
  630. {
  631. const ScopedLock sl (lock);
  632. for (int i = jmin (numChans, channels.size()); --i >= 0;)
  633. channels.getUnchecked (i)->write (values[i], thumbIndex, numValues);
  634. auto start = thumbIndex * (int64) samplesPerThumbSample;
  635. auto end = (thumbIndex + numValues) * (int64) samplesPerThumbSample;
  636. if (numSamplesFinished >= start && end > numSamplesFinished)
  637. numSamplesFinished = end;
  638. totalSamples = jmax (numSamplesFinished, totalSamples);
  639. window->invalidate();
  640. sendChangeMessage();
  641. }
  642. //==============================================================================
  643. int AudioThumbnail::getNumChannels() const noexcept
  644. {
  645. const ScopedLock sl (lock);
  646. return numChannels;
  647. }
  648. double AudioThumbnail::getTotalLength() const noexcept
  649. {
  650. const ScopedLock sl (lock);
  651. return sampleRate > 0 ? ((double) totalSamples / sampleRate) : 0.0;
  652. }
  653. bool AudioThumbnail::isFullyLoaded() const noexcept
  654. {
  655. const ScopedLock sl (lock);
  656. return numSamplesFinished >= totalSamples - samplesPerThumbSample;
  657. }
  658. double AudioThumbnail::getProportionComplete() const noexcept
  659. {
  660. const ScopedLock sl (lock);
  661. return jlimit (0.0, 1.0, (double) numSamplesFinished / (double) jmax ((int64) 1, totalSamples));
  662. }
  663. int64 AudioThumbnail::getNumSamplesFinished() const noexcept
  664. {
  665. const ScopedLock sl (lock);
  666. return numSamplesFinished;
  667. }
  668. float AudioThumbnail::getApproximatePeak() const
  669. {
  670. const ScopedLock sl (lock);
  671. int peak = 0;
  672. for (auto* c : channels)
  673. peak = jmax (peak, c->getPeak());
  674. return (float) jlimit (0, 127, peak) / 127.0f;
  675. }
  676. void AudioThumbnail::getApproximateMinMax (double startTime, double endTime, int channelIndex,
  677. float& minValue, float& maxValue) const noexcept
  678. {
  679. const ScopedLock sl (lock);
  680. MinMaxValue result;
  681. auto* data = channels [channelIndex];
  682. if (data != nullptr && sampleRate > 0)
  683. {
  684. auto firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
  685. auto lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
  686. data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
  687. }
  688. minValue = result.getMinValue() / 128.0f;
  689. maxValue = result.getMaxValue() / 128.0f;
  690. }
  691. void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
  692. double endTime, int channelNum, float verticalZoomFactor)
  693. {
  694. const ScopedLock sl (lock);
  695. window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
  696. sampleRate, numChannels, samplesPerThumbSample, source.get(), channels);
  697. }
  698. void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
  699. double endTimeSeconds, float verticalZoomFactor)
  700. {
  701. for (int i = 0; i < numChannels; ++i)
  702. {
  703. auto y1 = roundToInt ((i * area.getHeight()) / numChannels);
  704. auto y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
  705. drawChannel (g, { area.getX(), area.getY() + y1, area.getWidth(), y2 - y1 },
  706. startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
  707. }
  708. }
  709. } // namespace juce