|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 7 End-User License
- Agreement and JUCE Privacy Policy.
-
- End User License Agreement: www.juce.com/juce-7-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- #pragma once
-
- #include <juce_core/system/juce_TargetPlatform.h>
-
- #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
-
- #include <JuceHeader.h>
-
- #include <ARA_API/ARAInterface.h>
- #include <ARA_Library/Dispatch/ARAHostDispatch.h>
-
- class FileAudioSource
- {
- auto getAudioSourceProperties() const
- {
- auto properties = ARAHostModel::AudioSource::getEmptyProperties();
- properties.name = formatReader->getFile().getFullPathName().toRawUTF8();
- properties.persistentID = formatReader->getFile().getFullPathName().toRawUTF8();
- properties.sampleCount = formatReader->lengthInSamples;
- properties.sampleRate = formatReader->sampleRate;
- properties.channelCount = (int) formatReader->numChannels;
- properties.merits64BitSamples = false;
- return properties;
- }
-
- public:
- FileAudioSource (ARA::Host::DocumentController& dc, const juce::File& file)
- : formatReader ([&file]
- {
- auto result = rawToUniquePtr (WavAudioFormat().createMemoryMappedReader (file));
- result->mapEntireFile();
- return result;
- }()),
- audioSource (Converter::toHostRef (this), dc, getAudioSourceProperties())
- {
- audioSource.enableAudioSourceSamplesAccess (true);
- }
-
- bool readAudioSamples (float* const* buffers, int64 startSample, int64 numSamples)
- {
- // TODO: the ARA interface defines numSamples as int64. We should do multiple reads if necessary with the reader.
- if (numSamples > std::numeric_limits<int>::max())
- return false;
-
- return formatReader->read (buffers, (int) formatReader->numChannels, startSample, (int) (numSamples));
- }
-
- bool readAudioSamples (double* const* buffers, int64 startSample, int64 numSamples)
- {
- ignoreUnused (buffers, startSample, numSamples);
- return false;
- }
-
- MemoryMappedAudioFormatReader& getFormatReader() const { return *formatReader; }
-
- auto getPluginRef() const { return audioSource.getPluginRef(); }
-
- auto& getSource() { return audioSource; }
-
- using Converter = ARAHostModel::ConversionFunctions<FileAudioSource*, ARA::ARAAudioSourceHostRef>;
-
- private:
- std::unique_ptr<MemoryMappedAudioFormatReader> formatReader;
- ARAHostModel::AudioSource audioSource;
- };
-
- //==============================================================================
- class MusicalContext
- {
- auto getMusicalContextProperties() const
- {
- auto properties = ARAHostModel::MusicalContext::getEmptyProperties();
- properties.name = "MusicalContext";
- properties.orderIndex = 0;
- properties.color = nullptr;
- return properties;
- }
-
- public:
- MusicalContext (ARA::Host::DocumentController& dc)
- : context (Converter::toHostRef (this), dc, getMusicalContextProperties())
- {
- }
-
- auto getPluginRef() const { return context.getPluginRef(); }
-
- private:
- using Converter = ARAHostModel::ConversionFunctions<MusicalContext*, ARA::ARAMusicalContextHostRef>;
-
- ARAHostModel::MusicalContext context;
- };
-
- //==============================================================================
- class RegionSequence
- {
- auto getRegionSequenceProperties() const
- {
- auto properties = ARAHostModel::RegionSequence::getEmptyProperties();
- properties.name = name.toRawUTF8();
- properties.orderIndex = 0;
- properties.musicalContextRef = context.getPluginRef();
- properties.color = nullptr;
- return properties;
- }
-
- public:
- RegionSequence (ARA::Host::DocumentController& dc, MusicalContext& contextIn, String nameIn)
- : context (contextIn),
- name (std::move (nameIn)),
- sequence (Converter::toHostRef (this), dc, getRegionSequenceProperties())
- {
- }
-
- auto& getMusicalContext() const { return context; }
- auto getPluginRef() const { return sequence.getPluginRef(); }
-
- private:
- using Converter = ARAHostModel::ConversionFunctions<RegionSequence*, ARA::ARARegionSequenceHostRef>;
-
- MusicalContext& context;
- String name;
- ARAHostModel::RegionSequence sequence;
- };
-
- class AudioModification
- {
- auto getProperties() const
- {
- auto properties = ARAHostModel::AudioModification::getEmptyProperties();
- properties.persistentID = "x";
- return properties;
- }
-
- public:
- AudioModification (ARA::Host::DocumentController& dc, FileAudioSource& source)
- : modification (Converter::toHostRef (this), dc, source.getSource(), getProperties())
- {
- }
-
- auto& getModification() { return modification; }
-
- private:
- using Converter = ARAHostModel::ConversionFunctions<AudioModification*, ARA::ARAAudioModificationHostRef>;
-
- ARAHostModel::AudioModification modification;
- };
-
- //==============================================================================
- class PlaybackRegion
- {
- auto getPlaybackRegionProperties() const
- {
- auto properties = ARAHostModel::PlaybackRegion::getEmptyProperties();
- properties.transformationFlags = ARA::kARAPlaybackTransformationNoChanges;
- properties.startInModificationTime = 0.0;
- const auto& formatReader = audioSource.getFormatReader();
- properties.durationInModificationTime = formatReader.lengthInSamples / formatReader.sampleRate;
- properties.startInPlaybackTime = 0.0;
- properties.durationInPlaybackTime = properties.durationInModificationTime;
- properties.musicalContextRef = sequence.getMusicalContext().getPluginRef();
- properties.regionSequenceRef = sequence.getPluginRef();
-
- properties.name = nullptr;
- properties.color = nullptr;
- return properties;
- }
-
- public:
- PlaybackRegion (ARA::Host::DocumentController& dc,
- RegionSequence& s,
- AudioModification& m,
- FileAudioSource& source)
- : sequence (s),
- audioSource (source),
- region (Converter::toHostRef (this), dc, m.getModification(), getPlaybackRegionProperties())
- {
- jassert (source.getPluginRef() == m.getModification().getAudioSource().getPluginRef());
- }
-
- auto& getPlaybackRegion() { return region; }
-
- private:
- using Converter = ARAHostModel::ConversionFunctions<PlaybackRegion*, ARA::ARAPlaybackRegionHostRef>;
-
- RegionSequence& sequence;
- FileAudioSource& audioSource;
- ARAHostModel::PlaybackRegion region;
- };
-
- //==============================================================================
- class AudioAccessController : public ARA::Host::AudioAccessControllerInterface
- {
- public:
- ARA::ARAAudioReaderHostRef createAudioReaderForSource (ARA::ARAAudioSourceHostRef audioSourceHostRef,
- bool use64BitSamples) noexcept override
- {
- auto audioReader = std::make_unique<AudioReader> (audioSourceHostRef, use64BitSamples);
- auto audioReaderHostRef = Converter::toHostRef (audioReader.get());
- auto* readerPtr = audioReader.get();
- audioReaders.emplace (readerPtr, std::move (audioReader));
- return audioReaderHostRef;
- }
-
- bool readAudioSamples (ARA::ARAAudioReaderHostRef readerRef,
- ARA::ARASamplePosition samplePosition,
- ARA::ARASampleCount samplesPerChannel,
- void* const* buffers) noexcept override
- {
- const auto use64BitSamples = Converter::fromHostRef (readerRef)->use64Bit;
- auto* audioSource = FileAudioSource::Converter::fromHostRef (Converter::fromHostRef (readerRef)->sourceHostRef);
-
- if (use64BitSamples)
- return audioSource->readAudioSamples (
- reinterpret_cast<double* const*> (buffers), samplePosition, samplesPerChannel);
-
- return audioSource->readAudioSamples (
- reinterpret_cast<float* const*> (buffers), samplePosition, samplesPerChannel);
- }
-
- void destroyAudioReader (ARA::ARAAudioReaderHostRef readerRef) noexcept override
- {
- audioReaders.erase (Converter::fromHostRef (readerRef));
- }
-
- private:
- struct AudioReader
- {
- AudioReader (ARA::ARAAudioSourceHostRef source, bool use64BitSamples)
- : sourceHostRef (source), use64Bit (use64BitSamples)
- {
- }
-
- ARA::ARAAudioSourceHostRef sourceHostRef;
- bool use64Bit;
- };
-
- using Converter = ARAHostModel::ConversionFunctions<AudioReader*, ARA::ARAAudioReaderHostRef>;
-
- std::map<AudioReader*, std::unique_ptr<AudioReader>> audioReaders;
- };
-
- class ArchivingController : public ARA::Host::ArchivingControllerInterface
- {
- public:
- using ReaderConverter = ARAHostModel::ConversionFunctions<MemoryBlock*, ARA::ARAArchiveReaderHostRef>;
- using WriterConverter = ARAHostModel::ConversionFunctions<MemoryOutputStream*, ARA::ARAArchiveWriterHostRef>;
-
- ARA::ARASize getArchiveSize (ARA::ARAArchiveReaderHostRef archiveReaderHostRef) noexcept override
- {
- return (ARA::ARASize) ReaderConverter::fromHostRef (archiveReaderHostRef)->getSize();
- }
-
- bool readBytesFromArchive (ARA::ARAArchiveReaderHostRef archiveReaderHostRef,
- ARA::ARASize position,
- ARA::ARASize length,
- ARA::ARAByte* buffer) noexcept override
- {
- auto* archiveReader = ReaderConverter::fromHostRef (archiveReaderHostRef);
-
- if ((position + length) <= archiveReader->getSize())
- {
- std::memcpy (buffer, addBytesToPointer (archiveReader->getData(), position), length);
- return true;
- }
-
- return false;
- }
-
- bool writeBytesToArchive (ARA::ARAArchiveWriterHostRef archiveWriterHostRef,
- ARA::ARASize position,
- ARA::ARASize length,
- const ARA::ARAByte* buffer) noexcept override
- {
- auto* archiveWriter = WriterConverter::fromHostRef (archiveWriterHostRef);
-
- if (archiveWriter->setPosition ((int64) position) && archiveWriter->write (buffer, length))
- return true;
-
- return false;
- }
-
- void notifyDocumentArchivingProgress (float value) noexcept override { ignoreUnused (value); }
-
- void notifyDocumentUnarchivingProgress (float value) noexcept override { ignoreUnused (value); }
-
- ARA::ARAPersistentID getDocumentArchiveID (ARA::ARAArchiveReaderHostRef archiveReaderHostRef) noexcept override
- {
- ignoreUnused (archiveReaderHostRef);
-
- return nullptr;
- }
- };
-
- class ContentAccessController : public ARA::Host::ContentAccessControllerInterface
- {
- public:
- using Converter = ARAHostModel::ConversionFunctions<ARA::ARAContentType, ARA::ARAContentReaderHostRef>;
-
- bool isMusicalContextContentAvailable (ARA::ARAMusicalContextHostRef musicalContextHostRef,
- ARA::ARAContentType type) noexcept override
- {
- ignoreUnused (musicalContextHostRef);
-
- return (type == ARA::kARAContentTypeTempoEntries || type == ARA::kARAContentTypeBarSignatures);
- }
-
- ARA::ARAContentGrade getMusicalContextContentGrade (ARA::ARAMusicalContextHostRef musicalContextHostRef,
- ARA::ARAContentType type) noexcept override
- {
- ignoreUnused (musicalContextHostRef, type);
-
- return ARA::kARAContentGradeInitial;
- }
-
- ARA::ARAContentReaderHostRef
- createMusicalContextContentReader (ARA::ARAMusicalContextHostRef musicalContextHostRef,
- ARA::ARAContentType type,
- const ARA::ARAContentTimeRange* range) noexcept override
- {
- ignoreUnused (musicalContextHostRef, range);
-
- return Converter::toHostRef (type);
- }
-
- bool isAudioSourceContentAvailable (ARA::ARAAudioSourceHostRef audioSourceHostRef,
- ARA::ARAContentType type) noexcept override
- {
- ignoreUnused (audioSourceHostRef, type);
-
- return false;
- }
-
- ARA::ARAContentGrade getAudioSourceContentGrade (ARA::ARAAudioSourceHostRef audioSourceHostRef,
- ARA::ARAContentType type) noexcept override
- {
- ignoreUnused (audioSourceHostRef, type);
-
- return 0;
- }
-
- ARA::ARAContentReaderHostRef
- createAudioSourceContentReader (ARA::ARAAudioSourceHostRef audioSourceHostRef,
- ARA::ARAContentType type,
- const ARA::ARAContentTimeRange* range) noexcept override
- {
- ignoreUnused (audioSourceHostRef, type, range);
-
- return nullptr;
- }
-
- ARA::ARAInt32 getContentReaderEventCount (ARA::ARAContentReaderHostRef contentReaderHostRef) noexcept override
- {
- const auto contentType = Converter::fromHostRef (contentReaderHostRef);
-
- if (contentType == ARA::kARAContentTypeTempoEntries || contentType == ARA::kARAContentTypeBarSignatures)
- return 2;
-
- return 0;
- }
-
- const void* getContentReaderDataForEvent (ARA::ARAContentReaderHostRef contentReaderHostRef,
- ARA::ARAInt32 eventIndex) noexcept override
- {
- if (Converter::fromHostRef (contentReaderHostRef) == ARA::kARAContentTypeTempoEntries)
- {
- if (eventIndex == 0)
- {
- tempoEntry.timePosition = 0.0;
- tempoEntry.quarterPosition = 0.0;
- }
- else if (eventIndex == 1)
- {
- tempoEntry.timePosition = 2.0;
- tempoEntry.quarterPosition = 4.0;
- }
-
- return &tempoEntry;
- }
- else if (Converter::fromHostRef (contentReaderHostRef) == ARA::kARAContentTypeBarSignatures)
- {
- if (eventIndex == 0)
- {
- barSignature.position = 0.0;
- barSignature.numerator = 4;
- barSignature.denominator = 4;
- }
-
- if (eventIndex == 1)
- {
- barSignature.position = 1.0;
- barSignature.numerator = 4;
- barSignature.denominator = 4;
- }
-
- return &barSignature;
- }
-
- jassertfalse;
- return nullptr;
- }
-
- void destroyContentReader (ARA::ARAContentReaderHostRef contentReaderHostRef) noexcept override
- {
- ignoreUnused (contentReaderHostRef);
- }
-
- ARA::ARAContentTempoEntry tempoEntry;
- ARA::ARAContentBarSignature barSignature;
- };
-
- class ModelUpdateController : public ARA::Host::ModelUpdateControllerInterface
- {
- public:
- void notifyAudioSourceAnalysisProgress (ARA::ARAAudioSourceHostRef audioSourceHostRef,
- ARA::ARAAnalysisProgressState state,
- float value) noexcept override
- {
- ignoreUnused (audioSourceHostRef, state, value);
- }
-
- void notifyAudioSourceContentChanged (ARA::ARAAudioSourceHostRef audioSourceHostRef,
- const ARA::ARAContentTimeRange* range,
- ARA::ContentUpdateScopes scopeFlags) noexcept override
- {
- ignoreUnused (audioSourceHostRef, range, scopeFlags);
- }
-
- void notifyAudioModificationContentChanged (ARA::ARAAudioModificationHostRef audioModificationHostRef,
- const ARA::ARAContentTimeRange* range,
- ARA::ContentUpdateScopes scopeFlags) noexcept override
- {
- ignoreUnused (audioModificationHostRef, range, scopeFlags);
- }
-
- void notifyPlaybackRegionContentChanged (ARA::ARAPlaybackRegionHostRef playbackRegionHostRef,
- const ARA::ARAContentTimeRange* range,
- ARA::ContentUpdateScopes scopeFlags) noexcept override
- {
- ignoreUnused (playbackRegionHostRef, range, scopeFlags);
- }
- };
-
- class PlaybackController : public ARA::Host::PlaybackControllerInterface
- {
- public:
- void requestStartPlayback() noexcept override {}
- void requestStopPlayback() noexcept override {}
-
- void requestSetPlaybackPosition (ARA::ARATimePosition timePosition) noexcept override
- {
- ignoreUnused (timePosition);
- }
-
- void requestSetCycleRange (ARA::ARATimePosition startTime, ARA::ARATimeDuration duration) noexcept override
- {
- ignoreUnused (startTime, duration);
- }
-
- void requestEnableCycle (bool enable) noexcept override { ignoreUnused (enable); }
- };
-
- struct SimplePlayHead : public juce::AudioPlayHead
- {
- bool getCurrentPosition (CurrentPositionInfo& result) override
- {
- result.timeInSamples = timeInSamples.load();
- result.isPlaying = isPlaying.load();
- return true;
- }
-
- std::atomic<int64_t> timeInSamples { 0 };
- std::atomic<bool> isPlaying { false };
- };
-
- struct HostPlaybackController
- {
- virtual ~HostPlaybackController() = default;
-
- virtual void setPlaying (bool isPlaying) = 0;
- virtual void goToStart() = 0;
- virtual File getAudioSource() const = 0;
- virtual void setAudioSource (File audioSourceFile) = 0;
- virtual void clearAudioSource() = 0;
- };
-
- class AudioSourceComponent : public Component,
- public FileDragAndDropTarget,
- public ChangeListener
- {
- public:
- explicit AudioSourceComponent (HostPlaybackController& controller, juce::ChangeBroadcaster& bc)
- : hostPlaybackController (controller),
- broadcaster (bc),
- waveformComponent (*this)
- {
- audioSourceLabel.setText ("You can drag and drop .wav files here", NotificationType::dontSendNotification);
-
- addAndMakeVisible (audioSourceLabel);
- addAndMakeVisible (waveformComponent);
-
- playButton.setButtonText ("Play / Pause");
- playButton.onClick = [this]
- {
- isPlaying = ! isPlaying;
- hostPlaybackController.setPlaying (isPlaying);
- };
-
- goToStartButton.setButtonText ("Go to start");
- goToStartButton.onClick = [this] { hostPlaybackController.goToStart(); };
-
- addAndMakeVisible (goToStartButton);
- addAndMakeVisible (playButton);
-
- broadcaster.addChangeListener (this);
-
- update();
- }
-
- ~AudioSourceComponent() override
- {
- broadcaster.removeChangeListener (this);
- }
-
- void changeListenerCallback (ChangeBroadcaster*) override
- {
- update();
- }
-
- void resized() override
- {
- auto localBounds = getLocalBounds();
- auto buttonsArea = localBounds.removeFromBottom (40).reduced (5);
- auto waveformArea = localBounds.removeFromBottom (150).reduced (5);
-
- juce::FlexBox fb;
- fb.justifyContent = juce::FlexBox::JustifyContent::center;
- fb.alignContent = juce::FlexBox::AlignContent::center;
-
- fb.items = { juce::FlexItem (goToStartButton).withMinWidth (100.0f).withMinHeight ((float) buttonsArea.getHeight()),
- juce::FlexItem (playButton).withMinWidth (100.0f).withMinHeight ((float) buttonsArea.getHeight()) };
-
- fb.performLayout (buttonsArea);
-
- waveformComponent.setBounds (waveformArea);
-
- audioSourceLabel.setBounds (localBounds);
- }
-
- bool isInterestedInFileDrag (const StringArray& files) override
- {
- if (files.size() != 1)
- return false;
-
- if (files.getReference (0).endsWithIgnoreCase (".wav"))
- return true;
-
- return false;
- }
-
- void update()
- {
- const auto currentAudioSource = hostPlaybackController.getAudioSource();
-
- if (currentAudioSource.existsAsFile())
- {
- waveformComponent.setSource (currentAudioSource);
- audioSourceLabel.setText (currentAudioSource.getFullPathName(),
- NotificationType::dontSendNotification);
- }
- else
- {
- waveformComponent.clearSource();
- audioSourceLabel.setText ("You can drag and drop .wav files here", NotificationType::dontSendNotification);
- }
- }
-
- void filesDropped (const StringArray& files, int, int) override
- {
- hostPlaybackController.setAudioSource (files.getReference (0));
- update();
- }
-
- private:
- class WaveformComponent : public Component,
- public ChangeListener
- {
- public:
- WaveformComponent (AudioSourceComponent& p)
- : parent (p),
- thumbCache (7),
- audioThumb (128, formatManager, thumbCache)
- {
- setWantsKeyboardFocus (true);
- formatManager.registerBasicFormats();
- audioThumb.addChangeListener (this);
- }
-
- ~WaveformComponent() override
- {
- audioThumb.removeChangeListener (this);
- }
-
- void mouseDown (const MouseEvent&) override
- {
- isSelected = true;
- repaint();
- }
-
- void changeListenerCallback (ChangeBroadcaster*) override
- {
- repaint();
- }
-
- void paint (juce::Graphics& g) override
- {
- if (! isEmpty)
- {
- auto rect = getLocalBounds();
-
- const auto waveformColour = Colours::cadetblue;
-
- if (rect.getWidth() > 2)
- {
- g.setColour (isSelected ? juce::Colours::yellow : juce::Colours::black);
- g.drawRect (rect);
- rect.reduce (1, 1);
- g.setColour (waveformColour.darker (1.0f));
- g.fillRect (rect);
- }
-
- g.setColour (Colours::cadetblue);
- audioThumb.drawChannels (g, rect, 0.0, audioThumb.getTotalLength(), 1.0f);
- }
- }
-
- void setSource (const File& source)
- {
- isEmpty = false;
- audioThumb.setSource (new FileInputSource (source));
- }
-
- void clearSource()
- {
- isEmpty = true;
- isSelected = false;
- audioThumb.clear();
- }
-
- bool keyPressed (const KeyPress& key) override
- {
- if (isSelected && key == KeyPress::deleteKey)
- {
- parent.hostPlaybackController.clearAudioSource();
- return true;
- }
-
- return false;
- }
-
- private:
- AudioSourceComponent& parent;
-
- bool isEmpty = true;
- bool isSelected = false;
- AudioFormatManager formatManager;
- AudioThumbnailCache thumbCache;
- AudioThumbnail audioThumb;
- };
-
- HostPlaybackController& hostPlaybackController;
- juce::ChangeBroadcaster& broadcaster;
- Label audioSourceLabel;
- WaveformComponent waveformComponent;
- bool isPlaying { false };
- TextButton playButton, goToStartButton;
- };
-
- class ARAPluginInstanceWrapper : public AudioPluginInstance
- {
- public:
- class ARATestHost : public HostPlaybackController,
- public juce::ChangeBroadcaster
- {
- public:
- class Editor : public AudioProcessorEditor
- {
- public:
- explicit Editor (ARATestHost& araTestHost)
- : AudioProcessorEditor (araTestHost.getAudioPluginInstance()),
- audioSourceComponent (araTestHost, araTestHost)
- {
- audioSourceComponent.update();
- addAndMakeVisible (audioSourceComponent);
- setSize (512, 220);
- }
-
- ~Editor() override { getAudioProcessor()->editorBeingDeleted (this); }
-
- void resized() override { audioSourceComponent.setBounds (getLocalBounds()); }
-
- private:
- AudioSourceComponent audioSourceComponent;
- };
-
- explicit ARATestHost (ARAPluginInstanceWrapper& instanceIn)
- : instance (instanceIn)
- {
- if (instance.inner->getPluginDescription().hasARAExtension)
- {
- instance.inner->setPlayHead (&playHead);
-
- createARAFactoryAsync (*instance.inner, [this] (ARAFactoryWrapper araFactory)
- {
- init (std::move (araFactory));
- });
- }
- }
-
- void init (ARAFactoryWrapper araFactory)
- {
- if (araFactory.get() != nullptr)
- {
- documentController = ARAHostDocumentController::create (std::move (araFactory),
- "AudioPluginHostDocument",
- std::make_unique<AudioAccessController>(),
- std::make_unique<ArchivingController>(),
- std::make_unique<ContentAccessController>(),
- std::make_unique<ModelUpdateController>(),
- std::make_unique<PlaybackController>());
-
- if (documentController != nullptr)
- {
- const auto allRoles = ARA::kARAPlaybackRendererRole | ARA::kARAEditorRendererRole | ARA::kARAEditorViewRole;
- const auto plugInExtensionInstance = documentController->bindDocumentToPluginInstance (*instance.inner,
- allRoles,
- allRoles);
- playbackRenderer = plugInExtensionInstance.getPlaybackRendererInterface();
- editorRenderer = plugInExtensionInstance.getEditorRendererInterface();
- synchronizeStateWithDocumentController();
- }
- else
- jassertfalse;
- }
- else
- jassertfalse;
- }
-
- void getStateInformation (juce::MemoryBlock& b)
- {
- std::lock_guard<std::mutex> configurationLock (instance.innerMutex);
-
- if (context != nullptr)
- context->getStateInformation (b);
- }
-
- void setStateInformation (const void* d, int s)
- {
- {
- std::lock_guard<std::mutex> lock { contextUpdateSourceMutex };
- contextUpdateSource = ContextUpdateSource { d, s };
- }
-
- synchronise();
- }
-
- ~ARATestHost() override { instance.inner->releaseResources(); }
-
- void afterProcessBlock (int numSamples)
- {
- const auto isPlayingNow = isPlaying.load();
- playHead.isPlaying.store (isPlayingNow);
-
- if (isPlayingNow)
- {
- const auto currentAudioSourceLength = audioSourceLength.load();
- const auto currentPlayHeadPosition = playHead.timeInSamples.load();
-
- // Rudimentary attempt to not seek beyond our sample data, assuming a fairly stable numSamples
- // value. We should gain control over calling the AudioProcessorGraph's processBlock() calls so
- // that we can do sample precise looping.
- if (currentAudioSourceLength - currentPlayHeadPosition < numSamples)
- playHead.timeInSamples.store (0);
- else
- playHead.timeInSamples.fetch_add (numSamples);
- }
-
- if (goToStartSignal.exchange (false))
- playHead.timeInSamples.store (0);
- }
-
- File getAudioSource() const override
- {
- std::lock_guard<std::mutex> lock { instance.innerMutex };
-
- if (context != nullptr)
- return context->audioFile;
-
- return {};
- }
-
- void setAudioSource (File audioSourceFile) override
- {
- if (audioSourceFile.existsAsFile())
- {
- {
- std::lock_guard<std::mutex> lock { contextUpdateSourceMutex };
- contextUpdateSource = ContextUpdateSource (std::move (audioSourceFile));
- }
-
- synchronise();
- }
- }
-
- void clearAudioSource() override
- {
- {
- std::lock_guard<std::mutex> lock { contextUpdateSourceMutex };
- contextUpdateSource = ContextUpdateSource (ContextUpdateSource::Type::reset);
- }
-
- synchronise();
- }
-
- void setPlaying (bool isPlayingIn) override { isPlaying.store (isPlayingIn); }
-
- void goToStart() override { goToStartSignal.store (true); }
-
- Editor* createEditor() { return new Editor (*this); }
-
- AudioPluginInstance& getAudioPluginInstance() { return instance; }
-
- private:
- /** Use this to put the plugin in an unprepared state for the duration of adding and removing PlaybackRegions
- to and from Renderers.
- */
- class ScopedPluginDeactivator
- {
- public:
- explicit ScopedPluginDeactivator (ARAPluginInstanceWrapper& inst) : instance (inst)
- {
- if (instance.prepareToPlayParams.isValid)
- instance.inner->releaseResources();
- }
-
- ~ScopedPluginDeactivator()
- {
- if (instance.prepareToPlayParams.isValid)
- instance.inner->prepareToPlay (instance.prepareToPlayParams.sampleRate,
- instance.prepareToPlayParams.samplesPerBlock);
- }
-
- private:
- ARAPluginInstanceWrapper& instance;
-
- JUCE_DECLARE_NON_COPYABLE (ScopedPluginDeactivator)
- };
-
- class ContextUpdateSource
- {
- public:
- enum class Type
- {
- empty,
- audioSourceFile,
- stateInformation,
- reset
- };
-
- ContextUpdateSource() = default;
-
- explicit ContextUpdateSource (const File& file)
- : type (Type::audioSourceFile),
- audioSourceFile (file)
- {
- }
-
- ContextUpdateSource (const void* d, int s)
- : type (Type::stateInformation),
- stateInformation (d, (size_t) s)
- {
- }
-
- ContextUpdateSource (Type t) : type (t)
- {
- jassert (t == Type::reset);
- }
-
- Type getType() const { return type; }
-
- const File& getAudioSourceFile() const
- {
- jassert (type == Type::audioSourceFile);
-
- return audioSourceFile;
- }
-
- const MemoryBlock& getStateInformation() const
- {
- jassert (type == Type::stateInformation);
-
- return stateInformation;
- }
-
- private:
- Type type = Type::empty;
-
- File audioSourceFile;
- MemoryBlock stateInformation;
- };
-
- void synchronise()
- {
- const SpinLock::ScopedLockType scope (instance.innerProcessBlockFlag);
- std::lock_guard<std::mutex> configurationLock (instance.innerMutex);
- synchronizeStateWithDocumentController();
- }
-
- void synchronizeStateWithDocumentController()
- {
- bool resetContext = false;
-
- auto newContext = [&]() -> std::unique_ptr<Context>
- {
- std::lock_guard<std::mutex> lock { contextUpdateSourceMutex };
-
- switch (contextUpdateSource.getType())
- {
- case ContextUpdateSource::Type::empty:
- return {};
-
- case ContextUpdateSource::Type::audioSourceFile:
- if (! (contextUpdateSource.getAudioSourceFile().existsAsFile()))
- return {};
-
- {
- const ARAEditGuard editGuard (documentController->getDocumentController());
- return std::make_unique<Context> (documentController->getDocumentController(),
- contextUpdateSource.getAudioSourceFile());
- }
-
- case ContextUpdateSource::Type::stateInformation:
- jassert (contextUpdateSource.getStateInformation().getSize() <= std::numeric_limits<int>::max());
-
- return Context::createFromStateInformation (documentController->getDocumentController(),
- contextUpdateSource.getStateInformation().getData(),
- (int) contextUpdateSource.getStateInformation().getSize());
-
- case ContextUpdateSource::Type::reset:
- resetContext = true;
- return {};
- }
-
- jassertfalse;
- return {};
- }();
-
- if (newContext != nullptr)
- {
- {
- ScopedPluginDeactivator deactivator (instance);
-
- context = std::move (newContext);
- audioSourceLength.store (context->fileAudioSource.getFormatReader().lengthInSamples);
-
- auto& region = context->playbackRegion.getPlaybackRegion();
- playbackRenderer.add (region);
- editorRenderer.add (region);
- }
-
- sendChangeMessage();
- }
-
- if (resetContext)
- {
- {
- ScopedPluginDeactivator deactivator (instance);
-
- context.reset();
- audioSourceLength.store (0);
- }
-
- sendChangeMessage();
- }
- }
-
- struct Context
- {
- Context (ARA::Host::DocumentController& dc, const File& audioFileIn)
- : audioFile (audioFileIn),
- musicalContext (dc),
- regionSequence (dc, musicalContext, "track 1"),
- fileAudioSource (dc, audioFile),
- audioModification (dc, fileAudioSource),
- playbackRegion (dc, regionSequence, audioModification, fileAudioSource)
- {
- }
-
- static std::unique_ptr<Context> createFromStateInformation (ARA::Host::DocumentController& dc, const void* d, int s)
- {
- if (auto xml = getXmlFromBinary (d, s))
- {
- if (xml->hasTagName (xmlRootTag))
- {
- File file { xml->getStringAttribute (xmlAudioFileAttrib) };
-
- if (file.existsAsFile())
- return std::make_unique<Context> (dc, std::move (file));
- }
- }
-
- return {};
- }
-
- void getStateInformation (juce::MemoryBlock& b)
- {
- XmlElement root { xmlRootTag };
- root.setAttribute (xmlAudioFileAttrib, audioFile.getFullPathName());
- copyXmlToBinary (root, b);
- }
-
- const static Identifier xmlRootTag;
- const static Identifier xmlAudioFileAttrib;
-
- File audioFile;
-
- MusicalContext musicalContext;
- RegionSequence regionSequence;
- FileAudioSource fileAudioSource;
- AudioModification audioModification;
- PlaybackRegion playbackRegion;
- };
-
- SimplePlayHead playHead;
- ARAPluginInstanceWrapper& instance;
-
- std::unique_ptr<ARAHostDocumentController> documentController;
- ARAHostModel::PlaybackRendererInterface playbackRenderer;
- ARAHostModel::EditorRendererInterface editorRenderer;
-
- std::unique_ptr<Context> context;
-
- mutable std::mutex contextUpdateSourceMutex;
- ContextUpdateSource contextUpdateSource;
-
- std::atomic<bool> isPlaying { false };
- std::atomic<bool> goToStartSignal { false };
- std::atomic<int64> audioSourceLength { 0 };
- };
-
- explicit ARAPluginInstanceWrapper (std::unique_ptr<AudioPluginInstance> innerIn)
- : inner (std::move (innerIn)), araHost (*this)
- {
- jassert (inner != nullptr);
-
- for (auto isInput : { true, false })
- matchBuses (isInput);
-
- setBusesLayout (inner->getBusesLayout());
- }
-
- //==============================================================================
- AudioProcessorEditor* createARAHostEditor() { return araHost.createEditor(); }
-
- //==============================================================================
- const String getName() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->getName();
- }
-
- StringArray getAlternateDisplayNames() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->getAlternateDisplayNames();
- }
-
- double getTailLengthSeconds() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->getTailLengthSeconds();
- }
-
- bool acceptsMidi() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->acceptsMidi();
- }
-
- bool producesMidi() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->producesMidi();
- }
-
- AudioProcessorEditor* createEditor() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->createEditorIfNeeded();
- }
-
- bool hasEditor() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->hasEditor();
- }
-
- int getNumPrograms() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->getNumPrograms();
- }
-
- int getCurrentProgram() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->getCurrentProgram();
- }
-
- void setCurrentProgram (int i) override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->setCurrentProgram (i);
- }
-
- const String getProgramName (int i) override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->getProgramName (i);
- }
-
- void changeProgramName (int i, const String& n) override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->changeProgramName (i, n);
- }
-
- void getStateInformation (juce::MemoryBlock& b) override
- {
- XmlElement state ("ARAPluginInstanceWrapperState");
-
- {
- MemoryBlock m;
- araHost.getStateInformation (m);
- state.createNewChildElement ("host")->addTextElement (m.toBase64Encoding());
- }
-
- {
- std::lock_guard<std::mutex> lock (innerMutex);
-
- MemoryBlock m;
- inner->getStateInformation (m);
- state.createNewChildElement ("plugin")->addTextElement (m.toBase64Encoding());
- }
-
- copyXmlToBinary (state, b);
- }
-
- void setStateInformation (const void* d, int s) override
- {
- if (auto xml = getXmlFromBinary (d, s))
- {
- if (xml->hasTagName ("ARAPluginInstanceWrapperState"))
- {
- if (auto* hostState = xml->getChildByName ("host"))
- {
- MemoryBlock m;
- m.fromBase64Encoding (hostState->getAllSubText());
- jassert (m.getSize() <= std::numeric_limits<int>::max());
- araHost.setStateInformation (m.getData(), (int) m.getSize());
- }
-
- if (auto* pluginState = xml->getChildByName ("plugin"))
- {
- std::lock_guard<std::mutex> lock (innerMutex);
-
- MemoryBlock m;
- m.fromBase64Encoding (pluginState->getAllSubText());
- jassert (m.getSize() <= std::numeric_limits<int>::max());
- inner->setStateInformation (m.getData(), (int) m.getSize());
- }
- }
- }
- }
-
- void getCurrentProgramStateInformation (juce::MemoryBlock& b) override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->getCurrentProgramStateInformation (b);
- }
-
- void setCurrentProgramStateInformation (const void* d, int s) override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->setCurrentProgramStateInformation (d, s);
- }
-
- void prepareToPlay (double sr, int bs) override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->setRateAndBufferSizeDetails (sr, bs);
- inner->prepareToPlay (sr, bs);
- prepareToPlayParams = { sr, bs };
- }
-
- void releaseResources() override { inner->releaseResources(); }
-
- void memoryWarningReceived() override { inner->memoryWarningReceived(); }
-
- void processBlock (AudioBuffer<float>& a, MidiBuffer& m) override
- {
- const SpinLock::ScopedTryLockType scope (innerProcessBlockFlag);
-
- if (! scope.isLocked())
- return;
-
- inner->processBlock (a, m);
- araHost.afterProcessBlock (a.getNumSamples());
- }
-
- void processBlock (AudioBuffer<double>& a, MidiBuffer& m) override
- {
- const SpinLock::ScopedTryLockType scope (innerProcessBlockFlag);
-
- if (! scope.isLocked())
- return;
-
- inner->processBlock (a, m);
- araHost.afterProcessBlock (a.getNumSamples());
- }
-
- void processBlockBypassed (AudioBuffer<float>& a, MidiBuffer& m) override
- {
- const SpinLock::ScopedTryLockType scope (innerProcessBlockFlag);
-
- if (! scope.isLocked())
- return;
-
- inner->processBlockBypassed (a, m);
- araHost.afterProcessBlock (a.getNumSamples());
- }
-
- void processBlockBypassed (AudioBuffer<double>& a, MidiBuffer& m) override
- {
- const SpinLock::ScopedTryLockType scope (innerProcessBlockFlag);
-
- if (! scope.isLocked())
- return;
-
- inner->processBlockBypassed (a, m);
- araHost.afterProcessBlock (a.getNumSamples());
- }
-
- bool supportsDoublePrecisionProcessing() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->supportsDoublePrecisionProcessing();
- }
-
- bool supportsMPE() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->supportsMPE();
- }
-
- bool isMidiEffect() const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->isMidiEffect();
- }
-
- void reset() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->reset();
- }
-
- void setNonRealtime (bool b) noexcept override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->setNonRealtime (b);
- }
-
- void refreshParameterList() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->refreshParameterList();
- }
-
- void numChannelsChanged() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->numChannelsChanged();
- }
-
- void numBusesChanged() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->numBusesChanged();
- }
-
- void processorLayoutsChanged() override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->processorLayoutsChanged();
- }
-
- void setPlayHead (AudioPlayHead* p) override { ignoreUnused (p); }
-
- void updateTrackProperties (const TrackProperties& p) override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- inner->updateTrackProperties (p);
- }
-
- bool isBusesLayoutSupported (const BusesLayout& layout) const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return inner->checkBusesLayoutSupported (layout);
- }
-
- bool canAddBus (bool) const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return true;
- }
- bool canRemoveBus (bool) const override
- {
- std::lock_guard<std::mutex> lock (innerMutex);
- return true;
- }
-
- //==============================================================================
- void fillInPluginDescription (PluginDescription& description) const override
- {
- return inner->fillInPluginDescription (description);
- }
-
- private:
- void matchBuses (bool isInput)
- {
- const auto inBuses = inner->getBusCount (isInput);
-
- while (getBusCount (isInput) < inBuses)
- addBus (isInput);
-
- while (inBuses < getBusCount (isInput))
- removeBus (isInput);
- }
-
- // Used for mutual exclusion between the audio and other threads
- SpinLock innerProcessBlockFlag;
-
- // Used for mutual exclusion on non-audio threads
- mutable std::mutex innerMutex;
-
- std::unique_ptr<AudioPluginInstance> inner;
-
- ARATestHost araHost;
-
- struct PrepareToPlayParams
- {
- PrepareToPlayParams() : isValid (false) {}
-
- PrepareToPlayParams (double sampleRateIn, int samplesPerBlockIn)
- : isValid (true), sampleRate (sampleRateIn), samplesPerBlock (samplesPerBlockIn)
- {
- }
-
- bool isValid;
- double sampleRate;
- int samplesPerBlock;
- };
-
- PrepareToPlayParams prepareToPlayParams;
-
- //==============================================================================
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAPluginInstanceWrapper)
- };
- #endif
|