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.

1444 lines
50KB

  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. private:
  311. //==============================================================================
  312. // We're subclassing here only to provide a proper default c'tor for our shared resource
  313. SharedResourcePointer<SharedTimeSliceThread> sharedTimesliceThread;
  314. std::map<ARA::PlugIn::AudioSource*, PossiblyBufferedReader> audioSourceReaders;
  315. bool useBufferedAudioSourceReader = true;
  316. int numChannels = 2;
  317. double sampleRate = 48000.0;
  318. int maximumSamplesPerBlock = 128;
  319. std::unique_ptr<AudioBuffer<float>> tempBuffer;
  320. };
  321. class EditorRenderer : public ARAEditorRenderer,
  322. private ARARegionSequence::Listener
  323. {
  324. public:
  325. EditorRenderer (ARA::PlugIn::DocumentController* documentController, const PreviewState* previewStateIn)
  326. : ARAEditorRenderer (documentController), previewState (previewStateIn), previewBuffer()
  327. {
  328. jassert (previewState != nullptr);
  329. }
  330. ~EditorRenderer() override
  331. {
  332. for (const auto& rs : regionSequences)
  333. rs->removeListener (this);
  334. }
  335. void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion*) override
  336. {
  337. asyncConfigCallback.startConfigure();
  338. }
  339. void didAddRegionSequence (ARA::PlugIn::RegionSequence* rs) noexcept override
  340. {
  341. auto* sequence = dynamic_cast<ARARegionSequence*> (rs);
  342. sequence->addListener (this);
  343. regionSequences.insert (sequence);
  344. asyncConfigCallback.startConfigure();
  345. }
  346. void didAddPlaybackRegion (ARA::PlugIn::PlaybackRegion*) noexcept override
  347. {
  348. asyncConfigCallback.startConfigure();
  349. }
  350. /* An ARA host could be using either the `addPlaybackRegion()` or `addRegionSequence()` interface
  351. so we need to check the other side of both.
  352. The callback must have a signature of `bool (ARAPlaybackRegion*)`
  353. */
  354. template <typename Callback>
  355. void forEachPlaybackRegion (Callback&& cb)
  356. {
  357. for (const auto& playbackRegion : getPlaybackRegions())
  358. if (! cb (playbackRegion))
  359. return;
  360. for (const auto& regionSequence : getRegionSequences())
  361. for (const auto& playbackRegion : regionSequence->getPlaybackRegions())
  362. if (! cb (playbackRegion))
  363. return;
  364. }
  365. void prepareToPlay (double sampleRateIn,
  366. int maximumExpectedSamplesPerBlock,
  367. int numChannels,
  368. AudioProcessor::ProcessingPrecision,
  369. AlwaysNonRealtime alwaysNonRealtime) override
  370. {
  371. sampleRate = sampleRateIn;
  372. previewBuffer = std::make_unique<AudioBuffer<float>> (numChannels, (int) (2 * sampleRateIn));
  373. ignoreUnused (maximumExpectedSamplesPerBlock, alwaysNonRealtime);
  374. }
  375. void releaseResources() override
  376. {
  377. audioSourceReaders.clear();
  378. }
  379. void reset() override
  380. {
  381. previewBuffer->clear();
  382. }
  383. bool processBlock (AudioBuffer<float>& buffer,
  384. AudioProcessor::Realtime realtime,
  385. const AudioPlayHead::PositionInfo& positionInfo) noexcept override
  386. {
  387. ignoreUnused (realtime);
  388. return asyncConfigCallback.withLock ([&] (bool locked)
  389. {
  390. if (! locked)
  391. return true;
  392. if (positionInfo.getIsPlaying())
  393. return true;
  394. if (const auto previewedRegion = previewState->previewedRegion.load())
  395. {
  396. const auto regionIsAssignedToEditor = [&]()
  397. {
  398. bool regionIsAssigned = false;
  399. forEachPlaybackRegion ([&previewedRegion, &regionIsAssigned] (const auto& region)
  400. {
  401. if (region == previewedRegion)
  402. {
  403. regionIsAssigned = true;
  404. return false;
  405. }
  406. return true;
  407. });
  408. return regionIsAssigned;
  409. }();
  410. if (regionIsAssignedToEditor)
  411. {
  412. const auto previewTime = previewState->previewTime.load();
  413. if (lastPreviewTime != previewTime || lastPlaybackRegion != previewedRegion)
  414. {
  415. Range<double> previewRangeInPlaybackTime { previewTime - 0.25, previewTime + 0.25 };
  416. previewBuffer->clear();
  417. const auto rangeInOutput = readPlaybackRangeIntoBuffer (previewRangeInPlaybackTime,
  418. previewedRegion,
  419. *previewBuffer,
  420. [this] (auto* source) -> auto*
  421. {
  422. const auto iter = audioSourceReaders.find (source);
  423. return iter != audioSourceReaders.end() ? iter->second.get() : nullptr;
  424. });
  425. if (rangeInOutput)
  426. {
  427. lastPreviewTime = previewTime;
  428. lastPlaybackRegion = previewedRegion;
  429. previewLooper = Looper (previewBuffer.get(), *rangeInOutput);
  430. }
  431. }
  432. else
  433. {
  434. previewLooper.writeInto (buffer);
  435. }
  436. }
  437. }
  438. return true;
  439. });
  440. }
  441. private:
  442. void configure()
  443. {
  444. forEachPlaybackRegion ([this, maximumExpectedSamplesPerBlock = 1000] (const auto& playbackRegion)
  445. {
  446. const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
  447. if (audioSourceReaders.find (audioSource) == audioSourceReaders.end())
  448. {
  449. audioSourceReaders[audioSource] = std::make_unique<BufferingAudioReader> (
  450. new ARAAudioSourceReader (playbackRegion->getAudioModification()->getAudioSource()),
  451. *timeSliceThread,
  452. std::max (4 * maximumExpectedSamplesPerBlock, (int) sampleRate));
  453. }
  454. return true;
  455. });
  456. }
  457. const PreviewState* previewState = nullptr;
  458. AsyncConfigurationCallback asyncConfigCallback { [this] { configure(); } };
  459. double lastPreviewTime = 0.0;
  460. ARAPlaybackRegion* lastPlaybackRegion = nullptr;
  461. std::unique_ptr<AudioBuffer<float>> previewBuffer;
  462. Looper previewLooper;
  463. double sampleRate = 48000.0;
  464. SharedResourcePointer<SharedTimeSliceThread> timeSliceThread;
  465. std::map<ARA::PlugIn::AudioSource*, std::unique_ptr<BufferingAudioReader>> audioSourceReaders;
  466. std::set<ARARegionSequence*> regionSequences;
  467. };
  468. //==============================================================================
  469. class ARADemoPluginDocumentControllerSpecialisation : public ARADocumentControllerSpecialisation
  470. {
  471. public:
  472. using ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation;
  473. PreviewState previewState;
  474. protected:
  475. ARAPlaybackRenderer* doCreatePlaybackRenderer() noexcept override
  476. {
  477. return new PlaybackRenderer (getDocumentController());
  478. }
  479. EditorRenderer* doCreateEditorRenderer() noexcept override
  480. {
  481. return new EditorRenderer (getDocumentController(), &previewState);
  482. }
  483. bool doRestoreObjectsFromStream (ARAInputStream& input,
  484. const ARARestoreObjectsFilter* filter) noexcept override
  485. {
  486. ignoreUnused (input, filter);
  487. return false;
  488. }
  489. bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) noexcept override
  490. {
  491. ignoreUnused (output, filter);
  492. return false;
  493. }
  494. };
  495. struct PlayHeadState
  496. {
  497. void update (AudioPlayHead* aph)
  498. {
  499. const auto info = aph->getPosition();
  500. if (info.hasValue() && info->getIsPlaying())
  501. {
  502. isPlaying.store (true);
  503. timeInSeconds.store (info->getTimeInSeconds().orFallback (0));
  504. }
  505. else
  506. {
  507. isPlaying.store (false);
  508. }
  509. }
  510. std::atomic<bool> isPlaying { false };
  511. std::atomic<double> timeInSeconds { 0.0 };
  512. };
  513. //==============================================================================
  514. class ARADemoPluginAudioProcessorImpl : public AudioProcessor,
  515. public AudioProcessorARAExtension
  516. {
  517. public:
  518. //==============================================================================
  519. ARADemoPluginAudioProcessorImpl()
  520. : AudioProcessor (getBusesProperties())
  521. {}
  522. ~ARADemoPluginAudioProcessorImpl() override = default;
  523. //==============================================================================
  524. void prepareToPlay (double sampleRate, int samplesPerBlock) override
  525. {
  526. playHeadState.isPlaying.store (false);
  527. prepareToPlayForARA (sampleRate, samplesPerBlock, getMainBusNumOutputChannels(), getProcessingPrecision());
  528. }
  529. void releaseResources() override
  530. {
  531. playHeadState.isPlaying.store (false);
  532. releaseResourcesForARA();
  533. }
  534. bool isBusesLayoutSupported (const BusesLayout& layouts) const override
  535. {
  536. if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
  537. && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
  538. return false;
  539. return true;
  540. }
  541. void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
  542. {
  543. ignoreUnused (midiMessages);
  544. ScopedNoDenormals noDenormals;
  545. auto* audioPlayHead = getPlayHead();
  546. playHeadState.update (audioPlayHead);
  547. if (! processBlockForARA (buffer, isRealtime(), audioPlayHead))
  548. processBlockBypassed (buffer, midiMessages);
  549. }
  550. //==============================================================================
  551. const String getName() const override { return "ARAPluginDemo"; }
  552. bool acceptsMidi() const override { return true; }
  553. bool producesMidi() const override { return true; }
  554. double getTailLengthSeconds() const override { return 0.0; }
  555. //==============================================================================
  556. int getNumPrograms() override { return 0; }
  557. int getCurrentProgram() override { return 0; }
  558. void setCurrentProgram (int) override {}
  559. const String getProgramName (int) override { return "None"; }
  560. void changeProgramName (int, const String&) override {}
  561. //==============================================================================
  562. void getStateInformation (MemoryBlock&) override {}
  563. void setStateInformation (const void*, int) override {}
  564. PlayHeadState playHeadState;
  565. private:
  566. //==============================================================================
  567. static BusesProperties getBusesProperties()
  568. {
  569. return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
  570. .withOutput ("Output", AudioChannelSet::stereo(), true);
  571. }
  572. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginAudioProcessorImpl)
  573. };
  574. //==============================================================================
  575. struct WaveformCache : private ARAAudioSource::Listener
  576. {
  577. WaveformCache() : thumbnailCache (20)
  578. {
  579. }
  580. ~WaveformCache() override
  581. {
  582. for (const auto& entry : thumbnails)
  583. {
  584. entry.first->removeListener (this);
  585. }
  586. }
  587. //==============================================================================
  588. void willDestroyAudioSource (ARAAudioSource* audioSource) override
  589. {
  590. removeAudioSource (audioSource);
  591. }
  592. AudioThumbnail& getOrCreateThumbnail (ARAAudioSource* audioSource)
  593. {
  594. const auto iter = thumbnails.find (audioSource);
  595. if (iter != std::end (thumbnails))
  596. return *iter->second;
  597. auto thumb = std::make_unique<AudioThumbnail> (128, dummyManager, thumbnailCache);
  598. auto& result = *thumb;
  599. ++hash;
  600. thumb->setReader (new ARAAudioSourceReader (audioSource), hash);
  601. audioSource->addListener (this);
  602. thumbnails.emplace (audioSource, std::move (thumb));
  603. return result;
  604. }
  605. private:
  606. void removeAudioSource (ARAAudioSource* audioSource)
  607. {
  608. audioSource->removeListener (this);
  609. thumbnails.erase (audioSource);
  610. }
  611. int64 hash = 0;
  612. AudioFormatManager dummyManager;
  613. AudioThumbnailCache thumbnailCache;
  614. std::map<ARAAudioSource*, std::unique_ptr<AudioThumbnail>> thumbnails;
  615. };
  616. class PlaybackRegionView : public Component,
  617. public ChangeListener
  618. {
  619. public:
  620. PlaybackRegionView (ARAPlaybackRegion& region, WaveformCache& cache)
  621. : playbackRegion (region), waveformCache (cache)
  622. {
  623. auto* audioSource = playbackRegion.getAudioModification()->getAudioSource();
  624. waveformCache.getOrCreateThumbnail (audioSource).addChangeListener (this);
  625. }
  626. ~PlaybackRegionView() override
  627. {
  628. waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource())
  629. .removeChangeListener (this);
  630. }
  631. void mouseDown (const MouseEvent& m) override
  632. {
  633. const auto relativeTime = (double) m.getMouseDownX() / getLocalBounds().getWidth();
  634. const auto previewTime = playbackRegion.getStartInPlaybackTime()
  635. + relativeTime * playbackRegion.getDurationInPlaybackTime();
  636. auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
  637. previewState.previewTime.store (previewTime);
  638. previewState.previewedRegion.store (&playbackRegion);
  639. }
  640. void mouseUp (const MouseEvent&) override
  641. {
  642. auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
  643. previewState.previewTime.store (0.0);
  644. previewState.previewedRegion.store (nullptr);
  645. }
  646. void changeListenerCallback (ChangeBroadcaster*) override
  647. {
  648. repaint();
  649. }
  650. void paint (Graphics& g) override
  651. {
  652. g.fillAll (Colours::white.darker());
  653. g.setColour (Colours::darkgrey.darker());
  654. auto& thumbnail = waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource());
  655. thumbnail.drawChannels (g,
  656. getLocalBounds(),
  657. playbackRegion.getStartInAudioModificationTime(),
  658. playbackRegion.getEndInAudioModificationTime(),
  659. 1.0f);
  660. g.setColour (Colours::black);
  661. g.drawRect (getLocalBounds());
  662. }
  663. void resized() override
  664. {
  665. repaint();
  666. }
  667. private:
  668. ARAPlaybackRegion& playbackRegion;
  669. WaveformCache& waveformCache;
  670. };
  671. class RegionSequenceView : public Component,
  672. public ARARegionSequence::Listener,
  673. public ChangeBroadcaster,
  674. private ARAPlaybackRegion::Listener
  675. {
  676. public:
  677. RegionSequenceView (ARARegionSequence& rs, WaveformCache& cache, double pixelPerSec)
  678. : regionSequence (rs), waveformCache (cache), zoomLevelPixelPerSecond (pixelPerSec)
  679. {
  680. regionSequence.addListener (this);
  681. for (auto* playbackRegion : regionSequence.getPlaybackRegions())
  682. createAndAddPlaybackRegionView (playbackRegion);
  683. updatePlaybackDuration();
  684. }
  685. ~RegionSequenceView() override
  686. {
  687. regionSequence.removeListener (this);
  688. for (const auto& it : playbackRegionViews)
  689. it.first->removeListener (this);
  690. }
  691. //==============================================================================
  692. // ARA Document change callback overrides
  693. void willRemovePlaybackRegionFromRegionSequence (ARARegionSequence*,
  694. ARAPlaybackRegion* playbackRegion) override
  695. {
  696. playbackRegion->removeListener (this);
  697. removeChildComponent (playbackRegionViews[playbackRegion].get());
  698. playbackRegionViews.erase (playbackRegion);
  699. updatePlaybackDuration();
  700. }
  701. void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion* playbackRegion) override
  702. {
  703. createAndAddPlaybackRegionView (playbackRegion);
  704. updatePlaybackDuration();
  705. }
  706. void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) override
  707. {
  708. playbackRegion->removeListener (this);
  709. removeChildComponent (playbackRegionViews[playbackRegion].get());
  710. playbackRegionViews.erase (playbackRegion);
  711. updatePlaybackDuration();
  712. }
  713. void willUpdatePlaybackRegionProperties (ARAPlaybackRegion*, ARAPlaybackRegion::PropertiesPtr) override
  714. {
  715. }
  716. void didUpdatePlaybackRegionProperties (ARAPlaybackRegion*) override
  717. {
  718. updatePlaybackDuration();
  719. }
  720. void resized() override
  721. {
  722. for (auto& pbr : playbackRegionViews)
  723. {
  724. const auto playbackRegion = pbr.first;
  725. pbr.second->setBounds (
  726. getLocalBounds()
  727. .withTrimmedLeft (roundToInt (playbackRegion->getStartInPlaybackTime() * zoomLevelPixelPerSecond))
  728. .withWidth (roundToInt (playbackRegion->getDurationInPlaybackTime() * zoomLevelPixelPerSecond)));
  729. }
  730. }
  731. auto getPlaybackDuration() const noexcept
  732. {
  733. return playbackDuration;
  734. }
  735. void setZoomLevel (double pixelPerSecond)
  736. {
  737. zoomLevelPixelPerSecond = pixelPerSecond;
  738. resized();
  739. }
  740. private:
  741. void createAndAddPlaybackRegionView (ARAPlaybackRegion* playbackRegion)
  742. {
  743. playbackRegionViews[playbackRegion] = std::make_unique<PlaybackRegionView> (*playbackRegion, waveformCache);
  744. playbackRegion->addListener (this);
  745. addAndMakeVisible (*playbackRegionViews[playbackRegion]);
  746. }
  747. void updatePlaybackDuration()
  748. {
  749. const auto iter = std::max_element (
  750. playbackRegionViews.begin(),
  751. playbackRegionViews.end(),
  752. [] (const auto& a, const auto& b) { return a.first->getEndInPlaybackTime() < b.first->getEndInPlaybackTime(); });
  753. playbackDuration = iter != playbackRegionViews.end() ? iter->first->getEndInPlaybackTime()
  754. : 0.0;
  755. sendChangeMessage();
  756. }
  757. ARARegionSequence& regionSequence;
  758. WaveformCache& waveformCache;
  759. std::unordered_map<ARAPlaybackRegion*, std::unique_ptr<PlaybackRegionView>> playbackRegionViews;
  760. double playbackDuration = 0.0;
  761. double zoomLevelPixelPerSecond;
  762. };
  763. class ZoomControls : public Component
  764. {
  765. public:
  766. ZoomControls()
  767. {
  768. addAndMakeVisible (zoomInButton);
  769. addAndMakeVisible (zoomOutButton);
  770. }
  771. void setZoomInCallback (std::function<void()> cb) { zoomInButton.onClick = std::move (cb); }
  772. void setZoomOutCallback (std::function<void()> cb) { zoomOutButton.onClick = std::move (cb); }
  773. void resized() override
  774. {
  775. FlexBox fb;
  776. fb.justifyContent = FlexBox::JustifyContent::flexEnd;
  777. for (auto* button : { &zoomInButton, &zoomOutButton })
  778. fb.items.add (FlexItem (*button).withMinHeight (30.0f).withMinWidth (30.0f).withMargin ({ 5, 5, 5, 0 }));
  779. fb.performLayout (getLocalBounds());
  780. }
  781. private:
  782. TextButton zoomInButton { "+" }, zoomOutButton { "-" };
  783. };
  784. class TrackHeader : public Component
  785. {
  786. public:
  787. explicit TrackHeader (const ARARegionSequence& regionSequenceIn) : regionSequence (regionSequenceIn)
  788. {
  789. update();
  790. addAndMakeVisible (trackNameLabel);
  791. }
  792. void resized() override
  793. {
  794. trackNameLabel.setBounds (getLocalBounds().reduced (2));
  795. }
  796. void paint (Graphics& g) override
  797. {
  798. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  799. g.fillRoundedRectangle (getLocalBounds().reduced (2).toType<float>(), 6.0f);
  800. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting());
  801. g.drawRoundedRectangle (getLocalBounds().reduced (2).toType<float>(), 6.0f, 1.0f);
  802. }
  803. private:
  804. void update()
  805. {
  806. const auto getWithDefaultValue =
  807. [] (const ARA::PlugIn::OptionalProperty<ARA::ARAUtf8String>& optional, String defaultValue)
  808. {
  809. if (const ARA::ARAUtf8String value = optional)
  810. return String (value);
  811. return defaultValue;
  812. };
  813. trackNameLabel.setText (getWithDefaultValue (regionSequence.getName(), "No track name"),
  814. NotificationType::dontSendNotification);
  815. }
  816. const ARARegionSequence& regionSequence;
  817. Label trackNameLabel;
  818. };
  819. constexpr auto trackHeight = 60;
  820. class VerticalLayoutViewportContent : public Component
  821. {
  822. public:
  823. void resized() override
  824. {
  825. auto bounds = getLocalBounds();
  826. for (auto* component : getChildren())
  827. {
  828. component->setBounds (bounds.removeFromTop (trackHeight));
  829. component->resized();
  830. }
  831. }
  832. void setOverlayComponent (Component* component)
  833. {
  834. if (overlayComponent != nullptr && overlayComponent != component)
  835. removeChildComponent (overlayComponent);
  836. addChildComponent (component);
  837. overlayComponent = component;
  838. }
  839. private:
  840. Component* overlayComponent = nullptr;
  841. };
  842. class VerticalLayoutViewport : public Viewport
  843. {
  844. public:
  845. VerticalLayoutViewport()
  846. {
  847. setViewedComponent (&content, false);
  848. }
  849. void paint (Graphics& g) override
  850. {
  851. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter());
  852. }
  853. std::function<void (Rectangle<int>)> onVisibleAreaChanged;
  854. VerticalLayoutViewportContent content;
  855. private:
  856. void visibleAreaChanged (const Rectangle<int>& newVisibleArea) override
  857. {
  858. NullCheckedInvocation::invoke (onVisibleAreaChanged, newVisibleArea);
  859. }
  860. };
  861. class OverlayComponent : public Component,
  862. private Timer
  863. {
  864. public:
  865. class PlayheadMarkerComponent : public Component
  866. {
  867. void paint (Graphics& g) override { g.fillAll (juce::Colours::yellow.darker (0.2f)); }
  868. };
  869. OverlayComponent (PlayHeadState& playHeadStateIn)
  870. : playHeadState (&playHeadStateIn)
  871. {
  872. addChildComponent (playheadMarker);
  873. setInterceptsMouseClicks (false, false);
  874. startTimerHz (30);
  875. }
  876. ~OverlayComponent() override
  877. {
  878. stopTimer();
  879. }
  880. void resized() override
  881. {
  882. doResize();
  883. }
  884. void setZoomLevel (double pixelPerSecondIn)
  885. {
  886. pixelPerSecond = pixelPerSecondIn;
  887. }
  888. void setHorizontalOffset (int offset)
  889. {
  890. horizontalOffset = offset;
  891. }
  892. private:
  893. void doResize()
  894. {
  895. if (playHeadState->isPlaying.load())
  896. {
  897. const auto markerX = playHeadState->timeInSeconds.load() * pixelPerSecond;
  898. const auto playheadLine = getLocalBounds().withTrimmedLeft ((int) (markerX - markerWidth / 2.0) - horizontalOffset)
  899. .removeFromLeft ((int) markerWidth);
  900. playheadMarker.setVisible (true);
  901. playheadMarker.setBounds (playheadLine);
  902. }
  903. else
  904. {
  905. playheadMarker.setVisible (false);
  906. }
  907. }
  908. void timerCallback() override
  909. {
  910. doResize();
  911. }
  912. static constexpr double markerWidth = 2.0;
  913. PlayHeadState* playHeadState;
  914. double pixelPerSecond = 1.0;
  915. int horizontalOffset = 0;
  916. PlayheadMarkerComponent playheadMarker;
  917. };
  918. class DocumentView : public Component,
  919. public ChangeListener,
  920. private ARADocument::Listener,
  921. private ARAEditorView::Listener
  922. {
  923. public:
  924. explicit DocumentView (ARADocument& document, PlayHeadState& playHeadState)
  925. : araDocument (document),
  926. overlay (playHeadState)
  927. {
  928. addAndMakeVisible (tracksBackground);
  929. viewport.onVisibleAreaChanged = [this] (const auto& r)
  930. {
  931. viewportHeightOffset = r.getY();
  932. overlay.setHorizontalOffset (r.getX());
  933. resized();
  934. };
  935. addAndMakeVisible (viewport);
  936. overlay.setZoomLevel (zoomLevelPixelPerSecond);
  937. addAndMakeVisible (overlay);
  938. zoomControls.setZoomInCallback ([this] { zoom (2.0); });
  939. zoomControls.setZoomOutCallback ([this] { zoom (0.5); });
  940. addAndMakeVisible (zoomControls);
  941. invalidateRegionSequenceViews();
  942. araDocument.addListener (this);
  943. }
  944. ~DocumentView() override
  945. {
  946. araDocument.removeListener (this);
  947. }
  948. //==============================================================================
  949. // ARADocument::Listener overrides
  950. void didReorderRegionSequencesInDocument (ARADocument*) override
  951. {
  952. invalidateRegionSequenceViews();
  953. }
  954. void didAddRegionSequenceToDocument (ARADocument*, ARARegionSequence*) override
  955. {
  956. invalidateRegionSequenceViews();
  957. }
  958. void willRemoveRegionSequenceFromDocument (ARADocument*, ARARegionSequence* regionSequence) override
  959. {
  960. removeRegionSequenceView (regionSequence);
  961. }
  962. void didEndEditing (ARADocument*) override
  963. {
  964. rebuildRegionSequenceViews();
  965. update();
  966. }
  967. //==============================================================================
  968. void changeListenerCallback (ChangeBroadcaster*) override
  969. {
  970. update();
  971. }
  972. //==============================================================================
  973. // ARAEditorView::Listener overrides
  974. void onNewSelection (const ARA::PlugIn::ViewSelection&) override
  975. {
  976. }
  977. void onHideRegionSequences (const std::vector<ARARegionSequence*>&) override
  978. {
  979. }
  980. //==============================================================================
  981. void paint (Graphics& g) override
  982. {
  983. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
  984. }
  985. void resized() override
  986. {
  987. auto bounds = getLocalBounds();
  988. const auto bottomControlsBounds = bounds.removeFromBottom (40);
  989. const auto headerBounds = bounds.removeFromLeft (headerWidth).reduced (2);
  990. zoomControls.setBounds (bottomControlsBounds);
  991. layOutVertically (headerBounds, trackHeaders, viewportHeightOffset);
  992. tracksBackground.setBounds (bounds);
  993. viewport.setBounds (bounds);
  994. overlay.setBounds (bounds);
  995. }
  996. //==============================================================================
  997. void setZoomLevel (double pixelPerSecond)
  998. {
  999. zoomLevelPixelPerSecond = pixelPerSecond;
  1000. for (const auto& view : regionSequenceViews)
  1001. view.second->setZoomLevel (zoomLevelPixelPerSecond);
  1002. overlay.setZoomLevel (zoomLevelPixelPerSecond);
  1003. update();
  1004. }
  1005. static constexpr int headerWidth = 120;
  1006. private:
  1007. struct RegionSequenceViewKey
  1008. {
  1009. explicit RegionSequenceViewKey (ARARegionSequence* regionSequence)
  1010. : orderIndex (regionSequence->getOrderIndex()), sequence (regionSequence)
  1011. {
  1012. }
  1013. bool operator< (const RegionSequenceViewKey& other) const
  1014. {
  1015. return std::tie (orderIndex, sequence) < std::tie (other.orderIndex, other.sequence);
  1016. }
  1017. ARA::ARAInt32 orderIndex;
  1018. ARARegionSequence* sequence;
  1019. };
  1020. void zoom (double factor)
  1021. {
  1022. zoomLevelPixelPerSecond = jlimit (minimumZoom, minimumZoom * 32, zoomLevelPixelPerSecond * factor);
  1023. setZoomLevel (zoomLevelPixelPerSecond);
  1024. }
  1025. template <typename T>
  1026. void layOutVertically (Rectangle<int> bounds, T& components, int verticalOffset = 0)
  1027. {
  1028. bounds = bounds.withY (bounds.getY() - verticalOffset).withHeight (bounds.getHeight() + verticalOffset);
  1029. for (auto& component : components)
  1030. {
  1031. component.second->setBounds (bounds.removeFromTop (trackHeight));
  1032. component.second->resized();
  1033. }
  1034. }
  1035. void update()
  1036. {
  1037. timelineLength = 0.0;
  1038. for (const auto& view : regionSequenceViews)
  1039. timelineLength = std::max (timelineLength, view.second->getPlaybackDuration());
  1040. const Rectangle<int> timelineSize (roundToInt (timelineLength * zoomLevelPixelPerSecond),
  1041. (int) regionSequenceViews.size() * trackHeight);
  1042. viewport.content.setSize (timelineSize.getWidth(), timelineSize.getHeight());
  1043. viewport.content.resized();
  1044. resized();
  1045. }
  1046. void addTrackViews (ARARegionSequence* regionSequence)
  1047. {
  1048. const auto insertIntoMap = [](auto& map, auto key, auto value) -> auto&
  1049. {
  1050. auto it = map.insert ({ std::move (key), std::move (value) });
  1051. return *(it.first->second);
  1052. };
  1053. auto& regionSequenceView = insertIntoMap (
  1054. regionSequenceViews,
  1055. RegionSequenceViewKey { regionSequence },
  1056. std::make_unique<RegionSequenceView> (*regionSequence, waveformCache, zoomLevelPixelPerSecond));
  1057. regionSequenceView.addChangeListener (this);
  1058. viewport.content.addAndMakeVisible (regionSequenceView);
  1059. auto& trackHeader = insertIntoMap (trackHeaders,
  1060. RegionSequenceViewKey { regionSequence },
  1061. std::make_unique<TrackHeader> (*regionSequence));
  1062. addAndMakeVisible (trackHeader);
  1063. }
  1064. void removeRegionSequenceView (ARARegionSequence* regionSequence)
  1065. {
  1066. const auto& view = regionSequenceViews.find (RegionSequenceViewKey { regionSequence });
  1067. if (view != regionSequenceViews.cend())
  1068. {
  1069. removeChildComponent (view->second.get());
  1070. regionSequenceViews.erase (view);
  1071. }
  1072. invalidateRegionSequenceViews();
  1073. }
  1074. void invalidateRegionSequenceViews()
  1075. {
  1076. regionSequenceViewsAreValid = false;
  1077. rebuildRegionSequenceViews();
  1078. }
  1079. void rebuildRegionSequenceViews()
  1080. {
  1081. if (! regionSequenceViewsAreValid && ! araDocument.getDocumentController()->isHostEditingDocument())
  1082. {
  1083. for (auto& view : regionSequenceViews)
  1084. removeChildComponent (view.second.get());
  1085. regionSequenceViews.clear();
  1086. for (auto& view : trackHeaders)
  1087. removeChildComponent (view.second.get());
  1088. trackHeaders.clear();
  1089. for (auto* regionSequence : araDocument.getRegionSequences())
  1090. {
  1091. addTrackViews (regionSequence);
  1092. }
  1093. update();
  1094. regionSequenceViewsAreValid = true;
  1095. }
  1096. }
  1097. class TracksBackgroundComponent : public Component
  1098. {
  1099. void paint (Graphics& g) override
  1100. {
  1101. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter());
  1102. }
  1103. };
  1104. static constexpr auto minimumZoom = 10.0;
  1105. static constexpr auto trackHeight = 60;
  1106. ARADocument& araDocument;
  1107. bool regionSequenceViewsAreValid = false;
  1108. double timelineLength = 0;
  1109. double zoomLevelPixelPerSecond = minimumZoom * 4;
  1110. WaveformCache waveformCache;
  1111. TracksBackgroundComponent tracksBackground;
  1112. std::map<RegionSequenceViewKey, std::unique_ptr<TrackHeader>> trackHeaders;
  1113. std::map<RegionSequenceViewKey, std::unique_ptr<RegionSequenceView>> regionSequenceViews;
  1114. VerticalLayoutViewport viewport;
  1115. OverlayComponent overlay;
  1116. ZoomControls zoomControls;
  1117. int viewportHeightOffset = 0;
  1118. };
  1119. class ARADemoPluginProcessorEditor : public AudioProcessorEditor,
  1120. public AudioProcessorEditorARAExtension
  1121. {
  1122. public:
  1123. explicit ARADemoPluginProcessorEditor (ARADemoPluginAudioProcessorImpl& p)
  1124. : AudioProcessorEditor (&p),
  1125. AudioProcessorEditorARAExtension (&p)
  1126. {
  1127. if (auto* editorView = getARAEditorView())
  1128. {
  1129. auto* document = ARADocumentControllerSpecialisation::getSpecialisedDocumentController(editorView->getDocumentController())->getDocument();
  1130. documentView = std::make_unique<DocumentView> (*document, p.playHeadState );
  1131. }
  1132. addAndMakeVisible (documentView.get());
  1133. // ARA requires that plugin editors are resizable to support tight integration
  1134. // into the host UI
  1135. setResizable (true, false);
  1136. setSize (400, 300);
  1137. }
  1138. //==============================================================================
  1139. void paint (Graphics& g) override
  1140. {
  1141. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  1142. if (! isARAEditorView())
  1143. {
  1144. g.setColour (Colours::white);
  1145. g.setFont (15.0f);
  1146. g.drawFittedText ("ARA host isn't detected. This plugin only supports ARA mode",
  1147. getLocalBounds(),
  1148. Justification::centred,
  1149. 1);
  1150. }
  1151. }
  1152. void resized() override
  1153. {
  1154. if (documentView != nullptr)
  1155. documentView->setBounds (getLocalBounds());
  1156. }
  1157. private:
  1158. std::unique_ptr<Component> documentView;
  1159. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginProcessorEditor)
  1160. };
  1161. class ARADemoPluginAudioProcessor : public ARADemoPluginAudioProcessorImpl
  1162. {
  1163. public:
  1164. bool hasEditor() const override { return true; }
  1165. AudioProcessorEditor* createEditor() override { return new ARADemoPluginProcessorEditor (*this); }
  1166. };