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.

727 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_AudioThumbnail.h"
  21. #include "juce_AudioThumbnailCache.h"
  22. #include "../../events/juce_MessageManager.h"
  23. //==============================================================================
  24. struct AudioThumbnail::MinMaxValue
  25. {
  26. char minValue;
  27. char maxValue;
  28. MinMaxValue() : minValue (0), maxValue (0)
  29. {
  30. }
  31. inline void set (const char newMin, const char newMax) throw()
  32. {
  33. minValue = newMin;
  34. maxValue = newMax;
  35. }
  36. inline void setFloat (const float newMin, const float newMax) throw()
  37. {
  38. minValue = (char) jlimit (-128, 127, roundFloatToInt (newMin * 127.0f));
  39. maxValue = (char) jlimit (-128, 127, roundFloatToInt (newMax * 127.0f));
  40. if (maxValue == minValue)
  41. maxValue = (char) jmin (127, maxValue + 1);
  42. }
  43. inline bool isNonZero() const throw()
  44. {
  45. return maxValue > minValue;
  46. }
  47. inline void read (InputStream& input)
  48. {
  49. minValue = input.readByte();
  50. maxValue = input.readByte();
  51. }
  52. inline void write (OutputStream& output)
  53. {
  54. output.writeByte (minValue);
  55. output.writeByte (maxValue);
  56. }
  57. };
  58. //==============================================================================
  59. class AudioThumbnail::LevelDataSource : public TimeSliceClient,
  60. public Timer
  61. {
  62. public:
  63. LevelDataSource (AudioThumbnail& owner_, AudioFormatReader* newReader, int64 hash)
  64. : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
  65. hashCode (hash), owner (owner_), reader (newReader)
  66. {
  67. }
  68. LevelDataSource (AudioThumbnail& owner_, InputSource* source_)
  69. : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0),
  70. hashCode (source_->hashCode()), owner (owner_), source (source_)
  71. {
  72. }
  73. ~LevelDataSource()
  74. {
  75. owner.cache.removeTimeSliceClient (this);
  76. }
  77. enum { timeBeforeDeletingReader = 2000 };
  78. void initialise (int64 numSamplesFinished_)
  79. {
  80. const ScopedLock sl (readerLock);
  81. numSamplesFinished = numSamplesFinished_;
  82. createReader();
  83. if (reader != 0)
  84. {
  85. lengthInSamples = reader->lengthInSamples;
  86. numChannels = reader->numChannels;
  87. sampleRate = reader->sampleRate;
  88. if (lengthInSamples <= 0)
  89. reader = 0;
  90. else if (! isFullyLoaded())
  91. owner.cache.addTimeSliceClient (this);
  92. }
  93. }
  94. void getLevels (int64 startSample, int numSamples, Array<float>& levels)
  95. {
  96. const ScopedLock sl (readerLock);
  97. createReader();
  98. if (reader != 0)
  99. {
  100. float l[4] = { 0 };
  101. reader->readMaxLevels (startSample, numSamples, l[0], l[1], l[2], l[3]);
  102. levels.clearQuick();
  103. levels.addArray ((const float*) l, 4);
  104. }
  105. }
  106. void releaseResources()
  107. {
  108. const ScopedLock sl (readerLock);
  109. reader = 0;
  110. }
  111. bool useTimeSlice()
  112. {
  113. if (isFullyLoaded())
  114. {
  115. if (reader != 0 && source != 0)
  116. startTimer (timeBeforeDeletingReader);
  117. owner.cache.removeTimeSliceClient (this);
  118. return false;
  119. }
  120. stopTimer();
  121. bool justFinished = false;
  122. {
  123. const ScopedLock sl (readerLock);
  124. createReader();
  125. if (reader != 0)
  126. {
  127. if (! readNextBlock())
  128. return true;
  129. justFinished = true;
  130. }
  131. }
  132. if (justFinished)
  133. owner.cache.storeThumb (owner, hashCode);
  134. return false;
  135. }
  136. void timerCallback()
  137. {
  138. stopTimer();
  139. releaseResources();
  140. }
  141. bool isFullyLoaded() const throw()
  142. {
  143. return numSamplesFinished >= lengthInSamples;
  144. }
  145. inline int sampleToThumbSample (const int64 originalSample) const throw()
  146. {
  147. return (int) (originalSample / owner.samplesPerThumbSample);
  148. }
  149. int64 lengthInSamples, numSamplesFinished;
  150. double sampleRate;
  151. int numChannels;
  152. int64 hashCode;
  153. private:
  154. AudioThumbnail& owner;
  155. ScopedPointer <InputSource> source;
  156. ScopedPointer <AudioFormatReader> reader;
  157. CriticalSection readerLock;
  158. void createReader()
  159. {
  160. if (reader == 0 && source != 0)
  161. {
  162. InputStream* audioFileStream = source->createInputStream();
  163. if (audioFileStream != 0)
  164. reader = owner.formatManagerToUse.createReaderFor (audioFileStream);
  165. }
  166. }
  167. bool readNextBlock()
  168. {
  169. jassert (reader != 0);
  170. if (! isFullyLoaded())
  171. {
  172. const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished);
  173. if (numToDo > 0)
  174. {
  175. int64 startSample = numSamplesFinished;
  176. const int firstThumbIndex = sampleToThumbSample (startSample);
  177. const int lastThumbIndex = sampleToThumbSample (startSample + numToDo);
  178. const int numThumbSamps = lastThumbIndex - firstThumbIndex;
  179. HeapBlock<MinMaxValue> levelData (numThumbSamps * 2);
  180. MinMaxValue* levels[2] = { levelData, levelData + numThumbSamps };
  181. for (int i = 0; i < numThumbSamps; ++i)
  182. {
  183. float lowestLeft, highestLeft, lowestRight, highestRight;
  184. reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample, owner.samplesPerThumbSample,
  185. lowestLeft, highestLeft, lowestRight, highestRight);
  186. levels[0][i].setFloat (lowestLeft, highestLeft);
  187. levels[1][i].setFloat (lowestRight, highestRight);
  188. }
  189. {
  190. const ScopedUnlock su (readerLock);
  191. owner.setLevels (levels, firstThumbIndex, 2, numThumbSamps);
  192. }
  193. numSamplesFinished += numToDo;
  194. }
  195. }
  196. return isFullyLoaded();
  197. }
  198. };
  199. //==============================================================================
  200. class AudioThumbnail::ThumbData
  201. {
  202. public:
  203. ThumbData (const int numThumbSamples)
  204. {
  205. ensureSize (numThumbSamples);
  206. }
  207. inline MinMaxValue* getData (const int thumbSampleIndex) throw()
  208. {
  209. jassert (thumbSampleIndex < data.size());
  210. return data.getRawDataPointer() + thumbSampleIndex;
  211. }
  212. int getSize() const throw()
  213. {
  214. return data.size();
  215. }
  216. void getMinMax (int startSample, int endSample, MinMaxValue& result) throw()
  217. {
  218. if (startSample >= 0)
  219. {
  220. endSample = jmin (endSample, data.size() - 1);
  221. char mx = -128;
  222. char mn = 127;
  223. while (startSample <= endSample)
  224. {
  225. const MinMaxValue& v = data.getReference (startSample);
  226. if (v.minValue < mn) mn = v.minValue;
  227. if (v.maxValue > mx) mx = v.maxValue;
  228. ++startSample;
  229. }
  230. if (mn <= mx)
  231. {
  232. result.set (mn, mx);
  233. return;
  234. }
  235. }
  236. result.set (1, 0);
  237. }
  238. void write (const MinMaxValue* const source, const int startIndex, const int numValues)
  239. {
  240. if (startIndex + numValues > data.size())
  241. ensureSize (startIndex + numValues);
  242. MinMaxValue* const dest = getData (startIndex);
  243. for (int i = 0; i < numValues; ++i)
  244. dest[i] = source[i];
  245. }
  246. private:
  247. Array <MinMaxValue> data;
  248. void ensureSize (const int thumbSamples)
  249. {
  250. const int extraNeeded = thumbSamples - data.size();
  251. if (extraNeeded > 0)
  252. data.insertMultiple (-1, MinMaxValue(), extraNeeded);
  253. }
  254. };
  255. //==============================================================================
  256. class AudioThumbnail::CachedWindow
  257. {
  258. public:
  259. CachedWindow()
  260. : cachedStart (0), cachedTimePerPixel (0),
  261. numChannelsCached (0), numSamplesCached (0),
  262. cacheNeedsRefilling (true)
  263. {
  264. }
  265. void invalidate()
  266. {
  267. cacheNeedsRefilling = true;
  268. }
  269. void drawChannel (Graphics& g, const Rectangle<int>& area,
  270. const double startTime, const double endTime,
  271. const int channelNum, const float verticalZoomFactor,
  272. const double sampleRate, const int numChannels, const int samplesPerThumbSample,
  273. LevelDataSource* levelData, const OwnedArray<ThumbData>& channels)
  274. {
  275. refillCache (area.getWidth(), startTime, endTime, sampleRate,
  276. numChannels, samplesPerThumbSample, levelData, channels);
  277. if (isPositiveAndBelow (channelNum, numChannelsCached))
  278. {
  279. const Rectangle<int> clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth()))));
  280. if (! clip.isEmpty())
  281. {
  282. const float topY = (float) area.getY();
  283. const float bottomY = (float) area.getBottom();
  284. const float midY = (topY + bottomY) * 0.5f;
  285. const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
  286. const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX());
  287. int x = clip.getX();
  288. for (int w = clip.getWidth(); --w >= 0;)
  289. {
  290. if (cacheData->isNonZero())
  291. g.drawVerticalLine (x, jmax (midY - cacheData->maxValue * vscale - 0.3f, topY),
  292. jmin (midY - cacheData->minValue * vscale + 0.3f, bottomY));
  293. ++x;
  294. ++cacheData;
  295. }
  296. }
  297. }
  298. }
  299. private:
  300. Array <MinMaxValue> data;
  301. double cachedStart, cachedTimePerPixel;
  302. int numChannelsCached, numSamplesCached;
  303. bool cacheNeedsRefilling;
  304. void refillCache (const int numSamples, double startTime, const double endTime,
  305. const double sampleRate, const int numChannels, const int samplesPerThumbSample,
  306. LevelDataSource* levelData, const OwnedArray<ThumbData>& channels)
  307. {
  308. const double timePerPixel = (endTime - startTime) / numSamples;
  309. if (numSamples <= 0 || timePerPixel <= 0.0 || sampleRate <= 0)
  310. {
  311. invalidate();
  312. return;
  313. }
  314. if (numSamples == numSamplesCached
  315. && numChannelsCached == numChannels
  316. && startTime == cachedStart
  317. && timePerPixel == cachedTimePerPixel
  318. && ! cacheNeedsRefilling)
  319. {
  320. return;
  321. }
  322. numSamplesCached = numSamples;
  323. numChannelsCached = numChannels;
  324. cachedStart = startTime;
  325. cachedTimePerPixel = timePerPixel;
  326. cacheNeedsRefilling = false;
  327. ensureSize (numSamples);
  328. if (timePerPixel * sampleRate <= samplesPerThumbSample && levelData != 0)
  329. {
  330. int sample = roundToInt (startTime * sampleRate);
  331. Array<float> levels;
  332. int i;
  333. for (i = 0; i < numSamples; ++i)
  334. {
  335. const int nextSample = roundToInt ((startTime + timePerPixel) * sampleRate);
  336. if (sample >= 0)
  337. {
  338. if (sample >= levelData->lengthInSamples)
  339. break;
  340. levelData->getLevels (sample, jmax (1, nextSample - sample), levels);
  341. const int numChans = jmin (levels.size() / 2, numChannelsCached);
  342. for (int chan = 0; chan < numChans; ++chan)
  343. getData (chan, i)->setFloat (levels.getUnchecked (chan * 2),
  344. levels.getUnchecked (chan * 2 + 1));
  345. }
  346. startTime += timePerPixel;
  347. sample = nextSample;
  348. }
  349. numSamplesCached = i;
  350. }
  351. else
  352. {
  353. jassert (channels.size() == numChannelsCached);
  354. for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum)
  355. {
  356. ThumbData* channelData = channels.getUnchecked (channelNum);
  357. MinMaxValue* cacheData = getData (channelNum, 0);
  358. const double timeToThumbSampleFactor = sampleRate / (double) samplesPerThumbSample;
  359. startTime = cachedStart;
  360. int sample = roundToInt (startTime * timeToThumbSampleFactor);
  361. for (int i = numSamples; --i >= 0;)
  362. {
  363. const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor);
  364. channelData->getMinMax (sample, nextSample, *cacheData);
  365. ++cacheData;
  366. startTime += timePerPixel;
  367. sample = nextSample;
  368. }
  369. }
  370. }
  371. }
  372. MinMaxValue* getData (const int channelNum, const int cacheIndex) throw()
  373. {
  374. jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size()));
  375. return data.getRawDataPointer() + channelNum * numSamplesCached
  376. + cacheIndex;
  377. }
  378. void ensureSize (const int numSamples)
  379. {
  380. const int itemsRequired = numSamples * numChannelsCached;
  381. if (data.size() < itemsRequired)
  382. data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size());
  383. }
  384. };
  385. //==============================================================================
  386. AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample,
  387. AudioFormatManager& formatManagerToUse_,
  388. AudioThumbnailCache& cacheToUse)
  389. : formatManagerToUse (formatManagerToUse_),
  390. cache (cacheToUse),
  391. window (new CachedWindow()),
  392. samplesPerThumbSample (originalSamplesPerThumbnailSample),
  393. totalSamples (0),
  394. numChannels (0),
  395. sampleRate (0)
  396. {
  397. }
  398. AudioThumbnail::~AudioThumbnail()
  399. {
  400. clear();
  401. }
  402. void AudioThumbnail::clear()
  403. {
  404. source = 0;
  405. const ScopedLock sl (lock);
  406. window->invalidate();
  407. channels.clear();
  408. totalSamples = numSamplesFinished = 0;
  409. numChannels = 0;
  410. sampleRate = 0;
  411. sendChangeMessage();
  412. }
  413. void AudioThumbnail::reset (int newNumChannels, double newSampleRate)
  414. {
  415. clear();
  416. numChannels = newNumChannels;
  417. sampleRate = newSampleRate;
  418. createChannels (0);
  419. }
  420. void AudioThumbnail::createChannels (const int length)
  421. {
  422. while (channels.size() < numChannels)
  423. channels.add (new ThumbData (length));
  424. }
  425. //==============================================================================
  426. void AudioThumbnail::loadFrom (InputStream& input)
  427. {
  428. clear();
  429. if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm')
  430. return;
  431. samplesPerThumbSample = input.readInt();
  432. totalSamples = input.readInt64(); // Total number of source samples.
  433. numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail.
  434. int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data.
  435. numChannels = input.readInt(); // Number of audio channels.
  436. sampleRate = input.readInt(); // Source sample rate.
  437. input.skipNextBytes (16); // reserved area
  438. createChannels (numThumbnailSamples);
  439. for (int i = 0; i < numThumbnailSamples; ++i)
  440. for (int chan = 0; chan < numChannels; ++chan)
  441. channels.getUnchecked(chan)->getData(i)->read (input);
  442. }
  443. void AudioThumbnail::saveTo (OutputStream& output) const
  444. {
  445. const ScopedLock sl (lock);
  446. const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize();
  447. output.write ("jatm", 4);
  448. output.writeInt (samplesPerThumbSample);
  449. output.writeInt64 (totalSamples);
  450. output.writeInt64 (numSamplesFinished);
  451. output.writeInt (numThumbnailSamples);
  452. output.writeInt (numChannels);
  453. output.writeInt ((int) sampleRate);
  454. output.writeInt64 (0);
  455. output.writeInt64 (0);
  456. for (int i = 0; i < numThumbnailSamples; ++i)
  457. for (int chan = 0; chan < numChannels; ++chan)
  458. channels.getUnchecked(chan)->getData(i)->write (output);
  459. }
  460. //==============================================================================
  461. bool AudioThumbnail::setDataSource (LevelDataSource* newSource)
  462. {
  463. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
  464. numSamplesFinished = 0;
  465. if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded())
  466. {
  467. source = newSource; // (make sure this isn't done before loadThumb is called)
  468. source->lengthInSamples = totalSamples;
  469. source->sampleRate = sampleRate;
  470. source->numChannels = numChannels;
  471. source->numSamplesFinished = numSamplesFinished;
  472. }
  473. else
  474. {
  475. source = newSource; // (make sure this isn't done before loadThumb is called)
  476. const ScopedLock sl (lock);
  477. source->initialise (numSamplesFinished);
  478. totalSamples = source->lengthInSamples;
  479. sampleRate = source->sampleRate;
  480. numChannels = source->numChannels;
  481. createChannels (1 + (int) (totalSamples / samplesPerThumbSample));
  482. }
  483. return sampleRate > 0 && totalSamples > 0;
  484. }
  485. bool AudioThumbnail::setSource (InputSource* const newSource)
  486. {
  487. clear();
  488. return newSource != 0 && setDataSource (new LevelDataSource (*this, newSource));
  489. }
  490. void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash)
  491. {
  492. clear();
  493. if (newReader != 0)
  494. setDataSource (new LevelDataSource (*this, newReader, hash));
  495. }
  496. int64 AudioThumbnail::getHashCode() const
  497. {
  498. return source == 0 ? 0 : source->hashCode;
  499. }
  500. void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming,
  501. int startOffsetInBuffer, int numSamples)
  502. {
  503. jassert (startSample >= 0);
  504. const int firstThumbIndex = (int) (startSample / samplesPerThumbSample);
  505. const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample);
  506. const int numToDo = lastThumbIndex - firstThumbIndex;
  507. if (numToDo > 0)
  508. {
  509. const int numChans = jmin (channels.size(), incoming.getNumChannels());
  510. const HeapBlock<MinMaxValue> thumbData (numToDo * numChans);
  511. const HeapBlock<MinMaxValue*> thumbChannels (numChans);
  512. for (int chan = 0; chan < numChans; ++chan)
  513. {
  514. const float* const source = incoming.getSampleData (chan, startOffsetInBuffer);
  515. MinMaxValue* const dest = thumbData + numToDo * chan;
  516. thumbChannels [chan] = dest;
  517. for (int i = 0; i < numToDo; ++i)
  518. {
  519. float low, high;
  520. const int start = i * samplesPerThumbSample;
  521. findMinAndMax (source + start, jmin (samplesPerThumbSample, numSamples - start), low, high);
  522. dest[i].setFloat (low, high);
  523. }
  524. }
  525. setLevels (thumbChannels, firstThumbIndex, numChans, numToDo);
  526. }
  527. }
  528. void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues)
  529. {
  530. const ScopedLock sl (lock);
  531. for (int i = jmin (numChans, channels.size()); --i >= 0;)
  532. channels.getUnchecked(i)->write (values[i], thumbIndex, numValues);
  533. numSamplesFinished = jmax (numSamplesFinished, (thumbIndex + numValues) * (int64) samplesPerThumbSample);
  534. totalSamples = jmax (numSamplesFinished, totalSamples);
  535. window->invalidate();
  536. sendChangeMessage();
  537. }
  538. //==============================================================================
  539. int AudioThumbnail::getNumChannels() const throw()
  540. {
  541. return numChannels;
  542. }
  543. double AudioThumbnail::getTotalLength() const throw()
  544. {
  545. return totalSamples / sampleRate;
  546. }
  547. bool AudioThumbnail::isFullyLoaded() const throw()
  548. {
  549. return numSamplesFinished >= totalSamples - samplesPerThumbSample;
  550. }
  551. void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
  552. double endTime, int channelNum, float verticalZoomFactor)
  553. {
  554. const ScopedLock sl (lock);
  555. window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor,
  556. sampleRate, numChannels, samplesPerThumbSample, source, channels);
  557. }
  558. void AudioThumbnail::drawChannels (Graphics& g, const Rectangle<int>& area, double startTimeSeconds,
  559. double endTimeSeconds, float verticalZoomFactor)
  560. {
  561. for (int i = 0; i < numChannels; ++i)
  562. {
  563. const int y1 = roundToInt ((i * area.getHeight()) / numChannels);
  564. const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels);
  565. drawChannel (g, Rectangle<int> (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1),
  566. startTimeSeconds, endTimeSeconds, i, verticalZoomFactor);
  567. }
  568. }
  569. END_JUCE_NAMESPACE