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.

2350 lines
88KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: ARAPluginDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Audio plugin using the ARA API.
  24. dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
  25. juce_audio_plugin_client, juce_audio_processors,
  26. juce_audio_utils, juce_core, juce_data_structures,
  27. juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
  28. exporters: xcode_mac, vs2022
  29. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  30. type: AudioProcessor
  31. mainClass: ARADemoPluginAudioProcessor
  32. documentControllerClass: ARADemoPluginDocumentControllerSpecialisation
  33. useLocalCopy: 1
  34. END_JUCE_PIP_METADATA
  35. *******************************************************************************/
  36. #pragma once
  37. #include <ARA_Library/Utilities/ARAPitchInterpretation.h>
  38. #include <ARA_Library/Utilities/ARATimelineConversion.h>
  39. //==============================================================================
  40. class ARADemoPluginAudioModification final : public ARAAudioModification
  41. {
  42. public:
  43. ARADemoPluginAudioModification (ARAAudioSource* audioSource,
  44. ARA::ARAAudioModificationHostRef hostRef,
  45. const ARAAudioModification* optionalModificationToClone)
  46. : ARAAudioModification (audioSource, hostRef, optionalModificationToClone)
  47. {
  48. if (optionalModificationToClone != nullptr)
  49. dimmed = static_cast<const ARADemoPluginAudioModification*> (optionalModificationToClone)->dimmed;
  50. }
  51. bool isDimmed() const { return dimmed; }
  52. void setDimmed (bool shouldDim) { dimmed = shouldDim; }
  53. private:
  54. bool dimmed = false;
  55. };
  56. //==============================================================================
  57. struct PreviewState
  58. {
  59. std::atomic<double> previewTime { 0.0 };
  60. std::atomic<ARAPlaybackRegion*> previewedRegion { nullptr };
  61. };
  62. class SharedTimeSliceThread final : public TimeSliceThread
  63. {
  64. public:
  65. SharedTimeSliceThread()
  66. : TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread")
  67. {
  68. startThread (Priority::high); // Above default priority so playback is fluent, but below realtime
  69. }
  70. };
  71. class AsyncConfigurationCallback final : private AsyncUpdater
  72. {
  73. public:
  74. explicit AsyncConfigurationCallback (std::function<void()> callbackIn)
  75. : callback (std::move (callbackIn)) {}
  76. ~AsyncConfigurationCallback() override { cancelPendingUpdate(); }
  77. template <typename RequiresLock>
  78. auto withLock (RequiresLock&& fn)
  79. {
  80. const SpinLock::ScopedTryLockType scope (processingFlag);
  81. return fn (scope.isLocked());
  82. }
  83. void startConfigure() { triggerAsyncUpdate(); }
  84. private:
  85. void handleAsyncUpdate() override
  86. {
  87. const SpinLock::ScopedLockType scope (processingFlag);
  88. callback();
  89. }
  90. std::function<void()> callback;
  91. SpinLock processingFlag;
  92. };
  93. static void crossfade (const float* sourceA,
  94. const float* sourceB,
  95. float aProportionAtStart,
  96. float aProportionAtFinish,
  97. float* destinationBuffer,
  98. int numSamples)
  99. {
  100. AudioBuffer<float> destination { &destinationBuffer, 1, numSamples };
  101. destination.copyFromWithRamp (0, 0, sourceA, numSamples, aProportionAtStart, aProportionAtFinish);
  102. destination.addFromWithRamp (0, 0, sourceB, numSamples, 1.0f - aProportionAtStart, 1.0f - aProportionAtFinish);
  103. }
  104. class Looper
  105. {
  106. public:
  107. Looper() : inputBuffer (nullptr), pos (loopRange.getStart())
  108. {
  109. }
  110. Looper (const AudioBuffer<float>* buffer, Range<int64> range)
  111. : inputBuffer (buffer), loopRange (range), pos (range.getStart())
  112. {
  113. }
  114. void writeInto (AudioBuffer<float>& buffer)
  115. {
  116. if (loopRange.getLength() == 0)
  117. {
  118. buffer.clear();
  119. return;
  120. }
  121. const auto numChannelsToCopy = std::min (inputBuffer->getNumChannels(), buffer.getNumChannels());
  122. const auto actualCrossfadeLengthSamples = std::min (loopRange.getLength() / 2, (int64) desiredCrossfadeLengthSamples);
  123. for (auto samplesCopied = 0; samplesCopied < buffer.getNumSamples();)
  124. {
  125. const auto [needsCrossfade, samplePosOfNextCrossfadeTransition] = [&]() -> std::pair<bool, int64>
  126. {
  127. if (const auto endOfFadeIn = loopRange.getStart() + actualCrossfadeLengthSamples; pos < endOfFadeIn)
  128. return { true, endOfFadeIn };
  129. return { false, loopRange.getEnd() - actualCrossfadeLengthSamples };
  130. }();
  131. const auto samplesToNextCrossfadeTransition = samplePosOfNextCrossfadeTransition - pos;
  132. const auto numSamplesToCopy = std::min (buffer.getNumSamples() - samplesCopied,
  133. (int) samplesToNextCrossfadeTransition);
  134. const auto getFadeInGainAtPos = [this, actualCrossfadeLengthSamples] (auto p)
  135. {
  136. return jmap ((float) p, (float) loopRange.getStart(), (float) loopRange.getStart() + (float) actualCrossfadeLengthSamples - 1.0f, 0.0f, 1.0f);
  137. };
  138. for (int i = 0; i < numChannelsToCopy; ++i)
  139. {
  140. if (needsCrossfade)
  141. {
  142. const auto overlapStart = loopRange.getEnd() - actualCrossfadeLengthSamples
  143. + (pos - loopRange.getStart());
  144. crossfade (inputBuffer->getReadPointer (i, (int) pos),
  145. inputBuffer->getReadPointer (i, (int) overlapStart),
  146. getFadeInGainAtPos (pos),
  147. getFadeInGainAtPos (pos + numSamplesToCopy),
  148. buffer.getWritePointer (i, samplesCopied),
  149. numSamplesToCopy);
  150. }
  151. else
  152. {
  153. buffer.copyFrom (i, samplesCopied, *inputBuffer, i, (int) pos, numSamplesToCopy);
  154. }
  155. }
  156. samplesCopied += numSamplesToCopy;
  157. pos += numSamplesToCopy;
  158. jassert (pos <= loopRange.getEnd() - actualCrossfadeLengthSamples);
  159. if (pos == loopRange.getEnd() - actualCrossfadeLengthSamples)
  160. pos = loopRange.getStart();
  161. }
  162. }
  163. private:
  164. static constexpr int desiredCrossfadeLengthSamples = 50;
  165. const AudioBuffer<float>* inputBuffer;
  166. Range<int64> loopRange;
  167. int64 pos;
  168. };
  169. //==============================================================================
  170. // Returns the modified sample range in the output buffer.
  171. inline std::optional<Range<int64>> readPlaybackRangeIntoBuffer (Range<double> playbackRange,
  172. const ARAPlaybackRegion* playbackRegion,
  173. AudioBuffer<float>& buffer,
  174. const std::function<AudioFormatReader* (ARAAudioSource*)>& getReader)
  175. {
  176. const auto rangeInAudioModificationTime = playbackRange - playbackRegion->getStartInPlaybackTime()
  177. + playbackRegion->getStartInAudioModificationTime();
  178. const auto audioModification = playbackRegion->getAudioModification<ARADemoPluginAudioModification>();
  179. const auto audioSource = audioModification->getAudioSource();
  180. const auto audioModificationSampleRate = audioSource->getSampleRate();
  181. const Range<int64_t> sampleRangeInAudioModification {
  182. ARA::roundSamplePosition (rangeInAudioModificationTime.getStart() * audioModificationSampleRate),
  183. ARA::roundSamplePosition (rangeInAudioModificationTime.getEnd() * audioModificationSampleRate) - 1
  184. };
  185. const auto inputOffset = jlimit ((int64_t) 0, audioSource->getSampleCount(), sampleRangeInAudioModification.getStart());
  186. // With the output offset it can always be said of the output buffer, that the zeroth element
  187. // corresponds to beginning of the playbackRange.
  188. const auto outputOffset = std::max (-sampleRangeInAudioModification.getStart(), (int64_t) 0);
  189. /* TODO: Handle different AudioSource and playback sample rates.
  190. The conversion should be done inside a specialized AudioFormatReader so that we could use
  191. playbackSampleRate everywhere in this function and we could still read `readLength` number of samples
  192. from the source.
  193. The current implementation will be incorrect when sampling rates differ.
  194. */
  195. const auto readLength = [&]
  196. {
  197. const auto sourceReadLength =
  198. std::min (sampleRangeInAudioModification.getEnd(), audioSource->getSampleCount()) - inputOffset;
  199. const auto outputReadLength =
  200. std::min (outputOffset + sourceReadLength, (int64_t) buffer.getNumSamples()) - outputOffset;
  201. return std::min (sourceReadLength, outputReadLength);
  202. }();
  203. if (readLength == 0)
  204. return Range<int64>();
  205. auto* reader = getReader (audioSource);
  206. if (reader != nullptr && reader->read (&buffer, (int) outputOffset, (int) readLength, inputOffset, true, true))
  207. {
  208. if (audioModification->isDimmed())
  209. buffer.applyGain ((int) outputOffset, (int) readLength, 0.25f);
  210. return Range<int64>::withStartAndLength (outputOffset, readLength);
  211. }
  212. return {};
  213. }
  214. class PossiblyBufferedReader
  215. {
  216. public:
  217. PossiblyBufferedReader() = default;
  218. explicit PossiblyBufferedReader (std::unique_ptr<BufferingAudioReader> readerIn)
  219. : setTimeoutFn ([ptr = readerIn.get()] (int ms) { ptr->setReadTimeout (ms); }),
  220. reader (std::move (readerIn))
  221. {}
  222. explicit PossiblyBufferedReader (std::unique_ptr<AudioFormatReader> readerIn)
  223. : setTimeoutFn(),
  224. reader (std::move (readerIn))
  225. {}
  226. void setReadTimeout (int ms)
  227. {
  228. NullCheckedInvocation::invoke (setTimeoutFn, ms);
  229. }
  230. AudioFormatReader* get() const { return reader.get(); }
  231. private:
  232. std::function<void (int)> setTimeoutFn;
  233. std::unique_ptr<AudioFormatReader> reader;
  234. };
  235. struct ProcessingLockInterface
  236. {
  237. virtual ~ProcessingLockInterface() = default;
  238. virtual ScopedTryReadLock getProcessingLock() = 0;
  239. };
  240. //==============================================================================
  241. class PlaybackRenderer final : public ARAPlaybackRenderer
  242. {
  243. public:
  244. PlaybackRenderer (ARA::PlugIn::DocumentController* dc, ProcessingLockInterface& lockInterfaceIn)
  245. : ARAPlaybackRenderer (dc), lockInterface (lockInterfaceIn) {}
  246. void prepareToPlay (double sampleRateIn,
  247. int maximumSamplesPerBlockIn,
  248. int numChannelsIn,
  249. AudioProcessor::ProcessingPrecision,
  250. AlwaysNonRealtime alwaysNonRealtime) override
  251. {
  252. numChannels = numChannelsIn;
  253. sampleRate = sampleRateIn;
  254. maximumSamplesPerBlock = maximumSamplesPerBlockIn;
  255. tempBuffer.reset (new AudioBuffer<float> (numChannels, maximumSamplesPerBlock));
  256. useBufferedAudioSourceReader = alwaysNonRealtime == AlwaysNonRealtime::no;
  257. for (const auto playbackRegion : getPlaybackRegions())
  258. {
  259. auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  260. if (audioSourceReaders.find (audioSource) == audioSourceReaders.end())
  261. {
  262. auto reader = std::make_unique<ARAAudioSourceReader> (audioSource);
  263. if (! useBufferedAudioSourceReader)
  264. {
  265. audioSourceReaders.emplace (audioSource,
  266. PossiblyBufferedReader { std::move (reader) });
  267. }
  268. else
  269. {
  270. const auto readAheadSize = jmax (4 * maximumSamplesPerBlock,
  271. roundToInt (2.0 * sampleRate));
  272. audioSourceReaders.emplace (audioSource,
  273. PossiblyBufferedReader { std::make_unique<BufferingAudioReader> (reader.release(),
  274. *sharedTimesliceThread,
  275. readAheadSize) });
  276. }
  277. }
  278. }
  279. }
  280. void releaseResources() override
  281. {
  282. audioSourceReaders.clear();
  283. tempBuffer.reset();
  284. }
  285. bool processBlock (AudioBuffer<float>& buffer,
  286. AudioProcessor::Realtime realtime,
  287. const AudioPlayHead::PositionInfo& positionInfo) noexcept override
  288. {
  289. const auto lock = lockInterface.getProcessingLock();
  290. if (! lock.isLocked())
  291. return true;
  292. const auto numSamples = buffer.getNumSamples();
  293. jassert (numSamples <= maximumSamplesPerBlock);
  294. jassert (numChannels == buffer.getNumChannels());
  295. jassert (realtime == AudioProcessor::Realtime::no || useBufferedAudioSourceReader);
  296. const auto timeInSamples = positionInfo.getTimeInSamples().orFallback (0);
  297. const auto isPlaying = positionInfo.getIsPlaying();
  298. bool success = true;
  299. bool didRenderAnyRegion = false;
  300. if (isPlaying)
  301. {
  302. const auto blockRange = Range<int64>::withStartAndLength (timeInSamples, numSamples);
  303. for (const auto& playbackRegion : getPlaybackRegions())
  304. {
  305. // Evaluate region borders in song time, calculate sample range to render in song time.
  306. // Note that this example does not use head- or tailtime, so the includeHeadAndTail
  307. // parameter is set to false here - this might need to be adjusted in actual plug-ins.
  308. const auto playbackSampleRange = playbackRegion->getSampleRange (sampleRate, ARAPlaybackRegion::IncludeHeadAndTail::no);
  309. auto renderRange = blockRange.getIntersectionWith (playbackSampleRange);
  310. if (renderRange.isEmpty())
  311. continue;
  312. // Evaluate region borders in modification/source time and calculate offset between
  313. // song and source samples, then clip song samples accordingly
  314. // (if an actual plug-in supports time stretching, this must be taken into account here).
  315. Range<int64> modificationSampleRange { playbackRegion->getStartInAudioModificationSamples(),
  316. playbackRegion->getEndInAudioModificationSamples() };
  317. const auto modificationSampleOffset = modificationSampleRange.getStart() - playbackSampleRange.getStart();
  318. renderRange = renderRange.getIntersectionWith (modificationSampleRange.movedToStartAt (playbackSampleRange.getStart()));
  319. if (renderRange.isEmpty())
  320. continue;
  321. // Get the audio source for the region and find the reader for that source.
  322. // This simplified example code only produces audio if sample rate and channel count match -
  323. // a robust plug-in would need to do conversion, see ARA SDK documentation.
  324. const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  325. const auto readerIt = audioSourceReaders.find (audioSource);
  326. if (std::make_tuple (audioSource->getChannelCount(), audioSource->getSampleRate()) != std::make_tuple (numChannels, sampleRate)
  327. || (readerIt == audioSourceReaders.end()))
  328. {
  329. success = false;
  330. continue;
  331. }
  332. auto& reader = readerIt->second;
  333. reader.setReadTimeout (realtime == AudioProcessor::Realtime::no ? 100 : 0);
  334. // Calculate buffer offsets.
  335. const int numSamplesToRead = (int) renderRange.getLength();
  336. const int startInBuffer = (int) (renderRange.getStart() - blockRange.getStart());
  337. auto startInSource = renderRange.getStart() + modificationSampleOffset;
  338. // Read samples:
  339. // first region can write directly into output, later regions need to use local buffer.
  340. auto& readBuffer = (didRenderAnyRegion) ? *tempBuffer : buffer;
  341. if (! reader.get()->read (&readBuffer, startInBuffer, numSamplesToRead, startInSource, true, true))
  342. {
  343. success = false;
  344. continue;
  345. }
  346. // Apply dim if enabled
  347. if (playbackRegion->getAudioModification<ARADemoPluginAudioModification>()->isDimmed())
  348. readBuffer.applyGain (startInBuffer, numSamplesToRead, 0.25f); // dim by about 12 dB
  349. // Mix output of all regions
  350. if (didRenderAnyRegion)
  351. {
  352. // Mix local buffer into the output buffer.
  353. for (int c = 0; c < numChannels; ++c)
  354. buffer.addFrom (c, startInBuffer, *tempBuffer, c, startInBuffer, numSamplesToRead);
  355. }
  356. else
  357. {
  358. // Clear any excess at start or end of the region.
  359. if (startInBuffer != 0)
  360. buffer.clear (0, startInBuffer);
  361. const int endInBuffer = startInBuffer + numSamplesToRead;
  362. const int remainingSamples = numSamples - endInBuffer;
  363. if (remainingSamples != 0)
  364. buffer.clear (endInBuffer, remainingSamples);
  365. didRenderAnyRegion = true;
  366. }
  367. }
  368. }
  369. // If no playback or no region did intersect, clear buffer now.
  370. if (! didRenderAnyRegion)
  371. buffer.clear();
  372. return success;
  373. }
  374. using ARAPlaybackRenderer::processBlock;
  375. private:
  376. //==============================================================================
  377. ProcessingLockInterface& lockInterface;
  378. SharedResourcePointer<SharedTimeSliceThread> sharedTimesliceThread;
  379. std::map<ARAAudioSource*, PossiblyBufferedReader> audioSourceReaders;
  380. bool useBufferedAudioSourceReader = true;
  381. int numChannels = 2;
  382. double sampleRate = 48000.0;
  383. int maximumSamplesPerBlock = 128;
  384. std::unique_ptr<AudioBuffer<float>> tempBuffer;
  385. };
  386. class EditorRenderer final : public ARAEditorRenderer,
  387. private ARARegionSequence::Listener
  388. {
  389. public:
  390. EditorRenderer (ARA::PlugIn::DocumentController* documentController,
  391. const PreviewState* previewStateIn,
  392. ProcessingLockInterface& lockInterfaceIn)
  393. : ARAEditorRenderer (documentController),
  394. lockInterface (lockInterfaceIn),
  395. previewState (previewStateIn)
  396. {
  397. jassert (previewState != nullptr);
  398. }
  399. ~EditorRenderer() override
  400. {
  401. for (const auto& rs : regionSequences)
  402. rs->removeListener (this);
  403. }
  404. void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion*) override
  405. {
  406. asyncConfigCallback.startConfigure();
  407. }
  408. void didAddRegionSequence (ARA::PlugIn::RegionSequence* rs) noexcept override
  409. {
  410. auto* sequence = static_cast<ARARegionSequence*> (rs);
  411. sequence->addListener (this);
  412. regionSequences.insert (sequence);
  413. asyncConfigCallback.startConfigure();
  414. }
  415. void willRemoveRegionSequence (ARA::PlugIn::RegionSequence* rs) noexcept override
  416. {
  417. auto* rsToRemove = static_cast<ARARegionSequence*> (rs);
  418. rsToRemove->removeListener (this);
  419. regionSequences.erase (rsToRemove);
  420. }
  421. void didAddPlaybackRegion (ARA::PlugIn::PlaybackRegion*) noexcept override
  422. {
  423. asyncConfigCallback.startConfigure();
  424. }
  425. /* An ARA host could be using either the `addPlaybackRegion()` or `addRegionSequence()` interface
  426. so we need to check the other side of both.
  427. The callback must have a signature of `bool (ARAPlaybackRegion*)`
  428. */
  429. template <typename Callback>
  430. void forEachPlaybackRegion (Callback&& cb)
  431. {
  432. for (const auto& playbackRegion : getPlaybackRegions())
  433. if (! cb (playbackRegion))
  434. return;
  435. for (const auto& regionSequence : getRegionSequences())
  436. for (const auto& playbackRegion : regionSequence->getPlaybackRegions())
  437. if (! cb (playbackRegion))
  438. return;
  439. }
  440. void prepareToPlay (double sampleRateIn,
  441. int maximumExpectedSamplesPerBlock,
  442. int numChannels,
  443. AudioProcessor::ProcessingPrecision,
  444. AlwaysNonRealtime alwaysNonRealtime) override
  445. {
  446. sampleRate = sampleRateIn;
  447. previewBuffer = std::make_unique<AudioBuffer<float>> (numChannels, (int) (2 * sampleRateIn));
  448. ignoreUnused (maximumExpectedSamplesPerBlock, alwaysNonRealtime);
  449. }
  450. void releaseResources() override
  451. {
  452. audioSourceReaders.clear();
  453. }
  454. void reset() override
  455. {
  456. previewBuffer->clear();
  457. }
  458. bool processBlock (AudioBuffer<float>& buffer,
  459. AudioProcessor::Realtime realtime,
  460. const AudioPlayHead::PositionInfo& positionInfo) noexcept override
  461. {
  462. ignoreUnused (realtime);
  463. const auto lock = lockInterface.getProcessingLock();
  464. if (! lock.isLocked())
  465. return true;
  466. return asyncConfigCallback.withLock ([&] (bool locked)
  467. {
  468. if (! locked)
  469. return true;
  470. const auto fadeOutIfNecessary = [this, &buffer]
  471. {
  472. if (std::exchange (wasPreviewing, false))
  473. {
  474. previewLooper.writeInto (buffer);
  475. const auto fadeOutStart = std::max (0, buffer.getNumSamples() - 50);
  476. buffer.applyGainRamp (fadeOutStart, buffer.getNumSamples() - fadeOutStart, 1.0f, 0.0f);
  477. }
  478. };
  479. if (positionInfo.getIsPlaying())
  480. {
  481. fadeOutIfNecessary();
  482. return true;
  483. }
  484. if (const auto previewedRegion = previewState->previewedRegion.load())
  485. {
  486. const auto regionIsAssignedToEditor = [&]()
  487. {
  488. bool regionIsAssigned = false;
  489. forEachPlaybackRegion ([&previewedRegion, &regionIsAssigned] (const auto& region)
  490. {
  491. if (region == previewedRegion)
  492. {
  493. regionIsAssigned = true;
  494. return false;
  495. }
  496. return true;
  497. });
  498. return regionIsAssigned;
  499. }();
  500. if (regionIsAssignedToEditor)
  501. {
  502. const auto previewTime = previewState->previewTime.load();
  503. const auto previewDimmed = previewedRegion->getAudioModification<ARADemoPluginAudioModification>()
  504. ->isDimmed();
  505. if (! exactlyEqual (lastPreviewTime, previewTime)
  506. || ! exactlyEqual (lastPlaybackRegion, previewedRegion)
  507. || ! exactlyEqual (lastPreviewDimmed, previewDimmed))
  508. {
  509. Range<double> previewRangeInPlaybackTime { previewTime - 0.25, previewTime + 0.25 };
  510. previewBuffer->clear();
  511. const auto rangeInOutput = readPlaybackRangeIntoBuffer (previewRangeInPlaybackTime,
  512. previewedRegion,
  513. *previewBuffer,
  514. [this] (auto* source) -> auto*
  515. {
  516. const auto iter = audioSourceReaders.find (source);
  517. return iter != audioSourceReaders.end() ? iter->second.get() : nullptr;
  518. });
  519. if (rangeInOutput)
  520. {
  521. lastPreviewTime = previewTime;
  522. lastPlaybackRegion = previewedRegion;
  523. lastPreviewDimmed = previewDimmed;
  524. previewLooper = Looper (previewBuffer.get(), *rangeInOutput);
  525. }
  526. }
  527. else
  528. {
  529. previewLooper.writeInto (buffer);
  530. if (! std::exchange (wasPreviewing, true))
  531. {
  532. const auto fadeInLength = std::min (50, buffer.getNumSamples());
  533. buffer.applyGainRamp (0, fadeInLength, 0.0f, 1.0f);
  534. }
  535. }
  536. }
  537. }
  538. else
  539. {
  540. fadeOutIfNecessary();
  541. }
  542. return true;
  543. });
  544. }
  545. using ARAEditorRenderer::processBlock;
  546. private:
  547. void configure()
  548. {
  549. forEachPlaybackRegion ([this, maximumExpectedSamplesPerBlock = 1000] (const auto& playbackRegion)
  550. {
  551. const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  552. if (audioSourceReaders.find (audioSource) == audioSourceReaders.end())
  553. {
  554. audioSourceReaders[audioSource] = std::make_unique<BufferingAudioReader> (
  555. new ARAAudioSourceReader (playbackRegion->getAudioModification()->getAudioSource()),
  556. *timeSliceThread,
  557. std::max (4 * maximumExpectedSamplesPerBlock, (int) sampleRate));
  558. }
  559. return true;
  560. });
  561. }
  562. ProcessingLockInterface& lockInterface;
  563. const PreviewState* previewState = nullptr;
  564. AsyncConfigurationCallback asyncConfigCallback { [this] { configure(); } };
  565. double lastPreviewTime = 0.0;
  566. ARAPlaybackRegion* lastPlaybackRegion = nullptr;
  567. bool lastPreviewDimmed = false;
  568. bool wasPreviewing = false;
  569. std::unique_ptr<AudioBuffer<float>> previewBuffer;
  570. Looper previewLooper;
  571. double sampleRate = 48000.0;
  572. SharedResourcePointer<SharedTimeSliceThread> timeSliceThread;
  573. std::map<ARAAudioSource*, std::unique_ptr<BufferingAudioReader>> audioSourceReaders;
  574. std::set<ARARegionSequence*> regionSequences;
  575. };
  576. //==============================================================================
  577. class ARADemoPluginDocumentControllerSpecialisation final : public ARADocumentControllerSpecialisation,
  578. private ProcessingLockInterface
  579. {
  580. public:
  581. using ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation;
  582. PreviewState previewState;
  583. protected:
  584. void willBeginEditing (ARADocument*) override
  585. {
  586. processBlockLock.enterWrite();
  587. }
  588. void didEndEditing (ARADocument*) override
  589. {
  590. processBlockLock.exitWrite();
  591. }
  592. ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource,
  593. ARA::ARAAudioModificationHostRef hostRef,
  594. const ARAAudioModification* optionalModificationToClone) noexcept override
  595. {
  596. return new ARADemoPluginAudioModification (audioSource,
  597. hostRef,
  598. static_cast<const ARADemoPluginAudioModification*> (optionalModificationToClone));
  599. }
  600. ARAPlaybackRenderer* doCreatePlaybackRenderer() noexcept override
  601. {
  602. return new PlaybackRenderer (getDocumentController(), *this);
  603. }
  604. EditorRenderer* doCreateEditorRenderer() noexcept override
  605. {
  606. return new EditorRenderer (getDocumentController(), &previewState, *this);
  607. }
  608. bool doRestoreObjectsFromStream (ARAInputStream& input,
  609. const ARARestoreObjectsFilter* filter) noexcept override
  610. {
  611. // Start reading data from the archive, starting with the number of audio modifications in the archive
  612. const auto numAudioModifications = input.readInt64();
  613. // Loop over stored audio modification data
  614. for (int64 i = 0; i < numAudioModifications; ++i)
  615. {
  616. const auto progressVal = (float) i / (float) numAudioModifications;
  617. getDocumentController()->getHostArchivingController()->notifyDocumentUnarchivingProgress (progressVal);
  618. // Read audio modification persistent ID and analysis result from archive
  619. const String persistentID = input.readString();
  620. const bool dimmed = input.readBool();
  621. // Find audio modification to restore the state to (drop state if not to be loaded)
  622. auto audioModification = filter->getAudioModificationToRestoreStateWithID<ARADemoPluginAudioModification> (persistentID.getCharPointer());
  623. if (audioModification == nullptr)
  624. continue;
  625. const bool dimChanged = (dimmed != audioModification->isDimmed());
  626. audioModification->setDimmed (dimmed);
  627. // If the dim state changed, send a sample content change notification without notifying the host
  628. if (dimChanged)
  629. {
  630. audioModification->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), false);
  631. for (auto playbackRegion : audioModification->getPlaybackRegions())
  632. playbackRegion->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), false);
  633. }
  634. }
  635. getDocumentController()->getHostArchivingController()->notifyDocumentUnarchivingProgress (1.0f);
  636. return ! input.failed();
  637. }
  638. bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) noexcept override
  639. {
  640. // This example implementation only deals with audio modification states
  641. const auto& audioModificationsToPersist { filter->getAudioModificationsToStore<ARADemoPluginAudioModification>() };
  642. const auto reportProgress = [archivingController = getDocumentController()->getHostArchivingController()] (float p)
  643. {
  644. archivingController->notifyDocumentArchivingProgress (p);
  645. };
  646. const ScopeGuard scope { [&reportProgress] { reportProgress (1.0f); } };
  647. // Write the number of audio modifications we are persisting
  648. const auto numAudioModifications = audioModificationsToPersist.size();
  649. if (! output.writeInt64 ((int64) numAudioModifications))
  650. return false;
  651. // For each audio modification to persist, persist its ID followed by whether it's dimmed
  652. for (size_t i = 0; i < numAudioModifications; ++i)
  653. {
  654. // Write persistent ID and dim state
  655. if (! output.writeString (audioModificationsToPersist[i]->getPersistentID()))
  656. return false;
  657. if (! output.writeBool (audioModificationsToPersist[i]->isDimmed()))
  658. return false;
  659. const auto progressVal = (float) i / (float) numAudioModifications;
  660. reportProgress (progressVal);
  661. }
  662. return true;
  663. }
  664. private:
  665. ScopedTryReadLock getProcessingLock() override
  666. {
  667. return ScopedTryReadLock { processBlockLock };
  668. }
  669. ReadWriteLock processBlockLock;
  670. };
  671. struct PlayHeadState
  672. {
  673. void update (const Optional<AudioPlayHead::PositionInfo>& info)
  674. {
  675. if (info.hasValue())
  676. {
  677. isPlaying.store (info->getIsPlaying(), std::memory_order_relaxed);
  678. timeInSeconds.store (info->getTimeInSeconds().orFallback (0), std::memory_order_relaxed);
  679. isLooping.store (info->getIsLooping(), std::memory_order_relaxed);
  680. const auto loopPoints = info->getLoopPoints();
  681. if (loopPoints.hasValue())
  682. {
  683. loopPpqStart = loopPoints->ppqStart;
  684. loopPpqEnd = loopPoints->ppqEnd;
  685. }
  686. }
  687. else
  688. {
  689. isPlaying.store (false, std::memory_order_relaxed);
  690. isLooping.store (false, std::memory_order_relaxed);
  691. }
  692. }
  693. std::atomic<bool> isPlaying { false },
  694. isLooping { false };
  695. std::atomic<double> timeInSeconds { 0.0 },
  696. loopPpqStart { 0.0 },
  697. loopPpqEnd { 0.0 };
  698. };
  699. //==============================================================================
  700. class ARADemoPluginAudioProcessorImpl : public AudioProcessor,
  701. public AudioProcessorARAExtension
  702. {
  703. public:
  704. //==============================================================================
  705. ARADemoPluginAudioProcessorImpl()
  706. : AudioProcessor (getBusesProperties())
  707. {}
  708. ~ARADemoPluginAudioProcessorImpl() override = default;
  709. //==============================================================================
  710. void prepareToPlay (double sampleRate, int samplesPerBlock) override
  711. {
  712. playHeadState.update (nullopt);
  713. prepareToPlayForARA (sampleRate, samplesPerBlock, getMainBusNumOutputChannels(), getProcessingPrecision());
  714. }
  715. void releaseResources() override
  716. {
  717. playHeadState.update (nullopt);
  718. releaseResourcesForARA();
  719. }
  720. bool isBusesLayoutSupported (const BusesLayout& layouts) const override
  721. {
  722. if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
  723. && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
  724. return false;
  725. return true;
  726. }
  727. void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
  728. {
  729. ignoreUnused (midiMessages);
  730. ScopedNoDenormals noDenormals;
  731. auto* audioPlayHead = getPlayHead();
  732. playHeadState.update (audioPlayHead->getPosition());
  733. if (! processBlockForARA (buffer, isRealtime(), audioPlayHead))
  734. processBlockBypassed (buffer, midiMessages);
  735. }
  736. using AudioProcessor::processBlock;
  737. //==============================================================================
  738. const String getName() const override { return "ARAPluginDemo"; }
  739. bool acceptsMidi() const override { return true; }
  740. bool producesMidi() const override { return true; }
  741. double getTailLengthSeconds() const override
  742. {
  743. double tail;
  744. if (getTailLengthSecondsForARA (tail))
  745. return tail;
  746. return 0.0;
  747. }
  748. //==============================================================================
  749. int getNumPrograms() override { return 0; }
  750. int getCurrentProgram() override { return 0; }
  751. void setCurrentProgram (int) override {}
  752. const String getProgramName (int) override { return "None"; }
  753. void changeProgramName (int, const String&) override {}
  754. //==============================================================================
  755. void getStateInformation (MemoryBlock&) override {}
  756. void setStateInformation (const void*, int) override {}
  757. PlayHeadState playHeadState;
  758. private:
  759. //==============================================================================
  760. static BusesProperties getBusesProperties()
  761. {
  762. return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
  763. .withOutput ("Output", AudioChannelSet::stereo(), true);
  764. }
  765. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginAudioProcessorImpl)
  766. };
  767. //==============================================================================
  768. class TimeToViewScaling
  769. {
  770. public:
  771. class Listener
  772. {
  773. public:
  774. virtual ~Listener() = default;
  775. virtual void zoomLevelChanged (double newPixelPerSecond) = 0;
  776. };
  777. void addListener (Listener* l) { listeners.add (l); }
  778. void removeListener (Listener* l) { listeners.remove (l); }
  779. TimeToViewScaling() = default;
  780. void zoom (double factor)
  781. {
  782. zoomLevelPixelPerSecond = jlimit (minimumZoom, minimumZoom * 32, zoomLevelPixelPerSecond * factor);
  783. setZoomLevel (zoomLevelPixelPerSecond);
  784. }
  785. void setZoomLevel (double pixelPerSecond)
  786. {
  787. zoomLevelPixelPerSecond = pixelPerSecond;
  788. listeners.call ([this] (Listener& l) { l.zoomLevelChanged (zoomLevelPixelPerSecond); });
  789. }
  790. int getXForTime (double time) const
  791. {
  792. return roundToInt (time * zoomLevelPixelPerSecond);
  793. }
  794. double getTimeForX (int x) const
  795. {
  796. return x / zoomLevelPixelPerSecond;
  797. }
  798. private:
  799. static constexpr auto minimumZoom = 10.0;
  800. double zoomLevelPixelPerSecond = minimumZoom * 4;
  801. ListenerList<Listener> listeners;
  802. };
  803. class RulersView final : public Component,
  804. public SettableTooltipClient,
  805. private Timer,
  806. private TimeToViewScaling::Listener,
  807. private ARAMusicalContext::Listener
  808. {
  809. public:
  810. class CycleMarkerComponent final : public Component
  811. {
  812. void paint (Graphics& g) override
  813. {
  814. g.setColour (Colours::yellow.darker (0.2f));
  815. const auto bounds = getLocalBounds().toFloat();
  816. g.drawRoundedRectangle (bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), 6.0f, 2.0f);
  817. }
  818. };
  819. RulersView (PlayHeadState& playHeadStateIn, TimeToViewScaling& timeToViewScalingIn, ARADocument& document)
  820. : playHeadState (playHeadStateIn), timeToViewScaling (timeToViewScalingIn), araDocument (document)
  821. {
  822. timeToViewScaling.addListener (this);
  823. addChildComponent (cycleMarker);
  824. cycleMarker.setInterceptsMouseClicks (false, false);
  825. setTooltip ("Double-click to start playback, click to stop playback or to reposition, drag horizontal range to set cycle.");
  826. startTimerHz (30);
  827. }
  828. ~RulersView() override
  829. {
  830. stopTimer();
  831. timeToViewScaling.removeListener (this);
  832. selectMusicalContext (nullptr);
  833. }
  834. void paint (Graphics& g) override
  835. {
  836. auto drawBounds = g.getClipBounds();
  837. const auto drawStartTime = timeToViewScaling.getTimeForX (drawBounds.getX());
  838. const auto drawEndTime = timeToViewScaling.getTimeForX (drawBounds.getRight());
  839. const auto bounds = getLocalBounds();
  840. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  841. g.fillRect (bounds);
  842. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting());
  843. g.drawRect (bounds);
  844. const auto rulerHeight = bounds.getHeight() / 3;
  845. g.drawRect (drawBounds.getX(), rulerHeight, drawBounds.getRight(), rulerHeight);
  846. g.setFont (Font (12.0f));
  847. const int lightLineWidth = 1;
  848. const int heavyLineWidth = 3;
  849. if (selectedMusicalContext != nullptr)
  850. {
  851. const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeTempoEntries> tempoReader (selectedMusicalContext);
  852. const ARA::TempoConverter<decltype (tempoReader)> tempoConverter (tempoReader);
  853. // chord ruler: one rect per chord, skipping empty "no chords"
  854. const auto chordBounds = drawBounds.removeFromTop (rulerHeight);
  855. const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeSheetChords> chordsReader (selectedMusicalContext);
  856. if (tempoReader && chordsReader)
  857. {
  858. const ARA::ChordInterpreter interpreter (true);
  859. for (auto itChord = chordsReader.begin(); itChord != chordsReader.end(); ++itChord)
  860. {
  861. if (interpreter.isNoChord (*itChord))
  862. continue;
  863. const auto chordStartTime = (itChord == chordsReader.begin()) ? 0 : tempoConverter.getTimeForQuarter (itChord->position);
  864. if (chordStartTime >= drawEndTime)
  865. break;
  866. auto chordRect = chordBounds;
  867. chordRect.setLeft (timeToViewScaling.getXForTime (chordStartTime));
  868. if (std::next (itChord) != chordsReader.end())
  869. {
  870. const auto nextChordStartTime = tempoConverter.getTimeForQuarter (std::next (itChord)->position);
  871. if (nextChordStartTime < drawStartTime)
  872. continue;
  873. chordRect.setRight (timeToViewScaling.getXForTime (nextChordStartTime));
  874. }
  875. g.drawRect (chordRect);
  876. g.drawText (convertARAString (interpreter.getNameForChord (*itChord).c_str()),
  877. chordRect.withTrimmedLeft (2),
  878. Justification::centredLeft);
  879. }
  880. }
  881. // beat ruler: evaluates tempo and bar signatures to draw a line for each beat
  882. const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeBarSignatures> barSignaturesReader (selectedMusicalContext);
  883. if (barSignaturesReader)
  884. {
  885. const ARA::BarSignaturesConverter<decltype (barSignaturesReader)> barSignaturesConverter (barSignaturesReader);
  886. const double beatStart = barSignaturesConverter.getBeatForQuarter (tempoConverter.getQuarterForTime (drawStartTime));
  887. const double beatEnd = barSignaturesConverter.getBeatForQuarter (tempoConverter.getQuarterForTime (drawEndTime));
  888. const int endBeat = roundToInt (std::floor (beatEnd));
  889. RectangleList<int> rects;
  890. for (int beat = roundToInt (std::ceil (beatStart)); beat <= endBeat; ++beat)
  891. {
  892. const auto quarterPos = barSignaturesConverter.getQuarterForBeat (beat);
  893. const int x = timeToViewScaling.getXForTime (tempoConverter.getTimeForQuarter (quarterPos));
  894. const auto barSignature = barSignaturesConverter.getBarSignatureForQuarter (quarterPos);
  895. const int lineWidth = (approximatelyEqual (quarterPos, barSignature.position)) ? heavyLineWidth : lightLineWidth;
  896. const int beatsSinceBarStart = roundToInt (barSignaturesConverter.getBeatDistanceFromBarStartForQuarter (quarterPos));
  897. const int lineHeight = (beatsSinceBarStart == 0) ? rulerHeight : rulerHeight / 2;
  898. rects.addWithoutMerging (Rectangle<int> (x - lineWidth / 2, 2 * rulerHeight - lineHeight, lineWidth, lineHeight));
  899. }
  900. g.fillRectList (rects);
  901. }
  902. }
  903. // time ruler: one tick for each second
  904. {
  905. RectangleList<int> rects;
  906. for (auto time = std::floor (drawStartTime); time <= drawEndTime; time += 1.0)
  907. {
  908. const int lineWidth = (std::fmod (time, 60.0) <= 0.001) ? heavyLineWidth : lightLineWidth;
  909. const int lineHeight = (std::fmod (time, 10.0) <= 0.001) ? rulerHeight : rulerHeight / 2;
  910. rects.addWithoutMerging (Rectangle<int> (timeToViewScaling.getXForTime (time) - lineWidth / 2,
  911. bounds.getHeight() - lineHeight,
  912. lineWidth,
  913. lineHeight));
  914. }
  915. g.fillRectList (rects);
  916. }
  917. }
  918. void mouseDrag (const MouseEvent& m) override
  919. {
  920. isDraggingCycle = true;
  921. auto cycleRect = getBounds();
  922. cycleRect.setLeft (jmin (m.getMouseDownX(), m.x));
  923. cycleRect.setRight (jmax (m.getMouseDownX(), m.x));
  924. cycleMarker.setBounds (cycleRect);
  925. }
  926. void mouseUp (const MouseEvent& m) override
  927. {
  928. auto playbackController = araDocument.getDocumentController()->getHostPlaybackController();
  929. if (playbackController != nullptr)
  930. {
  931. const auto startTime = timeToViewScaling.getTimeForX (jmin (m.getMouseDownX(), m.x));
  932. const auto endTime = timeToViewScaling.getTimeForX (jmax (m.getMouseDownX(), m.x));
  933. if (playHeadState.isPlaying.load (std::memory_order_relaxed))
  934. playbackController->requestStopPlayback();
  935. else
  936. playbackController->requestSetPlaybackPosition (startTime);
  937. if (isDraggingCycle)
  938. playbackController->requestSetCycleRange (startTime, endTime - startTime);
  939. }
  940. isDraggingCycle = false;
  941. }
  942. void mouseDoubleClick (const MouseEvent&) override
  943. {
  944. if (auto* playbackController = araDocument.getDocumentController()->getHostPlaybackController())
  945. {
  946. if (! playHeadState.isPlaying.load (std::memory_order_relaxed))
  947. playbackController->requestStartPlayback();
  948. }
  949. }
  950. void selectMusicalContext (ARAMusicalContext* newSelectedMusicalContext)
  951. {
  952. if (auto* oldSelection = std::exchange (selectedMusicalContext, newSelectedMusicalContext);
  953. oldSelection != selectedMusicalContext)
  954. {
  955. if (oldSelection != nullptr)
  956. oldSelection->removeListener (this);
  957. if (selectedMusicalContext != nullptr)
  958. selectedMusicalContext->addListener (this);
  959. repaint();
  960. }
  961. }
  962. void zoomLevelChanged (double) override
  963. {
  964. repaint();
  965. }
  966. void doUpdateMusicalContextContent (ARAMusicalContext*, ARAContentUpdateScopes) override
  967. {
  968. repaint();
  969. }
  970. private:
  971. void updateCyclePosition()
  972. {
  973. if (selectedMusicalContext != nullptr)
  974. {
  975. const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeTempoEntries> tempoReader (selectedMusicalContext);
  976. const ARA::TempoConverter<decltype (tempoReader)> tempoConverter (tempoReader);
  977. const auto loopStartTime = tempoConverter.getTimeForQuarter (playHeadState.loopPpqStart.load (std::memory_order_relaxed));
  978. const auto loopEndTime = tempoConverter.getTimeForQuarter (playHeadState.loopPpqEnd.load (std::memory_order_relaxed));
  979. auto cycleRect = getBounds();
  980. cycleRect.setLeft (timeToViewScaling.getXForTime (loopStartTime));
  981. cycleRect.setRight (timeToViewScaling.getXForTime (loopEndTime));
  982. cycleMarker.setVisible (true);
  983. cycleMarker.setBounds (cycleRect);
  984. }
  985. else
  986. {
  987. cycleMarker.setVisible (false);
  988. }
  989. }
  990. void timerCallback() override
  991. {
  992. if (! isDraggingCycle)
  993. updateCyclePosition();
  994. }
  995. private:
  996. PlayHeadState& playHeadState;
  997. TimeToViewScaling& timeToViewScaling;
  998. ARADocument& araDocument;
  999. ARAMusicalContext* selectedMusicalContext = nullptr;
  1000. CycleMarkerComponent cycleMarker;
  1001. bool isDraggingCycle = false;
  1002. };
  1003. class RulersHeader final : public Component
  1004. {
  1005. public:
  1006. RulersHeader()
  1007. {
  1008. chordsLabel.setText ("Chords", NotificationType::dontSendNotification);
  1009. addAndMakeVisible (chordsLabel);
  1010. barsLabel.setText ("Bars", NotificationType::dontSendNotification);
  1011. addAndMakeVisible (barsLabel);
  1012. timeLabel.setText ("Time", NotificationType::dontSendNotification);
  1013. addAndMakeVisible (timeLabel);
  1014. }
  1015. void resized() override
  1016. {
  1017. auto bounds = getLocalBounds();
  1018. const auto rulerHeight = bounds.getHeight() / 3;
  1019. for (auto* label : { &chordsLabel, &barsLabel, &timeLabel })
  1020. label->setBounds (bounds.removeFromTop (rulerHeight));
  1021. }
  1022. void paint (Graphics& g) override
  1023. {
  1024. auto bounds = getLocalBounds();
  1025. const auto rulerHeight = bounds.getHeight() / 3;
  1026. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  1027. g.fillRect (bounds);
  1028. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting());
  1029. g.drawRect (bounds);
  1030. bounds.removeFromTop (rulerHeight);
  1031. g.drawRect (bounds.removeFromTop (rulerHeight));
  1032. }
  1033. private:
  1034. Label chordsLabel, barsLabel, timeLabel;
  1035. };
  1036. //==============================================================================
  1037. struct WaveformCache final : private ARAAudioSource::Listener
  1038. {
  1039. WaveformCache() : thumbnailCache (20)
  1040. {
  1041. }
  1042. ~WaveformCache() override
  1043. {
  1044. for (const auto& entry : thumbnails)
  1045. {
  1046. entry.first->removeListener (this);
  1047. }
  1048. }
  1049. //==============================================================================
  1050. void willDestroyAudioSource (ARAAudioSource* audioSource) override
  1051. {
  1052. removeAudioSource (audioSource);
  1053. }
  1054. AudioThumbnail& getOrCreateThumbnail (ARAAudioSource* audioSource)
  1055. {
  1056. const auto iter = thumbnails.find (audioSource);
  1057. if (iter != std::end (thumbnails))
  1058. return *iter->second;
  1059. auto thumb = std::make_unique<AudioThumbnail> (128, dummyManager, thumbnailCache);
  1060. auto& result = *thumb;
  1061. ++hash;
  1062. thumb->setReader (new ARAAudioSourceReader (audioSource), hash);
  1063. audioSource->addListener (this);
  1064. thumbnails.emplace (audioSource, std::move (thumb));
  1065. return result;
  1066. }
  1067. private:
  1068. void removeAudioSource (ARAAudioSource* audioSource)
  1069. {
  1070. audioSource->removeListener (this);
  1071. thumbnails.erase (audioSource);
  1072. }
  1073. int64 hash = 0;
  1074. AudioFormatManager dummyManager;
  1075. AudioThumbnailCache thumbnailCache;
  1076. std::map<ARAAudioSource*, std::unique_ptr<AudioThumbnail>> thumbnails;
  1077. };
  1078. class PlaybackRegionView final : public Component,
  1079. public ChangeListener,
  1080. public SettableTooltipClient,
  1081. private ARAAudioSource::Listener,
  1082. private ARAPlaybackRegion::Listener,
  1083. private ARAEditorView::Listener
  1084. {
  1085. public:
  1086. PlaybackRegionView (ARAEditorView& editorView, ARAPlaybackRegion& region, WaveformCache& cache)
  1087. : araEditorView (editorView), playbackRegion (region), waveformCache (cache), previewRegionOverlay (*this)
  1088. {
  1089. auto* audioSource = playbackRegion.getAudioModification()->getAudioSource();
  1090. waveformCache.getOrCreateThumbnail (audioSource).addChangeListener (this);
  1091. audioSource->addListener (this);
  1092. playbackRegion.addListener (this);
  1093. araEditorView.addListener (this);
  1094. addAndMakeVisible (previewRegionOverlay);
  1095. setTooltip ("Double-click to toggle dim state of the region, click and hold to prelisten region near click.");
  1096. }
  1097. ~PlaybackRegionView() override
  1098. {
  1099. auto* audioSource = playbackRegion.getAudioModification()->getAudioSource();
  1100. audioSource->removeListener (this);
  1101. playbackRegion.removeListener (this);
  1102. araEditorView.removeListener (this);
  1103. waveformCache.getOrCreateThumbnail (audioSource).removeChangeListener (this);
  1104. }
  1105. void mouseDown (const MouseEvent& m) override
  1106. {
  1107. const auto relativeTime = (double) m.getMouseDownX() / getLocalBounds().getWidth();
  1108. const auto previewTime = playbackRegion.getStartInPlaybackTime()
  1109. + relativeTime * playbackRegion.getDurationInPlaybackTime();
  1110. auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
  1111. previewState.previewTime.store (previewTime);
  1112. previewState.previewedRegion.store (&playbackRegion);
  1113. previewRegionOverlay.update();
  1114. }
  1115. void mouseUp (const MouseEvent&) override
  1116. {
  1117. auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
  1118. previewState.previewTime.store (0.0);
  1119. previewState.previewedRegion.store (nullptr);
  1120. previewRegionOverlay.update();
  1121. }
  1122. void mouseDoubleClick (const MouseEvent&) override
  1123. {
  1124. // Set the dim flag on our region's audio modification when double-clicked
  1125. auto audioModification = playbackRegion.getAudioModification<ARADemoPluginAudioModification>();
  1126. audioModification->setDimmed (! audioModification->isDimmed());
  1127. // Send a content change notification for the modification and all associated playback regions
  1128. audioModification->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), true);
  1129. for (auto region : audioModification->getPlaybackRegions())
  1130. region->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), true);
  1131. }
  1132. void changeListenerCallback (ChangeBroadcaster*) override
  1133. {
  1134. repaint();
  1135. }
  1136. void didEnableAudioSourceSamplesAccess (ARAAudioSource*, bool) override
  1137. {
  1138. repaint();
  1139. }
  1140. void willUpdatePlaybackRegionProperties (ARAPlaybackRegion*,
  1141. ARAPlaybackRegion::PropertiesPtr newProperties) override
  1142. {
  1143. if (playbackRegion.getName() != newProperties->name
  1144. || playbackRegion.getColor() != newProperties->color)
  1145. {
  1146. repaint();
  1147. }
  1148. }
  1149. void didUpdatePlaybackRegionContent (ARAPlaybackRegion*, ARAContentUpdateScopes) override
  1150. {
  1151. repaint();
  1152. }
  1153. void onNewSelection (const ARAViewSelection& viewSelection) override
  1154. {
  1155. const auto& selectedPlaybackRegions = viewSelection.getPlaybackRegions();
  1156. const bool selected = std::find (selectedPlaybackRegions.begin(), selectedPlaybackRegions.end(), &playbackRegion) != selectedPlaybackRegions.end();
  1157. if (selected != isSelected)
  1158. {
  1159. isSelected = selected;
  1160. repaint();
  1161. }
  1162. }
  1163. void paint (Graphics& g) override
  1164. {
  1165. g.fillAll (convertOptionalARAColour (playbackRegion.getEffectiveColor(), Colours::black));
  1166. const auto* audioModification = playbackRegion.getAudioModification<ARADemoPluginAudioModification>();
  1167. g.setColour (audioModification->isDimmed() ? Colours::darkgrey.darker() : Colours::darkgrey.brighter());
  1168. if (audioModification->getAudioSource()->isSampleAccessEnabled())
  1169. {
  1170. auto& thumbnail = waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource());
  1171. thumbnail.drawChannels (g,
  1172. getLocalBounds(),
  1173. playbackRegion.getStartInAudioModificationTime(),
  1174. playbackRegion.getEndInAudioModificationTime(),
  1175. 1.0f);
  1176. }
  1177. else
  1178. {
  1179. g.setFont (Font (12.0f));
  1180. g.drawText ("Audio Access Disabled", getLocalBounds(), Justification::centred);
  1181. }
  1182. g.setColour (Colours::white.withMultipliedAlpha (0.9f));
  1183. g.setFont (Font (12.0f));
  1184. g.drawText (convertOptionalARAString (playbackRegion.getEffectiveName()),
  1185. getLocalBounds(),
  1186. Justification::topLeft);
  1187. if (audioModification->isDimmed())
  1188. g.drawText ("DIMMED", getLocalBounds(), Justification::bottomLeft);
  1189. g.setColour (isSelected ? Colours::white : Colours::black);
  1190. g.drawRect (getLocalBounds());
  1191. }
  1192. void resized() override
  1193. {
  1194. repaint();
  1195. }
  1196. private:
  1197. class PreviewRegionOverlay final : public Component
  1198. {
  1199. static constexpr auto previewLength = 0.5;
  1200. public:
  1201. PreviewRegionOverlay (PlaybackRegionView& ownerIn) : owner (ownerIn)
  1202. {
  1203. }
  1204. void update()
  1205. {
  1206. const auto& previewState = owner.getDocumentController()->previewState;
  1207. if (previewState.previewedRegion.load() == &owner.playbackRegion)
  1208. {
  1209. const auto previewStartTime = previewState.previewTime.load() - owner.playbackRegion.getStartInPlaybackTime();
  1210. const auto pixelPerSecond = owner.getWidth() / owner.playbackRegion.getDurationInPlaybackTime();
  1211. setBounds (roundToInt ((previewStartTime - previewLength / 2) * pixelPerSecond),
  1212. 0,
  1213. roundToInt (previewLength * pixelPerSecond),
  1214. owner.getHeight());
  1215. setVisible (true);
  1216. }
  1217. else
  1218. {
  1219. setVisible (false);
  1220. }
  1221. repaint();
  1222. }
  1223. void paint (Graphics& g) override
  1224. {
  1225. g.setColour (Colours::yellow.withAlpha (0.5f));
  1226. g.fillRect (getLocalBounds());
  1227. }
  1228. private:
  1229. PlaybackRegionView& owner;
  1230. };
  1231. ARADemoPluginDocumentControllerSpecialisation* getDocumentController() const
  1232. {
  1233. return ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController());
  1234. }
  1235. ARAEditorView& araEditorView;
  1236. ARAPlaybackRegion& playbackRegion;
  1237. WaveformCache& waveformCache;
  1238. PreviewRegionOverlay previewRegionOverlay;
  1239. bool isSelected = false;
  1240. };
  1241. class RegionSequenceView final : public Component,
  1242. public ChangeBroadcaster,
  1243. private TimeToViewScaling::Listener,
  1244. private ARARegionSequence::Listener,
  1245. private ARAPlaybackRegion::Listener
  1246. {
  1247. public:
  1248. RegionSequenceView (ARAEditorView& editorView, TimeToViewScaling& scaling, ARARegionSequence& rs, WaveformCache& cache)
  1249. : araEditorView (editorView), timeToViewScaling (scaling), regionSequence (rs), waveformCache (cache)
  1250. {
  1251. regionSequence.addListener (this);
  1252. for (auto* playbackRegion : regionSequence.getPlaybackRegions())
  1253. createAndAddPlaybackRegionView (playbackRegion);
  1254. updatePlaybackDuration();
  1255. timeToViewScaling.addListener (this);
  1256. }
  1257. ~RegionSequenceView() override
  1258. {
  1259. timeToViewScaling.removeListener (this);
  1260. regionSequence.removeListener (this);
  1261. for (const auto& it : playbackRegionViews)
  1262. it.first->removeListener (this);
  1263. }
  1264. //==============================================================================
  1265. // ARA Document change callback overrides
  1266. void willUpdateRegionSequenceProperties (ARARegionSequence*,
  1267. ARARegionSequence::PropertiesPtr newProperties) override
  1268. {
  1269. if (regionSequence.getColor() != newProperties->color)
  1270. {
  1271. for (auto& pbr : playbackRegionViews)
  1272. pbr.second->repaint();
  1273. }
  1274. }
  1275. void willRemovePlaybackRegionFromRegionSequence (ARARegionSequence*,
  1276. ARAPlaybackRegion* playbackRegion) override
  1277. {
  1278. playbackRegion->removeListener (this);
  1279. removeChildComponent (playbackRegionViews[playbackRegion].get());
  1280. playbackRegionViews.erase (playbackRegion);
  1281. updatePlaybackDuration();
  1282. }
  1283. void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion* playbackRegion) override
  1284. {
  1285. createAndAddPlaybackRegionView (playbackRegion);
  1286. updatePlaybackDuration();
  1287. }
  1288. void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) override
  1289. {
  1290. playbackRegion->removeListener (this);
  1291. removeChildComponent (playbackRegionViews[playbackRegion].get());
  1292. playbackRegionViews.erase (playbackRegion);
  1293. updatePlaybackDuration();
  1294. }
  1295. void didUpdatePlaybackRegionProperties (ARAPlaybackRegion*) override
  1296. {
  1297. updatePlaybackDuration();
  1298. }
  1299. void zoomLevelChanged (double) override
  1300. {
  1301. resized();
  1302. }
  1303. void resized() override
  1304. {
  1305. for (auto& pbr : playbackRegionViews)
  1306. {
  1307. const auto playbackRegion = pbr.first;
  1308. pbr.second->setBounds (
  1309. getLocalBounds()
  1310. .withTrimmedLeft (timeToViewScaling.getXForTime (playbackRegion->getStartInPlaybackTime()))
  1311. .withWidth (timeToViewScaling.getXForTime (playbackRegion->getDurationInPlaybackTime())));
  1312. }
  1313. }
  1314. auto getPlaybackDuration() const noexcept
  1315. {
  1316. return playbackDuration;
  1317. }
  1318. private:
  1319. void createAndAddPlaybackRegionView (ARAPlaybackRegion* playbackRegion)
  1320. {
  1321. playbackRegionViews[playbackRegion] = std::make_unique<PlaybackRegionView> (araEditorView,
  1322. *playbackRegion,
  1323. waveformCache);
  1324. playbackRegion->addListener (this);
  1325. addAndMakeVisible (*playbackRegionViews[playbackRegion]);
  1326. }
  1327. void updatePlaybackDuration()
  1328. {
  1329. const auto iter = std::max_element (
  1330. playbackRegionViews.begin(),
  1331. playbackRegionViews.end(),
  1332. [] (const auto& a, const auto& b) { return a.first->getEndInPlaybackTime() < b.first->getEndInPlaybackTime(); });
  1333. playbackDuration = iter != playbackRegionViews.end() ? iter->first->getEndInPlaybackTime()
  1334. : 0.0;
  1335. sendChangeMessage();
  1336. }
  1337. ARAEditorView& araEditorView;
  1338. TimeToViewScaling& timeToViewScaling;
  1339. ARARegionSequence& regionSequence;
  1340. WaveformCache& waveformCache;
  1341. std::unordered_map<ARAPlaybackRegion*, std::unique_ptr<PlaybackRegionView>> playbackRegionViews;
  1342. double playbackDuration = 0.0;
  1343. };
  1344. class ZoomControls final : public Component
  1345. {
  1346. public:
  1347. ZoomControls()
  1348. {
  1349. addAndMakeVisible (zoomInButton);
  1350. addAndMakeVisible (zoomOutButton);
  1351. }
  1352. void setZoomInCallback (std::function<void()> cb) { zoomInButton.onClick = std::move (cb); }
  1353. void setZoomOutCallback (std::function<void()> cb) { zoomOutButton.onClick = std::move (cb); }
  1354. void resized() override
  1355. {
  1356. FlexBox fb;
  1357. fb.justifyContent = FlexBox::JustifyContent::flexEnd;
  1358. for (auto* button : { &zoomInButton, &zoomOutButton })
  1359. fb.items.add (FlexItem (*button).withMinHeight (30.0f).withMinWidth (30.0f).withMargin ({ 5, 5, 5, 0 }));
  1360. fb.performLayout (getLocalBounds());
  1361. }
  1362. private:
  1363. TextButton zoomInButton { "+" }, zoomOutButton { "-" };
  1364. };
  1365. class PlayheadPositionLabel final : public Label,
  1366. private Timer
  1367. {
  1368. public:
  1369. PlayheadPositionLabel (PlayHeadState& playHeadStateIn)
  1370. : playHeadState (playHeadStateIn)
  1371. {
  1372. startTimerHz (30);
  1373. }
  1374. ~PlayheadPositionLabel() override
  1375. {
  1376. stopTimer();
  1377. }
  1378. void selectMusicalContext (ARAMusicalContext* newSelectedMusicalContext)
  1379. {
  1380. selectedMusicalContext = newSelectedMusicalContext;
  1381. }
  1382. private:
  1383. void timerCallback() override
  1384. {
  1385. const auto timePosition = playHeadState.timeInSeconds.load (std::memory_order_relaxed);
  1386. auto text = timeToTimecodeString (timePosition);
  1387. if (playHeadState.isPlaying.load (std::memory_order_relaxed))
  1388. text += " (playing)";
  1389. else
  1390. text += " (stopped)";
  1391. if (selectedMusicalContext != nullptr)
  1392. {
  1393. const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeTempoEntries> tempoReader (selectedMusicalContext);
  1394. const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeBarSignatures> barSignaturesReader (selectedMusicalContext);
  1395. if (tempoReader && barSignaturesReader)
  1396. {
  1397. const ARA::TempoConverter<decltype (tempoReader)> tempoConverter (tempoReader);
  1398. const ARA::BarSignaturesConverter<decltype (barSignaturesReader)> barSignaturesConverter (barSignaturesReader);
  1399. const auto quarterPosition = tempoConverter.getQuarterForTime (timePosition);
  1400. const auto barIndex = barSignaturesConverter.getBarIndexForQuarter (quarterPosition);
  1401. const auto beatDistance = barSignaturesConverter.getBeatDistanceFromBarStartForQuarter (quarterPosition);
  1402. const auto quartersPerBeat = 4.0 / (double) barSignaturesConverter.getBarSignatureForQuarter (quarterPosition).denominator;
  1403. const auto beatIndex = (int) beatDistance;
  1404. const auto tickIndex = juce::roundToInt ((beatDistance - beatIndex) * quartersPerBeat * 960.0);
  1405. text += newLine;
  1406. text += String::formatted ("bar %d | beat %d | tick %03d", (barIndex >= 0) ? barIndex + 1 : barIndex, beatIndex + 1, tickIndex + 1);
  1407. text += " - ";
  1408. const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeSheetChords> chordsReader (selectedMusicalContext);
  1409. if (chordsReader && chordsReader.getEventCount() > 0)
  1410. {
  1411. const auto begin = chordsReader.begin();
  1412. const auto end = chordsReader.end();
  1413. auto it = begin;
  1414. while (it != end && it->position <= quarterPosition)
  1415. ++it;
  1416. if (it != begin)
  1417. --it;
  1418. const ARA::ChordInterpreter interpreter (true);
  1419. text += "chord ";
  1420. text += String (interpreter.getNameForChord (*it));
  1421. }
  1422. else
  1423. {
  1424. text += "(no chords provided)";
  1425. }
  1426. }
  1427. }
  1428. setText (text, NotificationType::dontSendNotification);
  1429. }
  1430. // Copied from AudioPluginDemo.h: quick-and-dirty function to format a timecode string
  1431. static String timeToTimecodeString (double seconds)
  1432. {
  1433. auto millisecs = roundToInt (seconds * 1000.0);
  1434. auto absMillisecs = std::abs (millisecs);
  1435. return String::formatted ("%02d:%02d:%02d.%03d",
  1436. millisecs / 3600000,
  1437. (absMillisecs / 60000) % 60,
  1438. (absMillisecs / 1000) % 60,
  1439. absMillisecs % 1000);
  1440. }
  1441. PlayHeadState& playHeadState;
  1442. ARAMusicalContext* selectedMusicalContext = nullptr;
  1443. };
  1444. class TrackHeader final : public Component,
  1445. private ARARegionSequence::Listener,
  1446. private ARAEditorView::Listener
  1447. {
  1448. public:
  1449. TrackHeader (ARAEditorView& editorView, ARARegionSequence& regionSequenceIn)
  1450. : araEditorView (editorView), regionSequence (regionSequenceIn)
  1451. {
  1452. updateTrackName (regionSequence.getName());
  1453. onNewSelection (araEditorView.getViewSelection());
  1454. addAndMakeVisible (trackNameLabel);
  1455. regionSequence.addListener (this);
  1456. araEditorView.addListener (this);
  1457. }
  1458. ~TrackHeader() override
  1459. {
  1460. araEditorView.removeListener (this);
  1461. regionSequence.removeListener (this);
  1462. }
  1463. void willUpdateRegionSequenceProperties (ARARegionSequence*, ARARegionSequence::PropertiesPtr newProperties) override
  1464. {
  1465. if (regionSequence.getName() != newProperties->name)
  1466. updateTrackName (newProperties->name);
  1467. if (regionSequence.getColor() != newProperties->color)
  1468. repaint();
  1469. }
  1470. void resized() override
  1471. {
  1472. trackNameLabel.setBounds (getLocalBounds().reduced (2));
  1473. }
  1474. void paint (Graphics& g) override
  1475. {
  1476. const auto backgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
  1477. g.setColour (isSelected ? backgroundColour.brighter() : backgroundColour);
  1478. g.fillRoundedRectangle (getLocalBounds().reduced (2).toFloat(), 6.0f);
  1479. g.setColour (backgroundColour.contrasting());
  1480. g.drawRoundedRectangle (getLocalBounds().reduced (2).toFloat(), 6.0f, 1.0f);
  1481. if (auto colour = regionSequence.getColor())
  1482. {
  1483. g.setColour (convertARAColour (colour));
  1484. g.fillRect (getLocalBounds().removeFromTop (16).reduced (6));
  1485. g.fillRect (getLocalBounds().removeFromBottom (16).reduced (6));
  1486. }
  1487. }
  1488. void onNewSelection (const ARAViewSelection& viewSelection) override
  1489. {
  1490. const auto& selectedRegionSequences = viewSelection.getRegionSequences();
  1491. const bool selected = std::find (selectedRegionSequences.begin(), selectedRegionSequences.end(), &regionSequence) != selectedRegionSequences.end();
  1492. if (selected != isSelected)
  1493. {
  1494. isSelected = selected;
  1495. repaint();
  1496. }
  1497. }
  1498. private:
  1499. void updateTrackName (ARA::ARAUtf8String optionalName)
  1500. {
  1501. trackNameLabel.setText (optionalName ? optionalName : "No track name",
  1502. NotificationType::dontSendNotification);
  1503. }
  1504. ARAEditorView& araEditorView;
  1505. ARARegionSequence& regionSequence;
  1506. Label trackNameLabel;
  1507. bool isSelected = false;
  1508. };
  1509. constexpr auto trackHeight = 60;
  1510. class VerticalLayoutViewportContent final : public Component
  1511. {
  1512. public:
  1513. void resized() override
  1514. {
  1515. auto bounds = getLocalBounds();
  1516. for (auto* component : getChildren())
  1517. {
  1518. component->setBounds (bounds.removeFromTop (trackHeight));
  1519. component->resized();
  1520. }
  1521. }
  1522. };
  1523. class VerticalLayoutViewport final : public Viewport
  1524. {
  1525. public:
  1526. VerticalLayoutViewport()
  1527. {
  1528. setViewedComponent (&content, false);
  1529. }
  1530. void paint (Graphics& g) override
  1531. {
  1532. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter());
  1533. }
  1534. std::function<void (Rectangle<int>)> onVisibleAreaChanged;
  1535. VerticalLayoutViewportContent content;
  1536. private:
  1537. void visibleAreaChanged (const Rectangle<int>& newVisibleArea) override
  1538. {
  1539. NullCheckedInvocation::invoke (onVisibleAreaChanged, newVisibleArea);
  1540. }
  1541. };
  1542. class OverlayComponent final : public Component,
  1543. private Timer,
  1544. private TimeToViewScaling::Listener
  1545. {
  1546. public:
  1547. class PlayheadMarkerComponent final : public Component
  1548. {
  1549. void paint (Graphics& g) override { g.fillAll (Colours::yellow.darker (0.2f)); }
  1550. };
  1551. OverlayComponent (PlayHeadState& playHeadStateIn, TimeToViewScaling& timeToViewScalingIn)
  1552. : playHeadState (playHeadStateIn), timeToViewScaling (timeToViewScalingIn)
  1553. {
  1554. addChildComponent (playheadMarker);
  1555. setInterceptsMouseClicks (false, false);
  1556. startTimerHz (30);
  1557. timeToViewScaling.addListener (this);
  1558. }
  1559. ~OverlayComponent() override
  1560. {
  1561. timeToViewScaling.removeListener (this);
  1562. stopTimer();
  1563. }
  1564. void resized() override
  1565. {
  1566. updatePlayHeadPosition();
  1567. }
  1568. void setHorizontalOffset (int offset)
  1569. {
  1570. horizontalOffset = offset;
  1571. }
  1572. void setSelectedTimeRange (std::optional<ARA::ARAContentTimeRange> timeRange)
  1573. {
  1574. selectedTimeRange = timeRange;
  1575. repaint();
  1576. }
  1577. void zoomLevelChanged (double) override
  1578. {
  1579. updatePlayHeadPosition();
  1580. repaint();
  1581. }
  1582. void paint (Graphics& g) override
  1583. {
  1584. if (selectedTimeRange)
  1585. {
  1586. auto bounds = getLocalBounds();
  1587. bounds.setLeft (timeToViewScaling.getXForTime (selectedTimeRange->start));
  1588. bounds.setRight (timeToViewScaling.getXForTime (selectedTimeRange->start + selectedTimeRange->duration));
  1589. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter().withAlpha (0.3f));
  1590. g.fillRect (bounds);
  1591. g.setColour (Colours::whitesmoke.withAlpha (0.5f));
  1592. g.drawRect (bounds);
  1593. }
  1594. }
  1595. private:
  1596. void updatePlayHeadPosition()
  1597. {
  1598. if (playHeadState.isPlaying.load (std::memory_order_relaxed))
  1599. {
  1600. const auto markerX = timeToViewScaling.getXForTime (playHeadState.timeInSeconds.load (std::memory_order_relaxed));
  1601. const auto playheadLine = getLocalBounds().withTrimmedLeft ((int) (markerX - markerWidth / 2.0) - horizontalOffset)
  1602. .removeFromLeft ((int) markerWidth);
  1603. playheadMarker.setVisible (true);
  1604. playheadMarker.setBounds (playheadLine);
  1605. }
  1606. else
  1607. {
  1608. playheadMarker.setVisible (false);
  1609. }
  1610. }
  1611. void timerCallback() override
  1612. {
  1613. updatePlayHeadPosition();
  1614. }
  1615. static constexpr double markerWidth = 2.0;
  1616. PlayHeadState& playHeadState;
  1617. TimeToViewScaling& timeToViewScaling;
  1618. int horizontalOffset = 0;
  1619. std::optional<ARA::ARAContentTimeRange> selectedTimeRange;
  1620. PlayheadMarkerComponent playheadMarker;
  1621. };
  1622. class DocumentView final : public Component,
  1623. public ChangeListener,
  1624. public ARAMusicalContext::Listener,
  1625. private ARADocument::Listener,
  1626. private ARAEditorView::Listener
  1627. {
  1628. public:
  1629. DocumentView (ARAEditorView& editorView, PlayHeadState& playHeadState)
  1630. : araEditorView (editorView),
  1631. araDocument (*editorView.getDocumentController()->getDocument<ARADocument>()),
  1632. rulersView (playHeadState, timeToViewScaling, araDocument),
  1633. overlay (playHeadState, timeToViewScaling),
  1634. playheadPositionLabel (playHeadState)
  1635. {
  1636. if (araDocument.getMusicalContexts().size() > 0)
  1637. selectMusicalContext (araDocument.getMusicalContexts().front());
  1638. addAndMakeVisible (rulersHeader);
  1639. viewport.content.addAndMakeVisible (rulersView);
  1640. viewport.onVisibleAreaChanged = [this] (const auto& r)
  1641. {
  1642. viewportHeightOffset = r.getY();
  1643. overlay.setHorizontalOffset (r.getX());
  1644. resized();
  1645. };
  1646. addAndMakeVisible (viewport);
  1647. addAndMakeVisible (overlay);
  1648. addAndMakeVisible (playheadPositionLabel);
  1649. zoomControls.setZoomInCallback ([this] { zoom (2.0); });
  1650. zoomControls.setZoomOutCallback ([this] { zoom (0.5); });
  1651. addAndMakeVisible (zoomControls);
  1652. invalidateRegionSequenceViews();
  1653. araDocument.addListener (this);
  1654. araEditorView.addListener (this);
  1655. }
  1656. ~DocumentView() override
  1657. {
  1658. araEditorView.removeListener (this);
  1659. araDocument.removeListener (this);
  1660. selectMusicalContext (nullptr);
  1661. }
  1662. //==============================================================================
  1663. // ARADocument::Listener overrides
  1664. void didAddMusicalContextToDocument (ARADocument*, ARAMusicalContext* musicalContext) override
  1665. {
  1666. if (selectedMusicalContext == nullptr)
  1667. selectMusicalContext (musicalContext);
  1668. }
  1669. void willDestroyMusicalContext (ARAMusicalContext* musicalContext) override
  1670. {
  1671. if (selectedMusicalContext == musicalContext)
  1672. selectMusicalContext (nullptr);
  1673. }
  1674. void didReorderRegionSequencesInDocument (ARADocument*) override
  1675. {
  1676. invalidateRegionSequenceViews();
  1677. }
  1678. void didAddRegionSequenceToDocument (ARADocument*, ARARegionSequence*) override
  1679. {
  1680. invalidateRegionSequenceViews();
  1681. }
  1682. void willRemoveRegionSequenceFromDocument (ARADocument*, ARARegionSequence* regionSequence) override
  1683. {
  1684. removeRegionSequenceView (regionSequence);
  1685. }
  1686. void didEndEditing (ARADocument*) override
  1687. {
  1688. rebuildRegionSequenceViews();
  1689. update();
  1690. }
  1691. //==============================================================================
  1692. void changeListenerCallback (ChangeBroadcaster*) override
  1693. {
  1694. update();
  1695. }
  1696. //==============================================================================
  1697. // ARAEditorView::Listener overrides
  1698. void onNewSelection (const ARAViewSelection& viewSelection) override
  1699. {
  1700. auto getNewSelectedMusicalContext = [&viewSelection]() -> ARAMusicalContext*
  1701. {
  1702. if (! viewSelection.getRegionSequences().empty())
  1703. return viewSelection.getRegionSequences<ARARegionSequence>().front()->getMusicalContext();
  1704. else if (! viewSelection.getPlaybackRegions().empty())
  1705. return viewSelection.getPlaybackRegions<ARAPlaybackRegion>().front()->getRegionSequence()->getMusicalContext();
  1706. return nullptr;
  1707. };
  1708. if (auto* newSelectedMusicalContext = getNewSelectedMusicalContext())
  1709. if (newSelectedMusicalContext != selectedMusicalContext)
  1710. selectMusicalContext (newSelectedMusicalContext);
  1711. if (const auto timeRange = viewSelection.getTimeRange())
  1712. overlay.setSelectedTimeRange (*timeRange);
  1713. else
  1714. overlay.setSelectedTimeRange (std::nullopt);
  1715. }
  1716. void onHideRegionSequences (const std::vector<ARARegionSequence*>& regionSequences) override
  1717. {
  1718. hiddenRegionSequences = regionSequences;
  1719. invalidateRegionSequenceViews();
  1720. }
  1721. //==============================================================================
  1722. void paint (Graphics& g) override
  1723. {
  1724. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
  1725. }
  1726. void resized() override
  1727. {
  1728. auto bounds = getLocalBounds();
  1729. FlexBox fb;
  1730. fb.justifyContent = FlexBox::JustifyContent::spaceBetween;
  1731. fb.items.add (FlexItem (playheadPositionLabel).withWidth (450.0f).withMinWidth (250.0f));
  1732. fb.items.add (FlexItem (zoomControls).withMinWidth (80.0f));
  1733. fb.performLayout (bounds.removeFromBottom (40));
  1734. auto headerBounds = bounds.removeFromLeft (headerWidth);
  1735. rulersHeader.setBounds (headerBounds.removeFromTop (trackHeight));
  1736. layOutVertically (headerBounds, trackHeaders, viewportHeightOffset);
  1737. viewport.setBounds (bounds);
  1738. overlay.setBounds (bounds.reduced (1));
  1739. const auto width = jmax (timeToViewScaling.getXForTime (timelineLength), viewport.getWidth());
  1740. const auto height = (int) (regionSequenceViews.size() + 1) * trackHeight;
  1741. viewport.content.setSize (width, height);
  1742. viewport.content.resized();
  1743. }
  1744. //==============================================================================
  1745. static constexpr int headerWidth = 120;
  1746. private:
  1747. struct RegionSequenceViewKey
  1748. {
  1749. explicit RegionSequenceViewKey (ARARegionSequence* regionSequence)
  1750. : orderIndex (regionSequence->getOrderIndex()), sequence (regionSequence)
  1751. {
  1752. }
  1753. bool operator< (const RegionSequenceViewKey& other) const
  1754. {
  1755. return std::tie (orderIndex, sequence) < std::tie (other.orderIndex, other.sequence);
  1756. }
  1757. ARA::ARAInt32 orderIndex;
  1758. ARARegionSequence* sequence;
  1759. };
  1760. void selectMusicalContext (ARAMusicalContext* newSelectedMusicalContext)
  1761. {
  1762. if (auto oldContext = std::exchange (selectedMusicalContext, newSelectedMusicalContext);
  1763. oldContext != selectedMusicalContext)
  1764. {
  1765. if (oldContext != nullptr)
  1766. oldContext->removeListener (this);
  1767. if (selectedMusicalContext != nullptr)
  1768. selectedMusicalContext->addListener (this);
  1769. rulersView.selectMusicalContext (selectedMusicalContext);
  1770. playheadPositionLabel.selectMusicalContext (selectedMusicalContext);
  1771. }
  1772. }
  1773. void zoom (double factor)
  1774. {
  1775. timeToViewScaling.zoom (factor);
  1776. update();
  1777. }
  1778. template <typename T>
  1779. void layOutVertically (Rectangle<int> bounds, T& components, int verticalOffset = 0)
  1780. {
  1781. bounds = bounds.withY (bounds.getY() - verticalOffset).withHeight (bounds.getHeight() + verticalOffset);
  1782. for (auto& component : components)
  1783. {
  1784. component.second->setBounds (bounds.removeFromTop (trackHeight));
  1785. component.second->resized();
  1786. }
  1787. }
  1788. void update()
  1789. {
  1790. timelineLength = 0.0;
  1791. for (const auto& view : regionSequenceViews)
  1792. timelineLength = std::max (timelineLength, view.second->getPlaybackDuration());
  1793. resized();
  1794. }
  1795. void addTrackViews (ARARegionSequence* regionSequence)
  1796. {
  1797. const auto insertIntoMap = [] (auto& map, auto key, auto value) -> auto&
  1798. {
  1799. auto it = map.insert ({ std::move (key), std::move (value) });
  1800. return *(it.first->second);
  1801. };
  1802. auto& regionSequenceView = insertIntoMap (
  1803. regionSequenceViews,
  1804. RegionSequenceViewKey { regionSequence },
  1805. std::make_unique<RegionSequenceView> (araEditorView, timeToViewScaling, *regionSequence, waveformCache));
  1806. regionSequenceView.addChangeListener (this);
  1807. viewport.content.addAndMakeVisible (regionSequenceView);
  1808. auto& trackHeader = insertIntoMap (trackHeaders,
  1809. RegionSequenceViewKey { regionSequence },
  1810. std::make_unique<TrackHeader> (araEditorView, *regionSequence));
  1811. addAndMakeVisible (trackHeader);
  1812. }
  1813. void removeRegionSequenceView (ARARegionSequence* regionSequence)
  1814. {
  1815. const auto& view = regionSequenceViews.find (RegionSequenceViewKey { regionSequence });
  1816. if (view != regionSequenceViews.cend())
  1817. {
  1818. removeChildComponent (view->second.get());
  1819. regionSequenceViews.erase (view);
  1820. }
  1821. invalidateRegionSequenceViews();
  1822. }
  1823. void invalidateRegionSequenceViews()
  1824. {
  1825. regionSequenceViewsAreValid = false;
  1826. rebuildRegionSequenceViews();
  1827. }
  1828. void rebuildRegionSequenceViews()
  1829. {
  1830. if (! regionSequenceViewsAreValid && ! araDocument.getDocumentController()->isHostEditingDocument())
  1831. {
  1832. for (auto& view : regionSequenceViews)
  1833. removeChildComponent (view.second.get());
  1834. regionSequenceViews.clear();
  1835. for (auto& view : trackHeaders)
  1836. removeChildComponent (view.second.get());
  1837. trackHeaders.clear();
  1838. for (auto* regionSequence : araDocument.getRegionSequences())
  1839. if (std::find (hiddenRegionSequences.begin(), hiddenRegionSequences.end(), regionSequence) == hiddenRegionSequences.end())
  1840. addTrackViews (regionSequence);
  1841. update();
  1842. regionSequenceViewsAreValid = true;
  1843. }
  1844. }
  1845. ARAEditorView& araEditorView;
  1846. ARADocument& araDocument;
  1847. bool regionSequenceViewsAreValid = false;
  1848. TimeToViewScaling timeToViewScaling;
  1849. double timelineLength = 0.0;
  1850. ARAMusicalContext* selectedMusicalContext = nullptr;
  1851. std::vector<ARARegionSequence*> hiddenRegionSequences;
  1852. WaveformCache waveformCache;
  1853. std::map<RegionSequenceViewKey, std::unique_ptr<TrackHeader>> trackHeaders;
  1854. std::map<RegionSequenceViewKey, std::unique_ptr<RegionSequenceView>> regionSequenceViews;
  1855. RulersHeader rulersHeader;
  1856. RulersView rulersView;
  1857. VerticalLayoutViewport viewport;
  1858. OverlayComponent overlay;
  1859. ZoomControls zoomControls;
  1860. PlayheadPositionLabel playheadPositionLabel;
  1861. TooltipWindow tooltip;
  1862. int viewportHeightOffset = 0;
  1863. };
  1864. class ARADemoPluginProcessorEditor final : public AudioProcessorEditor,
  1865. public AudioProcessorEditorARAExtension
  1866. {
  1867. public:
  1868. explicit ARADemoPluginProcessorEditor (ARADemoPluginAudioProcessorImpl& p)
  1869. : AudioProcessorEditor (&p),
  1870. AudioProcessorEditorARAExtension (&p)
  1871. {
  1872. if (auto* editorView = getARAEditorView())
  1873. documentView = std::make_unique<DocumentView> (*editorView, p.playHeadState);
  1874. addAndMakeVisible (documentView.get());
  1875. // ARA requires that plugin editors are resizable to support tight integration
  1876. // into the host UI
  1877. setResizable (true, false);
  1878. setSize (800, 300);
  1879. }
  1880. //==============================================================================
  1881. void paint (Graphics& g) override
  1882. {
  1883. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  1884. if (! isARAEditorView())
  1885. {
  1886. g.setColour (Colours::white);
  1887. g.setFont (15.0f);
  1888. g.drawFittedText ("ARA host isn't detected. This plugin only supports ARA mode",
  1889. getLocalBounds(),
  1890. Justification::centred,
  1891. 1);
  1892. }
  1893. }
  1894. void resized() override
  1895. {
  1896. if (documentView != nullptr)
  1897. documentView->setBounds (getLocalBounds());
  1898. }
  1899. private:
  1900. std::unique_ptr<Component> documentView;
  1901. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginProcessorEditor)
  1902. };
  1903. class ARADemoPluginAudioProcessor final : public ARADemoPluginAudioProcessorImpl
  1904. {
  1905. public:
  1906. bool hasEditor() const override { return true; }
  1907. AudioProcessorEditor* createEditor() override { return new ARADemoPluginProcessorEditor (*this); }
  1908. };