1406 lines
46 KiB
C++
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
|