/* ============================================================================== 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 #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) #include #include #include 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::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; private: std::unique_ptr 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; 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; 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; 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; 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 (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 (buffers), samplePosition, samplesPerChannel); return audioSource->readAudioSamples ( reinterpret_cast (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; std::map> audioReaders; }; class ArchivingController : public ARA::Host::ArchivingControllerInterface { public: using ReaderConverter = ARAHostModel::ConversionFunctions; using WriterConverter = ARAHostModel::ConversionFunctions; 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; 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 timeInSamples { 0 }; std::atomic 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(), std::make_unique(), std::make_unique(), std::make_unique(), std::make_unique()); 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 configurationLock (instance.innerMutex); if (context != nullptr) context->getStateInformation (b); } void setStateInformation (const void* d, int s) { { std::lock_guard 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 lock { instance.innerMutex }; if (context != nullptr) return context->audioFile; return {}; } void setAudioSource (File audioSourceFile) override { if (audioSourceFile.existsAsFile()) { { std::lock_guard lock { contextUpdateSourceMutex }; contextUpdateSource = ContextUpdateSource (std::move (audioSourceFile)); } synchronise(); } } void clearAudioSource() override { { std::lock_guard 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 configurationLock (instance.innerMutex); synchronizeStateWithDocumentController(); } void synchronizeStateWithDocumentController() { bool resetContext = false; auto newContext = [&]() -> std::unique_ptr { std::lock_guard 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 (documentController->getDocumentController(), contextUpdateSource.getAudioSourceFile()); } case ContextUpdateSource::Type::stateInformation: jassert (contextUpdateSource.getStateInformation().getSize() <= std::numeric_limits::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 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 (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 documentController; ARAHostModel::PlaybackRendererInterface playbackRenderer; ARAHostModel::EditorRendererInterface editorRenderer; std::unique_ptr context; mutable std::mutex contextUpdateSourceMutex; ContextUpdateSource contextUpdateSource; std::atomic isPlaying { false }; std::atomic goToStartSignal { false }; std::atomic audioSourceLength { 0 }; }; explicit ARAPluginInstanceWrapper (std::unique_ptr 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 lock (innerMutex); return inner->getName(); } StringArray getAlternateDisplayNames() const override { std::lock_guard lock (innerMutex); return inner->getAlternateDisplayNames(); } double getTailLengthSeconds() const override { std::lock_guard lock (innerMutex); return inner->getTailLengthSeconds(); } bool acceptsMidi() const override { std::lock_guard lock (innerMutex); return inner->acceptsMidi(); } bool producesMidi() const override { std::lock_guard lock (innerMutex); return inner->producesMidi(); } AudioProcessorEditor* createEditor() override { std::lock_guard lock (innerMutex); return inner->createEditorIfNeeded(); } bool hasEditor() const override { std::lock_guard lock (innerMutex); return inner->hasEditor(); } int getNumPrograms() override { std::lock_guard lock (innerMutex); return inner->getNumPrograms(); } int getCurrentProgram() override { std::lock_guard lock (innerMutex); return inner->getCurrentProgram(); } void setCurrentProgram (int i) override { std::lock_guard lock (innerMutex); inner->setCurrentProgram (i); } const String getProgramName (int i) override { std::lock_guard lock (innerMutex); return inner->getProgramName (i); } void changeProgramName (int i, const String& n) override { std::lock_guard 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 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::max()); araHost.setStateInformation (m.getData(), (int) m.getSize()); } if (auto* pluginState = xml->getChildByName ("plugin")) { std::lock_guard lock (innerMutex); MemoryBlock m; m.fromBase64Encoding (pluginState->getAllSubText()); jassert (m.getSize() <= std::numeric_limits::max()); inner->setStateInformation (m.getData(), (int) m.getSize()); } } } } void getCurrentProgramStateInformation (juce::MemoryBlock& b) override { std::lock_guard lock (innerMutex); inner->getCurrentProgramStateInformation (b); } void setCurrentProgramStateInformation (const void* d, int s) override { std::lock_guard lock (innerMutex); inner->setCurrentProgramStateInformation (d, s); } void prepareToPlay (double sr, int bs) override { std::lock_guard 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& a, MidiBuffer& m) override { const SpinLock::ScopedTryLockType scope (innerProcessBlockFlag); if (! scope.isLocked()) return; inner->processBlock (a, m); araHost.afterProcessBlock (a.getNumSamples()); } void processBlock (AudioBuffer& a, MidiBuffer& m) override { const SpinLock::ScopedTryLockType scope (innerProcessBlockFlag); if (! scope.isLocked()) return; inner->processBlock (a, m); araHost.afterProcessBlock (a.getNumSamples()); } void processBlockBypassed (AudioBuffer& a, MidiBuffer& m) override { const SpinLock::ScopedTryLockType scope (innerProcessBlockFlag); if (! scope.isLocked()) return; inner->processBlockBypassed (a, m); araHost.afterProcessBlock (a.getNumSamples()); } void processBlockBypassed (AudioBuffer& 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 lock (innerMutex); return inner->supportsDoublePrecisionProcessing(); } bool supportsMPE() const override { std::lock_guard lock (innerMutex); return inner->supportsMPE(); } bool isMidiEffect() const override { std::lock_guard lock (innerMutex); return inner->isMidiEffect(); } void reset() override { std::lock_guard lock (innerMutex); inner->reset(); } void setNonRealtime (bool b) noexcept override { std::lock_guard lock (innerMutex); inner->setNonRealtime (b); } void refreshParameterList() override { std::lock_guard lock (innerMutex); inner->refreshParameterList(); } void numChannelsChanged() override { std::lock_guard lock (innerMutex); inner->numChannelsChanged(); } void numBusesChanged() override { std::lock_guard lock (innerMutex); inner->numBusesChanged(); } void processorLayoutsChanged() override { std::lock_guard lock (innerMutex); inner->processorLayoutsChanged(); } void setPlayHead (AudioPlayHead* p) override { ignoreUnused (p); } void updateTrackProperties (const TrackProperties& p) override { std::lock_guard lock (innerMutex); inner->updateTrackProperties (p); } bool isBusesLayoutSupported (const BusesLayout& layout) const override { std::lock_guard lock (innerMutex); return inner->checkBusesLayoutSupported (layout); } bool canAddBus (bool) const override { std::lock_guard lock (innerMutex); return true; } bool canRemoveBus (bool) const override { std::lock_guard 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 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