1406 lines
46 KiB
C++

/*
==============================================================================
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 || JUCE_LINUX)
#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 = (double) 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
{
Optional<PositionInfo> getPosition() const override
{
PositionInfo result;
result.setTimeInSamples (timeInSamples.load());
result.setIsPlaying (isPlaying.load());
return result;
}
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