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.

1450 lines
51KB

  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. //==============================================================================
  38. struct PreviewState
  39. {
  40. std::atomic<double> previewTime { 0.0 };
  41. std::atomic<ARAPlaybackRegion*> previewedRegion { nullptr };
  42. };
  43. class SharedTimeSliceThread : public TimeSliceThread
  44. {
  45. public:
  46. SharedTimeSliceThread()
  47. : TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread")
  48. {
  49. startThread (7); // Above default priority so playback is fluent, but below realtime
  50. }
  51. };
  52. class AsyncConfigurationCallback : private AsyncUpdater
  53. {
  54. public:
  55. explicit AsyncConfigurationCallback (std::function<void()> callbackIn)
  56. : callback (std::move (callbackIn)) {}
  57. ~AsyncConfigurationCallback() override { cancelPendingUpdate(); }
  58. template <typename RequiresLock>
  59. auto withLock (RequiresLock&& fn)
  60. {
  61. const SpinLock::ScopedTryLockType scope (processingFlag);
  62. return fn (scope.isLocked());
  63. }
  64. void startConfigure() { triggerAsyncUpdate(); }
  65. private:
  66. void handleAsyncUpdate() override
  67. {
  68. const SpinLock::ScopedLockType scope (processingFlag);
  69. callback();
  70. }
  71. std::function<void()> callback;
  72. SpinLock processingFlag;
  73. };
  74. class Looper
  75. {
  76. public:
  77. Looper() : inputBuffer (nullptr), pos (loopRange.getStart())
  78. {
  79. }
  80. Looper (const AudioBuffer<float>* buffer, Range<int64> range)
  81. : inputBuffer (buffer), loopRange (range), pos (range.getStart())
  82. {
  83. }
  84. void writeInto (AudioBuffer<float>& buffer)
  85. {
  86. if (loopRange.getLength() == 0)
  87. buffer.clear();
  88. const auto numChannelsToCopy = std::min (inputBuffer->getNumChannels(), buffer.getNumChannels());
  89. for (auto samplesCopied = 0; samplesCopied < buffer.getNumSamples();)
  90. {
  91. const auto numSamplesToCopy =
  92. std::min (buffer.getNumSamples() - samplesCopied, (int) (loopRange.getEnd() - pos));
  93. for (int i = 0; i < numChannelsToCopy; ++i)
  94. {
  95. buffer.copyFrom (i, samplesCopied, *inputBuffer, i, (int) pos, numSamplesToCopy);
  96. }
  97. samplesCopied += numSamplesToCopy;
  98. pos += numSamplesToCopy;
  99. jassert (pos <= loopRange.getEnd());
  100. if (pos == loopRange.getEnd())
  101. pos = loopRange.getStart();
  102. }
  103. }
  104. private:
  105. const AudioBuffer<float>* inputBuffer;
  106. Range<int64> loopRange;
  107. int64 pos;
  108. };
  109. class OptionalRange
  110. {
  111. public:
  112. using Type = Range<int64>;
  113. OptionalRange() : valid (false) {}
  114. explicit OptionalRange (Type valueIn) : valid (true), value (std::move (valueIn)) {}
  115. explicit operator bool() const noexcept { return valid; }
  116. const auto& operator*() const
  117. {
  118. jassert (valid);
  119. return value;
  120. }
  121. private:
  122. bool valid;
  123. Type value;
  124. };
  125. //==============================================================================
  126. // Returns the modified sample range in the output buffer.
  127. inline OptionalRange readPlaybackRangeIntoBuffer (Range<double> playbackRange,
  128. const ARAPlaybackRegion* playbackRegion,
  129. AudioBuffer<float>& buffer,
  130. const std::function<AudioFormatReader* (ARA::PlugIn::AudioSource*)>& getReader)
  131. {
  132. const auto rangeInAudioModificationTime = playbackRange.movedToStartAt (playbackRange.getStart()
  133. - playbackRegion->getStartInAudioModificationTime());
  134. const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  135. const auto audioModificationSampleRate = audioSource->getSampleRate();
  136. const Range<int64_t> sampleRangeInAudioModification {
  137. ARA::roundSamplePosition (rangeInAudioModificationTime.getStart() * audioModificationSampleRate),
  138. ARA::roundSamplePosition (rangeInAudioModificationTime.getEnd() * audioModificationSampleRate) - 1
  139. };
  140. const auto inputOffset = jlimit ((int64_t) 0, audioSource->getSampleCount(), sampleRangeInAudioModification.getStart());
  141. const auto outputOffset = -std::min (sampleRangeInAudioModification.getStart(), (int64_t) 0);
  142. /* TODO: Handle different AudioSource and playback sample rates.
  143. The conversion should be done inside a specialized AudioFormatReader so that we could use
  144. playbackSampleRate everywhere in this function and we could still read `readLength` number of samples
  145. from the source.
  146. The current implementation will be incorrect when sampling rates differ.
  147. */
  148. const auto readLength = [&]
  149. {
  150. const auto sourceReadLength =
  151. std::min (sampleRangeInAudioModification.getEnd(), audioSource->getSampleCount()) - inputOffset;
  152. const auto outputReadLength =
  153. std::min (outputOffset + sourceReadLength, (int64_t) buffer.getNumSamples()) - outputOffset;
  154. return std::min (sourceReadLength, outputReadLength);
  155. }();
  156. if (readLength == 0)
  157. return OptionalRange { {} };
  158. auto* reader = getReader (audioSource);
  159. if (reader != nullptr && reader->read (&buffer, (int) outputOffset, (int) readLength, inputOffset, true, true))
  160. return OptionalRange { { outputOffset, readLength } };
  161. return {};
  162. }
  163. class PossiblyBufferedReader
  164. {
  165. public:
  166. PossiblyBufferedReader() = default;
  167. explicit PossiblyBufferedReader (std::unique_ptr<BufferingAudioReader> readerIn)
  168. : setTimeoutFn ([ptr = readerIn.get()] (int ms) { ptr->setReadTimeout (ms); }),
  169. reader (std::move (readerIn))
  170. {}
  171. explicit PossiblyBufferedReader (std::unique_ptr<AudioFormatReader> readerIn)
  172. : setTimeoutFn(),
  173. reader (std::move (readerIn))
  174. {}
  175. void setReadTimeout (int ms)
  176. {
  177. NullCheckedInvocation::invoke (setTimeoutFn, ms);
  178. }
  179. AudioFormatReader* get() const { return reader.get(); }
  180. private:
  181. std::function<void (int)> setTimeoutFn;
  182. std::unique_ptr<AudioFormatReader> reader;
  183. };
  184. //==============================================================================
  185. class PlaybackRenderer : public ARAPlaybackRenderer
  186. {
  187. public:
  188. using ARAPlaybackRenderer::ARAPlaybackRenderer;
  189. void prepareToPlay (double sampleRateIn,
  190. int maximumSamplesPerBlockIn,
  191. int numChannelsIn,
  192. AudioProcessor::ProcessingPrecision,
  193. AlwaysNonRealtime alwaysNonRealtime) override
  194. {
  195. numChannels = numChannelsIn;
  196. sampleRate = sampleRateIn;
  197. maximumSamplesPerBlock = maximumSamplesPerBlockIn;
  198. tempBuffer.reset (new AudioBuffer<float> (numChannels, maximumSamplesPerBlock));
  199. useBufferedAudioSourceReader = alwaysNonRealtime == AlwaysNonRealtime::no;
  200. for (const auto playbackRegion : getPlaybackRegions())
  201. {
  202. auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  203. if (audioSourceReaders.find (audioSource) == audioSourceReaders.end())
  204. {
  205. auto reader = std::make_unique<ARAAudioSourceReader> (audioSource);
  206. if (! useBufferedAudioSourceReader)
  207. {
  208. audioSourceReaders.emplace (audioSource,
  209. PossiblyBufferedReader { std::move (reader) });
  210. }
  211. else
  212. {
  213. const auto readAheadSize = jmax (4 * maximumSamplesPerBlock,
  214. roundToInt (2.0 * sampleRate));
  215. audioSourceReaders.emplace (audioSource,
  216. PossiblyBufferedReader { std::make_unique<BufferingAudioReader> (reader.release(),
  217. *sharedTimesliceThread,
  218. readAheadSize) });
  219. }
  220. }
  221. }
  222. }
  223. void releaseResources() override
  224. {
  225. audioSourceReaders.clear();
  226. tempBuffer.reset();
  227. }
  228. bool processBlock (AudioBuffer<float>& buffer,
  229. AudioProcessor::Realtime realtime,
  230. const AudioPlayHead::PositionInfo& positionInfo) noexcept override
  231. {
  232. const auto numSamples = buffer.getNumSamples();
  233. jassert (numSamples <= maximumSamplesPerBlock);
  234. jassert (numChannels == buffer.getNumChannels());
  235. jassert (realtime == AudioProcessor::Realtime::no || useBufferedAudioSourceReader);
  236. const auto timeInSamples = positionInfo.getTimeInSamples().orFallback (0);
  237. const auto isPlaying = positionInfo.getIsPlaying();
  238. bool success = true;
  239. bool didRenderAnyRegion = false;
  240. if (isPlaying)
  241. {
  242. const auto blockRange = Range<int64>::withStartAndLength (timeInSamples, numSamples);
  243. for (const auto& playbackRegion : getPlaybackRegions())
  244. {
  245. // Evaluate region borders in song time, calculate sample range to render in song time.
  246. // Note that this example does not use head- or tailtime, so the includeHeadAndTail
  247. // parameter is set to false here - this might need to be adjusted in actual plug-ins.
  248. const auto playbackSampleRange = playbackRegion->getSampleRange (sampleRate, ARAPlaybackRegion::IncludeHeadAndTail::no);
  249. auto renderRange = blockRange.getIntersectionWith (playbackSampleRange);
  250. if (renderRange.isEmpty())
  251. continue;
  252. // Evaluate region borders in modification/source time and calculate offset between
  253. // song and source samples, then clip song samples accordingly
  254. // (if an actual plug-in supports time stretching, this must be taken into account here).
  255. Range<int64> modificationSampleRange { playbackRegion->getStartInAudioModificationSamples(),
  256. playbackRegion->getEndInAudioModificationSamples() };
  257. const auto modificationSampleOffset = modificationSampleRange.getStart() - playbackSampleRange.getStart();
  258. renderRange = renderRange.getIntersectionWith (modificationSampleRange.movedToStartAt (playbackSampleRange.getStart()));
  259. if (renderRange.isEmpty())
  260. continue;
  261. // Get the audio source for the region and find the reader for that source.
  262. // This simplified example code only produces audio if sample rate and channel count match -
  263. // a robust plug-in would need to do conversion, see ARA SDK documentation.
  264. const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  265. const auto readerIt = audioSourceReaders.find (audioSource);
  266. if (std::make_tuple (audioSource->getChannelCount(), audioSource->getSampleRate()) != std::make_tuple (numChannels, sampleRate)
  267. || (readerIt == audioSourceReaders.end()))
  268. {
  269. success = false;
  270. continue;
  271. }
  272. auto& reader = readerIt->second;
  273. reader.setReadTimeout (realtime == AudioProcessor::Realtime::no ? 100 : 0);
  274. // Calculate buffer offsets.
  275. const int numSamplesToRead = (int) renderRange.getLength();
  276. const int startInBuffer = (int) (renderRange.getStart() - blockRange.getStart());
  277. auto startInSource = renderRange.getStart() + modificationSampleOffset;
  278. // Read samples:
  279. // first region can write directly into output, later regions need to use local buffer.
  280. auto& readBuffer = (didRenderAnyRegion) ? *tempBuffer : buffer;
  281. if (! reader.get()->read (&readBuffer, startInBuffer, numSamplesToRead, startInSource, true, true))
  282. {
  283. success = false;
  284. continue;
  285. }
  286. if (didRenderAnyRegion)
  287. {
  288. // Mix local buffer into the output buffer.
  289. for (int c = 0; c < numChannels; ++c)
  290. buffer.addFrom (c, startInBuffer, *tempBuffer, c, startInBuffer, numSamplesToRead);
  291. }
  292. else
  293. {
  294. // Clear any excess at start or end of the region.
  295. if (startInBuffer != 0)
  296. buffer.clear (0, startInBuffer);
  297. const int endInBuffer = startInBuffer + numSamplesToRead;
  298. const int remainingSamples = numSamples - endInBuffer;
  299. if (remainingSamples != 0)
  300. buffer.clear (endInBuffer, remainingSamples);
  301. didRenderAnyRegion = true;
  302. }
  303. }
  304. }
  305. // If no playback or no region did intersect, clear buffer now.
  306. if (! didRenderAnyRegion)
  307. buffer.clear();
  308. return success;
  309. }
  310. using ARAPlaybackRenderer::processBlock;
  311. private:
  312. //==============================================================================
  313. // We're subclassing here only to provide a proper default c'tor for our shared resource
  314. SharedResourcePointer<SharedTimeSliceThread> sharedTimesliceThread;
  315. std::map<ARA::PlugIn::AudioSource*, PossiblyBufferedReader> audioSourceReaders;
  316. bool useBufferedAudioSourceReader = true;
  317. int numChannels = 2;
  318. double sampleRate = 48000.0;
  319. int maximumSamplesPerBlock = 128;
  320. std::unique_ptr<AudioBuffer<float>> tempBuffer;
  321. };
  322. class EditorRenderer : public ARAEditorRenderer,
  323. private ARARegionSequence::Listener
  324. {
  325. public:
  326. EditorRenderer (ARA::PlugIn::DocumentController* documentController, const PreviewState* previewStateIn)
  327. : ARAEditorRenderer (documentController), previewState (previewStateIn), previewBuffer()
  328. {
  329. jassert (previewState != nullptr);
  330. }
  331. ~EditorRenderer() override
  332. {
  333. for (const auto& rs : regionSequences)
  334. rs->removeListener (this);
  335. }
  336. void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion*) override
  337. {
  338. asyncConfigCallback.startConfigure();
  339. }
  340. void didAddRegionSequence (ARA::PlugIn::RegionSequence* rs) noexcept override
  341. {
  342. auto* sequence = dynamic_cast<ARARegionSequence*> (rs);
  343. sequence->addListener (this);
  344. regionSequences.insert (sequence);
  345. asyncConfigCallback.startConfigure();
  346. }
  347. void didAddPlaybackRegion (ARA::PlugIn::PlaybackRegion*) noexcept override
  348. {
  349. asyncConfigCallback.startConfigure();
  350. }
  351. /* An ARA host could be using either the `addPlaybackRegion()` or `addRegionSequence()` interface
  352. so we need to check the other side of both.
  353. The callback must have a signature of `bool (ARAPlaybackRegion*)`
  354. */
  355. template <typename Callback>
  356. void forEachPlaybackRegion (Callback&& cb)
  357. {
  358. for (const auto& playbackRegion : getPlaybackRegions())
  359. if (! cb (playbackRegion))
  360. return;
  361. for (const auto& regionSequence : getRegionSequences())
  362. for (const auto& playbackRegion : regionSequence->getPlaybackRegions())
  363. if (! cb (playbackRegion))
  364. return;
  365. }
  366. void prepareToPlay (double sampleRateIn,
  367. int maximumExpectedSamplesPerBlock,
  368. int numChannels,
  369. AudioProcessor::ProcessingPrecision,
  370. AlwaysNonRealtime alwaysNonRealtime) override
  371. {
  372. sampleRate = sampleRateIn;
  373. previewBuffer = std::make_unique<AudioBuffer<float>> (numChannels, (int) (2 * sampleRateIn));
  374. ignoreUnused (maximumExpectedSamplesPerBlock, alwaysNonRealtime);
  375. }
  376. void releaseResources() override
  377. {
  378. audioSourceReaders.clear();
  379. }
  380. void reset() override
  381. {
  382. previewBuffer->clear();
  383. }
  384. bool processBlock (AudioBuffer<float>& buffer,
  385. AudioProcessor::Realtime realtime,
  386. const AudioPlayHead::PositionInfo& positionInfo) noexcept override
  387. {
  388. ignoreUnused (realtime);
  389. return asyncConfigCallback.withLock ([&] (bool locked)
  390. {
  391. if (! locked)
  392. return true;
  393. if (positionInfo.getIsPlaying())
  394. return true;
  395. if (const auto previewedRegion = previewState->previewedRegion.load())
  396. {
  397. const auto regionIsAssignedToEditor = [&]()
  398. {
  399. bool regionIsAssigned = false;
  400. forEachPlaybackRegion ([&previewedRegion, &regionIsAssigned] (const auto& region)
  401. {
  402. if (region == previewedRegion)
  403. {
  404. regionIsAssigned = true;
  405. return false;
  406. }
  407. return true;
  408. });
  409. return regionIsAssigned;
  410. }();
  411. if (regionIsAssignedToEditor)
  412. {
  413. const auto previewTime = previewState->previewTime.load();
  414. if (lastPreviewTime != previewTime || lastPlaybackRegion != previewedRegion)
  415. {
  416. Range<double> previewRangeInPlaybackTime { previewTime - 0.25, previewTime + 0.25 };
  417. previewBuffer->clear();
  418. const auto rangeInOutput = readPlaybackRangeIntoBuffer (previewRangeInPlaybackTime,
  419. previewedRegion,
  420. *previewBuffer,
  421. [this] (auto* source) -> auto*
  422. {
  423. const auto iter = audioSourceReaders.find (source);
  424. return iter != audioSourceReaders.end() ? iter->second.get() : nullptr;
  425. });
  426. if (rangeInOutput)
  427. {
  428. lastPreviewTime = previewTime;
  429. lastPlaybackRegion = previewedRegion;
  430. previewLooper = Looper (previewBuffer.get(), *rangeInOutput);
  431. }
  432. }
  433. else
  434. {
  435. previewLooper.writeInto (buffer);
  436. }
  437. }
  438. }
  439. return true;
  440. });
  441. }
  442. using ARAEditorRenderer::processBlock;
  443. private:
  444. void configure()
  445. {
  446. forEachPlaybackRegion ([this, maximumExpectedSamplesPerBlock = 1000] (const auto& playbackRegion)
  447. {
  448. const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  449. if (audioSourceReaders.find (audioSource) == audioSourceReaders.end())
  450. {
  451. audioSourceReaders[audioSource] = std::make_unique<BufferingAudioReader> (
  452. new ARAAudioSourceReader (playbackRegion->getAudioModification()->getAudioSource()),
  453. *timeSliceThread,
  454. std::max (4 * maximumExpectedSamplesPerBlock, (int) sampleRate));
  455. }
  456. return true;
  457. });
  458. }
  459. const PreviewState* previewState = nullptr;
  460. AsyncConfigurationCallback asyncConfigCallback { [this] { configure(); } };
  461. double lastPreviewTime = 0.0;
  462. ARAPlaybackRegion* lastPlaybackRegion = nullptr;
  463. std::unique_ptr<AudioBuffer<float>> previewBuffer;
  464. Looper previewLooper;
  465. double sampleRate = 48000.0;
  466. SharedResourcePointer<SharedTimeSliceThread> timeSliceThread;
  467. std::map<ARA::PlugIn::AudioSource*, std::unique_ptr<BufferingAudioReader>> audioSourceReaders;
  468. std::set<ARARegionSequence*> regionSequences;
  469. };
  470. //==============================================================================
  471. class ARADemoPluginDocumentControllerSpecialisation : public ARADocumentControllerSpecialisation
  472. {
  473. public:
  474. using ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation;
  475. PreviewState previewState;
  476. protected:
  477. ARAPlaybackRenderer* doCreatePlaybackRenderer() noexcept override
  478. {
  479. return new PlaybackRenderer (getDocumentController());
  480. }
  481. EditorRenderer* doCreateEditorRenderer() noexcept override
  482. {
  483. return new EditorRenderer (getDocumentController(), &previewState);
  484. }
  485. bool doRestoreObjectsFromStream (ARAInputStream& input,
  486. const ARARestoreObjectsFilter* filter) noexcept override
  487. {
  488. ignoreUnused (input, filter);
  489. return false;
  490. }
  491. bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) noexcept override
  492. {
  493. ignoreUnused (output, filter);
  494. return false;
  495. }
  496. };
  497. struct PlayHeadState
  498. {
  499. void update (AudioPlayHead* aph)
  500. {
  501. const auto info = aph->getPosition();
  502. if (info.hasValue() && info->getIsPlaying())
  503. {
  504. isPlaying.store (true);
  505. timeInSeconds.store (info->getTimeInSeconds().orFallback (0));
  506. }
  507. else
  508. {
  509. isPlaying.store (false);
  510. }
  511. }
  512. std::atomic<bool> isPlaying { false };
  513. std::atomic<double> timeInSeconds { 0.0 };
  514. };
  515. //==============================================================================
  516. class ARADemoPluginAudioProcessorImpl : public AudioProcessor,
  517. public AudioProcessorARAExtension
  518. {
  519. public:
  520. //==============================================================================
  521. ARADemoPluginAudioProcessorImpl()
  522. : AudioProcessor (getBusesProperties())
  523. {}
  524. ~ARADemoPluginAudioProcessorImpl() override = default;
  525. //==============================================================================
  526. void prepareToPlay (double sampleRate, int samplesPerBlock) override
  527. {
  528. playHeadState.isPlaying.store (false);
  529. prepareToPlayForARA (sampleRate, samplesPerBlock, getMainBusNumOutputChannels(), getProcessingPrecision());
  530. }
  531. void releaseResources() override
  532. {
  533. playHeadState.isPlaying.store (false);
  534. releaseResourcesForARA();
  535. }
  536. bool isBusesLayoutSupported (const BusesLayout& layouts) const override
  537. {
  538. if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
  539. && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
  540. return false;
  541. return true;
  542. }
  543. void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
  544. {
  545. ignoreUnused (midiMessages);
  546. ScopedNoDenormals noDenormals;
  547. auto* audioPlayHead = getPlayHead();
  548. playHeadState.update (audioPlayHead);
  549. if (! processBlockForARA (buffer, isRealtime(), audioPlayHead))
  550. processBlockBypassed (buffer, midiMessages);
  551. }
  552. using AudioProcessor::processBlock;
  553. //==============================================================================
  554. const String getName() const override { return "ARAPluginDemo"; }
  555. bool acceptsMidi() const override { return true; }
  556. bool producesMidi() const override { return true; }
  557. double getTailLengthSeconds() const override { return 0.0; }
  558. //==============================================================================
  559. int getNumPrograms() override { return 0; }
  560. int getCurrentProgram() override { return 0; }
  561. void setCurrentProgram (int) override {}
  562. const String getProgramName (int) override { return "None"; }
  563. void changeProgramName (int, const String&) override {}
  564. //==============================================================================
  565. void getStateInformation (MemoryBlock&) override {}
  566. void setStateInformation (const void*, int) override {}
  567. PlayHeadState playHeadState;
  568. private:
  569. //==============================================================================
  570. static BusesProperties getBusesProperties()
  571. {
  572. return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
  573. .withOutput ("Output", AudioChannelSet::stereo(), true);
  574. }
  575. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginAudioProcessorImpl)
  576. };
  577. //==============================================================================
  578. struct WaveformCache : private ARAAudioSource::Listener
  579. {
  580. WaveformCache() : thumbnailCache (20)
  581. {
  582. }
  583. ~WaveformCache() override
  584. {
  585. for (const auto& entry : thumbnails)
  586. {
  587. entry.first->removeListener (this);
  588. }
  589. }
  590. //==============================================================================
  591. void willDestroyAudioSource (ARAAudioSource* audioSource) override
  592. {
  593. removeAudioSource (audioSource);
  594. }
  595. AudioThumbnail& getOrCreateThumbnail (ARAAudioSource* audioSource)
  596. {
  597. const auto iter = thumbnails.find (audioSource);
  598. if (iter != std::end (thumbnails))
  599. return *iter->second;
  600. auto thumb = std::make_unique<AudioThumbnail> (128, dummyManager, thumbnailCache);
  601. auto& result = *thumb;
  602. ++hash;
  603. thumb->setReader (new ARAAudioSourceReader (audioSource), hash);
  604. audioSource->addListener (this);
  605. thumbnails.emplace (audioSource, std::move (thumb));
  606. return result;
  607. }
  608. private:
  609. void removeAudioSource (ARAAudioSource* audioSource)
  610. {
  611. audioSource->removeListener (this);
  612. thumbnails.erase (audioSource);
  613. }
  614. int64 hash = 0;
  615. AudioFormatManager dummyManager;
  616. AudioThumbnailCache thumbnailCache;
  617. std::map<ARAAudioSource*, std::unique_ptr<AudioThumbnail>> thumbnails;
  618. };
  619. class PlaybackRegionView : public Component,
  620. public ChangeListener
  621. {
  622. public:
  623. PlaybackRegionView (ARAPlaybackRegion& region, WaveformCache& cache)
  624. : playbackRegion (region), waveformCache (cache)
  625. {
  626. auto* audioSource = playbackRegion.getAudioModification()->getAudioSource();
  627. waveformCache.getOrCreateThumbnail (audioSource).addChangeListener (this);
  628. }
  629. ~PlaybackRegionView() override
  630. {
  631. waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource())
  632. .removeChangeListener (this);
  633. }
  634. void mouseDown (const MouseEvent& m) override
  635. {
  636. const auto relativeTime = (double) m.getMouseDownX() / getLocalBounds().getWidth();
  637. const auto previewTime = playbackRegion.getStartInPlaybackTime()
  638. + relativeTime * playbackRegion.getDurationInPlaybackTime();
  639. auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
  640. previewState.previewTime.store (previewTime);
  641. previewState.previewedRegion.store (&playbackRegion);
  642. }
  643. void mouseUp (const MouseEvent&) override
  644. {
  645. auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
  646. previewState.previewTime.store (0.0);
  647. previewState.previewedRegion.store (nullptr);
  648. }
  649. void changeListenerCallback (ChangeBroadcaster*) override
  650. {
  651. repaint();
  652. }
  653. void paint (Graphics& g) override
  654. {
  655. g.fillAll (Colours::white.darker());
  656. g.setColour (Colours::darkgrey.darker());
  657. auto& thumbnail = waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource());
  658. thumbnail.drawChannels (g,
  659. getLocalBounds(),
  660. playbackRegion.getStartInAudioModificationTime(),
  661. playbackRegion.getEndInAudioModificationTime(),
  662. 1.0f);
  663. g.setColour (Colours::black);
  664. g.drawRect (getLocalBounds());
  665. }
  666. void resized() override
  667. {
  668. repaint();
  669. }
  670. private:
  671. ARAPlaybackRegion& playbackRegion;
  672. WaveformCache& waveformCache;
  673. };
  674. class RegionSequenceView : public Component,
  675. public ARARegionSequence::Listener,
  676. public ChangeBroadcaster,
  677. private ARAPlaybackRegion::Listener
  678. {
  679. public:
  680. RegionSequenceView (ARARegionSequence& rs, WaveformCache& cache, double pixelPerSec)
  681. : regionSequence (rs), waveformCache (cache), zoomLevelPixelPerSecond (pixelPerSec)
  682. {
  683. regionSequence.addListener (this);
  684. for (auto* playbackRegion : regionSequence.getPlaybackRegions())
  685. createAndAddPlaybackRegionView (playbackRegion);
  686. updatePlaybackDuration();
  687. }
  688. ~RegionSequenceView() override
  689. {
  690. regionSequence.removeListener (this);
  691. for (const auto& it : playbackRegionViews)
  692. it.first->removeListener (this);
  693. }
  694. //==============================================================================
  695. // ARA Document change callback overrides
  696. void willRemovePlaybackRegionFromRegionSequence (ARARegionSequence*,
  697. ARAPlaybackRegion* playbackRegion) override
  698. {
  699. playbackRegion->removeListener (this);
  700. removeChildComponent (playbackRegionViews[playbackRegion].get());
  701. playbackRegionViews.erase (playbackRegion);
  702. updatePlaybackDuration();
  703. }
  704. void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion* playbackRegion) override
  705. {
  706. createAndAddPlaybackRegionView (playbackRegion);
  707. updatePlaybackDuration();
  708. }
  709. void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) override
  710. {
  711. playbackRegion->removeListener (this);
  712. removeChildComponent (playbackRegionViews[playbackRegion].get());
  713. playbackRegionViews.erase (playbackRegion);
  714. updatePlaybackDuration();
  715. }
  716. void willUpdatePlaybackRegionProperties (ARAPlaybackRegion*, ARAPlaybackRegion::PropertiesPtr) override
  717. {
  718. }
  719. void didUpdatePlaybackRegionProperties (ARAPlaybackRegion*) override
  720. {
  721. updatePlaybackDuration();
  722. }
  723. void resized() override
  724. {
  725. for (auto& pbr : playbackRegionViews)
  726. {
  727. const auto playbackRegion = pbr.first;
  728. pbr.second->setBounds (
  729. getLocalBounds()
  730. .withTrimmedLeft (roundToInt (playbackRegion->getStartInPlaybackTime() * zoomLevelPixelPerSecond))
  731. .withWidth (roundToInt (playbackRegion->getDurationInPlaybackTime() * zoomLevelPixelPerSecond)));
  732. }
  733. }
  734. auto getPlaybackDuration() const noexcept
  735. {
  736. return playbackDuration;
  737. }
  738. void setZoomLevel (double pixelPerSecond)
  739. {
  740. zoomLevelPixelPerSecond = pixelPerSecond;
  741. resized();
  742. }
  743. private:
  744. void createAndAddPlaybackRegionView (ARAPlaybackRegion* playbackRegion)
  745. {
  746. playbackRegionViews[playbackRegion] = std::make_unique<PlaybackRegionView> (*playbackRegion, waveformCache);
  747. playbackRegion->addListener (this);
  748. addAndMakeVisible (*playbackRegionViews[playbackRegion]);
  749. }
  750. void updatePlaybackDuration()
  751. {
  752. const auto iter = std::max_element (
  753. playbackRegionViews.begin(),
  754. playbackRegionViews.end(),
  755. [] (const auto& a, const auto& b) { return a.first->getEndInPlaybackTime() < b.first->getEndInPlaybackTime(); });
  756. playbackDuration = iter != playbackRegionViews.end() ? iter->first->getEndInPlaybackTime()
  757. : 0.0;
  758. sendChangeMessage();
  759. }
  760. ARARegionSequence& regionSequence;
  761. WaveformCache& waveformCache;
  762. std::unordered_map<ARAPlaybackRegion*, std::unique_ptr<PlaybackRegionView>> playbackRegionViews;
  763. double playbackDuration = 0.0;
  764. double zoomLevelPixelPerSecond;
  765. };
  766. class ZoomControls : public Component
  767. {
  768. public:
  769. ZoomControls()
  770. {
  771. addAndMakeVisible (zoomInButton);
  772. addAndMakeVisible (zoomOutButton);
  773. }
  774. void setZoomInCallback (std::function<void()> cb) { zoomInButton.onClick = std::move (cb); }
  775. void setZoomOutCallback (std::function<void()> cb) { zoomOutButton.onClick = std::move (cb); }
  776. void resized() override
  777. {
  778. FlexBox fb;
  779. fb.justifyContent = FlexBox::JustifyContent::flexEnd;
  780. for (auto* button : { &zoomInButton, &zoomOutButton })
  781. fb.items.add (FlexItem (*button).withMinHeight (30.0f).withMinWidth (30.0f).withMargin ({ 5, 5, 5, 0 }));
  782. fb.performLayout (getLocalBounds());
  783. }
  784. private:
  785. TextButton zoomInButton { "+" }, zoomOutButton { "-" };
  786. };
  787. class TrackHeader : public Component
  788. {
  789. public:
  790. explicit TrackHeader (const ARARegionSequence& regionSequenceIn) : regionSequence (regionSequenceIn)
  791. {
  792. update();
  793. addAndMakeVisible (trackNameLabel);
  794. }
  795. void resized() override
  796. {
  797. trackNameLabel.setBounds (getLocalBounds().reduced (2));
  798. }
  799. void paint (Graphics& g) override
  800. {
  801. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  802. g.fillRoundedRectangle (getLocalBounds().reduced (2).toType<float>(), 6.0f);
  803. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting());
  804. g.drawRoundedRectangle (getLocalBounds().reduced (2).toType<float>(), 6.0f, 1.0f);
  805. }
  806. private:
  807. void update()
  808. {
  809. const auto getWithDefaultValue =
  810. [] (const ARA::PlugIn::OptionalProperty<ARA::ARAUtf8String>& optional, String defaultValue)
  811. {
  812. if (const ARA::ARAUtf8String value = optional)
  813. return String (value);
  814. return defaultValue;
  815. };
  816. trackNameLabel.setText (getWithDefaultValue (regionSequence.getName(), "No track name"),
  817. NotificationType::dontSendNotification);
  818. }
  819. const ARARegionSequence& regionSequence;
  820. Label trackNameLabel;
  821. };
  822. constexpr auto trackHeight = 60;
  823. class VerticalLayoutViewportContent : public Component
  824. {
  825. public:
  826. void resized() override
  827. {
  828. auto bounds = getLocalBounds();
  829. for (auto* component : getChildren())
  830. {
  831. component->setBounds (bounds.removeFromTop (trackHeight));
  832. component->resized();
  833. }
  834. }
  835. void setOverlayComponent (Component* component)
  836. {
  837. if (overlayComponent != nullptr && overlayComponent != component)
  838. removeChildComponent (overlayComponent);
  839. addChildComponent (component);
  840. overlayComponent = component;
  841. }
  842. private:
  843. Component* overlayComponent = nullptr;
  844. };
  845. class VerticalLayoutViewport : public Viewport
  846. {
  847. public:
  848. VerticalLayoutViewport()
  849. {
  850. setViewedComponent (&content, false);
  851. }
  852. void paint (Graphics& g) override
  853. {
  854. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter());
  855. }
  856. std::function<void (Rectangle<int>)> onVisibleAreaChanged;
  857. VerticalLayoutViewportContent content;
  858. private:
  859. void visibleAreaChanged (const Rectangle<int>& newVisibleArea) override
  860. {
  861. NullCheckedInvocation::invoke (onVisibleAreaChanged, newVisibleArea);
  862. }
  863. };
  864. class OverlayComponent : public Component,
  865. private Timer
  866. {
  867. public:
  868. class PlayheadMarkerComponent : public Component
  869. {
  870. void paint (Graphics& g) override { g.fillAll (juce::Colours::yellow.darker (0.2f)); }
  871. };
  872. OverlayComponent (PlayHeadState& playHeadStateIn)
  873. : playHeadState (&playHeadStateIn)
  874. {
  875. addChildComponent (playheadMarker);
  876. setInterceptsMouseClicks (false, false);
  877. startTimerHz (30);
  878. }
  879. ~OverlayComponent() override
  880. {
  881. stopTimer();
  882. }
  883. void resized() override
  884. {
  885. doResize();
  886. }
  887. void setZoomLevel (double pixelPerSecondIn)
  888. {
  889. pixelPerSecond = pixelPerSecondIn;
  890. }
  891. void setHorizontalOffset (int offset)
  892. {
  893. horizontalOffset = offset;
  894. }
  895. private:
  896. void doResize()
  897. {
  898. if (playHeadState->isPlaying.load())
  899. {
  900. const auto markerX = playHeadState->timeInSeconds.load() * pixelPerSecond;
  901. const auto playheadLine = getLocalBounds().withTrimmedLeft ((int) (markerX - markerWidth / 2.0) - horizontalOffset)
  902. .removeFromLeft ((int) markerWidth);
  903. playheadMarker.setVisible (true);
  904. playheadMarker.setBounds (playheadLine);
  905. }
  906. else
  907. {
  908. playheadMarker.setVisible (false);
  909. }
  910. }
  911. void timerCallback() override
  912. {
  913. doResize();
  914. }
  915. static constexpr double markerWidth = 2.0;
  916. PlayHeadState* playHeadState;
  917. double pixelPerSecond = 1.0;
  918. int horizontalOffset = 0;
  919. PlayheadMarkerComponent playheadMarker;
  920. };
  921. class DocumentView : public Component,
  922. public ChangeListener,
  923. private ARADocument::Listener,
  924. private ARAEditorView::Listener
  925. {
  926. public:
  927. explicit DocumentView (ARADocument& document, PlayHeadState& playHeadState)
  928. : araDocument (document),
  929. overlay (playHeadState)
  930. {
  931. addAndMakeVisible (tracksBackground);
  932. viewport.onVisibleAreaChanged = [this] (const auto& r)
  933. {
  934. viewportHeightOffset = r.getY();
  935. overlay.setHorizontalOffset (r.getX());
  936. resized();
  937. };
  938. addAndMakeVisible (viewport);
  939. overlay.setZoomLevel (zoomLevelPixelPerSecond);
  940. addAndMakeVisible (overlay);
  941. zoomControls.setZoomInCallback ([this] { zoom (2.0); });
  942. zoomControls.setZoomOutCallback ([this] { zoom (0.5); });
  943. addAndMakeVisible (zoomControls);
  944. invalidateRegionSequenceViews();
  945. araDocument.addListener (this);
  946. }
  947. ~DocumentView() override
  948. {
  949. araDocument.removeListener (this);
  950. }
  951. //==============================================================================
  952. // ARADocument::Listener overrides
  953. void didReorderRegionSequencesInDocument (ARADocument*) override
  954. {
  955. invalidateRegionSequenceViews();
  956. }
  957. void didAddRegionSequenceToDocument (ARADocument*, ARARegionSequence*) override
  958. {
  959. invalidateRegionSequenceViews();
  960. }
  961. void willRemoveRegionSequenceFromDocument (ARADocument*, ARARegionSequence* regionSequence) override
  962. {
  963. removeRegionSequenceView (regionSequence);
  964. }
  965. void didEndEditing (ARADocument*) override
  966. {
  967. rebuildRegionSequenceViews();
  968. update();
  969. }
  970. //==============================================================================
  971. void changeListenerCallback (ChangeBroadcaster*) override
  972. {
  973. update();
  974. }
  975. //==============================================================================
  976. // ARAEditorView::Listener overrides
  977. void onNewSelection (const ARA::PlugIn::ViewSelection&) override
  978. {
  979. }
  980. void onHideRegionSequences (const std::vector<ARARegionSequence*>&) override
  981. {
  982. }
  983. //==============================================================================
  984. void paint (Graphics& g) override
  985. {
  986. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
  987. }
  988. void resized() override
  989. {
  990. auto bounds = getLocalBounds();
  991. const auto bottomControlsBounds = bounds.removeFromBottom (40);
  992. const auto headerBounds = bounds.removeFromLeft (headerWidth).reduced (2);
  993. zoomControls.setBounds (bottomControlsBounds);
  994. layOutVertically (headerBounds, trackHeaders, viewportHeightOffset);
  995. tracksBackground.setBounds (bounds);
  996. viewport.setBounds (bounds);
  997. overlay.setBounds (bounds);
  998. }
  999. //==============================================================================
  1000. void setZoomLevel (double pixelPerSecond)
  1001. {
  1002. zoomLevelPixelPerSecond = pixelPerSecond;
  1003. for (const auto& view : regionSequenceViews)
  1004. view.second->setZoomLevel (zoomLevelPixelPerSecond);
  1005. overlay.setZoomLevel (zoomLevelPixelPerSecond);
  1006. update();
  1007. }
  1008. static constexpr int headerWidth = 120;
  1009. private:
  1010. struct RegionSequenceViewKey
  1011. {
  1012. explicit RegionSequenceViewKey (ARARegionSequence* regionSequence)
  1013. : orderIndex (regionSequence->getOrderIndex()), sequence (regionSequence)
  1014. {
  1015. }
  1016. bool operator< (const RegionSequenceViewKey& other) const
  1017. {
  1018. return std::tie (orderIndex, sequence) < std::tie (other.orderIndex, other.sequence);
  1019. }
  1020. ARA::ARAInt32 orderIndex;
  1021. ARARegionSequence* sequence;
  1022. };
  1023. void zoom (double factor)
  1024. {
  1025. zoomLevelPixelPerSecond = jlimit (minimumZoom, minimumZoom * 32, zoomLevelPixelPerSecond * factor);
  1026. setZoomLevel (zoomLevelPixelPerSecond);
  1027. }
  1028. template <typename T>
  1029. void layOutVertically (Rectangle<int> bounds, T& components, int verticalOffset = 0)
  1030. {
  1031. bounds = bounds.withY (bounds.getY() - verticalOffset).withHeight (bounds.getHeight() + verticalOffset);
  1032. for (auto& component : components)
  1033. {
  1034. component.second->setBounds (bounds.removeFromTop (trackHeight));
  1035. component.second->resized();
  1036. }
  1037. }
  1038. void update()
  1039. {
  1040. timelineLength = 0.0;
  1041. for (const auto& view : regionSequenceViews)
  1042. timelineLength = std::max (timelineLength, view.second->getPlaybackDuration());
  1043. const Rectangle<int> timelineSize (roundToInt (timelineLength * zoomLevelPixelPerSecond),
  1044. (int) regionSequenceViews.size() * trackHeight);
  1045. viewport.content.setSize (timelineSize.getWidth(), timelineSize.getHeight());
  1046. viewport.content.resized();
  1047. resized();
  1048. }
  1049. void addTrackViews (ARARegionSequence* regionSequence)
  1050. {
  1051. const auto insertIntoMap = [](auto& map, auto key, auto value) -> auto&
  1052. {
  1053. auto it = map.insert ({ std::move (key), std::move (value) });
  1054. return *(it.first->second);
  1055. };
  1056. auto& regionSequenceView = insertIntoMap (
  1057. regionSequenceViews,
  1058. RegionSequenceViewKey { regionSequence },
  1059. std::make_unique<RegionSequenceView> (*regionSequence, waveformCache, zoomLevelPixelPerSecond));
  1060. regionSequenceView.addChangeListener (this);
  1061. viewport.content.addAndMakeVisible (regionSequenceView);
  1062. auto& trackHeader = insertIntoMap (trackHeaders,
  1063. RegionSequenceViewKey { regionSequence },
  1064. std::make_unique<TrackHeader> (*regionSequence));
  1065. addAndMakeVisible (trackHeader);
  1066. }
  1067. void removeRegionSequenceView (ARARegionSequence* regionSequence)
  1068. {
  1069. const auto& view = regionSequenceViews.find (RegionSequenceViewKey { regionSequence });
  1070. if (view != regionSequenceViews.cend())
  1071. {
  1072. removeChildComponent (view->second.get());
  1073. regionSequenceViews.erase (view);
  1074. }
  1075. invalidateRegionSequenceViews();
  1076. }
  1077. void invalidateRegionSequenceViews()
  1078. {
  1079. regionSequenceViewsAreValid = false;
  1080. rebuildRegionSequenceViews();
  1081. }
  1082. void rebuildRegionSequenceViews()
  1083. {
  1084. if (! regionSequenceViewsAreValid && ! araDocument.getDocumentController()->isHostEditingDocument())
  1085. {
  1086. for (auto& view : regionSequenceViews)
  1087. removeChildComponent (view.second.get());
  1088. regionSequenceViews.clear();
  1089. for (auto& view : trackHeaders)
  1090. removeChildComponent (view.second.get());
  1091. trackHeaders.clear();
  1092. for (auto* regionSequence : araDocument.getRegionSequences())
  1093. {
  1094. addTrackViews (regionSequence);
  1095. }
  1096. update();
  1097. regionSequenceViewsAreValid = true;
  1098. }
  1099. }
  1100. class TracksBackgroundComponent : public Component
  1101. {
  1102. void paint (Graphics& g) override
  1103. {
  1104. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter());
  1105. }
  1106. };
  1107. static constexpr auto minimumZoom = 10.0;
  1108. static constexpr auto trackHeight = 60;
  1109. ARADocument& araDocument;
  1110. bool regionSequenceViewsAreValid = false;
  1111. double timelineLength = 0;
  1112. double zoomLevelPixelPerSecond = minimumZoom * 4;
  1113. WaveformCache waveformCache;
  1114. TracksBackgroundComponent tracksBackground;
  1115. std::map<RegionSequenceViewKey, std::unique_ptr<TrackHeader>> trackHeaders;
  1116. std::map<RegionSequenceViewKey, std::unique_ptr<RegionSequenceView>> regionSequenceViews;
  1117. VerticalLayoutViewport viewport;
  1118. OverlayComponent overlay;
  1119. ZoomControls zoomControls;
  1120. int viewportHeightOffset = 0;
  1121. };
  1122. class ARADemoPluginProcessorEditor : public AudioProcessorEditor,
  1123. public AudioProcessorEditorARAExtension
  1124. {
  1125. public:
  1126. explicit ARADemoPluginProcessorEditor (ARADemoPluginAudioProcessorImpl& p)
  1127. : AudioProcessorEditor (&p),
  1128. AudioProcessorEditorARAExtension (&p)
  1129. {
  1130. if (auto* editorView = getARAEditorView())
  1131. {
  1132. auto* document = ARADocumentControllerSpecialisation::getSpecialisedDocumentController(editorView->getDocumentController())->getDocument();
  1133. documentView = std::make_unique<DocumentView> (*document, p.playHeadState );
  1134. }
  1135. addAndMakeVisible (documentView.get());
  1136. // ARA requires that plugin editors are resizable to support tight integration
  1137. // into the host UI
  1138. setResizable (true, false);
  1139. setSize (400, 300);
  1140. }
  1141. //==============================================================================
  1142. void paint (Graphics& g) override
  1143. {
  1144. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  1145. if (! isARAEditorView())
  1146. {
  1147. g.setColour (Colours::white);
  1148. g.setFont (15.0f);
  1149. g.drawFittedText ("ARA host isn't detected. This plugin only supports ARA mode",
  1150. getLocalBounds(),
  1151. Justification::centred,
  1152. 1);
  1153. }
  1154. }
  1155. void resized() override
  1156. {
  1157. if (documentView != nullptr)
  1158. documentView->setBounds (getLocalBounds());
  1159. }
  1160. private:
  1161. std::unique_ptr<Component> documentView;
  1162. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginProcessorEditor)
  1163. };
  1164. class ARADemoPluginAudioProcessor : public ARADemoPluginAudioProcessorImpl
  1165. {
  1166. public:
  1167. bool hasEditor() const override { return true; }
  1168. AudioProcessorEditor* createEditor() override { return new ARADemoPluginProcessorEditor (*this); }
  1169. };