build(deps): vendor JUCE 7.0.12

This commit is contained in:
2026-09-08 14:55:21 +02:00
parent 5b99f51bd1
commit d6705ab29f
3591 changed files with 1218267 additions and 0 deletions
@@ -0,0 +1,67 @@
/*
==============================================================================
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_PlatformDefs.h>
#ifndef JUCE_API
#define JUCE_API
#endif
#if (JucePlugin_Enable_ARA || (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU))) && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
namespace juce
{
//==============================================================================
#if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) || JUCE_LOG_ASSERTIONS
#define ARA_ENABLE_INTERNAL_ASSERTS 1
#else
#define ARA_ENABLE_INTERNAL_ASSERTS 0
#endif // (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) || JUCE_LOG_ASSERTIONS
//==============================================================================
#if ARA_ENABLE_INTERNAL_ASSERTS
JUCE_API void JUCE_CALLTYPE handleARAAssertion (const char* file, const int line, const char* diagnosis) noexcept;
#if !defined(ARA_HANDLE_ASSERT)
#define ARA_HANDLE_ASSERT(file, line, diagnosis) juce::handleARAAssertion (file, line, diagnosis)
#endif
#if JUCE_LOG_ASSERTIONS
#define ARA_ENABLE_DEBUG_OUTPUT 1
#endif
#endif
} // namespace juce
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes")
#include <ARA_Library/Debug/ARADebug.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
@@ -0,0 +1,976 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
class ARADocumentControllerSpecialisation::ARADocumentControllerImpl : public ARADocumentController
{
public:
ARADocumentControllerImpl (const ARA::PlugIn::PlugInEntry* entry,
const ARA::ARADocumentControllerHostInstance* instance,
ARADocumentControllerSpecialisation* spec)
: ARADocumentController (entry, instance), specialisation (spec)
{
}
template <typename PlaybackRenderer_t = ARAPlaybackRenderer>
std::vector<PlaybackRenderer_t*> const& getPlaybackRenderers() const noexcept
{
return ARA::PlugIn::DocumentController::getPlaybackRenderers<PlaybackRenderer_t>();
}
template <typename EditorRenderer_t = ARAEditorRenderer>
std::vector<EditorRenderer_t*> const& getEditorRenderers() const noexcept
{
return ARA::PlugIn::DocumentController::getEditorRenderers<EditorRenderer_t>();
}
template <typename EditorView_t = ARAEditorView>
std::vector<EditorView_t*> const& getEditorViews() const noexcept
{
return ARA::PlugIn::DocumentController::getEditorViews<EditorView_t>();
}
auto getSpecialisation() { return specialisation; }
protected:
//==============================================================================
bool doRestoreObjectsFromStream (ARAInputStream& input, const ARARestoreObjectsFilter* filter) noexcept
{
return specialisation->doRestoreObjectsFromStream (input, filter);
}
bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) noexcept
{
return specialisation->doStoreObjectsToStream (output, filter);
}
//==============================================================================
// Model object creation
ARA::PlugIn::Document* doCreateDocument () noexcept override;
ARA::PlugIn::MusicalContext* doCreateMusicalContext (ARA::PlugIn::Document* document, ARA::ARAMusicalContextHostRef hostRef) noexcept override;
ARA::PlugIn::RegionSequence* doCreateRegionSequence (ARA::PlugIn::Document* document, ARA::ARARegionSequenceHostRef hostRef) noexcept override;
ARA::PlugIn::AudioSource* doCreateAudioSource (ARA::PlugIn::Document* document, ARA::ARAAudioSourceHostRef hostRef) noexcept override;
ARA::PlugIn::AudioModification* doCreateAudioModification (ARA::PlugIn::AudioSource* audioSource, ARA::ARAAudioModificationHostRef hostRef, const ARA::PlugIn::AudioModification* optionalModificationToClone) noexcept override;
ARA::PlugIn::PlaybackRegion* doCreatePlaybackRegion (ARA::PlugIn::AudioModification* modification, ARA::ARAPlaybackRegionHostRef hostRef) noexcept override;
//==============================================================================
// Plugin role implementation
friend class ARAPlaybackRegionReader;
ARA::PlugIn::PlaybackRenderer* doCreatePlaybackRenderer() noexcept override;
ARA::PlugIn::EditorRenderer* doCreateEditorRenderer() noexcept override;
ARA::PlugIn::EditorView* doCreateEditorView() noexcept override;
//==============================================================================
// ARAAudioSource content access
bool doIsAudioSourceContentAvailable (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type) noexcept override;
ARA::ARAContentGrade doGetAudioSourceContentGrade (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type) noexcept override;
ARA::PlugIn::ContentReader* doCreateAudioSourceContentReader (ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range) noexcept override;
//==============================================================================
// ARAAudioModification content access
bool doIsAudioModificationContentAvailable (const ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type) noexcept override;
ARA::ARAContentGrade doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type) noexcept override;
ARA::PlugIn::ContentReader* doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range) noexcept override;
//==============================================================================
// ARAPlaybackRegion content access
bool doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type) noexcept override;
ARA::ARAContentGrade doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type) noexcept override;
ARA::PlugIn::ContentReader* doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range) noexcept override;
void doGetPlaybackRegionHeadAndTailTime (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARATimeDuration* headTime,
ARA::ARATimeDuration* tailTime) noexcept override;
//==============================================================================
// ARAAudioSource analysis
bool doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type) noexcept override;
void doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource,
std::vector<ARA::ARAContentType> const& contentTypes) noexcept override;
//==============================================================================
// Analysis Algorithm selection
ARA::ARAInt32 doGetProcessingAlgorithmsCount() noexcept override;
const ARA::ARAProcessingAlgorithmProperties* doGetProcessingAlgorithmProperties (ARA::ARAInt32 algorithmIndex) noexcept override;
ARA::ARAInt32 doGetProcessingAlgorithmForAudioSource (const ARA::PlugIn::AudioSource* audioSource) noexcept override;
void doRequestProcessingAlgorithmForAudioSource (ARA::PlugIn::AudioSource* audioSource,
ARA::ARAInt32 algorithmIndex) noexcept override;
#ifndef DOXYGEN
//==============================================================================
bool doRestoreObjectsFromArchive (ARA::PlugIn::HostArchiveReader* archiveReader, const ARA::PlugIn::RestoreObjectsFilter* filter) noexcept override;
bool doStoreObjectsToArchive (ARA::PlugIn::HostArchiveWriter* archiveWriter, const ARA::PlugIn::StoreObjectsFilter* filter) noexcept override;
//==============================================================================
// Document notifications
void willBeginEditing() noexcept override;
void didEndEditing() noexcept override;
void willNotifyModelUpdates() noexcept override;
void didNotifyModelUpdates() noexcept override;
void willUpdateDocumentProperties (ARA::PlugIn::Document* document, ARADocument::PropertiesPtr newProperties) noexcept override;
void didUpdateDocumentProperties (ARA::PlugIn::Document* document) noexcept override;
void didAddMusicalContextToDocument (ARA::PlugIn::Document* document, ARA::PlugIn::MusicalContext* musicalContext) noexcept override;
void willRemoveMusicalContextFromDocument (ARA::PlugIn::Document* document, ARA::PlugIn::MusicalContext* musicalContext) noexcept override;
void didReorderMusicalContextsInDocument (ARA::PlugIn::Document* document) noexcept override;
void didAddRegionSequenceToDocument (ARA::PlugIn::Document* document, ARA::PlugIn::RegionSequence* regionSequence) noexcept override;
void willRemoveRegionSequenceFromDocument (ARA::PlugIn::Document* document, ARA::PlugIn::RegionSequence* regionSequence) noexcept override;
void didReorderRegionSequencesInDocument (ARA::PlugIn::Document* document) noexcept override;
void didAddAudioSourceToDocument (ARA::PlugIn::Document* document, ARA::PlugIn::AudioSource* audioSource) noexcept override;
void willRemoveAudioSourceFromDocument (ARA::PlugIn::Document* document, ARA::PlugIn::AudioSource* audioSource) noexcept override;
void willDestroyDocument (ARA::PlugIn::Document* document) noexcept override;
//==============================================================================
// MusicalContext notifications
void willUpdateMusicalContextProperties (ARA::PlugIn::MusicalContext* musicalContext, ARAMusicalContext::PropertiesPtr newProperties) noexcept override;
void didUpdateMusicalContextProperties (ARA::PlugIn::MusicalContext* musicalContext) noexcept override;
void doUpdateMusicalContextContent (ARA::PlugIn::MusicalContext* musicalContext, const ARA::ARAContentTimeRange* range, ARA::ContentUpdateScopes flags) noexcept override;
void didAddRegionSequenceToMusicalContext (ARA::PlugIn::MusicalContext* musicalContext, ARA::PlugIn::RegionSequence* regionSequence) noexcept override;
void willRemoveRegionSequenceFromMusicalContext (ARA::PlugIn::MusicalContext* musicalContext, ARA::PlugIn::RegionSequence* regionSequence) noexcept override;
void didReorderRegionSequencesInMusicalContext (ARA::PlugIn::MusicalContext* musicalContext) noexcept override;
void willDestroyMusicalContext (ARA::PlugIn::MusicalContext* musicalContext) noexcept override;
//==============================================================================
// RegionSequence notifications, typically not overridden further
void willUpdateRegionSequenceProperties (ARA::PlugIn::RegionSequence* regionSequence, ARARegionSequence::PropertiesPtr newProperties) noexcept override;
void didUpdateRegionSequenceProperties (ARA::PlugIn::RegionSequence* regionSequence) noexcept override;
void didAddPlaybackRegionToRegionSequence (ARA::PlugIn::RegionSequence* regionSequence, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override;
void willRemovePlaybackRegionFromRegionSequence (ARA::PlugIn::RegionSequence* regionSequence, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override;
void willDestroyRegionSequence (ARA::PlugIn::RegionSequence* regionSequence) noexcept override;
//==============================================================================
// AudioSource notifications
void willUpdateAudioSourceProperties (ARA::PlugIn::AudioSource* audioSource, ARAAudioSource::PropertiesPtr newProperties) noexcept override;
void didUpdateAudioSourceProperties (ARA::PlugIn::AudioSource* audioSource) noexcept override;
void doUpdateAudioSourceContent (ARA::PlugIn::AudioSource* audioSource, const ARA::ARAContentTimeRange* range, ARA::ContentUpdateScopes flags) noexcept override;
void willEnableAudioSourceSamplesAccess (ARA::PlugIn::AudioSource* audioSource, bool enable) noexcept override;
void didEnableAudioSourceSamplesAccess (ARA::PlugIn::AudioSource* audioSource, bool enable) noexcept override;
void didAddAudioModificationToAudioSource (ARA::PlugIn::AudioSource* audioSource, ARA::PlugIn::AudioModification* audioModification) noexcept override;
void willRemoveAudioModificationFromAudioSource (ARA::PlugIn::AudioSource* audioSource, ARA::PlugIn::AudioModification* audioModification) noexcept override;
void willDeactivateAudioSourceForUndoHistory (ARA::PlugIn::AudioSource* audioSource, bool deactivate) noexcept override;
void didDeactivateAudioSourceForUndoHistory (ARA::PlugIn::AudioSource* audioSource, bool deactivate) noexcept override;
void willDestroyAudioSource (ARA::PlugIn::AudioSource* audioSource) noexcept override;
//==============================================================================
// AudioModification notifications
void willUpdateAudioModificationProperties (ARA::PlugIn::AudioModification* audioModification, ARAAudioModification::PropertiesPtr newProperties) noexcept override;
void didUpdateAudioModificationProperties (ARA::PlugIn::AudioModification* audioModification) noexcept override;
void didAddPlaybackRegionToAudioModification (ARA::PlugIn::AudioModification* audioModification, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override;
void willRemovePlaybackRegionFromAudioModification (ARA::PlugIn::AudioModification* audioModification, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override;
void willDeactivateAudioModificationForUndoHistory (ARA::PlugIn::AudioModification* audioModification, bool deactivate) noexcept override;
void didDeactivateAudioModificationForUndoHistory (ARA::PlugIn::AudioModification* audioModification, bool deactivate) noexcept override;
void willDestroyAudioModification (ARA::PlugIn::AudioModification* audioModification) noexcept override;
//==============================================================================
// PlaybackRegion notifications
void willUpdatePlaybackRegionProperties (ARA::PlugIn::PlaybackRegion* playbackRegion, ARAPlaybackRegion::PropertiesPtr newProperties) noexcept override;
void didUpdatePlaybackRegionProperties (ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override;
void willDestroyPlaybackRegion (ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override;
public:
//==============================================================================
/** @internal */
void internalNotifyAudioSourceAnalysisProgressStarted (ARAAudioSource* audioSource) override;
/** @internal */
void internalNotifyAudioSourceAnalysisProgressUpdated (ARAAudioSource* audioSource, float progress) override;
/** @internal */
void internalNotifyAudioSourceAnalysisProgressCompleted (ARAAudioSource* audioSource) override;
/** @internal */
void internalDidUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource,
ARAAudioSource::ARAAnalysisProgressState state,
float progress) override;
//==============================================================================
/** @internal */
void internalNotifyAudioSourceContentChanged (ARAAudioSource* audioSource,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost) override;
/** @internal */
void internalNotifyAudioModificationContentChanged (ARAAudioModification* audioModification,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost) override;
/** @internal */
void internalNotifyPlaybackRegionContentChanged (ARAPlaybackRegion* playbackRegion,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost) override;
#endif
private:
//==============================================================================
ARADocumentControllerSpecialisation* specialisation;
std::atomic<bool> internalAnalysisProgressIsSynced { true };
ScopedJuceInitialiser_GUI libraryInitialiser;
int activeAudioSourcesCount = 0;
std::optional<TimedCallback> analysisTimer;
void analysisTimerCallback();
//==============================================================================
template <typename ModelObject, typename Function, typename... Ts>
void notifyListeners (Function ModelObject::Listener::* function, ModelObject* modelObject, Ts... ts)
{
(specialisation->*function) (modelObject, ts...);
modelObject->notifyListeners ([&] (auto& l)
{
try
{
(l.*function) (modelObject, ts...);
}
catch (...)
{
}
});
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADocumentControllerImpl)
};
ARA::PlugIn::DocumentController* ARADocumentControllerSpecialisation::getDocumentController() noexcept
{
return documentController.get();
}
//==============================================================================
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceAnalysisProgressStarted (ARAAudioSource* audioSource)
{
if (audioSource->internalAnalysisProgressTracker.updateProgress (ARA::kARAAnalysisProgressStarted, 0.0f))
internalAnalysisProgressIsSynced.store (false, std::memory_order_release);
DocumentController::notifyAudioSourceAnalysisProgressStarted (audioSource);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceAnalysisProgressUpdated (ARAAudioSource* audioSource,
float progress)
{
if (audioSource->internalAnalysisProgressTracker.updateProgress (ARA::kARAAnalysisProgressUpdated, progress))
internalAnalysisProgressIsSynced.store (false, std::memory_order_release);
DocumentController::notifyAudioSourceAnalysisProgressUpdated (audioSource, progress);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceAnalysisProgressCompleted (ARAAudioSource* audioSource)
{
if (audioSource->internalAnalysisProgressTracker.updateProgress (ARA::kARAAnalysisProgressCompleted, 1.0f))
internalAnalysisProgressIsSynced.store (false, std::memory_order_release);
DocumentController::notifyAudioSourceAnalysisProgressCompleted (audioSource);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalDidUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource,
ARAAudioSource::ARAAnalysisProgressState state,
float progress)
{
specialisation->didUpdateAudioSourceAnalysisProgress (audioSource, state, progress);
}
//==============================================================================
ARADocumentControllerSpecialisation* ARADocumentControllerSpecialisation::getSpecialisedDocumentControllerImpl (ARA::PlugIn::DocumentController* dc)
{
return static_cast<ARADocumentControllerImpl*> (dc)->getSpecialisation();
}
ARADocument* ARADocumentControllerSpecialisation::getDocumentImpl()
{
return documentController->getDocument();
}
//==============================================================================
// some helper macros to ease repeated declaration & implementation of notification functions below:
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments")
// no notification arguments
#define OVERRIDE_TO_NOTIFY_1(function, ModelObjectType, modelObject) \
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::function (ARA::PlugIn::ModelObjectType* modelObject) noexcept \
{ \
notifyListeners (&ARA##ModelObjectType::Listener::function, static_cast<ARA##ModelObjectType*> (modelObject)); \
}
// single notification argument, model object version
#define OVERRIDE_TO_NOTIFY_2(function, ModelObjectType, modelObject, ArgumentType, argument) \
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::function (ARA::PlugIn::ModelObjectType* modelObject, ARA::PlugIn::ArgumentType argument) noexcept \
{ \
notifyListeners (&ARA##ModelObjectType::Listener::function, static_cast<ARA##ModelObjectType*> (modelObject), static_cast<ARA##ArgumentType> (argument)); \
}
// single notification argument, non-model object version
#define OVERRIDE_TO_NOTIFY_3(function, ModelObjectType, modelObject, ArgumentType, argument) \
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::function (ARA::PlugIn::ModelObjectType* modelObject, ArgumentType argument) noexcept \
{ \
notifyListeners (&ARA##ModelObjectType::Listener::function, static_cast<ARA##ModelObjectType*> (modelObject), argument); \
}
//==============================================================================
ARA::PlugIn::Document* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateDocument() noexcept
{
auto* document = specialisation->doCreateDocument();
// Your Document subclass must inherit from juce::ARADocument
jassert (dynamic_cast<ARADocument*> (document));
return document;
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::willBeginEditing() noexcept
{
notifyListeners (&ARADocument::Listener::willBeginEditing, static_cast<ARADocument*> (getDocument()));
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::didEndEditing() noexcept
{
notifyListeners (&ARADocument::Listener::didEndEditing, static_cast<ARADocument*> (getDocument()));
if (activeAudioSourcesCount == 0)
{
analysisTimer.reset();
}
else if (! analysisTimer.has_value() && (activeAudioSourcesCount > 0))
{
analysisTimer.emplace ([this] { analysisTimerCallback(); });
analysisTimer->startTimerHz (20);
}
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::willNotifyModelUpdates() noexcept
{
notifyListeners (&ARADocument::Listener::willNotifyModelUpdates, static_cast<ARADocument*> (getDocument()));
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::didNotifyModelUpdates() noexcept
{
notifyListeners (&ARADocument::Listener::didNotifyModelUpdates, static_cast<ARADocument*> (getDocument()));
}
//==============================================================================
bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doRestoreObjectsFromArchive (ARA::PlugIn::HostArchiveReader* archiveReader,
const ARA::PlugIn::RestoreObjectsFilter* filter) noexcept
{
ARAInputStream reader (archiveReader);
return doRestoreObjectsFromStream (reader, filter);
}
bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doStoreObjectsToArchive (ARA::PlugIn::HostArchiveWriter* archiveWriter,
const ARA::PlugIn::StoreObjectsFilter* filter) noexcept
{
ARAOutputStream writer (archiveWriter);
return doStoreObjectsToStream (writer, filter);
}
//==============================================================================
OVERRIDE_TO_NOTIFY_3 (willUpdateDocumentProperties, Document, document, ARADocument::PropertiesPtr, newProperties)
OVERRIDE_TO_NOTIFY_1 (didUpdateDocumentProperties, Document, document)
OVERRIDE_TO_NOTIFY_2 (didAddMusicalContextToDocument, Document, document, MusicalContext*, musicalContext)
OVERRIDE_TO_NOTIFY_2 (willRemoveMusicalContextFromDocument, Document, document, MusicalContext*, musicalContext)
OVERRIDE_TO_NOTIFY_1 (didReorderMusicalContextsInDocument, Document, document)
OVERRIDE_TO_NOTIFY_2 (didAddRegionSequenceToDocument, Document, document, RegionSequence*, regionSequence)
OVERRIDE_TO_NOTIFY_2 (willRemoveRegionSequenceFromDocument, Document, document, RegionSequence*, regionSequence)
OVERRIDE_TO_NOTIFY_1 (didReorderRegionSequencesInDocument, Document, document)
OVERRIDE_TO_NOTIFY_2 (didAddAudioSourceToDocument, Document, document, AudioSource*, audioSource)
OVERRIDE_TO_NOTIFY_2 (willRemoveAudioSourceFromDocument, Document, document, AudioSource*, audioSource)
OVERRIDE_TO_NOTIFY_1 (willDestroyDocument, Document, document)
//==============================================================================
ARA::PlugIn::MusicalContext* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateMusicalContext (ARA::PlugIn::Document* document,
ARA::ARAMusicalContextHostRef hostRef) noexcept
{
return specialisation->doCreateMusicalContext (static_cast<ARADocument*> (document), hostRef);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doUpdateMusicalContextContent (ARA::PlugIn::MusicalContext* musicalContext,
const ARA::ARAContentTimeRange*,
ARA::ContentUpdateScopes flags) noexcept
{
notifyListeners (&ARAMusicalContext::Listener::doUpdateMusicalContextContent,
static_cast<ARAMusicalContext*> (musicalContext),
flags);
}
OVERRIDE_TO_NOTIFY_3 (willUpdateMusicalContextProperties, MusicalContext, musicalContext, ARAMusicalContext::PropertiesPtr, newProperties)
OVERRIDE_TO_NOTIFY_1 (didUpdateMusicalContextProperties, MusicalContext, musicalContext)
OVERRIDE_TO_NOTIFY_2 (didAddRegionSequenceToMusicalContext, MusicalContext, musicalContext, RegionSequence*, regionSequence)
OVERRIDE_TO_NOTIFY_2 (willRemoveRegionSequenceFromMusicalContext, MusicalContext, musicalContext, RegionSequence*, regionSequence)
OVERRIDE_TO_NOTIFY_1 (didReorderRegionSequencesInMusicalContext, MusicalContext, musicalContext)
OVERRIDE_TO_NOTIFY_1 (willDestroyMusicalContext, MusicalContext, musicalContext)
//==============================================================================
ARA::PlugIn::RegionSequence* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateRegionSequence (ARA::PlugIn::Document* document, ARA::ARARegionSequenceHostRef hostRef) noexcept
{
return specialisation->doCreateRegionSequence (static_cast<ARADocument*> (document), hostRef);
}
OVERRIDE_TO_NOTIFY_3 (willUpdateRegionSequenceProperties, RegionSequence, regionSequence, ARARegionSequence::PropertiesPtr, newProperties)
OVERRIDE_TO_NOTIFY_1 (didUpdateRegionSequenceProperties, RegionSequence, regionSequence)
OVERRIDE_TO_NOTIFY_2 (didAddPlaybackRegionToRegionSequence, RegionSequence, regionSequence, PlaybackRegion*, playbackRegion)
OVERRIDE_TO_NOTIFY_2 (willRemovePlaybackRegionFromRegionSequence, RegionSequence, regionSequence, PlaybackRegion*, playbackRegion)
OVERRIDE_TO_NOTIFY_1 (willDestroyRegionSequence, RegionSequence, regionSequence)
//==============================================================================
ARA::PlugIn::AudioSource* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioSource (ARA::PlugIn::Document* document, ARA::ARAAudioSourceHostRef hostRef) noexcept
{
++activeAudioSourcesCount;
return specialisation->doCreateAudioSource (static_cast<ARADocument*> (document), hostRef);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doUpdateAudioSourceContent (ARA::PlugIn::AudioSource* audioSource,
const ARA::ARAContentTimeRange*,
ARA::ContentUpdateScopes flags) noexcept
{
notifyListeners (&ARAAudioSource::Listener::doUpdateAudioSourceContent, static_cast<ARAAudioSource*> (audioSource), flags);
}
OVERRIDE_TO_NOTIFY_3 (willUpdateAudioSourceProperties, AudioSource, audioSource, ARAAudioSource::PropertiesPtr, newProperties)
OVERRIDE_TO_NOTIFY_1 (didUpdateAudioSourceProperties, AudioSource, audioSource)
OVERRIDE_TO_NOTIFY_3 (willEnableAudioSourceSamplesAccess, AudioSource, audioSource, bool, enable)
OVERRIDE_TO_NOTIFY_3 (didEnableAudioSourceSamplesAccess, AudioSource, audioSource, bool, enable)
OVERRIDE_TO_NOTIFY_2 (didAddAudioModificationToAudioSource, AudioSource, audioSource, AudioModification*, audioModification)
OVERRIDE_TO_NOTIFY_2 (willRemoveAudioModificationFromAudioSource, AudioSource, audioSource, AudioModification*, audioModification)
OVERRIDE_TO_NOTIFY_3 (willDeactivateAudioSourceForUndoHistory, AudioSource, audioSource, bool, deactivate)
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::didDeactivateAudioSourceForUndoHistory (ARA::PlugIn::AudioSource* audioSource,
bool deactivate) noexcept
{
activeAudioSourcesCount += (deactivate ? -1 : 1);
notifyListeners (&ARAAudioSource::Listener::didDeactivateAudioSourceForUndoHistory,
static_cast<ARAAudioSource*> (audioSource),
deactivate);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::willDestroyAudioSource (ARA::PlugIn::AudioSource* audioSource) noexcept
{
if (! audioSource->isDeactivatedForUndoHistory())
--activeAudioSourcesCount;
notifyListeners (&ARAAudioSource::Listener::willDestroyAudioSource, static_cast<ARAAudioSource*> (audioSource));
}
//==============================================================================
ARA::PlugIn::AudioModification* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioModification (ARA::PlugIn::AudioSource* audioSource,
ARA::ARAAudioModificationHostRef hostRef,
const ARA::PlugIn::AudioModification* optionalModificationToClone) noexcept
{
return specialisation->doCreateAudioModification (static_cast<ARAAudioSource*> (audioSource),
hostRef,
static_cast<const ARAAudioModification*> (optionalModificationToClone));
}
OVERRIDE_TO_NOTIFY_3 (willUpdateAudioModificationProperties, AudioModification, audioModification, ARAAudioModification::PropertiesPtr, newProperties)
OVERRIDE_TO_NOTIFY_1 (didUpdateAudioModificationProperties, AudioModification, audioModification)
OVERRIDE_TO_NOTIFY_2 (didAddPlaybackRegionToAudioModification, AudioModification, audioModification, PlaybackRegion*, playbackRegion)
OVERRIDE_TO_NOTIFY_2 (willRemovePlaybackRegionFromAudioModification, AudioModification, audioModification, PlaybackRegion*, playbackRegion)
OVERRIDE_TO_NOTIFY_3 (willDeactivateAudioModificationForUndoHistory, AudioModification, audioModification, bool, deactivate)
OVERRIDE_TO_NOTIFY_3 (didDeactivateAudioModificationForUndoHistory, AudioModification, audioModification, bool, deactivate)
OVERRIDE_TO_NOTIFY_1 (willDestroyAudioModification, AudioModification, audioModification)
//==============================================================================
ARA::PlugIn::PlaybackRegion* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreatePlaybackRegion (ARA::PlugIn::AudioModification* modification,
ARA::ARAPlaybackRegionHostRef hostRef) noexcept
{
return specialisation->doCreatePlaybackRegion (static_cast<ARAAudioModification*> (modification), hostRef);
}
OVERRIDE_TO_NOTIFY_3 (willUpdatePlaybackRegionProperties, PlaybackRegion, playbackRegion, ARAPlaybackRegion::PropertiesPtr, newProperties)
OVERRIDE_TO_NOTIFY_1 (didUpdatePlaybackRegionProperties, PlaybackRegion, playbackRegion)
OVERRIDE_TO_NOTIFY_1 (willDestroyPlaybackRegion, PlaybackRegion, playbackRegion)
//==============================================================================
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceContentChanged (ARAAudioSource* audioSource,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost)
{
if (notifyARAHost)
DocumentController::notifyAudioSourceContentChanged (audioSource, scopeFlags);
notifyListeners (&ARAAudioSource::Listener::doUpdateAudioSourceContent, audioSource, scopeFlags);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioModificationContentChanged (ARAAudioModification* audioModification,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost)
{
if (notifyARAHost)
DocumentController::notifyAudioModificationContentChanged (audioModification, scopeFlags);
notifyListeners (&ARAAudioModification::Listener::didUpdateAudioModificationContent, audioModification, scopeFlags);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyPlaybackRegionContentChanged (ARAPlaybackRegion* playbackRegion,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost)
{
if (notifyARAHost)
DocumentController::notifyPlaybackRegionContentChanged (playbackRegion, scopeFlags);
notifyListeners (&ARAPlaybackRegion::Listener::didUpdatePlaybackRegionContent, playbackRegion, scopeFlags);
}
//==============================================================================
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#undef OVERRIDE_TO_NOTIFY_1
#undef OVERRIDE_TO_NOTIFY_2
#undef OVERRIDE_TO_NOTIFY_3
//==============================================================================
ARADocument* ARADocumentControllerSpecialisation::doCreateDocument()
{
return new ARADocument (static_cast<ARADocumentControllerImpl*> (getDocumentController()));
}
ARAMusicalContext* ARADocumentControllerSpecialisation::doCreateMusicalContext (ARADocument* document,
ARA::ARAMusicalContextHostRef hostRef)
{
return new ARAMusicalContext (static_cast<ARADocument*> (document), hostRef);
}
ARARegionSequence* ARADocumentControllerSpecialisation::doCreateRegionSequence (ARADocument* document,
ARA::ARARegionSequenceHostRef hostRef)
{
return new ARARegionSequence (static_cast<ARADocument*> (document), hostRef);
}
ARAAudioSource* ARADocumentControllerSpecialisation::doCreateAudioSource (ARADocument* document,
ARA::ARAAudioSourceHostRef hostRef)
{
return new ARAAudioSource (static_cast<ARADocument*> (document), hostRef);
}
ARAAudioModification* ARADocumentControllerSpecialisation::doCreateAudioModification (
ARAAudioSource* audioSource,
ARA::ARAAudioModificationHostRef hostRef,
const ARAAudioModification* optionalModificationToClone)
{
return new ARAAudioModification (static_cast<ARAAudioSource*> (audioSource),
hostRef,
static_cast<const ARAAudioModification*> (optionalModificationToClone));
}
ARAPlaybackRegion*
ARADocumentControllerSpecialisation::doCreatePlaybackRegion (ARAAudioModification* modification,
ARA::ARAPlaybackRegionHostRef hostRef)
{
return new ARAPlaybackRegion (static_cast<ARAAudioModification*> (modification), hostRef);
}
//==============================================================================
ARA::PlugIn::PlaybackRenderer* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreatePlaybackRenderer() noexcept
{
return specialisation->doCreatePlaybackRenderer();
}
ARA::PlugIn::EditorRenderer* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateEditorRenderer() noexcept
{
return specialisation->doCreateEditorRenderer();
}
ARA::PlugIn::EditorView* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateEditorView() noexcept
{
return specialisation->doCreateEditorView();
}
bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsAudioSourceContentAvailable (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type) noexcept
{
return specialisation->doIsAudioSourceContentAvailable (audioSource, type);
}
ARA::ARAContentGrade ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetAudioSourceContentGrade (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type) noexcept
{
return specialisation->doGetAudioSourceContentGrade (audioSource, type);
}
ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioSourceContentReader (ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range) noexcept
{
return specialisation->doCreateAudioSourceContentReader (audioSource, type, range);
}
bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsAudioModificationContentAvailable (const ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type) noexcept
{
return specialisation->doIsAudioModificationContentAvailable (audioModification, type);
}
ARA::ARAContentGrade ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type) noexcept
{
return specialisation->doGetAudioModificationContentGrade (audioModification, type);
}
ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range) noexcept
{
return specialisation->doCreateAudioModificationContentReader (audioModification, type, range);
}
bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type) noexcept
{
return specialisation->doIsPlaybackRegionContentAvailable (playbackRegion, type);
}
ARA::ARAContentGrade
ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type) noexcept
{
return specialisation->doGetPlaybackRegionContentGrade (playbackRegion, type);
}
ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range) noexcept
{
return specialisation->doCreatePlaybackRegionContentReader (playbackRegion, type, range);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetPlaybackRegionHeadAndTailTime (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARATimeDuration* headTime,
ARA::ARATimeDuration* tailTime) noexcept
{
specialisation->doGetPlaybackRegionHeadAndTailTime (playbackRegion, headTime, tailTime);
}
bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type) noexcept
{
return specialisation->doIsAudioSourceContentAnalysisIncomplete (audioSource, type);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource,
std::vector<ARA::ARAContentType> const& contentTypes) noexcept
{
specialisation->doRequestAudioSourceContentAnalysis (audioSource, contentTypes);
}
ARA::ARAInt32 ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetProcessingAlgorithmsCount() noexcept
{
return specialisation->doGetProcessingAlgorithmsCount();
}
const ARA::ARAProcessingAlgorithmProperties* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetProcessingAlgorithmProperties (ARA::ARAInt32 algorithmIndex) noexcept
{
return specialisation->doGetProcessingAlgorithmProperties (algorithmIndex);
}
ARA::ARAInt32 ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetProcessingAlgorithmForAudioSource (const ARA::PlugIn::AudioSource* audioSource) noexcept
{
return specialisation->doGetProcessingAlgorithmForAudioSource (audioSource);
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doRequestProcessingAlgorithmForAudioSource (ARA::PlugIn::AudioSource* audioSource,
ARA::ARAInt32 algorithmIndex) noexcept
{
return specialisation->doRequestProcessingAlgorithmForAudioSource (audioSource, algorithmIndex);
}
//==============================================================================
// Helper code for ARADocumentControllerSpecialisation::ARADocumentControllerImpl::timerCallback() to
// rewire the host-related ARA SDK's progress tracker to our internal update mechanism.
namespace ModelUpdateControllerProgressAdapter
{
using namespace ARA;
static void ARA_CALL notifyAudioSourceAnalysisProgress (ARAModelUpdateControllerHostRef /*controllerHostRef*/,
ARAAudioSourceHostRef audioSourceHostRef, ARAAnalysisProgressState state, float value) noexcept
{
auto audioSource = reinterpret_cast<ARAAudioSource*> (audioSourceHostRef);
audioSource->getDocumentController<ARADocumentController>()->internalDidUpdateAudioSourceAnalysisProgress (audioSource, state, value);
audioSource->notifyListeners ([&] (ARAAudioSource::Listener& l) { l.didUpdateAudioSourceAnalysisProgress (audioSource, state, value); });
}
static void ARA_CALL notifyAudioSourceContentChanged (ARAModelUpdateControllerHostRef, ARAAudioSourceHostRef,
const ARAContentTimeRange*, ARAContentUpdateFlags) noexcept
{
jassertfalse; // not to be called - this adapter only forwards analysis progress
}
static void ARA_CALL notifyAudioModificationContentChanged (ARAModelUpdateControllerHostRef, ARAAudioModificationHostRef,
const ARAContentTimeRange*, ARAContentUpdateFlags) noexcept
{
jassertfalse; // not to be called - this adapter only forwards analysis progress
}
static void ARA_CALL notifyPlaybackRegionContentChanged (ARAModelUpdateControllerHostRef, ARAPlaybackRegionHostRef,
const ARAContentTimeRange*, ARAContentUpdateFlags) noexcept
{
jassertfalse; // not to be called - this adapter only forwards analysis progress
}
static ARA::PlugIn::HostModelUpdateController* get()
{
static const auto modelUpdateControllerInterface = makeARASizedStruct (&ARA::ARAModelUpdateControllerInterface::notifyPlaybackRegionContentChanged,
ModelUpdateControllerProgressAdapter::notifyAudioSourceAnalysisProgress,
ModelUpdateControllerProgressAdapter::notifyAudioSourceContentChanged,
ModelUpdateControllerProgressAdapter::notifyAudioModificationContentChanged,
ModelUpdateControllerProgressAdapter::notifyPlaybackRegionContentChanged);
static const auto instance = makeARASizedStruct (&ARA::ARADocumentControllerHostInstance::playbackControllerInterface,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
&modelUpdateControllerInterface,
nullptr,
nullptr);
static auto progressAdapter = ARA::PlugIn::HostModelUpdateController { &instance };
return &progressAdapter;
}
}
void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::analysisTimerCallback()
{
if (! internalAnalysisProgressIsSynced.exchange (true, std::memory_order_release))
for (auto& audioSource : getDocument()->getAudioSources())
audioSource->internalAnalysisProgressTracker.notifyProgress (ModelUpdateControllerProgressAdapter::get(),
reinterpret_cast<ARA::ARAAudioSourceHostRef> (audioSource));
}
//==============================================================================
ARAInputStream::ARAInputStream (ARA::PlugIn::HostArchiveReader* reader)
: archiveReader (reader),
size ((int64) reader->getArchiveSize())
{}
int ARAInputStream::read (void* destBuffer, int maxBytesToRead)
{
const auto bytesToRead = std::min ((int64) maxBytesToRead, size - position);
if (bytesToRead > 0 && ! archiveReader->readBytesFromArchive ((ARA::ARASize) position, (ARA::ARASize) bytesToRead,
static_cast<ARA::ARAByte*> (destBuffer)))
{
failure = true;
return 0;
}
position += bytesToRead;
return (int) bytesToRead;
}
bool ARAInputStream::setPosition (int64 newPosition)
{
position = jlimit ((int64) 0, size, newPosition);
return true;
}
bool ARAInputStream::isExhausted()
{
return position >= size;
}
ARAOutputStream::ARAOutputStream (ARA::PlugIn::HostArchiveWriter* writer)
: archiveWriter (writer)
{}
bool ARAOutputStream::write (const void* dataToWrite, size_t numberOfBytes)
{
if (! archiveWriter->writeBytesToArchive ((ARA::ARASize) position, numberOfBytes, (const ARA::ARAByte*) dataToWrite))
return false;
position += (int64) numberOfBytes;
return true;
}
bool ARAOutputStream::setPosition (int64 newPosition)
{
position = newPosition;
return true;
}
//==============================================================================
ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation (
const ARA::PlugIn::PlugInEntry* entry,
const ARA::ARADocumentControllerHostInstance* instance)
: documentController (std::make_unique<ARADocumentControllerImpl> (entry, instance, this))
{
}
ARADocumentControllerSpecialisation::~ARADocumentControllerSpecialisation() = default;
ARAPlaybackRenderer* ARADocumentControllerSpecialisation::doCreatePlaybackRenderer()
{
return new ARAPlaybackRenderer (getDocumentController());
}
ARAEditorRenderer* ARADocumentControllerSpecialisation::doCreateEditorRenderer()
{
return new ARAEditorRenderer (getDocumentController());
}
ARAEditorView* ARADocumentControllerSpecialisation::doCreateEditorView()
{
return new ARAEditorView (getDocumentController());
}
bool ARADocumentControllerSpecialisation::doIsAudioSourceContentAvailable ([[maybe_unused]] const ARA::PlugIn::AudioSource* audioSource,
[[maybe_unused]] ARA::ARAContentType type)
{
return false;
}
ARA::ARAContentGrade ARADocumentControllerSpecialisation::doGetAudioSourceContentGrade ([[maybe_unused]] const ARA::PlugIn::AudioSource* audioSource,
[[maybe_unused]] ARA::ARAContentType type)
{
// Overriding doIsAudioSourceContentAvailable() requires overriding
// doGetAudioSourceContentGrade() accordingly!
jassertfalse;
return ARA::kARAContentGradeInitial;
}
ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::doCreateAudioSourceContentReader ([[maybe_unused]] ARA::PlugIn::AudioSource* audioSource,
[[maybe_unused]] ARA::ARAContentType type,
[[maybe_unused]] const ARA::ARAContentTimeRange* range)
{
// Overriding doIsAudioSourceContentAvailable() requires overriding
// doCreateAudioSourceContentReader() accordingly!
jassertfalse;
return nullptr;
}
bool ARADocumentControllerSpecialisation::doIsAudioModificationContentAvailable ([[maybe_unused]] const ARA::PlugIn::AudioModification* audioModification,
[[maybe_unused]] ARA::ARAContentType type)
{
return false;
}
ARA::ARAContentGrade ARADocumentControllerSpecialisation::doGetAudioModificationContentGrade ([[maybe_unused]] const ARA::PlugIn::AudioModification* audioModification,
[[maybe_unused]] ARA::ARAContentType type)
{
// Overriding doIsAudioModificationContentAvailable() requires overriding
// doGetAudioModificationContentGrade() accordingly!
jassertfalse;
return ARA::kARAContentGradeInitial;
}
ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::doCreateAudioModificationContentReader ([[maybe_unused]] ARA::PlugIn::AudioModification* audioModification,
[[maybe_unused]] ARA::ARAContentType type,
[[maybe_unused]] const ARA::ARAContentTimeRange* range)
{
// Overriding doIsAudioModificationContentAvailable() requires overriding
// doCreateAudioModificationContentReader() accordingly!
jassertfalse;
return nullptr;
}
bool ARADocumentControllerSpecialisation::doIsPlaybackRegionContentAvailable ([[maybe_unused]] const ARA::PlugIn::PlaybackRegion* playbackRegion,
[[maybe_unused]] ARA::ARAContentType type)
{
return false;
}
ARA::ARAContentGrade ARADocumentControllerSpecialisation::doGetPlaybackRegionContentGrade ([[maybe_unused]] const ARA::PlugIn::PlaybackRegion* playbackRegion,
[[maybe_unused]] ARA::ARAContentType type)
{
// Overriding doIsPlaybackRegionContentAvailable() requires overriding
// doGetPlaybackRegionContentGrade() accordingly!
jassertfalse;
return ARA::kARAContentGradeInitial;
}
ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::doCreatePlaybackRegionContentReader ([[maybe_unused]] ARA::PlugIn::PlaybackRegion* playbackRegion,
[[maybe_unused]] ARA::ARAContentType type,
[[maybe_unused]] const ARA::ARAContentTimeRange* range)
{
// Overriding doIsPlaybackRegionContentAvailable() requires overriding
// doCreatePlaybackRegionContentReader() accordingly!
jassertfalse;
return nullptr;
}
void ARADocumentControllerSpecialisation::doGetPlaybackRegionHeadAndTailTime ([[maybe_unused]] const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARATimeDuration* headTime,
ARA::ARATimeDuration* tailTime)
{
*headTime = 0.0;
*tailTime = 0.0;
}
bool ARADocumentControllerSpecialisation::doIsAudioSourceContentAnalysisIncomplete ([[maybe_unused]] const ARA::PlugIn::AudioSource* audioSource,
[[maybe_unused]] ARA::ARAContentType type)
{
return false;
}
void ARADocumentControllerSpecialisation::doRequestAudioSourceContentAnalysis ([[maybe_unused]] ARA::PlugIn::AudioSource* audioSource,
[[maybe_unused]] std::vector<ARA::ARAContentType> const& contentTypes)
{
}
ARA::ARAInt32 ARADocumentControllerSpecialisation::doGetProcessingAlgorithmsCount() { return 0; }
const ARA::ARAProcessingAlgorithmProperties*
ARADocumentControllerSpecialisation::doGetProcessingAlgorithmProperties ([[maybe_unused]] ARA::ARAInt32 algorithmIndex)
{
return nullptr;
}
ARA::ARAInt32 ARADocumentControllerSpecialisation::doGetProcessingAlgorithmForAudioSource ([[maybe_unused]] const ARA::PlugIn::AudioSource* audioSource)
{
// doGetProcessingAlgorithmForAudioSource() must be implemented if the supported
// algorithm count is greater than zero.
if (getDocumentController()->getProcessingAlgorithmsCount() > 0)
jassertfalse;
return 0;
}
void ARADocumentControllerSpecialisation::doRequestProcessingAlgorithmForAudioSource ([[maybe_unused]] ARA::PlugIn::AudioSource* audioSource,
[[maybe_unused]] ARA::ARAInt32 algorithmIndex)
{
// doRequestProcessingAlgorithmForAudioSource() must be implemented if the supported
// algorithm count is greater than zero.
jassert (getDocumentController()->getProcessingAlgorithmsCount() <= 0);
}
} // namespace juce
@@ -0,0 +1,529 @@
/*
==============================================================================
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
namespace juce
{
class ARAPlaybackRenderer;
class ARAEditorRenderer;
class ARAEditorView;
class ARAInputStream;
class ARAOutputStream;
/** This class contains the customisation points for the JUCE provided ARA document controller
implementation.
Every ARA enabled plugin must provide its own document controller implementation. To do this,
inherit from this class, and override its protected methods as needed. Then you need to
implement a global function somewhere in your module called createARAFactory(). This function
must return an ARAFactory* that will instantiate document controller objects using your
specialisation. There are helper functions inside ARADocumentControllerSpecialisation, so the
implementation of createARAFactory() can always be a simple one-liner. For example
@code
class MyDocumentController : public ARADocumentControllerSpecialisation
{
//...
};
const ARA::ARAFactory* JUCE_CALLTYPE createARAFactory()
{
return juce::ARADocumentControllerSpecialisation::createARAFactory<MyDocumentController>();
}
@endcode
Most member functions have a default implementation so you can build up your required feature
set gradually. The protected functions of this class fall in three distinct groups:
- interactive editing and playback,
- analysis features provided by the plugin and utilised by the host, and
- maintaining the ARA model graph.
On top of the pure virtual functions, you will probably want to override
doCreatePlaybackRenderer() at the very least if you want your plugin to play any sound. This
function belongs to the first group.
If your plugin has analysis capabilities and wants to allow the host to access these, functions
in the second group should be overridden.
The default implementation of the ARA model object classes - i.e. ARADocument, ARAMusicalContext,
ARARegionSequence, ARAAudioSource, ARAAudioModification, ARAPlaybackRegion - should be sufficient
for maintaining a representation of the ARA model graph, hence overriding the model object
creation functions e.g. doCreateMusicalContext() is considered an advanced use case. Hence you
should be able to get a lot done without overriding functions in the third group.
In order to react to the various ARA state changes you can override any of the ARA model object
Listener functions that ARADocumentControllerSpecialisation inherits from. Such listener
functions can be attached to one particular model objects instance, but the listener functions
inside ARADocumentControllerSpecialisation will respond to the events of all instances of the
model objects.
@tags{ARA}
*/
class ARADocumentControllerSpecialisation : public ARADocument::Listener,
public ARAMusicalContext::Listener,
public ARARegionSequence::Listener,
public ARAAudioSource::Listener,
public ARAAudioModification::Listener,
public ARAPlaybackRegion::Listener
{
public:
//==============================================================================
/** Constructor. Used internally by the ARAFactory implementation.
*/
ARADocumentControllerSpecialisation (const ARA::PlugIn::PlugInEntry* entry,
const ARA::ARADocumentControllerHostInstance* instance);
/** Destructor. */
virtual ~ARADocumentControllerSpecialisation();
/** Returns the underlying DocumentController object that references this specialisation.
*/
ARA::PlugIn::DocumentController* getDocumentController() noexcept;
/** Helper function for implementing the global createARAFactory() function.
For example
@code
class MyDocumentController : public ARADocumentControllerSpecialisation
{
//...
};
const ARA::ARAFactory* JUCE_CALLTYPE createARAFactory()
{
return juce::ARADocumentControllerSpecialisation::createARAFactory<MyDocumentController>();
}
@endcode
*/
template <typename SpecialisationType>
static const ARA::ARAFactory* createARAFactory()
{
static_assert (std::is_base_of_v<ARADocumentControllerSpecialisation, SpecialisationType>,
"DocumentController specialization types must inherit from ARADocumentControllerSpecialisation");
return ARA::PlugIn::PlugInEntry::getPlugInEntry<FactoryConfig<SpecialisationType>>()->getFactory();
}
/** Returns a pointer to the ARADocumentControllerSpecialisation instance that is referenced
by the provided DocumentController. You can use this function to access your specialisation
from anywhere where you have access to ARA::PlugIn::DocumentController*.
*/
template <typename Specialisation = ARADocumentControllerSpecialisation>
static Specialisation* getSpecialisedDocumentController (ARA::PlugIn::DocumentController* dc)
{
return static_cast<Specialisation*> (getSpecialisedDocumentControllerImpl (dc));
}
/** Returns a pointer to the ARA document root maintained by this document controller. */
template <typename DocumentType = ARADocument>
DocumentType* getDocument()
{
return static_cast<DocumentType*> (getDocumentImpl());
}
protected:
//==============================================================================
/** Read an ARADocument archive from a juce::InputStream.
@param input Data stream containing previously persisted data to be used when restoring the ARADocument
@param filter A filter to be applied to the stream
Return true if the operation is successful.
@see ARADocumentControllerInterface::restoreObjectsFromArchive
*/
virtual bool doRestoreObjectsFromStream (ARAInputStream& input, const ARARestoreObjectsFilter* filter) = 0;
/** Write an ARADocument archive to a juce::OutputStream.
@param output Data stream that should be used to write the persistent ARADocument data
@param filter A filter to be applied to the stream
Returns true if the operation is successful.
@see ARADocumentControllerInterface::storeObjectsToArchive
*/
virtual bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) = 0;
//==============================================================================
/** Override to return a custom subclass instance of ARAPlaybackRenderer. */
virtual ARAPlaybackRenderer* doCreatePlaybackRenderer();
/** Override to return a custom subclass instance of ARAEditorRenderer. */
virtual ARAEditorRenderer* doCreateEditorRenderer();
/** Override to return a custom subclass instance of ARAEditorView. */
virtual ARAEditorView* doCreateEditorView();
//==============================================================================
// ARAAudioSource content access
/** Override to implement isAudioSourceContentAvailable() for all your supported content types -
the default implementation always returns false, preventing any calls to doGetAudioSourceContentGrade()
and doCreateAudioSourceContentReader().
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doIsAudioSourceContentAvailable.
*/
virtual bool doIsAudioSourceContentAvailable (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type);
/** Override to implement getAudioSourceContentGrade() for all your supported content types.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doGetAudioSourceContentGrade.
*/
virtual ARA::ARAContentGrade doGetAudioSourceContentGrade (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type);
/** Override to implement createAudioSourceContentReader() for all your supported content types,
returning a custom subclass instance of ContentReader providing data of the requested type.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doCreateAudioSourceContentReader.
*/
virtual ARA::PlugIn::ContentReader* doCreateAudioSourceContentReader (ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range);
//==============================================================================
// ARAAudioModification content access
/** Override to implement isAudioModificationContentAvailable() for all your supported content types -
the default implementation always returns false.
For read-only data directly inherited from the underlying audio source you can just delegate the
call to the audio source, but user-editable modification data must be specifically handled here.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doIsAudioModificationContentAvailable.
*/
virtual bool doIsAudioModificationContentAvailable (const ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type);
/** Override to implement getAudioModificationContentGrade() for all your supported content types.
For read-only data directly inherited from the underlying audio source you can just delegate the
call to the audio source, but user-editable modification data must be specifically handled here.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doGetAudioModificationContentGrade.
*/
virtual ARA::ARAContentGrade doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type);
/** Override to implement createAudioModificationContentReader() for all your supported content types,
returning a custom subclass instance of ContentReader providing data of the requested \p type.
For read-only data directly inherited from the underlying audio source you can just delegate the
call to the audio source, but user-editable modification data must be specifically handled here.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doCreateAudioModificationContentReader.
*/
virtual ARA::PlugIn::ContentReader* doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range);
//==============================================================================
// ARAPlaybackRegion content access
/** Override to implement isPlaybackRegionContentAvailable() for all your supported content types -
the default implementation always returns false.
Typically, this call can directly delegate to the underlying audio modification, since most
plug-ins will apply their modification data to the playback region with a transformation that
does not affect content availability.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doIsPlaybackRegionContentAvailable.
*/
virtual bool doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type);
/** Override to implement getPlaybackRegionContentGrade() for all your supported content types.
Typically, this call can directly delegate to the underlying audio modification, since most
plug-ins will apply their modification data to the playback region with a transformation that
does not affect content grade.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doGetPlaybackRegionContentGrade.
*/
virtual ARA::ARAContentGrade doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type);
/** Override to implement createPlaybackRegionContentReader() for all your supported content types,
returning a custom subclass instance of ContentReader providing data of the requested type.
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doCreatePlaybackRegionContentReader.
*/
virtual ARA::PlugIn::ContentReader* doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARAContentType type,
const ARA::ARAContentTimeRange* range);
/** Override to implement getPlaybackRegionHeadAndTailTime().
This function is called within
ARA::PlugIn::DocumentControllerDelegate::doGetPlaybackRegionHeadAndTailTime.
*/
virtual void doGetPlaybackRegionHeadAndTailTime (const ARA::PlugIn::PlaybackRegion* playbackRegion,
ARA::ARATimeDuration* headTime,
ARA::ARATimeDuration* tailTime);
//==============================================================================
// ARAAudioSource analysis
/** Override to implement isAudioSourceContentAnalysisIncomplete().
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doIsAudioSourceContentAnalysisIncomplete.
*/
virtual bool doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource,
ARA::ARAContentType type);
/** Override to implement requestAudioSourceContentAnalysis().
This function's called from
ARA::PlugIn::DocumentControllerDelegate::doRequestAudioSourceContentAnalysis.
*/
virtual void doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource,
std::vector<ARA::ARAContentType> const& contentTypes);
//==============================================================================
// Analysis Algorithm selection
/** Override to implement getProcessingAlgorithmsCount().
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doGetProcessingAlgorithmsCount.
*/
virtual ARA::ARAInt32 doGetProcessingAlgorithmsCount ();
/** Override to implement getProcessingAlgorithmProperties().
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doGetProcessingAlgorithmProperties.
*/
virtual const ARA::ARAProcessingAlgorithmProperties*
doGetProcessingAlgorithmProperties (ARA::ARAInt32 algorithmIndex);
/** Override to implement getProcessingAlgorithmForAudioSource().
This function's result is returned from
ARA::PlugIn::DocumentControllerDelegate::doGetProcessingAlgorithmForAudioSource.
*/
virtual ARA::ARAInt32 doGetProcessingAlgorithmForAudioSource (const ARA::PlugIn::AudioSource* audioSource);
/** Override to implement requestProcessingAlgorithmForAudioSource().
This function's called from
ARA::PlugIn::DocumentControllerDelegate::doRequestProcessingAlgorithmForAudioSource.
*/
virtual void doRequestProcessingAlgorithmForAudioSource (ARA::PlugIn::AudioSource* audioSource, ARA::ARAInt32 algorithmIndex);
//==============================================================================
/** Override to return a custom subclass instance of ARADocument. */
virtual ARADocument* doCreateDocument();
/** Override to return a custom subclass instance of ARAMusicalContext. */
virtual ARAMusicalContext* doCreateMusicalContext (ARADocument* document,
ARA::ARAMusicalContextHostRef hostRef);
/** Override to return a custom subclass instance of ARARegionSequence. */
virtual ARARegionSequence* doCreateRegionSequence (ARADocument* document,
ARA::ARARegionSequenceHostRef hostRef);
/** Override to return a custom subclass instance of ARAAudioSource. */
virtual ARAAudioSource* doCreateAudioSource (ARADocument* document,
ARA::ARAAudioSourceHostRef hostRef);
/** Override to return a custom subclass instance of ARAAudioModification. */
virtual ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource,
ARA::ARAAudioModificationHostRef hostRef,
const ARAAudioModification* optionalModificationToClone);
/** Override to return a custom subclass instance of ARAPlaybackRegion. */
virtual ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification,
ARA::ARAPlaybackRegionHostRef hostRef);
private:
//==============================================================================
template <typename SpecialisationType>
class FactoryConfig : public ARA::PlugIn::FactoryConfig
{
public:
FactoryConfig() noexcept
{
const juce::String compatibleDocumentArchiveIDString = JucePlugin_ARACompatibleArchiveIDs;
if (compatibleDocumentArchiveIDString.isNotEmpty())
{
compatibleDocumentArchiveIDStrings = juce::StringArray::fromLines (compatibleDocumentArchiveIDString);
for (const auto& compatibleID : compatibleDocumentArchiveIDStrings)
compatibleDocumentArchiveIDs.push_back (compatibleID.toRawUTF8());
}
// Update analyzeable content types
static constexpr std::array<ARA::ARAContentType, 6> contentTypes {
ARA::kARAContentTypeNotes,
ARA::kARAContentTypeTempoEntries,
ARA::kARAContentTypeBarSignatures,
ARA::kARAContentTypeStaticTuning,
ARA::kARAContentTypeKeySignatures,
ARA::kARAContentTypeSheetChords
};
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6313)
for (size_t i = 0; i < contentTypes.size(); ++i)
if (JucePlugin_ARAContentTypes & (1 << i))
analyzeableContentTypes.push_back (contentTypes[i]);
JUCE_END_IGNORE_WARNINGS_MSVC
// Update playback transformation flags
static constexpr std::array<ARA::ARAPlaybackTransformationFlags, 4> playbackTransformationFlags {
ARA::kARAPlaybackTransformationTimestretch,
ARA::kARAPlaybackTransformationTimestretchReflectingTempo,
ARA::kARAPlaybackTransformationContentBasedFadeAtTail,
ARA::kARAPlaybackTransformationContentBasedFadeAtHead
};
supportedPlaybackTransformationFlags = 0;
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6313)
for (size_t i = 0; i < playbackTransformationFlags.size(); ++i)
if (JucePlugin_ARATransformationFlags & (1 << i))
supportedPlaybackTransformationFlags |= playbackTransformationFlags[i];
JUCE_END_IGNORE_WARNINGS_MSVC
}
const char* getFactoryID() const noexcept override { return JucePlugin_ARAFactoryID; }
const char* getPlugInName() const noexcept override { return JucePlugin_Name; }
const char* getManufacturerName() const noexcept override { return JucePlugin_Manufacturer; }
const char* getInformationURL() const noexcept override { return JucePlugin_ManufacturerWebsite; }
const char* getVersion() const noexcept override { return JucePlugin_VersionString; }
const char* getDocumentArchiveID() const noexcept override { return JucePlugin_ARADocumentArchiveID; }
ARA::ARASize getCompatibleDocumentArchiveIDsCount() const noexcept override
{
return compatibleDocumentArchiveIDs.size();
}
const ARA::ARAPersistentID* getCompatibleDocumentArchiveIDs() const noexcept override
{
return compatibleDocumentArchiveIDs.empty() ? nullptr : compatibleDocumentArchiveIDs.data();
}
ARA::ARASize getAnalyzeableContentTypesCount() const noexcept override
{
return analyzeableContentTypes.size();
}
const ARA::ARAContentType* getAnalyzeableContentTypes() const noexcept override
{
return analyzeableContentTypes.empty() ? nullptr : analyzeableContentTypes.data();
}
ARA::ARAPlaybackTransformationFlags getSupportedPlaybackTransformationFlags() const noexcept override
{
return supportedPlaybackTransformationFlags;
}
ARA::PlugIn::DocumentController* createDocumentController (const ARA::PlugIn::PlugInEntry* entry,
const ARA::ARADocumentControllerHostInstance* instance) const noexcept override
{
auto* spec = new SpecialisationType (entry, instance);
return spec->getDocumentController();
}
void destroyDocumentController (ARA::PlugIn::DocumentController* controller) const noexcept override
{
delete getSpecialisedDocumentController (controller);
}
private:
juce::StringArray compatibleDocumentArchiveIDStrings;
std::vector<ARA::ARAPersistentID> compatibleDocumentArchiveIDs;
std::vector<ARA::ARAContentType> analyzeableContentTypes;
ARA::ARAPlaybackTransformationFlags supportedPlaybackTransformationFlags;
};
//==============================================================================
static ARADocumentControllerSpecialisation* getSpecialisedDocumentControllerImpl (ARA::PlugIn::DocumentController*);
ARADocument* getDocumentImpl();
//==============================================================================
class ARADocumentControllerImpl;
std::unique_ptr<ARADocumentControllerImpl> documentController;
};
/** Used to read persisted ARA archives - see doRestoreObjectsFromStream() for details.
@tags{ARA}
*/
class ARAInputStream : public InputStream
{
public:
explicit ARAInputStream (ARA::PlugIn::HostArchiveReader*);
int64 getPosition() override { return position; }
int64 getTotalLength() override { return size; }
int read (void*, int) override;
bool setPosition (int64) override;
bool isExhausted() override;
bool failed() const { return failure; }
private:
ARA::PlugIn::HostArchiveReader* archiveReader;
int64 position = 0;
int64 size;
bool failure = false;
};
/** Used to write persistent ARA archives - see doStoreObjectsToStream() for details.
@tags{ARA}
*/
class ARAOutputStream : public OutputStream
{
public:
explicit ARAOutputStream (ARA::PlugIn::HostArchiveWriter*);
int64 getPosition() override { return position; }
void flush() override {}
bool write (const void*, size_t) override;
bool setPosition (int64) override;
private:
ARA::PlugIn::HostArchiveWriter* archiveWriter;
int64 position = 0;
};
} // namespace juce
@@ -0,0 +1,71 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
class ARADocumentController : public ARA::PlugIn::DocumentController
{
public:
using ARA::PlugIn::DocumentController::DocumentController;
template <typename Document_t = ARADocument>
Document_t* getDocument() const noexcept { return ARA::PlugIn::DocumentController::getDocument<Document_t>(); }
//==============================================================================
/** @internal */
virtual void internalNotifyAudioSourceAnalysisProgressStarted (ARAAudioSource* audioSource) = 0;
/** @internal */
virtual void internalNotifyAudioSourceAnalysisProgressUpdated (ARAAudioSource* audioSource, float progress) = 0;
/** @internal */
virtual void internalNotifyAudioSourceAnalysisProgressCompleted (ARAAudioSource* audioSource) = 0;
/** @internal */
virtual void internalDidUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource,
ARAAudioSource::ARAAnalysisProgressState state,
float progress) = 0;
//==============================================================================
/** @internal */
virtual void internalNotifyAudioSourceContentChanged (ARAAudioSource* audioSource,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost) = 0;
/** @internal */
virtual void internalNotifyAudioModificationContentChanged (ARAAudioModification* audioModification,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost) = 0;
/** @internal */
virtual void internalNotifyPlaybackRegionContentChanged (ARAPlaybackRegion* playbackRegion,
ARAContentUpdateScopes scopeFlags,
bool notifyARAHost) = 0;
friend class ARAPlaybackRegionReader;
};
} // namespace juce
@@ -0,0 +1,293 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
size_t ARADocument::getNumChildren() const noexcept
{
return getMusicalContexts().size() + getRegionSequences().size() + getAudioSources().size();
}
ARAObject* ARADocument::getChild (size_t index)
{
auto& musicalContexts = getMusicalContexts();
if (index < musicalContexts.size())
return musicalContexts[index];
const auto numMusicalContexts = musicalContexts.size();
auto& regionSequences = getRegionSequences();
if (index < numMusicalContexts + regionSequences.size())
return regionSequences[index - numMusicalContexts];
const auto numMusicalContextsAndRegionSequences = numMusicalContexts + regionSequences.size();
auto& audioSources = getAudioSources();
if (index < numMusicalContextsAndRegionSequences + audioSources.size())
return getAudioSources()[index - numMusicalContextsAndRegionSequences];
return nullptr;
}
//==============================================================================
size_t ARARegionSequence::getNumChildren() const noexcept
{
return 0;
}
ARAObject* ARARegionSequence::getChild (size_t)
{
return nullptr;
}
Range<double> ARARegionSequence::getTimeRange (ARAPlaybackRegion::IncludeHeadAndTail includeHeadAndTail) const
{
if (getPlaybackRegions().empty())
return {};
auto startTime = std::numeric_limits<double>::max();
auto endTime = std::numeric_limits<double>::lowest();
for (const auto& playbackRegion : getPlaybackRegions())
{
const auto regionTimeRange = playbackRegion->getTimeRange (includeHeadAndTail);
startTime = jmin (startTime, regionTimeRange.getStart());
endTime = jmax (endTime, regionTimeRange.getEnd());
}
return { startTime, endTime };
}
double ARARegionSequence::getCommonSampleRate() const
{
const auto getSampleRate = [] (auto* playbackRegion)
{
return playbackRegion->getAudioModification()->getAudioSource()->getSampleRate();
};
const auto range = getPlaybackRegions();
const auto sampleRate = range.size() > 0 ? getSampleRate (range.front()) : 0.0;
if (std::any_of (range.begin(), range.end(), [&] (auto& x) { return ! exactlyEqual (getSampleRate (x), sampleRate); }))
return 0.0;
return sampleRate;
}
//==============================================================================
size_t ARAAudioSource::getNumChildren() const noexcept
{
return getAudioModifications().size();
}
ARAObject* ARAAudioSource::getChild (size_t index)
{
return getAudioModifications()[index];
}
void ARAAudioSource::notifyAnalysisProgressStarted()
{
getDocumentController<ARADocumentController>()->internalNotifyAudioSourceAnalysisProgressStarted (this);
}
void ARAAudioSource::notifyAnalysisProgressUpdated (float progress)
{
getDocumentController<ARADocumentController>()->internalNotifyAudioSourceAnalysisProgressUpdated (this, progress);
}
void ARAAudioSource::notifyAnalysisProgressCompleted()
{
getDocumentController<ARADocumentController>()->internalNotifyAudioSourceAnalysisProgressCompleted (this);
}
void ARAAudioSource::notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost)
{
getDocumentController<ARADocumentController>()->internalNotifyAudioSourceContentChanged (this,
scopeFlags,
notifyARAHost);
}
//==============================================================================
size_t ARAAudioModification::getNumChildren() const noexcept
{
return getPlaybackRegions().size();
}
ARAObject* ARAAudioModification::getChild (size_t index)
{
return getPlaybackRegions()[index];
}
void ARAAudioModification::notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost)
{
getDocumentController<ARADocumentController>()->internalNotifyAudioModificationContentChanged (this,
scopeFlags,
notifyARAHost);
}
//==============================================================================
ARAObject* ARAPlaybackRegion::getParent() { return getAudioModification(); }
Range<double> ARAPlaybackRegion::getTimeRange (IncludeHeadAndTail includeHeadAndTail) const
{
auto startTime = getStartInPlaybackTime();
auto endTime = getEndInPlaybackTime();
if (includeHeadAndTail == IncludeHeadAndTail::yes)
{
ARA::ARATimeDuration headTime {}, tailTime {};
getDocumentController()->getPlaybackRegionHeadAndTailTime (toRef (this), &headTime, &tailTime);
startTime -= headTime;
endTime += tailTime;
}
return { startTime, endTime };
}
Range<int64> ARAPlaybackRegion::getSampleRange (double sampleRate, IncludeHeadAndTail includeHeadAndTail) const
{
const auto timeRange = getTimeRange (includeHeadAndTail);
return { ARA::samplePositionAtTime (timeRange.getStart(), sampleRate),
ARA::samplePositionAtTime (timeRange.getEnd(), sampleRate) };
}
double ARAPlaybackRegion::getHeadTime() const
{
ARA::ARATimeDuration headTime {}, tailTime {};
getDocumentController()->getPlaybackRegionHeadAndTailTime (toRef (this), &headTime, &tailTime);
return headTime;
}
double ARAPlaybackRegion::getTailTime() const
{
ARA::ARATimeDuration headTime {}, tailTime {};
getDocumentController()->getPlaybackRegionHeadAndTailTime (toRef (this), &headTime, &tailTime);
return tailTime;
}
void ARAPlaybackRegion::notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost)
{
getDocumentController<ARADocumentController>()->internalNotifyPlaybackRegionContentChanged (this,
scopeFlags,
notifyARAHost);
}
//==============================================================================
void ARADocumentListener::willBeginEditing ([[maybe_unused]] ARADocument* document) {}
void ARADocumentListener::didEndEditing ([[maybe_unused]] ARADocument* document) {}
void ARADocumentListener::willNotifyModelUpdates ([[maybe_unused]] ARADocument* document) {}
void ARADocumentListener::didNotifyModelUpdates ([[maybe_unused]] ARADocument* document) {}
void ARADocumentListener::willUpdateDocumentProperties ([[maybe_unused]] ARADocument* document,
[[maybe_unused]] ARA::PlugIn::PropertiesPtr<ARA::ARADocumentProperties> newProperties) {}
void ARADocumentListener::didUpdateDocumentProperties ([[maybe_unused]] ARADocument* document) {}
void ARADocumentListener::didAddMusicalContextToDocument ([[maybe_unused]] ARADocument* document,
[[maybe_unused]] ARAMusicalContext* musicalContext) {}
void ARADocumentListener::willRemoveMusicalContextFromDocument ([[maybe_unused]] ARADocument* document,
[[maybe_unused]] ARAMusicalContext* musicalContext) {}
void ARADocumentListener::didReorderMusicalContextsInDocument ([[maybe_unused]] ARADocument* document) {}
void ARADocumentListener::didAddRegionSequenceToDocument ([[maybe_unused]] ARADocument* document,
[[maybe_unused]] ARARegionSequence* regionSequence) {}
void ARADocumentListener::willRemoveRegionSequenceFromDocument ([[maybe_unused]] ARADocument* document,
[[maybe_unused]] ARARegionSequence* regionSequence) {}
void ARADocumentListener::didReorderRegionSequencesInDocument ([[maybe_unused]] ARADocument* document) {}
void ARADocumentListener::didAddAudioSourceToDocument ([[maybe_unused]] ARADocument* document,
[[maybe_unused]] ARAAudioSource* audioSource) {}
void ARADocumentListener::willRemoveAudioSourceFromDocument ([[maybe_unused]] ARADocument* document,
[[maybe_unused]] ARAAudioSource* audioSource) {}
void ARADocumentListener::willDestroyDocument ([[maybe_unused]] ARADocument* document) {}
//==============================================================================
void ARAMusicalContextListener::willUpdateMusicalContextProperties ([[maybe_unused]] ARAMusicalContext* musicalContext,
[[maybe_unused]] ARA::PlugIn::PropertiesPtr<ARA::ARAMusicalContextProperties> newProperties) {}
void ARAMusicalContextListener::didUpdateMusicalContextProperties ([[maybe_unused]] ARAMusicalContext* musicalContext) {}
void ARAMusicalContextListener::doUpdateMusicalContextContent ([[maybe_unused]] ARAMusicalContext* musicalContext,
[[maybe_unused]] ARAContentUpdateScopes scopeFlags) {}
void ARAMusicalContextListener::didAddRegionSequenceToMusicalContext ([[maybe_unused]] ARAMusicalContext* musicalContext,
[[maybe_unused]] ARARegionSequence* regionSequence) {}
void ARAMusicalContextListener::willRemoveRegionSequenceFromMusicalContext ([[maybe_unused]] ARAMusicalContext* musicalContext,
[[maybe_unused]] ARARegionSequence* regionSequence) {}
void ARAMusicalContextListener::didReorderRegionSequencesInMusicalContext ([[maybe_unused]] ARAMusicalContext* musicalContext) {}
void ARAMusicalContextListener::willDestroyMusicalContext ([[maybe_unused]] ARAMusicalContext* musicalContext) {}
//==============================================================================
void ARAPlaybackRegionListener::willUpdatePlaybackRegionProperties ([[maybe_unused]] ARAPlaybackRegion* playbackRegion,
[[maybe_unused]] ARA::PlugIn::PropertiesPtr<ARA::ARAPlaybackRegionProperties> newProperties) {}
void ARAPlaybackRegionListener::didUpdatePlaybackRegionProperties ([[maybe_unused]] ARAPlaybackRegion* playbackRegion) {}
void ARAPlaybackRegionListener::didUpdatePlaybackRegionContent ([[maybe_unused]] ARAPlaybackRegion* playbackRegion,
[[maybe_unused]] ARAContentUpdateScopes scopeFlags) {}
void ARAPlaybackRegionListener::willDestroyPlaybackRegion ([[maybe_unused]] ARAPlaybackRegion* playbackRegion) {}
//==============================================================================
void ARARegionSequenceListener::willUpdateRegionSequenceProperties ([[maybe_unused]] ARARegionSequence* regionSequence,
[[maybe_unused]] ARA::PlugIn::PropertiesPtr<ARA::ARARegionSequenceProperties> newProperties) {}
void ARARegionSequenceListener::didUpdateRegionSequenceProperties ([[maybe_unused]] ARARegionSequence* regionSequence) {}
void ARARegionSequenceListener::willRemovePlaybackRegionFromRegionSequence ([[maybe_unused]] ARARegionSequence* regionSequence,
[[maybe_unused]] ARAPlaybackRegion* playbackRegion) {}
void ARARegionSequenceListener::didAddPlaybackRegionToRegionSequence ([[maybe_unused]] ARARegionSequence* regionSequence,
[[maybe_unused]] ARAPlaybackRegion* playbackRegion) {}
void ARARegionSequenceListener::willDestroyRegionSequence ([[maybe_unused]] ARARegionSequence* regionSequence) {}
//==============================================================================
void ARAAudioSourceListener::willUpdateAudioSourceProperties ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] ARA::PlugIn::PropertiesPtr<ARA::ARAAudioSourceProperties> newProperties) {}
void ARAAudioSourceListener::didUpdateAudioSourceProperties ([[maybe_unused]] ARAAudioSource* audioSource) {}
void ARAAudioSourceListener::doUpdateAudioSourceContent ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] ARAContentUpdateScopes scopeFlags) {}
void ARAAudioSourceListener::didUpdateAudioSourceAnalysisProgress ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] ARA::ARAAnalysisProgressState state,
[[maybe_unused]] float progress) {}
void ARAAudioSourceListener::willEnableAudioSourceSamplesAccess ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] bool enable) {}
void ARAAudioSourceListener::didEnableAudioSourceSamplesAccess ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] bool enable) {}
void ARAAudioSourceListener::willDeactivateAudioSourceForUndoHistory ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] bool deactivate) {}
void ARAAudioSourceListener::didDeactivateAudioSourceForUndoHistory ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] bool deactivate) {}
void ARAAudioSourceListener::didAddAudioModificationToAudioSource ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] ARAAudioModification* audioModification) {}
void ARAAudioSourceListener::willRemoveAudioModificationFromAudioSource ([[maybe_unused]] ARAAudioSource* audioSource,
[[maybe_unused]] ARAAudioModification* audioModification) {}
void ARAAudioSourceListener::willDestroyAudioSource ([[maybe_unused]] ARAAudioSource* audioSource) {}
//==============================================================================
void ARAAudioModificationListener::willUpdateAudioModificationProperties ([[maybe_unused]] ARAAudioModification* audioModification,
[[maybe_unused]] ARA::PlugIn::PropertiesPtr<ARA::ARAAudioModificationProperties> newProperties) {}
void ARAAudioModificationListener::didUpdateAudioModificationProperties ([[maybe_unused]] ARAAudioModification* audioModification) {}
void ARAAudioModificationListener::didUpdateAudioModificationContent ([[maybe_unused]] ARAAudioModification* audioModification,
[[maybe_unused]] ARAContentUpdateScopes scopeFlags) {}
void ARAAudioModificationListener::willDeactivateAudioModificationForUndoHistory ([[maybe_unused]] ARAAudioModification* audioModification,
[[maybe_unused]] bool deactivate) {}
void ARAAudioModificationListener::didDeactivateAudioModificationForUndoHistory ([[maybe_unused]] ARAAudioModification* audioModification,
[[maybe_unused]] bool deactivate) {}
void ARAAudioModificationListener::didAddPlaybackRegionToAudioModification ([[maybe_unused]] ARAAudioModification* audioModification,
[[maybe_unused]] ARAPlaybackRegion* playbackRegion) {}
void ARAAudioModificationListener::willRemovePlaybackRegionFromAudioModification ([[maybe_unused]] ARAAudioModification* audioModification,
[[maybe_unused]] ARAPlaybackRegion* playbackRegion) {}
void ARAAudioModificationListener::willDestroyAudioModification ([[maybe_unused]] ARAAudioModification* audioModification) {}
} // namespace juce
@@ -0,0 +1,974 @@
/*
==============================================================================
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
namespace juce
{
class ARADocumentController;
class ARADocument;
class ARAMusicalContext;
class ARARegionSequence;
class ARAAudioSource;
class ARAAudioModification;
class ARAPlaybackRegion;
/** Base class used by the JUCE ARA model objects to provide listenable interfaces.
@tags{ARA}
*/
template <class ListenerType>
class JUCE_API ARAListenableModelClass
{
public:
/** Constructor. */
ARAListenableModelClass() = default;
/** Destructor. */
virtual ~ARAListenableModelClass() = default;
/** Subscribe \p l to notified by changes to the object.
@param l The listener instance.
*/
void addListener (ListenerType* l) { listeners.add (l); }
/** Unsubscribe \p l from object notifications.
@param l The listener instance.
*/
void removeListener (ListenerType* l) { listeners.remove (l); }
/** Call the provided callback for each of the added listeners. */
template <typename Callback>
void notifyListeners (Callback&& callback)
{
listeners.call (callback);
}
private:
ListenerList<ListenerType> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAListenableModelClass)
};
/** Create a derived implementation of this class and pass it to ARAObject::visit() to retrieve the
concrete type of a model object.
Combined with ARAObject::traverse() on the ARADocument object it is possible to discover the
entire model graph.
Note that the references passed to the visit member functions are only guaranteed to live for
the duration of the function call, so don't store pointers to these objects!
@tags{Audio}
*/
class ARAObjectVisitor
{
public:
/** Destructor. */
virtual ~ARAObjectVisitor() = default;
/** Called when visiting an ARADocument object. */
virtual void visitDocument (juce::ARADocument&) {}
/** Called when visiting an ARAMusicalContext object. */
virtual void visitMusicalContext (juce::ARAMusicalContext&) {}
/** Called when visiting an ARARegionSequence object. */
virtual void visitRegionSequence (juce::ARARegionSequence&) {}
/** Called when visiting an ARAPlaybackRegion object. */
virtual void visitPlaybackRegion (juce::ARAPlaybackRegion&) {}
/** Called when visiting an ARAAudioModification object. */
virtual void visitAudioModification (juce::ARAAudioModification&) {}
/** Called when visiting an ARAAudioSource object. */
virtual void visitAudioSource (juce::ARAAudioSource&) {}
};
/** Common base class for all JUCE ARA model objects to aid with the discovery and traversal of the
entire ARA model graph.
@tags{ARA}
*/
class ARAObject
{
public:
/** Destructor. */
virtual ~ARAObject() = default;
/** Returns the number of ARA model objects aggregated by this object. Objects that are merely
referred to, but not aggregated by the current object are not included in this count, e.g.
a referenced RegionSequence does not count as a child of the referring PlaybackRegion.
See the ARA documentation's ARA Model Graph Overview for more details.
*/
virtual size_t getNumChildren() const noexcept = 0;
/** Returns the child object associated with the given index.
The index should be smaller than the value returned by getNumChildren().
Note that the index of a particular object may change when the ARA model graph is edited.
*/
virtual ARAObject* getChild (size_t index) = 0;
/** Returns the ARA model object that aggregates this object.
Returns nullptr for the ARADocument root object.
*/
virtual ARAObject* getParent() = 0;
/** Implements a depth first traversal of the ARA model graph starting from the current object,
and visiting its children recursively.
The provided function should accept a single ARAObject& parameter.
*/
template <typename Fn>
void traverse (Fn&& fn)
{
fn (*this);
for (size_t i = 0; i < getNumChildren(); ++i)
{
getChild (i)->traverse (fn);
}
}
/** Allows the retrieval of the concrete type of a model object.
To use this, create a new class derived from ARAObjectVisitor and override its functions
depending on which concrete types you are interested in.
Calling this function inside the function passed to ARAObject::traverse() allows you to
map the entire ARA model graph.
*/
virtual void visit (ARAObjectVisitor& visitor) = 0;
};
/** A base class for listeners that want to know about changes to an ARADocument object.
Use ARADocument::addListener() to register your listener with an ARADocument.
@tags{ARA}
*/
class JUCE_API ARADocumentListener
{
public:
/** Destructor */
virtual ~ARADocumentListener() = default;
/** Called before the document enters an editing state.
@param document The document about to enter an editing state.
*/
virtual void willBeginEditing (ARADocument* document);
/** Called after the document exits an editing state.
@param document The document about exit an editing state.
*/
virtual void didEndEditing (ARADocument* document);
/** Called before sending model updates do the host.
@param document The document whose model updates are about to be sent.
*/
virtual void willNotifyModelUpdates (ARADocument* document);
/** Called after sending model updates do the host.
@param document The document whose model updates have just been sent.
*/
virtual void didNotifyModelUpdates (ARADocument* document);
/** Called before the document's properties are updated.
@param document The document whose properties will be updated.
@param newProperties The document properties that will be assigned to \p document.
*/
virtual void willUpdateDocumentProperties (ARADocument* document,
ARA::PlugIn::PropertiesPtr<ARA::ARADocumentProperties> newProperties);
/** Called after the document's properties are updated.
@param document The document whose properties were updated.
*/
virtual void didUpdateDocumentProperties (ARADocument* document);
/** Called after a musical context is added to the document.
@param document The document that \p musicalContext was added to.
@param musicalContext The musical context that was added to \p document.
*/
virtual void didAddMusicalContextToDocument (ARADocument* document, ARAMusicalContext* musicalContext);
/** Called before a musical context is removed from the document.
@param document The document that \p musicalContext will be removed from.
@param musicalContext The musical context that will be removed from \p document.
*/
virtual void willRemoveMusicalContextFromDocument (ARADocument* document, ARAMusicalContext* musicalContext);
/** Called after the musical contexts are reordered in an ARA document
Musical contexts are sorted by their order index -
this callback signals a change in this ordering within the document.
@param document The document with reordered musical contexts.
*/
virtual void didReorderMusicalContextsInDocument (ARADocument* document);
/** Called after a region sequence is added to the document.
@param document The document that \p regionSequence was added to.
@param regionSequence The region sequence that was added to \p document.
*/
virtual void didAddRegionSequenceToDocument (ARADocument* document, ARARegionSequence* regionSequence);
/** Called before a region sequence is removed from the document.
@param document The document that \p regionSequence will be removed from.
@param regionSequence The region sequence that will be removed from \p document.
*/
virtual void willRemoveRegionSequenceFromDocument (ARADocument* document, ARARegionSequence* regionSequence);
/** Called after the region sequences are reordered in an ARA document
Region sequences are sorted by their order index -
this callback signals a change in this ordering within the document.
@param document The document with reordered region sequences.
*/
virtual void didReorderRegionSequencesInDocument (ARADocument* document);
/** Called after an audio source is added to the document.
@param document The document that \p audioSource was added to.
@param audioSource The audio source that was added to \p document.
*/
virtual void didAddAudioSourceToDocument (ARADocument* document, ARAAudioSource* audioSource);
/** Called before an audio source is removed from the document.
@param document The document that \p audioSource will be removed from .
@param audioSource The audio source that will be removed from \p document.
*/
virtual void willRemoveAudioSourceFromDocument (ARADocument* document, ARAAudioSource* audioSource);
/** Called before the document is destroyed by the ARA host.
@param document The document that will be destroyed.
*/
virtual void willDestroyDocument (ARADocument* document);
};
//==============================================================================
/** Base class representing an ARA document.
@tags{ARA}
*/
class JUCE_API ARADocument : public ARA::PlugIn::Document,
public ARAListenableModelClass<ARADocumentListener>,
public ARAObject
{
public:
using PropertiesPtr = ARA::PlugIn::PropertiesPtr<ARA::ARADocumentProperties>;
using Listener = ARADocumentListener;
using ARA::PlugIn::Document::Document;
/** Returns the result of ARA::PlugIn::Document::getAudioSources() with the pointers within
cast to ARAAudioSource*.
If you have overridden ARADocumentControllerSpecialisation::doCreateAudioSource(), then
you can use the template parameter to cast the pointers to your subclass of ARAAudioSource.
*/
template <typename AudioSource_t = ARAAudioSource>
const std::vector<AudioSource_t*>& getAudioSources() const noexcept
{
return ARA::PlugIn::Document::getAudioSources<AudioSource_t>();
}
/** Returns the result of ARA::PlugIn::Document::getMusicalContexts() with the pointers within
cast to ARAMusicalContext*.
If you have overridden ARADocumentControllerSpecialisation::doCreateMusicalContext(), then
you can use the template parameter to cast the pointers to your subclass of ARAMusicalContext.
*/
template <typename MusicalContext_t = ARAMusicalContext>
const std::vector<MusicalContext_t*>& getMusicalContexts() const noexcept
{
return ARA::PlugIn::Document::getMusicalContexts<MusicalContext_t>();
}
/** Returns the result of ARA::PlugIn::Document::getRegionSequences() with the pointers within
cast to ARARegionSequence*.
If you have overridden ARADocumentControllerSpecialisation::doCreateRegionSequence(), then
you can use the template parameter to cast the pointers to your subclass of ARARegionSequence.
*/
template <typename RegionSequence_t = ARARegionSequence>
const std::vector<RegionSequence_t*>& getRegionSequences() const noexcept
{
return ARA::PlugIn::Document::getRegionSequences<RegionSequence_t>();
}
size_t getNumChildren() const noexcept override;
ARAObject* getChild (size_t index) override;
ARAObject* getParent() override { return nullptr; }
void visit (ARAObjectVisitor& visitor) override { visitor.visitDocument (*this); }
};
/** A base class for listeners that want to know about changes to an ARAMusicalContext object.
Use ARAMusicalContext::addListener() to register your listener with an ARAMusicalContext.
@tags{ARA}
*/
class JUCE_API ARAMusicalContextListener
{
public:
virtual ~ARAMusicalContextListener() = default;
/** Called before the musical context's properties are updated.
@param musicalContext The musical context whose properties will be updated.
@param newProperties The musical context properties that will be assigned to \p musicalContext.
*/
virtual void willUpdateMusicalContextProperties (ARAMusicalContext* musicalContext,
ARA::PlugIn::PropertiesPtr<ARA::ARAMusicalContextProperties> newProperties);
/** Called after the musical context's properties are updated by the host.
@param musicalContext The musical context whose properties were updated.
*/
virtual void didUpdateMusicalContextProperties (ARAMusicalContext* musicalContext);
/** Called when the musical context's content (i.e tempo entries or chords) changes.
@param musicalContext The musical context with updated content.
@param scopeFlags The scope of the content update indicating what has changed.
*/
virtual void doUpdateMusicalContextContent (ARAMusicalContext* musicalContext, ARAContentUpdateScopes scopeFlags);
/** Called after a region sequence is added to the musical context.
@param musicalContext The musical context that \p regionSequence was added to.
@param regionSequence The region sequence that was added to \p musicalContext.
*/
virtual void didAddRegionSequenceToMusicalContext (ARAMusicalContext* musicalContext, ARARegionSequence* regionSequence);
/** Called before a region sequence is removed from the musical context.
@param musicalContext The musical context that \p regionSequence will be removed from.
@param regionSequence The region sequence that will be removed from \p musicalContext.
*/
virtual void willRemoveRegionSequenceFromMusicalContext (ARAMusicalContext* musicalContext,
ARARegionSequence* regionSequence);
/** Called after the region sequences are reordered in an ARA MusicalContext
Region sequences are sorted by their order index -
this callback signals a change in this ordering within the musical context.
@param musicalContext The musical context with reordered region sequences.
*/
virtual void didReorderRegionSequencesInMusicalContext (ARAMusicalContext* musicalContext);
/** Called before the musical context is destroyed.
@param musicalContext The musical context that will be destroyed.
*/
virtual void willDestroyMusicalContext (ARAMusicalContext* musicalContext);
};
//==============================================================================
/** Base class representing an ARA musical context.
@tags{ARA}
*/
class JUCE_API ARAMusicalContext : public ARA::PlugIn::MusicalContext,
public ARAListenableModelClass<ARAMusicalContextListener>,
public ARAObject
{
public:
using PropertiesPtr = ARA::PlugIn::PropertiesPtr<ARA::ARAMusicalContextProperties>;
using Listener = ARAMusicalContextListener;
using ARA::PlugIn::MusicalContext::MusicalContext;
/** Returns the result of ARA::PlugIn::MusicalContext::getDocument() with the pointer cast
to ARADocument*.
If you have overridden ARADocumentControllerSpecialisation::doCreateDocument(), then you
can use the template parameter to cast the pointers to your subclass of ARADocument.
*/
template <typename Document_t = ARADocument>
Document_t* getDocument() const noexcept
{
return ARA::PlugIn::MusicalContext::getDocument<Document_t>();
}
/** Returns the result of ARA::PlugIn::MusicalContext::getRegionSequences() with the pointers
within cast to ARARegionSequence*.
If you have overridden ARADocumentControllerSpecialisation::doCreateRegionSequence(), then you
can use the template parameter to cast the pointers to your subclass of ARARegionSequence.
*/
template <typename RegionSequence_t = ARARegionSequence>
const std::vector<RegionSequence_t*>& getRegionSequences() const noexcept
{
return ARA::PlugIn::MusicalContext::getRegionSequences<RegionSequence_t>();
}
size_t getNumChildren() const noexcept override { return 0; }
ARAObject* getChild (size_t) override { return nullptr; }
ARAObject* getParent() override { return getDocument(); }
void visit (ARAObjectVisitor& visitor) override { visitor.visitMusicalContext (*this); }
};
/** A base class for listeners that want to know about changes to an ARAPlaybackRegion object.
Use ARAPlaybackRegion::addListener() to register your listener with an ARAPlaybackRegion.
@tags{ARA}
*/
class JUCE_API ARAPlaybackRegionListener
{
public:
/** Destructor. */
virtual ~ARAPlaybackRegionListener() = default;
/** Called before the playback region's properties are updated.
@param playbackRegion The playback region whose properties will be updated.
@param newProperties The playback region properties that will be assigned to \p playbackRegion.
*/
virtual void willUpdatePlaybackRegionProperties (ARAPlaybackRegion* playbackRegion,
ARA::PlugIn::PropertiesPtr<ARA::ARAPlaybackRegionProperties> newProperties);
/** Called after the playback region's properties are updated.
@param playbackRegion The playback region whose properties were updated.
*/
virtual void didUpdatePlaybackRegionProperties (ARAPlaybackRegion* playbackRegion);
/** Called when the playback region's content (i.e. samples or notes) changes.
@param playbackRegion The playback region with updated content.
@param scopeFlags The scope of the content update.
*/
virtual void didUpdatePlaybackRegionContent (ARAPlaybackRegion* playbackRegion,
ARAContentUpdateScopes scopeFlags);
/** Called before the playback region is destroyed.
@param playbackRegion The playback region that will be destroyed.
*/
virtual void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion);
};
//==============================================================================
/** Base class representing an ARA playback region.
@tags{ARA}
*/
class JUCE_API ARAPlaybackRegion : public ARA::PlugIn::PlaybackRegion,
public ARAListenableModelClass<ARAPlaybackRegionListener>,
public ARAObject
{
public:
using PropertiesPtr = ARA::PlugIn::PropertiesPtr<ARA::ARAPlaybackRegionProperties>;
using Listener = ARAPlaybackRegionListener;
using ARA::PlugIn::PlaybackRegion::PlaybackRegion;
/** Returns the result of ARA::PlugIn::PlaybackRegion::getAudioModification() with the pointer cast
to ARAAudioModification*.
If you have overridden ARADocumentControllerSpecialisation::doCreateAudioModification(), then you
can use the template parameter to cast the pointers to your subclass of ARAAudioModification.
*/
template <typename AudioModification_t = ARAAudioModification>
AudioModification_t* getAudioModification() const noexcept
{
return ARA::PlugIn::PlaybackRegion::getAudioModification<AudioModification_t>();
}
/** Returns the result of ARA::PlugIn::PlaybackRegion::getRegionSequence() with the pointer cast
to ARARegionSequence*.
If you have overridden ARADocumentControllerSpecialisation::doCreateRegionSequence(), then you
can use the template parameter to cast the pointers to your subclass of ARARegionSequence.
*/
template <typename RegionSequence_t = ARARegionSequence>
RegionSequence_t* getRegionSequence() const noexcept
{
return ARA::PlugIn::PlaybackRegion::getRegionSequence<RegionSequence_t>();
}
size_t getNumChildren() const noexcept override { return 0; }
ARAObject* getChild (size_t) override { return nullptr; }
ARAObject* getParent() override;
void visit (ARAObjectVisitor& visitor) override { visitor.visitPlaybackRegion (*this); }
/** Used in getTimeRange to indicate whether the head and tail times should be included in the result.
*/
enum class IncludeHeadAndTail { no, yes };
/** Returns the playback time range of this playback region.
@param includeHeadAndTail Whether or not the range includes the head and tail
time of all playback regions in the sequence.
*/
Range<double> getTimeRange (IncludeHeadAndTail includeHeadAndTail = IncludeHeadAndTail::no) const;
/** Returns the playback sample range of this playback region.
@param sampleRate The rate at which the sample position should be calculated from
the time range.
@param includeHeadAndTail Whether or not the range includes the head and tail
time of all playback regions in the sequence.
*/
Range<int64> getSampleRange (double sampleRate, IncludeHeadAndTail includeHeadAndTail = IncludeHeadAndTail::no) const;
/** Get the head length in seconds before the start of the region's playback time. */
double getHeadTime() const;
/** Get the tail length in seconds after the end of the region's playback time. */
double getTailTime() const;
/** Notify the ARA host and any listeners of a content update initiated by the plug-in.
This must be called by the plug-in model management code on the message thread whenever updating
the internal content representation, such as after the user edited the pitch of a note in the
underlying audio modification.
Listeners will be notified immediately. If \p notifyARAHost is true, a notification to the host
will be enqueued and sent out the next time it polls for updates.
@param scopeFlags The scope of the content update.
@param notifyARAHost If true, the ARA host will be notified of the content change.
*/
void notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost);
};
/** A base class for listeners that want to know about changes to an ARARegionSequence object.
Use ARARegionSequence::addListener() to register your listener with an ARARegionSequence.
@tags{ARA}
*/
class JUCE_API ARARegionSequenceListener
{
public:
/** Destructor. */
virtual ~ARARegionSequenceListener() = default;
/** Called before the region sequence's properties are updated.
@param regionSequence The region sequence whose properties will be updated.
@param newProperties The region sequence properties that will be assigned to \p regionSequence.
*/
virtual void willUpdateRegionSequenceProperties (ARARegionSequence* regionSequence,
ARA::PlugIn::PropertiesPtr<ARA::ARARegionSequenceProperties> newProperties);
/** Called after the region sequence's properties are updated.
@param regionSequence The region sequence whose properties were updated.
*/
virtual void didUpdateRegionSequenceProperties (ARARegionSequence* regionSequence);
/** Called before a playback region is removed from the region sequence.
@param regionSequence The region sequence that \p playbackRegion will be removed from.
@param playbackRegion The playback region that will be removed from \p regionSequence.
*/
virtual void willRemovePlaybackRegionFromRegionSequence (ARARegionSequence* regionSequence,
ARAPlaybackRegion* playbackRegion);
/** Called after a playback region is added to the region sequence.
@param regionSequence The region sequence that \p playbackRegion was added to.
@param playbackRegion The playback region that was added to \p regionSequence.
*/
virtual void didAddPlaybackRegionToRegionSequence (ARARegionSequence* regionSequence,
ARAPlaybackRegion* playbackRegion);
/** Called before the region sequence is destroyed.
@param regionSequence The region sequence that will be destroyed.
*/
virtual void willDestroyRegionSequence (ARARegionSequence* regionSequence);
};
//==============================================================================
/** Base class representing an ARA region sequence.
@tags{ARA}
*/
class JUCE_API ARARegionSequence : public ARA::PlugIn::RegionSequence,
public ARAListenableModelClass<ARARegionSequenceListener>,
public ARAObject
{
public:
using PropertiesPtr = ARA::PlugIn::PropertiesPtr<ARA::ARARegionSequenceProperties>;
using Listener = ARARegionSequenceListener;
using ARA::PlugIn::RegionSequence::RegionSequence;
/** Returns the result of ARA::PlugIn::RegionSequence::getDocument() with the pointer cast
to ARADocument*.
If you have overridden ARADocumentControllerSpecialisation::doCreateDocument(), then you
can use the template parameter to cast the pointers to your subclass of ARADocument.
*/
template <typename Document_t = ARADocument>
Document_t* getDocument() const noexcept
{
return ARA::PlugIn::RegionSequence::getDocument<Document_t>();
}
/** Returns the result of ARA::PlugIn::RegionSequence::getMusicalContext() with the pointer cast
to ARAMusicalContext*.
If you have overridden ARADocumentControllerSpecialisation::doCreateMusicalContext(), then you
can use the template parameter to cast the pointers to your subclass of ARAMusicalContext.
*/
template <typename MusicalContext_t = ARAMusicalContext>
MusicalContext_t* getMusicalContext() const noexcept
{
return ARA::PlugIn::RegionSequence::getMusicalContext<MusicalContext_t>();
}
/** Returns the result of ARA::PlugIn::RegionSequence::getPlaybackRegions() with the pointers within cast
to ARAPlaybackRegion*.
If you have overridden ARADocumentControllerSpecialisation::doCreatePlaybackRegion(), then you
can use the template parameter to cast the pointers to your subclass of ARAPlaybackRegion.
*/
template <typename PlaybackRegion_t = ARAPlaybackRegion>
const std::vector<PlaybackRegion_t*>& getPlaybackRegions() const noexcept
{
return ARA::PlugIn::RegionSequence::getPlaybackRegions<PlaybackRegion_t>();
}
size_t getNumChildren() const noexcept override;
ARAObject* getChild (size_t index) override;
ARAObject* getParent() override { return getDocument(); }
void visit (ARAObjectVisitor& visitor) override { visitor.visitRegionSequence (*this); }
/** Returns the playback time range covered by the regions in this sequence.
@param includeHeadAndTail Whether or not the range includes the playback region's head and tail time.
*/
Range<double> getTimeRange (ARAPlaybackRegion::IncludeHeadAndTail includeHeadAndTail = ARAPlaybackRegion::IncludeHeadAndTail::no) const;
/** If all audio sources used by the playback regions in this region sequence have the same
sample rate, this rate is returned here, otherwise 0.0 is returned.
If the region sequence has no playback regions, this also returns 0.0.
*/
double getCommonSampleRate() const;
};
/** A base class for listeners that want to know about changes to an ARAAudioSource object.
Use ARAAudioSource::addListener() to register your listener with an ARAAudioSource.
@tags{ARA}
*/
class JUCE_API ARAAudioSourceListener
{
public:
/** Destructor. */
virtual ~ARAAudioSourceListener() = default;
/** Called before the audio source's properties are updated.
@param audioSource The audio source whose properties will be updated.
@param newProperties The audio source properties that will be assigned to \p audioSource.
*/
virtual void willUpdateAudioSourceProperties (ARAAudioSource* audioSource,
ARA::PlugIn::PropertiesPtr<ARA::ARAAudioSourceProperties> newProperties);
/** Called after the audio source's properties are updated.
@param audioSource The audio source whose properties were updated.
*/
virtual void didUpdateAudioSourceProperties (ARAAudioSource* audioSource);
/** Called when the audio source's content (i.e. samples or notes) changes.
@param audioSource The audio source with updated content.
@param scopeFlags The scope of the content update.
*/
virtual void doUpdateAudioSourceContent (ARAAudioSource* audioSource, ARAContentUpdateScopes scopeFlags);
/** Called to notify progress when an audio source is being analyzed.
@param audioSource The audio source being analyzed.
@param state Indicates start, intermediate update or completion of the analysis.
@param progress Progress normalized to the 0..1 range.
*/
virtual void didUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource,
ARA::ARAAnalysisProgressState state,
float progress);
/** Called before access to an audio source's samples is enabled or disabled.
@param audioSource The audio source whose sample access state will be changed.
@param enable A bool indicating whether or not sample access will be enabled or disabled.
*/
virtual void willEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource,
bool enable);
/** Called after access to an audio source's samples is enabled or disabled.
@param audioSource The audio source whose sample access state was changed.
@param enable A bool indicating whether or not sample access was enabled or disabled.
*/
virtual void didEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource,
bool enable);
/** Called before an audio source is activated or deactivated when being removed / added from the host's undo history.
@param audioSource The audio source that will be activated or deactivated
@param deactivate A bool indicating whether \p audioSource was deactivated or activated.
*/
virtual void willDeactivateAudioSourceForUndoHistory (ARAAudioSource* audioSource,
bool deactivate);
/** Called after an audio source is activated or deactivated when being removed / added from the host's undo history.
@param audioSource The audio source that was activated or deactivated
@param deactivate A bool indicating whether \p audioSource was deactivated or activated.
*/
virtual void didDeactivateAudioSourceForUndoHistory (ARAAudioSource* audioSource,
bool deactivate);
/** Called after an audio modification is added to the audio source.
@param audioSource The region sequence that \p audioModification was added to.
@param audioModification The playback region that was added to \p audioSource.
*/
virtual void didAddAudioModificationToAudioSource (ARAAudioSource* audioSource,
ARAAudioModification* audioModification);
/** Called before an audio modification is removed from the audio source.
@param audioSource The audio source that \p audioModification will be removed from.
@param audioModification The audio modification that will be removed from \p audioSource.
*/
virtual void willRemoveAudioModificationFromAudioSource (ARAAudioSource* audioSource,
ARAAudioModification* audioModification);
/** Called before the audio source is destroyed.
@param audioSource The audio source that will be destroyed.
*/
virtual void willDestroyAudioSource (ARAAudioSource* audioSource);
};
//==============================================================================
/** Base class representing an ARA audio source.
@tags{ARA}
*/
class JUCE_API ARAAudioSource : public ARA::PlugIn::AudioSource,
public ARAListenableModelClass<ARAAudioSourceListener>,
public ARAObject
{
public:
using PropertiesPtr = ARA::PlugIn::PropertiesPtr<ARA::ARAAudioSourceProperties>;
using ARAAnalysisProgressState = ARA::ARAAnalysisProgressState;
using Listener = ARAAudioSourceListener;
using ARA::PlugIn::AudioSource::AudioSource;
/** Returns the result of ARA::PlugIn::AudioSource::getDocument() with the pointer cast
to ARADocument*.
If you have overridden ARADocumentControllerSpecialisation::doCreateDocument(), then you
can use the template parameter to cast the pointers to your subclass of ARADocument.
*/
template <typename Document_t = ARADocument>
Document_t* getDocument() const noexcept
{
return ARA::PlugIn::AudioSource::getDocument<Document_t>();
}
/** Returns the result of ARA::PlugIn::AudioSource::getAudioModifications() with the pointers
within cast to ARAAudioModification*.
If you have overridden ARADocumentControllerSpecialisation::doCreateAudioModification(),
then you can use the template parameter to cast the pointers to your subclass of
ARAAudioModification.
*/
template <typename AudioModification_t = ARAAudioModification>
const std::vector<AudioModification_t*>& getAudioModifications() const noexcept
{
return ARA::PlugIn::AudioSource::getAudioModifications<AudioModification_t>();
}
size_t getNumChildren() const noexcept override;
ARAObject* getChild (size_t index) override;
ARAObject* getParent() override { return getDocument(); }
void visit (ARAObjectVisitor& visitor) override { visitor.visitAudioSource (*this); }
/** Notify the ARA host and any listeners of analysis progress.
Contrary to most ARA functions, this call can be made from any thread.
The implementation will enqueue these notifications and later post them from the message thread.
Calling code must ensure start and completion state are always balanced,
and must send updates in ascending order.
*/
void notifyAnalysisProgressStarted();
/** \copydoc notifyAnalysisProgressStarted
@param progress Progress normalized to the 0..1 range.
*/
void notifyAnalysisProgressUpdated (float progress);
/** \copydoc notifyAnalysisProgressStarted
*/
void notifyAnalysisProgressCompleted();
/** Notify the ARA host and any listeners of a content update initiated by the plug-in.
This must be called by the plug-in model management code on the message thread whenever updating
the internal content representation, such as after successfully analyzing a new tempo map.
Listeners will be notified immediately. If \p notifyARAHost is true, a notification to the host
will be enqueued and sent out the next time it polls for updates.
\p notifyARAHost must be false if the update was triggered by the host via doUpdateAudioSourceContent().
Furthermore, \p notifyARAHost must be false if the updated content is being restored from an archive.
@param scopeFlags The scope of the content update.
@param notifyARAHost If true, the ARA host will be notified of the content change.
*/
void notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost);
public:
friend ARADocumentController;
ARA::PlugIn::AnalysisProgressTracker internalAnalysisProgressTracker;
};
/** A base class for listeners that want to know about changes to an ARAAudioModification object.
Use ARAAudioModification::addListener() to register your listener with an ARAAudioModification.
@tags{ARA}
*/
class JUCE_API ARAAudioModificationListener
{
public:
/** Destructor. */
virtual ~ARAAudioModificationListener() = default;
/** Called before the audio modification's properties are updated.
@param audioModification The audio modification whose properties will be updated.
@param newProperties The audio modification properties that will be assigned to \p audioModification.
*/
virtual void willUpdateAudioModificationProperties (ARAAudioModification* audioModification,
ARA::PlugIn::PropertiesPtr<ARA::ARAAudioModificationProperties> newProperties);
/** Called after the audio modification's properties are updated.
@param audioModification The audio modification whose properties were updated.
*/
virtual void didUpdateAudioModificationProperties (ARAAudioModification* audioModification);
/** Called when the audio modification's content (i.e. samples or notes) changes.
@param audioModification The audio modification with updated content.
@param scopeFlags The scope of the content update.
*/
virtual void didUpdateAudioModificationContent (ARAAudioModification* audioModification,
ARAContentUpdateScopes scopeFlags);
/** Called before an audio modification is activated or deactivated when being removed / added from the host's undo history.
@param audioModification The audio modification that was activated or deactivated
@param deactivate A bool indicating whether \p audioModification was deactivated or activated.
*/
virtual void willDeactivateAudioModificationForUndoHistory (ARAAudioModification* audioModification,
bool deactivate);
/** Called after an audio modification is activated or deactivated when being removed / added from the host's undo history.
@param audioModification The audio modification that was activated or deactivated
@param deactivate A bool indicating whether \p audioModification was deactivated or activated.
*/
virtual void didDeactivateAudioModificationForUndoHistory (ARAAudioModification* audioModification,
bool deactivate);
/** Called after a playback region is added to the audio modification.
@param audioModification The audio modification that \p playbackRegion was added to.
@param playbackRegion The playback region that was added to \p audioModification.
*/
virtual void didAddPlaybackRegionToAudioModification (ARAAudioModification* audioModification,
ARAPlaybackRegion* playbackRegion);
/** Called before a playback region is removed from the audio modification.
@param audioModification The audio modification that \p playbackRegion will be removed from.
@param playbackRegion The playback region that will be removed from \p audioModification.
*/
virtual void willRemovePlaybackRegionFromAudioModification (ARAAudioModification* audioModification,
ARAPlaybackRegion* playbackRegion);
/** Called before the audio modification is destroyed.
@param audioModification The audio modification that will be destroyed.
*/
virtual void willDestroyAudioModification (ARAAudioModification* audioModification);
};
//==============================================================================
/** Base class representing an ARA audio modification.
@tags{ARA}
*/
class JUCE_API ARAAudioModification : public ARA::PlugIn::AudioModification,
public ARAListenableModelClass<ARAAudioModificationListener>,
public ARAObject
{
public:
using PropertiesPtr = ARA::PlugIn::PropertiesPtr<ARA::ARAAudioModificationProperties>;
using Listener = ARAAudioModificationListener;
using ARA::PlugIn::AudioModification::AudioModification;
/** Returns the result of ARA::PlugIn::AudioModification::getAudioSource() with the pointer cast
to ARAAudioSource*.
If you have overridden ARADocumentControllerSpecialisation::doCreateAudioSource(), then you
can use the template parameter to cast the pointers to your subclass of ARAAudioSource.
*/
template <typename AudioSource_t = ARAAudioSource>
AudioSource_t* getAudioSource() const noexcept
{
return ARA::PlugIn::AudioModification::getAudioSource<AudioSource_t>();
}
/** Returns the result of ARA::PlugIn::AudioModification::getPlaybackRegions() with the
pointers within cast to ARAPlaybackRegion*.
If you have overridden ARADocumentControllerSpecialisation::doCreatePlaybackRegion(), then
you can use the template parameter to cast the pointers to your subclass of ARAPlaybackRegion.
*/
template <typename PlaybackRegion_t = ARAPlaybackRegion>
const std::vector<PlaybackRegion_t*>& getPlaybackRegions() const noexcept
{
return ARA::PlugIn::AudioModification::getPlaybackRegions<PlaybackRegion_t>();
}
size_t getNumChildren() const noexcept override;
ARAObject* getChild (size_t index) override;
ARAObject* getParent() override { return getAudioSource(); }
void visit (ARAObjectVisitor& visitor) override { visitor.visitAudioModification (*this); }
/** Notify the ARA host and any listeners of a content update initiated by the plug-in.
This must be called by the plug-in model management code on the message thread whenever updating
the internal content representation, such as after the user editing the pitch of a note.
Listeners will be notified immediately. If \p notifyARAHost is true, a notification to the host
will be enqueued and sent out the next time it polls for updates.
\p notifyARAHost must be false if the updated content is being restored from an archive.
@param scopeFlags The scope of the content update.
@param notifyARAHost If true, the ARA host will be notified of the content change.
*/
void notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost);
};
} // namespace juce
@@ -0,0 +1,114 @@
/*
==============================================================================
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.
==============================================================================
*/
#include "juce_ARAPlugInInstanceRoles.h"
namespace juce
{
bool ARARenderer::processBlock ([[maybe_unused]] AudioBuffer<double>& buffer,
[[maybe_unused]] AudioProcessor::Realtime realtime,
[[maybe_unused]] const AudioPlayHead::PositionInfo& positionInfo) noexcept
{
// If you hit this assertion then either the caller called the double
// precision version of processBlock on a processor which does not support it
// (i.e. supportsDoublePrecisionProcessing() returns false), or the implementation
// of the ARARenderer forgot to override the double precision version of this method
jassertfalse;
return false;
}
void ARARenderer::prepareToPlay ([[maybe_unused]] double sampleRate,
[[maybe_unused]] int maximumSamplesPerBlock,
[[maybe_unused]] int numChannels,
[[maybe_unused]] AudioProcessor::ProcessingPrecision precision,
[[maybe_unused]] AlwaysNonRealtime alwaysNonRealtime) {}
//==============================================================================
#if ARA_VALIDATE_API_CALLS
void ARAPlaybackRenderer::addPlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept
{
if (araExtension)
ARA_VALIDATE_API_STATE (! araExtension->isPrepared);
ARA::PlugIn::PlaybackRenderer::addPlaybackRegion (playbackRegionRef);
}
void ARAPlaybackRenderer::removePlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept
{
if (araExtension)
ARA_VALIDATE_API_STATE (! araExtension->isPrepared);
ARA::PlugIn::PlaybackRenderer::removePlaybackRegion (playbackRegionRef);
}
#endif
bool ARAPlaybackRenderer::processBlock ([[maybe_unused]] AudioBuffer<float>& buffer,
[[maybe_unused]] AudioProcessor::Realtime realtime,
[[maybe_unused]] const AudioPlayHead::PositionInfo& positionInfo) noexcept
{
return false;
}
//==============================================================================
bool ARAEditorRenderer::processBlock ([[maybe_unused]] AudioBuffer<float>& buffer,
[[maybe_unused]] AudioProcessor::Realtime isNonRealtime,
[[maybe_unused]] const AudioPlayHead::PositionInfo& positionInfo) noexcept
{
return true;
}
//==============================================================================
void ARAEditorView::doNotifySelection (const ARA::PlugIn::ViewSelection* viewSelection) noexcept
{
listeners.call ([&] (Listener& l)
{
l.onNewSelection (*viewSelection);
});
}
void ARAEditorView::doNotifyHideRegionSequences (std::vector<ARA::PlugIn::RegionSequence*> const& regionSequences) noexcept
{
listeners.call ([&] (Listener& l)
{
l.onHideRegionSequences (ARA::vector_cast<ARARegionSequence*> (regionSequences));
});
}
void ARAEditorView::addListener (Listener* l)
{
listeners.add (l);
}
void ARAEditorView::removeListener (Listener* l)
{
listeners.remove (l);
}
void ARAEditorView::Listener::onNewSelection ([[maybe_unused]] const ARAViewSelection& viewSelection) {}
void ARAEditorView::Listener::onHideRegionSequences ([[maybe_unused]] const std::vector<ARARegionSequence*>& regionSequences) {}
} // namespace juce
@@ -0,0 +1,254 @@
/*
==============================================================================
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
namespace juce
{
//==============================================================================
/** Base class for a renderer fulfilling either the ARAPlaybackRenderer or the ARAEditorRenderer role.
Instances of either subclass are constructed by the DocumentController.
@tags{ARA}
*/
class JUCE_API ARARenderer
{
public:
enum class AlwaysNonRealtime { no, yes };
virtual ~ARARenderer() = default;
/** Initialises the renderer for playback.
@param sampleRate The sample rate that will be used for the data that is sent
to the renderer
@param maximumSamplesPerBlock The maximum number of samples that will be in the blocks
sent to process() method
@param numChannels The number of channels that the process() method will be
expected to handle
@param precision This should be the same as the result of getProcessingPrecision()
for the enclosing AudioProcessor
@param alwaysNonRealtime yes if this renderer is never used in realtime (e.g. if
providing data for views only)
*/
virtual void prepareToPlay (double sampleRate,
int maximumSamplesPerBlock,
int numChannels,
AudioProcessor::ProcessingPrecision precision,
AlwaysNonRealtime alwaysNonRealtime = AlwaysNonRealtime::no);
/** Frees render resources allocated in prepareToPlay(). */
virtual void releaseResources() {}
/** Resets the internal state variables of the renderer. */
virtual void reset() {}
/** Renders the output into the given buffer. Returns true if rendering executed without error,
false otherwise.
@param buffer The output buffer for the rendering. ARAPlaybackRenderers will
replace the sample data, while ARAEditorRenderer will add to it.
@param realtime Indicates whether the call is executed under real time constraints.
The value of this parameter may change from one call to the next,
and if the value is yes, the rendering may fail if the required
samples cannot be obtained in time.
@param positionInfo Current song position, playback state and playback loop location.
There should be no need to access the bpm, timeSig and ppqPosition
members in any ARA renderer since ARA provides that information with
random access in its model graph.
Returns false if non-ARA fallback rendering is required and true otherwise.
*/
virtual bool processBlock (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::PositionInfo& positionInfo) noexcept = 0;
/** Renders the output into the given buffer. Returns true if rendering executed without error,
false otherwise.
@param buffer The output buffer for the rendering. ARAPlaybackRenderers will
replace the sample data, while ARAEditorRenderer will add to it.
@param realtime Indicates whether the call is executed under real time constraints.
The value of this parameter may change from one call to the next,
and if the value is yes, the rendering may fail if the required
samples cannot be obtained in time.
@param positionInfo Current song position, playback state and playback loop location.
There should be no need to access the bpm, timeSig and ppqPosition
members in any ARA renderer since ARA provides that information with
random access in its model graph.
Returns false if non-ARA fallback rendering is required and true otherwise.
*/
virtual bool processBlock (AudioBuffer<double>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::PositionInfo& positionInfo) noexcept;
};
//==============================================================================
/** Base class for a renderer fulfilling the ARAPlaybackRenderer role as described in the ARA SDK.
Instances of this class are constructed by the DocumentController. If you are subclassing
ARAPlaybackRenderer, make sure to call the base class implementation of any overridden function,
except for processBlock.
@tags{ARA}
*/
class JUCE_API ARAPlaybackRenderer : public ARA::PlugIn::PlaybackRenderer,
public ARARenderer
{
public:
using ARA::PlugIn::PlaybackRenderer::PlaybackRenderer;
bool processBlock (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::PositionInfo& positionInfo) noexcept override;
using ARARenderer::processBlock;
// Shadowing templated getters to default to JUCE versions of the returned classes
/** Returns the PlaybackRegions
*
* @tparam PlaybackRegion_t
* @return
*/
template <typename PlaybackRegion_t = ARAPlaybackRegion>
std::vector<PlaybackRegion_t*> const& getPlaybackRegions() const noexcept
{
return ARA::PlugIn::PlaybackRenderer::getPlaybackRegions<PlaybackRegion_t>();
}
#if ARA_VALIDATE_API_CALLS
void addPlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept override;
void removePlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept override;
AudioProcessorARAExtension* araExtension {};
#endif
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAPlaybackRenderer)
};
//==============================================================================
/** Base class for a renderer fulfilling the ARAEditorRenderer role as described in the ARA SDK.
Instances of this class are constructed by the DocumentController. If you are subclassing
ARAEditorRenderer, make sure to call the base class implementation of any overridden function,
except for processBlock.
@tags{ARA}
*/
class JUCE_API ARAEditorRenderer : public ARA::PlugIn::EditorRenderer,
public ARARenderer
{
public:
using ARA::PlugIn::EditorRenderer::EditorRenderer;
// Shadowing templated getters to default to JUCE versions of the returned classes
template <typename PlaybackRegion_t = ARAPlaybackRegion>
std::vector<PlaybackRegion_t*> const& getPlaybackRegions() const noexcept
{
return ARA::PlugIn::EditorRenderer::getPlaybackRegions<PlaybackRegion_t>();
}
template <typename RegionSequence_t = ARARegionSequence>
std::vector<RegionSequence_t*> const& getRegionSequences() const noexcept
{
return ARA::PlugIn::EditorRenderer::getRegionSequences<RegionSequence_t>();
}
// By default, editor renderers will just let the signal pass through unaltered.
// If you're overriding this to implement actual audio preview, remember to check
// isNonRealtime of the process context - typically preview is limited to realtime.
bool processBlock (AudioBuffer<float>& buffer,
AudioProcessor::Realtime isNonRealtime,
const AudioPlayHead::PositionInfo& positionInfo) noexcept override;
using ARARenderer::processBlock;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAEditorRenderer)
};
//==============================================================================
/** Base class for fulfilling the ARAEditorView role as described in the ARA SDK.
Instances of this class are constructed by the DocumentController. If you are subclassing
ARAEditorView, make sure to call the base class implementation of overridden functions.
@tags{ARA}
*/
class JUCE_API ARAEditorView : public ARA::PlugIn::EditorView
{
public:
using ARA::PlugIn::EditorView::EditorView;
// Shadowing templated getters to default to JUCE versions of the returned classes
template <typename RegionSequence_t = ARARegionSequence>
const std::vector<RegionSequence_t*>& getHiddenRegionSequences() const noexcept
{
return ARA::PlugIn::EditorView::getHiddenRegionSequences<RegionSequence_t>();
}
// Base class implementation must be called if overridden
void doNotifySelection (const ARA::PlugIn::ViewSelection* currentSelection) noexcept override;
// Base class implementation must be called if overridden
void doNotifyHideRegionSequences (const std::vector<ARA::PlugIn::RegionSequence*>& regionSequences) noexcept override;
/** A base class for listeners that want to know about changes to an ARAEditorView object.
Use ARAEditorView::addListener() to register your listener with an ARAEditorView.
*/
class JUCE_API Listener
{
public:
/** Destructor. */
virtual ~Listener() = default;
/** Called when the editor view's selection changes.
@param viewSelection The current selection state
*/
virtual void onNewSelection (const ARAViewSelection& viewSelection);
/** Called when region sequences are flagged as hidden in the host UI.
@param regionSequences A vector containing all hidden region sequences.
*/
virtual void onHideRegionSequences (const std::vector<ARARegionSequence*>& regionSequences);
};
/** \copydoc ARAListenableModelClass::addListener */
void addListener (Listener* l);
/** \copydoc ARAListenableModelClass::removeListener */
void removeListener (Listener* l);
private:
ListenerList<Listener> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAEditorView)
};
}
@@ -0,0 +1,56 @@
/*
==============================================================================
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.
==============================================================================
*/
#if (JucePlugin_Enable_ARA || (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU))) && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
namespace juce
{
#if ARA_ENABLE_INTERNAL_ASSERTS
JUCE_API void JUCE_CALLTYPE handleARAAssertion (const char* file, const int line, const char* diagnosis) noexcept
{
#if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS)
DBG (diagnosis);
#endif
logAssertion (file, line);
#if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS)
if (juce_isRunningUnderDebugger())
JUCE_BREAK_IN_DEBUGGER;
JUCE_ANALYZER_NORETURN
#endif
}
#endif
}
#endif
#if JucePlugin_Enable_ARA
#include "juce_ARADocumentControllerCommon.cpp"
#include "juce_ARADocumentController.cpp"
#include "juce_ARAModelObjects.cpp"
#include "juce_ARAPlugInInstanceRoles.cpp"
#include "juce_AudioProcessor_ARAExtensions.cpp"
ARA_SETUP_DEBUG_MESSAGE_PREFIX (JucePlugin_Name);
#endif
@@ -0,0 +1,86 @@
/*
==============================================================================
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.
==============================================================================
*/
#if JucePlugin_Enable_ARA
// Include ARA SDK headers
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments",
"-Wunused-parameter",
"-Wfloat-equal")
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6387)
#include <ARA_Library/PlugIn/ARAPlug.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
JUCE_END_IGNORE_WARNINGS_MSVC
namespace juce
{
using ARAViewSelection = ARA::PlugIn::ViewSelection;
using ARAContentUpdateScopes = ARA::ContentUpdateScopes;
using ARARestoreObjectsFilter = ARA::PlugIn::RestoreObjectsFilter;
using ARAStoreObjectsFilter = ARA::PlugIn::StoreObjectsFilter;
/** Converts an ARA::ARAUtf8String to a JUCE String. */
inline String convertARAString (ARA::ARAUtf8String str)
{
return String (CharPointer_UTF8 (str));
}
/** Converts a potentially NULL ARA::ARAUtf8String to a JUCE String.
Returns the JUCE equivalent of the provided string if it's not nullptr, and the fallback string
otherwise.
*/
inline String convertOptionalARAString (ARA::ARAUtf8String str, const String& fallbackString = String())
{
return (str != nullptr) ? convertARAString (str) : fallbackString;
}
/** Converts an ARA::ARAColor* to a JUCE Colour. */
inline Colour convertARAColour (const ARA::ARAColor* colour)
{
return Colour::fromFloatRGBA (colour->r, colour->g, colour->b, 1.0f);
}
/** Converts a potentially NULL ARA::ARAColor* to a JUCE Colour.
Returns the JUCE equivalent of the provided colour if it's not nullptr, and the fallback colour
otherwise.
*/
inline Colour convertOptionalARAColour (const ARA::ARAColor* colour, const Colour& fallbackColour = Colour())
{
return (colour != nullptr) ? convertARAColour (colour) : fallbackColour;
}
} // namespace juce
#include "juce_ARAModelObjects.h"
#include "juce_ARADocumentController.h"
#include "juce_AudioProcessor_ARAExtensions.h"
#include "juce_ARAPlugInInstanceRoles.h"
#endif
@@ -0,0 +1,154 @@
/*
==============================================================================
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.
==============================================================================
*/
#include "juce_AudioProcessor_ARAExtensions.h"
namespace juce
{
//==============================================================================
bool AudioProcessorARAExtension::getTailLengthSecondsForARA (double& tailLength) const
{
if (! isBoundToARA())
return false;
tailLength = 0.0;
if (auto playbackRenderer = getPlaybackRenderer())
for (const auto& playbackRegion : playbackRenderer->getPlaybackRegions())
tailLength = jmax (tailLength, playbackRegion->getTailTime());
return true;
}
bool AudioProcessorARAExtension::prepareToPlayForARA (double sampleRate,
int samplesPerBlock,
int numChannels,
AudioProcessor::ProcessingPrecision precision)
{
#if ARA_VALIDATE_API_CALLS
isPrepared = true;
#endif
if (! isBoundToARA())
return false;
if (auto playbackRenderer = getPlaybackRenderer())
playbackRenderer->prepareToPlay (sampleRate, samplesPerBlock, numChannels, precision);
if (auto editorRenderer = getEditorRenderer())
editorRenderer->prepareToPlay (sampleRate, samplesPerBlock, numChannels, precision);
return true;
}
bool AudioProcessorARAExtension::releaseResourcesForARA()
{
#if ARA_VALIDATE_API_CALLS
isPrepared = false;
#endif
if (! isBoundToARA())
return false;
if (auto playbackRenderer = getPlaybackRenderer())
playbackRenderer->releaseResources();
if (auto editorRenderer = getEditorRenderer())
editorRenderer->releaseResources();
return true;
}
bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::PositionInfo& positionInfo)
{
// validate that the host has prepared us before processing
ARA_VALIDATE_API_STATE (isPrepared);
if (! isBoundToARA())
return false;
// Render our ARA playback regions for this buffer.
if (auto playbackRenderer = getPlaybackRenderer())
playbackRenderer->processBlock (buffer, realtime, positionInfo);
// Render our ARA editor regions and sequences for this buffer.
// This example does not support editor rendering and thus uses the default implementation,
// which is a no-op and could be omitted in actual plug-ins to optimize performance.
if (auto editorRenderer = getEditorRenderer())
editorRenderer->processBlock (buffer, realtime, positionInfo);
return true;
}
bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer<float>& buffer,
juce::AudioProcessor::Realtime realtime,
AudioPlayHead* playhead)
{
return processBlockForARA (buffer,
realtime,
playhead != nullptr ? playhead->getPosition().orFallback (AudioPlayHead::PositionInfo{})
: AudioPlayHead::PositionInfo{});
}
//==============================================================================
void AudioProcessorARAExtension::didBindToARA() noexcept
{
// validate that the ARA binding is not established by the host while prepared to play
#if ARA_VALIDATE_API_CALLS
ARA_VALIDATE_API_STATE (! isPrepared);
if (auto playbackRenderer = getPlaybackRenderer())
playbackRenderer->araExtension = this;
#endif
#if (! JUCE_DISABLE_ASSERTIONS)
// validate proper subclassing of the instance role classes
if (auto playbackRenderer = getPlaybackRenderer())
jassert (dynamic_cast<ARAPlaybackRenderer*> (playbackRenderer) != nullptr);
if (auto editorRenderer = getEditorRenderer())
jassert (dynamic_cast<ARAEditorRenderer*> (editorRenderer) != nullptr);
if (auto editorView = getEditorView())
jassert (dynamic_cast<ARAEditorView*> (editorView) != nullptr);
#endif
}
//==============================================================================
AudioProcessorEditorARAExtension::AudioProcessorEditorARAExtension (AudioProcessor* audioProcessor)
: araProcessorExtension (dynamic_cast<AudioProcessorARAExtension*> (audioProcessor))
{
if (isARAEditorView())
getARAEditorView()->setEditorOpen (true);
}
AudioProcessorEditorARAExtension::~AudioProcessorEditorARAExtension()
{
if (isARAEditorView())
getARAEditorView()->setEditorOpen (false);
}
} // namespace juce
@@ -0,0 +1,204 @@
/*
==============================================================================
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
namespace juce
{
class AudioProcessor;
class ARAPlaybackRenderer;
class ARAEditorRenderer;
class ARAEditorView;
//==============================================================================
/** Extension class meant to be subclassed by the plugin's implementation of @see AudioProcessor.
Subclassing AudioProcessorARAExtension allows access to the three possible plugin instance
roles as defined by the ARA SDK. Hosts can assign any subset of roles to each plugin instance.
@tags{ARA}
*/
class JUCE_API AudioProcessorARAExtension : public ARA::PlugIn::PlugInExtension
{
public:
AudioProcessorARAExtension() = default;
//==============================================================================
/** Returns the result of ARA::PlugIn::PlugInExtension::getPlaybackRenderer() with the pointer
cast to ARAPlaybackRenderer*.
If you have overridden ARADocumentControllerSpecialisation::doCreatePlaybackRenderer(),
then you can use the template parameter to cast the pointers to your subclass of
ARAPlaybackRenderer.
*/
template <typename PlaybackRenderer_t = ARAPlaybackRenderer>
PlaybackRenderer_t* getPlaybackRenderer() const noexcept
{
return ARA::PlugIn::PlugInExtension::getPlaybackRenderer<PlaybackRenderer_t>();
}
/** Returns the result of ARA::PlugIn::PlugInExtension::getEditorRenderer() with the pointer
cast to ARAEditorRenderer*.
If you have overridden ARADocumentControllerSpecialisation::doCreateEditorRenderer(),
then you can use the template parameter to cast the pointers to your subclass of
ARAEditorRenderer.
*/
template <typename EditorRenderer_t = ARAEditorRenderer>
EditorRenderer_t* getEditorRenderer() const noexcept
{
return ARA::PlugIn::PlugInExtension::getEditorRenderer<EditorRenderer_t>();
}
/** Returns the result of ARA::PlugIn::PlugInExtension::getEditorView() with the pointer
cast to ARAEditorView*.
If you have overridden ARADocumentControllerSpecialisation::doCreateEditorView(),
then you can use the template parameter to cast the pointers to your subclass of
ARAEditorView.
*/
template <typename EditorView_t = ARAEditorView>
EditorView_t* getEditorView() const noexcept
{
return ARA::PlugIn::PlugInExtension::getEditorView<EditorView_t>();
}
//==============================================================================
/** Returns true if plugin instance fulfills the ARAPlaybackRenderer role. */
bool isPlaybackRenderer() const noexcept
{
return ARA::PlugIn::PlugInExtension::getPlaybackRenderer() != nullptr;
}
/** Returns true if plugin instance fulfills the ARAEditorRenderer role. */
bool isEditorRenderer() const noexcept
{
return ARA::PlugIn::PlugInExtension::getEditorRenderer() != nullptr;
}
/** Returns true if plugin instance fulfills the ARAEditorView role. */
bool isEditorView() const noexcept
{
return ARA::PlugIn::PlugInExtension::getEditorView() != nullptr;
}
//==============================================================================
#if ARA_VALIDATE_API_CALLS
bool isPrepared { false };
#endif
protected:
/** Implementation helper for AudioProcessor::getTailLengthSeconds().
If bound to ARA, this traverses the instance roles to retrieve the respective tail time
and returns true. Otherwise returns false and leaves tailLength unmodified.
*/
bool getTailLengthSecondsForARA (double& tailLength) const;
/** Implementation helper for AudioProcessor::prepareToPlay().
If bound to ARA, this traverses the instance roles to prepare them for play and returns
true. Otherwise returns false and does nothing.
*/
bool prepareToPlayForARA (double sampleRate,
int samplesPerBlock,
int numChannels,
AudioProcessor::ProcessingPrecision precision);
/** Implementation helper for AudioProcessor::releaseResources().
If bound to ARA, this traverses the instance roles to let them release resources and returns
true. Otherwise returns false and does nothing.
*/
bool releaseResourcesForARA();
/** Implementation helper for AudioProcessor::processBlock().
If bound to ARA, this traverses the instance roles to let them process the block and returns
true. Otherwise returns false and does nothing.
Use this overload if your rendering code already has a current positionInfo available.
*/
bool processBlockForARA (AudioBuffer<float>& buffer,
AudioProcessor::Realtime realtime,
const AudioPlayHead::PositionInfo& positionInfo);
/** Implementation helper for AudioProcessor::processBlock().
If bound to ARA, this traverses the instance roles to let them process the block and returns
true. Otherwise returns false and does nothing.
Use this overload if your rendering code does not have a current positionInfo available.
*/
bool processBlockForARA (AudioBuffer<float>& buffer, AudioProcessor::Realtime isNonRealtime, AudioPlayHead* playhead);
//==============================================================================
/** Optional hook for derived classes to perform any additional initialization that may be needed.
If overriding this, make sure you call the base class implementation from your override.
*/
void didBindToARA() noexcept override;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorARAExtension)
};
//==============================================================================
/** Extension class meant to be subclassed by the plugin's implementation of @see AudioProcessorEditor.
Subclassing AudioProcessorARAExtension allows access to the ARAEditorView instance role as
described by the ARA SDK.
@tags{ARA}
*/
class JUCE_API AudioProcessorEditorARAExtension
{
public:
/** Constructor. */
explicit AudioProcessorEditorARAExtension (AudioProcessor* audioProcessor);
/** \copydoc AudioProcessorARAExtension::getEditorView */
template <typename EditorView_t = ARAEditorView>
EditorView_t* getARAEditorView() const noexcept
{
return (this->araProcessorExtension != nullptr) ? this->araProcessorExtension->getEditorView<EditorView_t>()
: nullptr;
}
/** \copydoc AudioProcessorARAExtension::isEditorView */
bool isARAEditorView() const noexcept { return getARAEditorView() != nullptr; }
protected:
/** Destructor. */
~AudioProcessorEditorARAExtension();
private:
AudioProcessorARAExtension* araProcessorExtension;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorEditorARAExtension)
};
} // namespace juce
@@ -0,0 +1,299 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
class AAXPluginId
{
public:
static std::optional<AAXPluginId> create (std::array<char, 4> letters)
{
std::array<size_t, 4> indices{};
for (size_t i = 0; i < letters.size(); ++i)
{
if (const auto index = findIndexOfChar (letters[i]))
indices[i] = *index;
else
return std::nullopt;
}
return AAXPluginId { std::move (indices) };
}
std::optional<AAXPluginId> withIncrementedLetter (size_t index, size_t increment) const
{
if (indices.size() <= index)
return std::nullopt;
auto copy = *this;
copy.indices[index] += increment;
if ((size_t) std::size (validChars) <= copy.indices[index])
return std::nullopt;
return copy;
}
int32 getPluginId() const
{
int32 result = 0;
for (size_t i = 0; i < indices.size(); ++i)
result |= static_cast<int32> (validChars[indices[i]]) << (8 * (indices.size() - 1 - i));
return result;
}
static std::optional<size_t> findIndexOfChar (char c)
{
const auto ptr = std::find (std::begin (validChars), std::end (validChars), c);
return ptr != std::end (validChars) ? std::make_optional (std::distance (std::begin (validChars), ptr))
: std::nullopt;
}
private:
static inline constexpr char validChars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
explicit AAXPluginId (std::array<size_t, 4> indicesIn)
: indices (std::move (indicesIn))
{}
std::array<size_t, 4> indices;
};
static const AudioChannelSet channelSets[] { AudioChannelSet::disabled(),
AudioChannelSet::mono(),
AudioChannelSet::stereo(),
AudioChannelSet::createLCR(),
AudioChannelSet::createLCRS(),
AudioChannelSet::quadraphonic(),
AudioChannelSet::create5point0(),
AudioChannelSet::create5point1(),
AudioChannelSet::create6point0(),
AudioChannelSet::create6point1(),
AudioChannelSet::create7point0(),
AudioChannelSet::create7point1(),
AudioChannelSet::create7point0SDDS(),
AudioChannelSet::create7point1SDDS(),
AudioChannelSet::create7point0point2(),
AudioChannelSet::create7point1point2(),
AudioChannelSet::ambisonic (1),
AudioChannelSet::ambisonic (2),
AudioChannelSet::ambisonic (3),
AudioChannelSet::create5point0point2(),
AudioChannelSet::create5point1point2(),
AudioChannelSet::create5point0point4(),
AudioChannelSet::create5point1point4(),
AudioChannelSet::create7point0point4(),
AudioChannelSet::create7point1point4(),
AudioChannelSet::create7point0point6(),
AudioChannelSet::create7point1point6(),
AudioChannelSet::create9point0point4(),
AudioChannelSet::create9point1point4(),
AudioChannelSet::create9point0point6(),
AudioChannelSet::create9point1point6(),
AudioChannelSet::ambisonic (4),
AudioChannelSet::ambisonic (5),
AudioChannelSet::ambisonic (6),
AudioChannelSet::ambisonic (7) };
int32 AAXClientExtensions::getPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout,
const AudioChannelSet& mainOutputLayout,
bool idForAudioSuite) const
{
auto pluginId = [&]
{
auto opt = idForAudioSuite ? AAXPluginId::create ({ 'j', 'y', 'a', 'a' })
: AAXPluginId::create ({ 'j', 'c', 'a', 'a' });
jassert (opt.has_value());
return *opt;
}();
for (const auto& [channelSet, indexToModify] : { std::tuple (&mainInputLayout, (size_t) 2),
std::tuple (&mainOutputLayout, (size_t) 3) })
{
const auto increment = (size_t) std::distance (std::begin (channelSets),
std::find (std::begin (channelSets),
std::end (channelSets),
*channelSet));
if (auto modifiedPluginIdOpt = pluginId.withIncrementedLetter (indexToModify, increment);
increment < (size_t) std::size (channelSets) && modifiedPluginIdOpt.has_value())
{
pluginId = *modifiedPluginIdOpt;
}
else
{
jassertfalse;
}
}
return pluginId.getPluginId();
}
String AAXClientExtensions::getPageFileName() const
{
#ifdef JucePlugin_AAXPageTableFile
JUCE_COMPILER_WARNING ("JucePlugin_AAXPageTableFile is deprecated, instead implement AAXClientExtensions::getPageFileName()")
return JucePlugin_AAXPageTableFile;
#else
return {};
#endif
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
static std::array<char, 4> toCharArray (int32 identifier)
{
std::array<char, 4> result;
for (size_t i = 0; i < result.size(); ++i)
result[i] = static_cast<char> ((identifier >> (i * 8)) & 0xff);
return result;
}
static bool isValidAAXPluginId (int32 pluginId)
{
const auto chars = toCharArray (pluginId);
return std::all_of (std::begin (chars),
std::end (chars),
[] (const auto& c)
{
return AAXPluginId::findIndexOfChar (c).has_value();
});
}
static int32 getPluginIDForMainBusConfigJuce705 (const AudioChannelSet& mainInputLayout,
const AudioChannelSet& mainOutputLayout,
bool idForAudioSuite)
{
int uniqueFormatId = 0;
for (int dir = 0; dir < 2; ++dir)
{
const bool isInput = (dir == 0);
auto& set = (isInput ? mainInputLayout : mainOutputLayout);
int aaxFormatIndex = 0;
const AudioChannelSet sets[]
{
AudioChannelSet::disabled(),
AudioChannelSet::mono(),
AudioChannelSet::stereo(),
AudioChannelSet::createLCR(),
AudioChannelSet::createLCRS(),
AudioChannelSet::quadraphonic(),
AudioChannelSet::create5point0(),
AudioChannelSet::create5point1(),
AudioChannelSet::create6point0(),
AudioChannelSet::create6point1(),
AudioChannelSet::create7point0(),
AudioChannelSet::create7point1(),
AudioChannelSet::create7point0SDDS(),
AudioChannelSet::create7point1SDDS(),
AudioChannelSet::create7point0point2(),
AudioChannelSet::create7point1point2(),
AudioChannelSet::ambisonic (1),
AudioChannelSet::ambisonic (2),
AudioChannelSet::ambisonic (3),
AudioChannelSet::create5point0point2(),
AudioChannelSet::create5point1point2(),
AudioChannelSet::create5point0point4(),
AudioChannelSet::create5point1point4(),
AudioChannelSet::create7point0point4(),
AudioChannelSet::create7point1point4(),
AudioChannelSet::create7point0point6(),
AudioChannelSet::create7point1point6(),
AudioChannelSet::create9point0point4(),
AudioChannelSet::create9point1point4(),
AudioChannelSet::create9point0point6(),
AudioChannelSet::create9point1point6(),
AudioChannelSet::ambisonic (4),
AudioChannelSet::ambisonic (5),
AudioChannelSet::ambisonic (6),
AudioChannelSet::ambisonic (7)
};
const auto index = (int) std::distance (std::begin (sets), std::find (std::begin (sets), std::end (sets), set));
if (index != (int) std::size (sets))
aaxFormatIndex = index;
else
jassertfalse;
uniqueFormatId = (uniqueFormatId << 8) | aaxFormatIndex;
}
return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId;
}
class AAXClientExtensionsTests final : public UnitTest
{
public:
AAXClientExtensionsTests()
: UnitTest ("AAXClientExtensionsTests", UnitTestCategories::audioProcessors)
{}
void runTest() override
{
AAXClientExtensions extensions;
beginTest ("Previously valid PluginIds should be unchanged");
{
for (const auto& input : channelSets)
for (const auto& output : channelSets)
for (const auto idForAudioSuite : { false, true })
if (const auto oldId = getPluginIDForMainBusConfigJuce705 (input, output, idForAudioSuite); isValidAAXPluginId (oldId))
expect (extensions.getPluginIDForMainBusConfig (input, output, idForAudioSuite) == oldId);
}
beginTest ("Valid, unique PluginIds should be generated for all configurations");
{
std::set<int32> pluginIds;
for (const auto& input : channelSets)
for (const auto& output : channelSets)
for (const auto idForAudioSuite : { false, true })
pluginIds.insert (extensions.getPluginIDForMainBusConfig (input, output, idForAudioSuite));
for (auto identifier : pluginIds)
expect (isValidAAXPluginId (identifier));
expect (pluginIds.size() == square (std::size (channelSets)) * 2);
}
}
};
static AAXClientExtensionsTests aaxClientExtensionsTests;
#endif
} // namespace juce
@@ -0,0 +1,83 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/**
An interface to allow an AudioProcessor to implement extended AAX-specific functionality.
To use this class, create an object that inherits from it, implement the methods, then return
a pointer to the object in your AudioProcessor `getAAXClientExtensions()` method.
@see AudioProcessor, VST2ClientExtensions, VST3ClientExtensions
@tags{Audio}
*/
struct AAXClientExtensions
{
virtual ~AAXClientExtensions() = default;
/** AAX plug-ins need to report a unique "plug-in id" for every audio layout
configuration that your AudioProcessor supports on the main bus. Override this
function if you want your AudioProcessor to use a custom "plug-in id" (for example
to stay backward compatible with older versions of JUCE).
The default implementation will compute a unique integer from the input and output
layout and add this value to the 4 character code 'jcaa' (for native AAX) or 'jyaa'
(for AudioSuite plug-ins).
*/
virtual int32 getPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout,
const AudioChannelSet& mainOutputLayout,
bool idForAudioSuite) const;
/** Returns an optional filename (including extension) for a page file to be used.
A page file allows an AAX plugin to specify how its parameters are displayed on
various control surfaces. For more information read the Page Table Guide in the
AAX SDK documentation.
By default this file will be searched for in `*.aaxplugin/Contents/Resources`.
@see getPageFileSearchPath
*/
virtual String getPageFileName() const;
/** Optionally returns a search path for finding a page table file.
This can be useful for specifying a location outside the plugin bundle so users can
make changes to a page table file without breaking any code signatures.
If this function returns a default-constructed File, then a default location will be used.
The AAX SDK states this location will be `*.aaxplugin/Contents/Resources`.
@note The returned path should be an absolute path to a directory.
@see getPageFileName
*/
virtual File getPageFileSearchPath() const { return {}; }
};
} // namespace juce
@@ -0,0 +1,95 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
AudioParameterBool::AudioParameterBool (const ParameterID& idToUse,
const String& nameToUse,
bool def,
const AudioParameterBoolAttributes& attributes)
: RangedAudioParameter (idToUse, nameToUse, attributes.getAudioProcessorParameterWithIDAttributes()),
value (def ? 1.0f : 0.0f),
valueDefault (def),
stringFromBoolFunction (attributes.getStringFromValueFunction() != nullptr
? attributes.getStringFromValueFunction()
: [] (bool v, int) { return v ? TRANS ("On") : TRANS ("Off"); }),
boolFromStringFunction (attributes.getValueFromStringFunction() != nullptr
? attributes.getValueFromStringFunction()
: [] (const String& text)
{
static const StringArray onStrings { TRANS ("on"), TRANS ("yes"), TRANS ("true") };
static const StringArray offStrings { TRANS ("off"), TRANS ("no"), TRANS ("false") };
String lowercaseText (text.toLowerCase());
for (auto& testText : onStrings)
if (lowercaseText == testText)
return true;
for (auto& testText : offStrings)
if (lowercaseText == testText)
return false;
return text.getIntValue() != 0;
})
{
}
AudioParameterBool::~AudioParameterBool()
{
#if __cpp_lib_atomic_is_always_lock_free
static_assert (std::atomic<float>::is_always_lock_free,
"AudioParameterBool requires a lock-free std::atomic<float>");
#endif
}
float AudioParameterBool::getValue() const { return value; }
void AudioParameterBool::setValue (float newValue) { value = newValue; valueChanged (get()); }
float AudioParameterBool::getDefaultValue() const { return valueDefault; }
int AudioParameterBool::getNumSteps() const { return 2; }
bool AudioParameterBool::isDiscrete() const { return true; }
bool AudioParameterBool::isBoolean() const { return true; }
void AudioParameterBool::valueChanged (bool) {}
float AudioParameterBool::getValueForText (const String& text) const
{
return boolFromStringFunction (text) ? 1.0f : 0.0f;
}
String AudioParameterBool::getText (float v, int maximumLength) const
{
return stringFromBoolFunction (v >= 0.5f, maximumLength);
}
AudioParameterBool& AudioParameterBool::operator= (bool newValue)
{
if (get() != newValue)
setValueNotifyingHost (newValue ? 1.0f : 0.0f);
return *this;
}
} // namespace juce
@@ -0,0 +1,140 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/** Properties of an AudioParameterBool.
@see AudioParameterBool(), RangedAudioParameterAttributes()
@tags{Audio}
*/
class AudioParameterBoolAttributes : public RangedAudioParameterAttributes<AudioParameterBoolAttributes, bool> {};
//==============================================================================
/**
Provides a class of AudioProcessorParameter that can be used as a boolean value.
@see AudioParameterFloat, AudioParameterInt, AudioParameterChoice
@tags{Audio}
*/
class JUCE_API AudioParameterBool : public RangedAudioParameter
{
public:
/** Creates a AudioParameterBool with the specified parameters.
Note that the attributes argument is optional and only needs to be
supplied if you want to change options from their default values.
Example usage:
@code
auto attributes = AudioParameterBoolAttributes().withStringFromValueFunction ([] (auto x, auto) { return x ? "On" : "Off"; })
.withLabel ("enabled");
auto param = std::make_unique<AudioParameterBool> ("paramID", "Parameter Name", false, attributes);
@endcode
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param defaultValue The default value
@param attributes Optional characteristics
*/
AudioParameterBool (const ParameterID& parameterID,
const String& parameterName,
bool defaultValue,
const AudioParameterBoolAttributes& attributes = {});
/** Creates a AudioParameterBool with the specified parameters.
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param defaultValue The default value
@param parameterLabel An optional label for the parameter's value
@param stringFromBool An optional lambda function that converts a bool
value to a string with a maximum length. This may
be used by hosts to display the parameter's value.
@param boolFromString An optional lambda function that parses a string and
converts it into a bool value. Some hosts use this
to allow users to type in parameter values.
*/
[[deprecated ("Prefer the signature taking an Attributes argument")]]
AudioParameterBool (const ParameterID& parameterID,
const String& parameterName,
bool defaultValue,
const String& parameterLabel,
std::function<String (bool value, int maximumStringLength)> stringFromBool = nullptr,
std::function<bool (const String& text)> boolFromString = nullptr)
: AudioParameterBool (parameterID,
parameterName,
defaultValue,
AudioParameterBoolAttributes().withLabel (parameterLabel)
.withStringFromValueFunction (std::move (stringFromBool))
.withValueFromStringFunction (std::move (boolFromString)))
{
}
/** Destructor. */
~AudioParameterBool() override;
/** Returns the parameter's current boolean value. */
bool get() const noexcept { return value >= 0.5f; }
/** Returns the parameter's current boolean value. */
operator bool() const noexcept { return get(); }
/** Changes the parameter's current value to a new boolean. */
AudioParameterBool& operator= (bool newValue);
/** Returns the range of values that the parameter can take. */
const NormalisableRange<float>& getNormalisableRange() const override { return range; }
protected:
/** Override this method if you are interested in receiving callbacks
when the parameter value changes.
*/
virtual void valueChanged (bool newValue);
private:
//==============================================================================
float getValue() const override;
void setValue (float newValue) override;
float getDefaultValue() const override;
int getNumSteps() const override;
bool isDiscrete() const override;
bool isBoolean() const override;
String getText (float, int) const override;
float getValueForText (const String&) const override;
const NormalisableRange<float> range { 0.0f, 1.0f, 1.0f };
std::atomic<float> value;
const float valueDefault;
std::function<String (bool, int)> stringFromBoolFunction;
std::function<bool (const String&)> boolFromStringFunction;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterBool)
};
} // namespace juce
@@ -0,0 +1,136 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
AudioParameterChoice::AudioParameterChoice (const ParameterID& idToUse,
const String& nameToUse,
const StringArray& c,
int def,
const AudioParameterChoiceAttributes& attributes)
: RangedAudioParameter (idToUse, nameToUse, attributes.getAudioProcessorParameterWithIDAttributes()),
choices (c),
range ([this]
{
NormalisableRange<float> rangeWithInterval { 0.0f, (float) choices.size() - 1.0f,
[] (float, float end, float v) { return jlimit (0.0f, end, v * end); },
[] (float, float end, float v) { return jlimit (0.0f, 1.0f, v / end); },
[] (float start, float end, float v) { return (float) roundToInt (juce::jlimit (start, end, v)); } };
rangeWithInterval.interval = 1.0f;
return rangeWithInterval;
}()),
value ((float) def),
defaultValue (convertTo0to1 ((float) def)),
stringFromIndexFunction (attributes.getStringFromValueFunction() != nullptr
? attributes.getStringFromValueFunction()
: [this] (int index, int) { return choices [index]; }),
indexFromStringFunction (attributes.getValueFromStringFunction() != nullptr
? attributes.getValueFromStringFunction()
: [this] (const String& text) { return choices.indexOf (text); })
{
jassert (choices.size() > 1); // you must supply an actual set of items to choose from!
}
AudioParameterChoice::~AudioParameterChoice()
{
#if __cpp_lib_atomic_is_always_lock_free
static_assert (std::atomic<float>::is_always_lock_free,
"AudioParameterChoice requires a lock-free std::atomic<float>");
#endif
}
float AudioParameterChoice::getValue() const { return convertTo0to1 (value); }
void AudioParameterChoice::setValue (float newValue) { value = convertFrom0to1 (newValue); valueChanged (getIndex()); }
float AudioParameterChoice::getDefaultValue() const { return defaultValue; }
int AudioParameterChoice::getNumSteps() const { return choices.size(); }
bool AudioParameterChoice::isDiscrete() const { return true; }
float AudioParameterChoice::getValueForText (const String& text) const { return convertTo0to1 ((float) indexFromStringFunction (text)); }
String AudioParameterChoice::getText (float v, int length) const { return stringFromIndexFunction ((int) convertFrom0to1 (v), length); }
void AudioParameterChoice::valueChanged (int) {}
AudioParameterChoice& AudioParameterChoice::operator= (int newValue)
{
if (getIndex() != newValue)
setValueNotifyingHost (convertTo0to1 ((float) newValue));
return *this;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct AudioParameterChoiceTests final : public UnitTest
{
AudioParameterChoiceTests()
: UnitTest ("AudioParameterChoice", UnitTestCategories::audioProcessorParameters)
{}
void runTest() override
{
beginTest ("Three options switches at the correct points");
{
AudioParameterChoice choice ({}, {}, { "a", "b", "c" }, {});
choice.setValueNotifyingHost (0.0f);
expectEquals (choice.getIndex(), 0);
choice.setValueNotifyingHost (0.2f);
expectEquals (choice.getIndex(), 0);
choice.setValueNotifyingHost (0.3f);
expectEquals (choice.getIndex(), 1);
choice.setValueNotifyingHost (0.7f);
expectEquals (choice.getIndex(), 1);
choice.setValueNotifyingHost (0.8f);
expectEquals (choice.getIndex(), 2);
choice.setValueNotifyingHost (1.0f);
expectEquals (choice.getIndex(), 2);
}
beginTest ("Out-of-bounds input");
{
AudioParameterChoice choiceParam ({}, {}, { "a", "b", "c" }, {});
choiceParam.setValueNotifyingHost (-0.5f);
expectEquals (choiceParam.getIndex(), 0);
choiceParam.setValueNotifyingHost (1.5f);
expectEquals (choiceParam.getIndex(), 2);
}
}
};
static AudioParameterChoiceTests audioParameterChoiceTests;
#endif
} // namespace juce
@@ -0,0 +1,153 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/** Properties of an AudioParameterChoice.
@see AudioParameterChoice(), RangedAudioParameterAttributes()
@tags{Audio}
*/
class AudioParameterChoiceAttributes : public RangedAudioParameterAttributes<AudioParameterChoiceAttributes, int> {};
//==============================================================================
/**
Provides a class of AudioProcessorParameter that can be used to select
an indexed, named choice from a list.
@see AudioParameterFloat, AudioParameterInt, AudioParameterBool
@tags{Audio}
*/
class JUCE_API AudioParameterChoice : public RangedAudioParameter
{
public:
/** Creates a AudioParameterChoice with the specified parameters.
Note that the attributes argument is optional and only needs to be
supplied if you want to change options from their default values.
Example usage:
@code
auto attributes = AudioParameterChoiceAttributes().withLabel ("selected");
auto param = std::make_unique<AudioParameterChoice> ("paramID", "Parameter Name", StringArray { "a", "b", "c" }, 0, attributes);
@endcode
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param choices The set of choices to use
@param defaultItemIndex The index of the default choice
@param attributes Optional characteristics
*/
AudioParameterChoice (const ParameterID& parameterID,
const String& parameterName,
const StringArray& choices,
int defaultItemIndex,
const AudioParameterChoiceAttributes& attributes = {});
/** Creates a AudioParameterChoice with the specified parameters.
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param choicesToUse The set of choices to use
@param defaultItemIndex The index of the default choice
@param parameterLabel An optional label for the parameter's value
@param stringFromIndex An optional lambda function that converts a choice
index to a string with a maximum length. This may
be used by hosts to display the parameter's value.
@param indexFromString An optional lambda function that parses a string and
converts it into a choice index. Some hosts use this
to allow users to type in parameter values.
*/
[[deprecated ("Prefer the signature taking an Attributes argument")]]
AudioParameterChoice (const ParameterID& parameterID,
const String& parameterName,
const StringArray& choicesToUse,
int defaultItemIndex,
const String& parameterLabel,
std::function<String (int index, int maximumStringLength)> stringFromIndex = nullptr,
std::function<int (const String& text)> indexFromString = nullptr)
: AudioParameterChoice (parameterID,
parameterName,
choicesToUse,
defaultItemIndex,
AudioParameterChoiceAttributes().withLabel (parameterLabel)
.withStringFromValueFunction (std::move (stringFromIndex))
.withValueFromStringFunction (std::move (indexFromString)))
{
}
/** Destructor. */
~AudioParameterChoice() override;
/** Returns the current index of the selected item. */
int getIndex() const noexcept { return roundToInt (value.load()); }
/** Returns the current index of the selected item. */
operator int() const noexcept { return getIndex(); }
/** Returns the name of the currently selected item. */
String getCurrentChoiceName() const noexcept { return choices[getIndex()]; }
/** Returns the name of the currently selected item. */
operator String() const noexcept { return getCurrentChoiceName(); }
/** Changes the selected item to a new index. */
AudioParameterChoice& operator= (int newValue);
/** Returns the range of values that the parameter can take. */
const NormalisableRange<float>& getNormalisableRange() const override { return range; }
/** Provides access to the list of choices that this parameter is working with. */
const StringArray choices;
protected:
/** Override this method if you are interested in receiving callbacks
when the parameter value changes.
*/
virtual void valueChanged (int newValue);
private:
//==============================================================================
float getValue() const override;
void setValue (float newValue) override;
float getDefaultValue() const override;
int getNumSteps() const override;
bool isDiscrete() const override;
String getText (float, int) const override;
float getValueForText (const String&) const override;
const NormalisableRange<float> range;
std::atomic<float> value;
const float defaultValue;
std::function<String (int, int)> stringFromIndexFunction;
std::function<int (const String&)> indexFromStringFunction;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterChoice)
};
} // namespace juce
@@ -0,0 +1,104 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
AudioParameterFloat::AudioParameterFloat (const ParameterID& idToUse,
const String& nameToUse,
NormalisableRange<float> r,
float def,
const AudioParameterFloatAttributes& attributes)
: RangedAudioParameter (idToUse, nameToUse, attributes.getAudioProcessorParameterWithIDAttributes()),
range (r),
value (def),
valueDefault (def),
stringFromValueFunction (attributes.getStringFromValueFunction()),
valueFromStringFunction (attributes.getValueFromStringFunction())
{
if (stringFromValueFunction == nullptr)
{
auto numDecimalPlacesToDisplay = [this]
{
int numDecimalPlaces = 7;
if (! approximatelyEqual (range.interval, 0.0f))
{
if (approximatelyEqual (std::abs (range.interval - std::floor (range.interval)), 0.0f))
return 0;
auto v = std::abs (roundToInt (range.interval * pow (10, numDecimalPlaces)));
while ((v % 10) == 0 && numDecimalPlaces > 0)
{
--numDecimalPlaces;
v /= 10;
}
}
return numDecimalPlaces;
}();
stringFromValueFunction = [numDecimalPlacesToDisplay] (float v, int length)
{
String asText (v, numDecimalPlacesToDisplay);
return length > 0 ? asText.substring (0, length) : asText;
};
}
if (valueFromStringFunction == nullptr)
valueFromStringFunction = [] (const String& text) { return text.getFloatValue(); };
}
AudioParameterFloat::AudioParameterFloat (const ParameterID& pid, const String& nm, float minValue, float maxValue, float def)
: AudioParameterFloat (pid, nm, { minValue, maxValue, 0.01f }, def)
{
}
AudioParameterFloat::~AudioParameterFloat()
{
#if __cpp_lib_atomic_is_always_lock_free
static_assert (std::atomic<float>::is_always_lock_free,
"AudioParameterFloat requires a lock-free std::atomic<float>");
#endif
}
float AudioParameterFloat::getValue() const { return convertTo0to1 (value); }
void AudioParameterFloat::setValue (float newValue) { value = convertFrom0to1 (newValue); valueChanged (get()); }
float AudioParameterFloat::getDefaultValue() const { return convertTo0to1 (valueDefault); }
int AudioParameterFloat::getNumSteps() const { return AudioProcessorParameterWithID::getNumSteps(); }
String AudioParameterFloat::getText (float v, int length) const { return stringFromValueFunction (convertFrom0to1 (v), length); }
float AudioParameterFloat::getValueForText (const String& text) const { return convertTo0to1 (valueFromStringFunction (text)); }
void AudioParameterFloat::valueChanged (float) {}
AudioParameterFloat& AudioParameterFloat::operator= (float newValue)
{
if (! approximatelyEqual ((float) value, newValue))
setValueNotifyingHost (convertTo0to1 (newValue));
return *this;
}
} // namespace juce
@@ -0,0 +1,160 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/** Properties of an AudioParameterFloat.
@see AudioParameterFloat(), RangedAudioParameterAttributes()
@tags{Audio}
*/
class AudioParameterFloatAttributes : public RangedAudioParameterAttributes<AudioParameterFloatAttributes, float> {};
//==============================================================================
/**
A subclass of AudioProcessorParameter that provides an easy way to create a
parameter which maps onto a given NormalisableRange.
@see AudioParameterInt, AudioParameterBool, AudioParameterChoice
@tags{Audio}
*/
class JUCE_API AudioParameterFloat : public RangedAudioParameter
{
public:
/** Creates a AudioParameterFloat with the specified parameters.
Note that the attributes argument is optional and only needs to be
supplied if you want to change options from their default values.
Example usage:
@code
auto attributes = AudioParameterFloatAttributes().withStringFromValueFunction ([] (auto x, auto) { return String (x * 100); })
.withLabel ("%");
auto param = std::make_unique<AudioParameterFloat> ("paramID", "Parameter Name", NormalisableRange<float>(), 0.5f, attributes);
@endcode
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param normalisableRange The NormalisableRange to use
@param defaultValue The non-normalised default value
@param attributes Optional characteristics
*/
AudioParameterFloat (const ParameterID& parameterID,
const String& parameterName,
NormalisableRange<float> normalisableRange,
float defaultValue,
const AudioParameterFloatAttributes& attributes = {});
/** Creates a AudioParameterFloat with the specified parameters.
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param normalisableRange The NormalisableRange to use
@param defaultValue The non-normalised default value
@param parameterLabel An optional label for the parameter's value
@param parameterCategory An optional parameter category
@param stringFromValue An optional lambda function that converts a non-normalised
value to a string with a maximum length. This may
be used by hosts to display the parameter's value.
@param valueFromString An optional lambda function that parses a string and
converts it into a non-normalised value. Some hosts use
this to allow users to type in parameter values.
*/
[[deprecated ("Prefer the signature taking an Attributes argument")]]
AudioParameterFloat (const ParameterID& parameterID,
const String& parameterName,
NormalisableRange<float> normalisableRange,
float defaultValue,
const String& parameterLabel,
Category parameterCategory = AudioProcessorParameter::genericParameter,
std::function<String (float value, int maximumStringLength)> stringFromValue = nullptr,
std::function<float (const String& text)> valueFromString = nullptr)
: AudioParameterFloat (parameterID,
parameterName,
std::move (normalisableRange),
defaultValue,
AudioParameterFloatAttributes().withLabel (parameterLabel)
.withCategory (parameterCategory)
.withStringFromValueFunction (std::move (stringFromValue))
.withValueFromStringFunction (std::move (valueFromString)))
{
}
/** Creates a AudioParameterFloat with an ID, name, and range.
On creation, its value is set to the default value.
For control over skew factors, you can use the other
constructor and provide a NormalisableRange.
*/
AudioParameterFloat (const ParameterID& parameterID,
const String& parameterName,
float minValue,
float maxValue,
float defaultValue);
/** Destructor. */
~AudioParameterFloat() override;
/** Returns the parameter's current value. */
float get() const noexcept { return value; }
/** Returns the parameter's current value. */
operator float() const noexcept { return value; }
/** Changes the parameter's current value. */
AudioParameterFloat& operator= (float newValue);
/** Returns the range of values that the parameter can take. */
const NormalisableRange<float>& getNormalisableRange() const override { return range; }
/** Provides access to the parameter's range. */
NormalisableRange<float> range;
protected:
/** Override this method if you are interested in receiving callbacks
when the parameter value changes.
*/
virtual void valueChanged (float newValue);
private:
//==============================================================================
float getValue() const override;
void setValue (float newValue) override;
float getDefaultValue() const override;
int getNumSteps() const override;
String getText (float, int) const override;
float getValueForText (const String&) const override;
std::atomic<float> value;
const float valueDefault;
std::function<String (float, int)> stringFromValueFunction;
std::function<float (const String&)> valueFromStringFunction;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterFloat)
};
} // namespace juce
@@ -0,0 +1,137 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
AudioParameterInt::AudioParameterInt (const ParameterID& idToUse, const String& nameToUse,
int minValue, int maxValue, int def,
const AudioParameterIntAttributes& attributes)
: RangedAudioParameter (idToUse, nameToUse, attributes.getAudioProcessorParameterWithIDAttributes()),
range ([minValue, maxValue]
{
NormalisableRange<float> rangeWithInterval { (float) minValue, (float) maxValue,
[] (float start, float end, float v) { return jlimit (start, end, v * (end - start) + start); },
[] (float start, float end, float v) { return jlimit (0.0f, 1.0f, (v - start) / (end - start)); },
[] (float start, float end, float v) { return (float) roundToInt (juce::jlimit (start, end, v)); } };
rangeWithInterval.interval = 1.0f;
return rangeWithInterval;
}()),
value ((float) def),
defaultValue (convertTo0to1 ((float) def)),
stringFromIntFunction (attributes.getStringFromValueFunction() != nullptr
? attributes.getStringFromValueFunction()
: [] (int v, int) { return String (v); }),
intFromStringFunction (attributes.getValueFromStringFunction() != nullptr
? attributes.getValueFromStringFunction()
: [] (const String& text) { return text.getIntValue(); })
{
jassert (minValue < maxValue); // must have a non-zero range of values!
}
AudioParameterInt::~AudioParameterInt()
{
#if __cpp_lib_atomic_is_always_lock_free
static_assert (std::atomic<float>::is_always_lock_free,
"AudioParameterInt requires a lock-free std::atomic<float>");
#endif
}
float AudioParameterInt::getValue() const { return convertTo0to1 (value); }
void AudioParameterInt::setValue (float newValue) { value = convertFrom0to1 (newValue); valueChanged (get()); }
float AudioParameterInt::getDefaultValue() const { return defaultValue; }
int AudioParameterInt::getNumSteps() const { return ((int) getNormalisableRange().getRange().getLength()) + 1; }
float AudioParameterInt::getValueForText (const String& text) const { return convertTo0to1 ((float) intFromStringFunction (text)); }
String AudioParameterInt::getText (float v, int length) const { return stringFromIntFunction ((int) convertFrom0to1 (v), length); }
void AudioParameterInt::valueChanged (int) {}
AudioParameterInt& AudioParameterInt::operator= (int newValue)
{
if (get() != newValue)
setValueNotifyingHost (convertTo0to1 ((float) newValue));
return *this;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct AudioParameterIntTests final : public UnitTest
{
AudioParameterIntTests()
: UnitTest ("AudioParameterInt", UnitTestCategories::audioProcessorParameters)
{}
void runTest() override
{
beginTest ("Three options switches at the correct points");
{
AudioParameterInt intParam ({}, {}, 1, 3, 1);
intParam.setValueNotifyingHost (0.0f);
expectEquals (intParam.get(), 1);
intParam.setValueNotifyingHost (0.2f);
expectEquals (intParam.get(), 1);
intParam.setValueNotifyingHost (0.3f);
expectEquals (intParam.get(), 2);
intParam.setValueNotifyingHost (0.7f);
expectEquals (intParam.get(), 2);
intParam.setValueNotifyingHost (0.8f);
expectEquals (intParam.get(), 3);
intParam.setValueNotifyingHost (1.0f);
expectEquals (intParam.get(), 3);
}
beginTest ("Out-of-bounds input");
{
AudioParameterInt intParam ({}, {}, -1, 2, 0);
intParam.setValueNotifyingHost (-0.5f);
expectEquals (intParam.get(), -1);
intParam.setValueNotifyingHost (1.5f);
expectEquals (intParam.get(), 2);
intParam = -5;
expectEquals (intParam.get(), -1);
intParam = 5;
expectEquals (intParam.get(), 2);
}
}
};
static AudioParameterIntTests audioParameterIntTests;
#endif
} // namespace juce
@@ -0,0 +1,154 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/** Properties of an AudioParameterInt.
@see AudioParameterInt(), RangedAudioParameterAttributes()
@tags{Audio}
*/
class AudioParameterIntAttributes : public RangedAudioParameterAttributes<AudioParameterIntAttributes, int> {};
//==============================================================================
/**
Provides a class of AudioProcessorParameter that can be used as an
integer value with a given range.
@see AudioParameterFloat, AudioParameterBool, AudioParameterChoice
@tags{Audio}
*/
class JUCE_API AudioParameterInt : public RangedAudioParameter
{
public:
/** Creates a AudioParameterInt with the specified parameters.
Note that the attributes argument is optional and only needs to be
supplied if you want to change options from their default values.
Example usage:
@code
auto attributes = AudioParameterIntAttributes().withStringFromValueFunction ([] (auto x, auto) { return String (x); })
.withLabel ("things");
auto param = std::make_unique<AudioParameterInt> ("paramID", "Parameter Name", 0, 100, 50, attributes);
@endcode
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param minValue The minimum parameter value
@param maxValue The maximum parameter value
@param defaultValue The default value
@param attributes Optional characteristics
*/
AudioParameterInt (const ParameterID& parameterID,
const String& parameterName,
int minValue,
int maxValue,
int defaultValue,
const AudioParameterIntAttributes& attributes = {});
/** Creates a AudioParameterInt with the specified parameters.
@param parameterID The parameter ID to use
@param parameterName The parameter name to use
@param minValue The minimum parameter value
@param maxValue The maximum parameter value
@param defaultValueIn The default value
@param parameterLabel An optional label for the parameter's value
@param stringFromInt An optional lambda function that converts a int
value to a string with a maximum length. This may
be used by hosts to display the parameter's value.
@param intFromString An optional lambda function that parses a string
and converts it into an int. Some hosts use this
to allow users to type in parameter values.
*/
[[deprecated ("Prefer the signature taking an Attributes argument")]]
AudioParameterInt (const ParameterID& parameterID,
const String& parameterName,
int minValue,
int maxValue,
int defaultValueIn,
const String& parameterLabel,
std::function<String (int value, int maximumStringLength)> stringFromInt = nullptr,
std::function<int (const String& text)> intFromString = nullptr)
: AudioParameterInt (parameterID,
parameterName,
minValue,
maxValue,
defaultValueIn,
AudioParameterIntAttributes().withLabel (parameterLabel)
.withStringFromValueFunction (std::move (stringFromInt))
.withValueFromStringFunction (std::move (intFromString)))
{
}
/** Destructor. */
~AudioParameterInt() override;
/** Returns the parameter's current value as an integer. */
int get() const noexcept { return roundToInt (value.load()); }
/** Returns the parameter's current value as an integer. */
operator int() const noexcept { return get(); }
/** Changes the parameter's current value to a new integer.
The value passed in will be snapped to the permitted range before being used.
*/
AudioParameterInt& operator= (int newValue);
/** Returns the parameter's range. */
Range<int> getRange() const noexcept { return { (int) getNormalisableRange().start, (int) getNormalisableRange().end }; }
/** Returns the range of values that the parameter can take. */
const NormalisableRange<float>& getNormalisableRange() const override { return range; }
protected:
/** Override this method if you are interested in receiving callbacks
when the parameter value changes.
*/
virtual void valueChanged (int newValue);
private:
//==============================================================================
float getValue() const override;
void setValue (float newValue) override;
float getDefaultValue() const override;
int getNumSteps() const override;
String getText (float, int) const override;
float getValueForText (const String&) const override;
const NormalisableRange<float> range;
std::atomic<float> value;
const float defaultValue;
std::function<String (int, int)> stringFromIntFunction;
std::function<int (const String&)> intFromStringFunction;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterInt)
};
} // namespace juce
@@ -0,0 +1,47 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
AudioProcessorParameterWithID::AudioProcessorParameterWithID (const ParameterID& idToUse,
const String& nameToUse,
const AudioProcessorParameterWithIDAttributes& attributes)
: HostedAudioProcessorParameter (idToUse.getVersionHint()),
paramID (idToUse.getParamID()),
name (nameToUse),
label (attributes.getLabel()),
category (attributes.getCategory()),
meta (attributes.getMeta()),
automatable (attributes.getAutomatable()),
inverted (attributes.getInverted())
{
}
String AudioProcessorParameterWithID::getName (int maximumStringLength) const { return name.substring (0, maximumStringLength); }
String AudioProcessorParameterWithID::getLabel() const { return label; }
AudioProcessorParameter::Category AudioProcessorParameterWithID::getCategory() const { return category; }
} // namespace juce
@@ -0,0 +1,191 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/**
Combines a parameter ID and a version hint.
@tags{Audio}
*/
class ParameterID
{
public:
ParameterID() = default;
/** Constructs an instance.
Note that this constructor implicitly converts from Strings and string-like types.
@param identifier A string that uniquely identifies a single parameter
@param versionHint Influences parameter ordering in Audio Unit plugins.
Used to provide backwards compatibility of Audio Unit plugins in
Logic and GarageBand.
@see AudioProcessorParameter (int)
*/
template <typename StringLike, typename = DisableIfSameOrDerived<ParameterID, StringLike>>
ParameterID (StringLike&& identifier, int versionHint = 0)
: paramID (std::forward<StringLike> (identifier)), version (versionHint) {}
/** @see AudioProcessorParameterWithID::paramID */
auto getParamID() const { return paramID; }
/** @see AudioProcessorParameter (int) */
auto getVersionHint() const { return version; }
private:
String paramID;
int version = 0;
};
/**
An instance of this class may be passed to the constructor of an AudioProcessorParameterWithID
to set optional characteristics of that parameter.
@tags{Audio}
*/
class AudioProcessorParameterWithIDAttributes
{
using This = AudioProcessorParameterWithIDAttributes;
public:
using Category = AudioProcessorParameter::Category;
/** An optional label for the parameter's value */
[[nodiscard]] auto withLabel (String x) const { return withMember (*this, &This::label, std::move (x)); }
/** The semantics of this parameter */
[[nodiscard]] auto withCategory (Category x) const { return withMember (*this, &This::category, std::move (x)); }
/** @see AudioProcessorParameter::isMetaParameter() */
[[nodiscard]] auto withMeta (bool x) const { return withMember (*this, &This::meta, std::move (x)); }
/** @see AudioProcessorParameter::isAutomatable() */
[[nodiscard]] auto withAutomatable (bool x) const { return withMember (*this, &This::automatable, std::move (x)); }
/** @see AudioProcessorParameter::isOrientationInverted() */
[[nodiscard]] auto withInverted (bool x) const { return withMember (*this, &This::inverted, std::move (x)); }
/** An optional label for the parameter's value */
[[nodiscard]] auto getLabel() const { return label; }
/** The semantics of this parameter */
[[nodiscard]] auto getCategory() const { return category; }
/** @see AudioProcessorParameter::isMetaParameter() */
[[nodiscard]] auto getMeta() const { return meta; }
/** @see AudioProcessorParameter::isAutomatable() */
[[nodiscard]] auto getAutomatable() const { return automatable; }
/** @see AudioProcessorParameter::isOrientationInverted() */
[[nodiscard]] auto getInverted() const { return inverted; }
private:
String label;
Category category = AudioProcessorParameter::genericParameter;
bool meta = false, automatable = true, inverted = false;
};
//==============================================================================
/**
This abstract base class is used by some AudioProcessorParameter helper classes.
@see AudioParameterFloat, AudioParameterInt, AudioParameterBool, AudioParameterChoice
@tags{Audio}
*/
class JUCE_API AudioProcessorParameterWithID : public HostedAudioProcessorParameter
{
public:
/** The creation of this object requires providing a name and ID which will be constant for its lifetime.
Given that AudioProcessorParameterWithID is abstract, you'll probably call this constructor
from a derived class constructor, e.g.
@code
MyParameterType (String paramID, String name, String label, bool automatable)
: AudioProcessorParameterWithID (paramID, name, AudioProcessorParameterWithIDAttributes().withLabel (label)
.withAutomatable (automatable))
{
}
@endcode
@param parameterID Specifies the identifier, and optionally the parameter's version hint.
@param parameterName The user-facing parameter name.
@param attributes Other parameter properties.
*/
AudioProcessorParameterWithID (const ParameterID& parameterID,
const String& parameterName,
const AudioProcessorParameterWithIDAttributes& attributes = {});
/** The creation of this object requires providing a name and ID which will be
constant for its lifetime.
@param parameterID Used to uniquely identify the parameter
@param parameterName The user-facing name of the parameter
@param parameterLabel An optional label for the parameter's value
@param parameterCategory The semantics of this parameter
*/
[[deprecated ("Prefer the signature taking an Attributes argument")]]
AudioProcessorParameterWithID (const ParameterID& parameterID,
const String& parameterName,
const String& parameterLabel,
Category parameterCategory = AudioProcessorParameter::genericParameter)
: AudioProcessorParameterWithID (parameterID,
parameterName,
AudioProcessorParameterWithIDAttributes().withLabel (parameterLabel)
.withCategory (parameterCategory))
{
}
/** Provides access to the parameter's ID string. */
const String paramID;
/** Provides access to the parameter's name. */
const String name;
/** Provides access to the parameter's label. */
const String label;
/** Provides access to the parameter's category. */
const Category category;
String getName (int) const override;
String getLabel() const override;
Category getCategory() const override;
String getParameterID() const override { return paramID; }
bool isMetaParameter() const override { return meta; }
bool isAutomatable() const override { return automatable; }
bool isOrientationInverted() const override { return inverted; }
private:
bool meta = false, automatable = true, inverted = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameterWithID)
};
} // namespace juce
@@ -0,0 +1,988 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
AudioProcessorValueTreeState::Parameter::Parameter (const ParameterID& parameterID,
const String& parameterName,
NormalisableRange<float> valueRange,
float defaultParameterValue,
const AudioProcessorValueTreeStateParameterAttributes& attributes)
: AudioParameterFloat (parameterID,
parameterName,
valueRange,
defaultParameterValue,
attributes.getAudioParameterFloatAttributes()),
unsnappedDefault (valueRange.convertTo0to1 (defaultParameterValue)),
discrete (attributes.getDiscrete()),
boolean (attributes.getBoolean())
{
}
float AudioProcessorValueTreeState::Parameter::getDefaultValue() const { return unsnappedDefault; }
int AudioProcessorValueTreeState::Parameter::getNumSteps() const { return RangedAudioParameter::getNumSteps(); }
bool AudioProcessorValueTreeState::Parameter::isDiscrete() const { return discrete; }
bool AudioProcessorValueTreeState::Parameter::isBoolean() const { return boolean; }
void AudioProcessorValueTreeState::Parameter::valueChanged (float newValue)
{
if (approximatelyEqual ((float) lastValue, newValue))
return;
lastValue = newValue;
NullCheckedInvocation::invoke (onValueChanged);
}
//==============================================================================
class AudioProcessorValueTreeState::ParameterAdapter final : private AudioProcessorParameter::Listener
{
private:
using Listener = AudioProcessorValueTreeState::Listener;
public:
explicit ParameterAdapter (RangedAudioParameter& parameterIn)
: parameter (parameterIn),
// For legacy reasons, the unnormalised value should *not* be snapped on construction
unnormalisedValue (getRange().convertFrom0to1 (parameter.getDefaultValue()))
{
parameter.addListener (this);
if (auto* ptr = dynamic_cast<Parameter*> (&parameter))
ptr->onValueChanged = [this] { parameterValueChanged ({}, {}); };
}
~ParameterAdapter() override { parameter.removeListener (this); }
void addListener (Listener* l) { listeners.add (l); }
void removeListener (Listener* l) { listeners.remove (l); }
RangedAudioParameter& getParameter() { return parameter; }
const RangedAudioParameter& getParameter() const { return parameter; }
const NormalisableRange<float>& getRange() const { return parameter.getNormalisableRange(); }
float getDenormalisedDefaultValue() const { return denormalise (parameter.getDefaultValue()); }
void setDenormalisedValue (float value)
{
if (! approximatelyEqual (value, (float) unnormalisedValue))
setNormalisedValue (normalise (value));
}
float getDenormalisedValueForText (const String& text) const
{
return denormalise (parameter.getValueForText (text));
}
String getTextForDenormalisedValue (float value) const
{
return parameter.getText (normalise (value), 0);
}
float getDenormalisedValue() const { return unnormalisedValue; }
std::atomic<float>& getRawDenormalisedValue() { return unnormalisedValue; }
bool flushToTree (const Identifier& key, UndoManager* um)
{
auto needsUpdateTestValue = true;
if (! needsUpdate.compare_exchange_strong (needsUpdateTestValue, false))
return false;
if (auto* valueProperty = tree.getPropertyPointer (key))
{
if (! approximatelyEqual ((float) *valueProperty, unnormalisedValue.load()))
{
ScopedValueSetter<bool> svs (ignoreParameterChangedCallbacks, true);
tree.setProperty (key, unnormalisedValue.load(), um);
}
}
else
{
tree.setProperty (key, unnormalisedValue.load(), nullptr);
}
return true;
}
ValueTree tree;
private:
void parameterGestureChanged (int, bool) override {}
void parameterValueChanged (int, float) override
{
const auto newValue = denormalise (parameter.getValue());
if (! listenersNeedCalling && approximatelyEqual ((float) unnormalisedValue, newValue))
return;
unnormalisedValue = newValue;
listeners.call ([this] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); });
listenersNeedCalling = false;
needsUpdate = true;
}
float denormalise (float normalised) const
{
return getParameter().convertFrom0to1 (normalised);
}
float normalise (float denormalised) const
{
return getParameter().convertTo0to1 (denormalised);
}
void setNormalisedValue (float value)
{
if (ignoreParameterChangedCallbacks)
return;
parameter.setValueNotifyingHost (value);
}
class LockedListeners
{
public:
template <typename Fn>
void call (Fn&& fn)
{
const CriticalSection::ScopedLockType lock (mutex);
listeners.call (std::forward<Fn> (fn));
}
void add (Listener* l)
{
const CriticalSection::ScopedLockType lock (mutex);
listeners.add (l);
}
void remove (Listener* l)
{
const CriticalSection::ScopedLockType lock (mutex);
listeners.remove (l);
}
private:
CriticalSection mutex;
ListenerList<Listener> listeners;
};
RangedAudioParameter& parameter;
LockedListeners listeners;
std::atomic<float> unnormalisedValue { 0.0f };
std::atomic<bool> needsUpdate { true }, listenersNeedCalling { true };
bool ignoreParameterChangedCallbacks { false };
};
//==============================================================================
AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo,
UndoManager* undoManagerToUse,
const Identifier& valueTreeType,
ParameterLayout parameterLayout)
: AudioProcessorValueTreeState (processorToConnectTo, undoManagerToUse)
{
struct PushBackVisitor final : ParameterLayout::Visitor
{
explicit PushBackVisitor (AudioProcessorValueTreeState& stateIn)
: state (&stateIn) {}
void visit (std::unique_ptr<RangedAudioParameter> param) const override
{
if (param == nullptr)
{
jassertfalse;
return;
}
state->addParameterAdapter (*param);
state->processor.addParameter (param.release());
}
void visit (std::unique_ptr<AudioProcessorParameterGroup> group) const override
{
if (group == nullptr)
{
jassertfalse;
return;
}
for (const auto param : group->getParameters (true))
{
if (const auto rangedParam = dynamic_cast<RangedAudioParameter*> (param))
{
state->addParameterAdapter (*rangedParam);
}
else
{
// If you hit this assertion then you are attempting to add a parameter that is
// not derived from RangedAudioParameter to the AudioProcessorValueTreeState.
jassertfalse;
}
}
state->processor.addParameterGroup (std::move (group));
}
AudioProcessorValueTreeState* state;
};
for (auto& item : parameterLayout.parameters)
item->accept (PushBackVisitor (*this));
state = ValueTree (valueTreeType);
}
AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& p, UndoManager* um)
: processor (p), undoManager (um)
{
startTimerHz (10);
state.addListener (this);
}
AudioProcessorValueTreeState::~AudioProcessorValueTreeState()
{
stopTimer();
}
//==============================================================================
RangedAudioParameter* AudioProcessorValueTreeState::createAndAddParameter (const String& paramID,
const String& paramName,
const String& labelText,
NormalisableRange<float> range,
float defaultVal,
std::function<String (float)> valueToTextFunction,
std::function<float (const String&)> textToValueFunction,
bool isMetaParameter,
bool isAutomatableParameter,
bool isDiscreteParameter,
AudioProcessorParameter::Category category,
bool isBooleanParameter)
{
auto attributes = AudioProcessorValueTreeStateParameterAttributes()
.withLabel (labelText)
.withStringFromValueFunction ([fn = std::move (valueToTextFunction)] (float v, int) { return fn (v); })
.withValueFromStringFunction (std::move (textToValueFunction))
.withMeta (isMetaParameter)
.withAutomatable (isAutomatableParameter)
.withDiscrete (isDiscreteParameter)
.withCategory (category)
.withBoolean (isBooleanParameter);
return createAndAddParameter (std::make_unique<Parameter> (paramID,
paramName,
range,
defaultVal,
std::move (attributes)));
}
RangedAudioParameter* AudioProcessorValueTreeState::createAndAddParameter (std::unique_ptr<RangedAudioParameter> param)
{
if (param == nullptr)
return nullptr;
// All parameters must be created before giving this manager a ValueTree state!
jassert (! state.isValid());
if (getParameter (param->paramID) != nullptr)
return nullptr;
addParameterAdapter (*param);
processor.addParameter (param.get());
return param.release();
}
//==============================================================================
void AudioProcessorValueTreeState::addParameterAdapter (RangedAudioParameter& param)
{
adapterTable.emplace (param.paramID, std::make_unique<ParameterAdapter> (param));
}
AudioProcessorValueTreeState::ParameterAdapter* AudioProcessorValueTreeState::getParameterAdapter (StringRef paramID) const
{
auto it = adapterTable.find (paramID);
return it == adapterTable.end() ? nullptr : it->second.get();
}
void AudioProcessorValueTreeState::addParameterListener (StringRef paramID, Listener* listener)
{
if (auto* p = getParameterAdapter (paramID))
p->addListener (listener);
}
void AudioProcessorValueTreeState::removeParameterListener (StringRef paramID, Listener* listener)
{
if (auto* p = getParameterAdapter (paramID))
p->removeListener (listener);
}
Value AudioProcessorValueTreeState::getParameterAsValue (StringRef paramID) const
{
if (auto* adapter = getParameterAdapter (paramID))
if (adapter->tree.isValid())
return adapter->tree.getPropertyAsValue (valuePropertyID, undoManager);
return {};
}
NormalisableRange<float> AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept
{
if (auto* p = getParameterAdapter (paramID))
return p->getRange();
return {};
}
RangedAudioParameter* AudioProcessorValueTreeState::getParameter (StringRef paramID) const noexcept
{
if (auto adapter = getParameterAdapter (paramID))
return &adapter->getParameter();
return nullptr;
}
std::atomic<float>* AudioProcessorValueTreeState::getRawParameterValue (StringRef paramID) const noexcept
{
if (auto* p = getParameterAdapter (paramID))
return &p->getRawDenormalisedValue();
return nullptr;
}
ValueTree AudioProcessorValueTreeState::copyState()
{
ScopedLock lock (valueTreeChanging);
flushParameterValuesToValueTree();
return state.createCopy();
}
void AudioProcessorValueTreeState::replaceState (const ValueTree& newState)
{
ScopedLock lock (valueTreeChanging);
state = newState;
if (undoManager != nullptr)
undoManager->clearUndoHistory();
}
void AudioProcessorValueTreeState::setNewState (ValueTree vt)
{
jassert (vt.getParent() == state);
if (auto* p = getParameterAdapter (vt.getProperty (idPropertyID).toString()))
{
p->tree = vt;
p->setDenormalisedValue (p->tree.getProperty (valuePropertyID, p->getDenormalisedDefaultValue()));
}
}
void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees()
{
ScopedLock lock (valueTreeChanging);
for (auto& p : adapterTable)
p.second->tree = ValueTree();
for (const auto& child : state)
setNewState (child);
for (auto& p : adapterTable)
{
auto& adapter = *p.second;
if (! adapter.tree.isValid())
{
adapter.tree = ValueTree (valueType);
adapter.tree.setProperty (idPropertyID, adapter.getParameter().paramID, nullptr);
state.appendChild (adapter.tree, nullptr);
}
}
flushParameterValuesToValueTree();
}
void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier&)
{
if (tree.hasType (valueType) && tree.getParent() == state)
setNewState (tree);
}
void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree)
{
if (parent == state && tree.hasType (valueType))
setNewState (tree);
}
void AudioProcessorValueTreeState::valueTreeRedirected (ValueTree& v)
{
if (v == state)
updateParameterConnectionsToChildTrees();
}
bool AudioProcessorValueTreeState::flushParameterValuesToValueTree()
{
ScopedLock lock (valueTreeChanging);
bool anyUpdated = false;
for (auto& p : adapterTable)
anyUpdated |= p.second->flushToTree (valuePropertyID, undoManager);
return anyUpdated;
}
void AudioProcessorValueTreeState::timerCallback()
{
auto anythingUpdated = flushParameterValuesToValueTree();
startTimer (anythingUpdated ? 1000 / 50
: jlimit (50, 500, getTimerInterval() + 20));
}
//==============================================================================
template <typename Attachment, typename Control>
std::unique_ptr<Attachment> makeAttachment (const AudioProcessorValueTreeState& stateToUse,
const String& parameterID,
Control& control)
{
if (auto* parameter = stateToUse.getParameter (parameterID))
return std::make_unique<Attachment> (*parameter, control, stateToUse.undoManager);
jassertfalse;
return nullptr;
}
AudioProcessorValueTreeState::SliderAttachment::SliderAttachment (AudioProcessorValueTreeState& stateToUse,
const String& parameterID,
Slider& slider)
: attachment (makeAttachment<SliderParameterAttachment> (stateToUse, parameterID, slider))
{
}
AudioProcessorValueTreeState::ComboBoxAttachment::ComboBoxAttachment (AudioProcessorValueTreeState& stateToUse,
const String& parameterID,
ComboBox& combo)
: attachment (makeAttachment<ComboBoxParameterAttachment> (stateToUse, parameterID, combo))
{
}
AudioProcessorValueTreeState::ButtonAttachment::ButtonAttachment (AudioProcessorValueTreeState& stateToUse,
const String& parameterID,
Button& button)
: attachment (makeAttachment<ButtonParameterAttachment> (stateToUse, parameterID, button))
{
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct ParameterAdapterTests final : public UnitTest
{
ParameterAdapterTests()
: UnitTest ("Parameter Adapter", UnitTestCategories::audioProcessorParameters)
{}
void runTest() override
{
beginTest ("The default value is returned correctly");
{
const auto test = [&] (NormalisableRange<float> range, float value)
{
AudioParameterFloat param ({}, {}, range, value);
AudioProcessorValueTreeState::ParameterAdapter adapter (param);
expectEquals (adapter.getDenormalisedDefaultValue(), value);
};
test ({ -100, 100 }, 0);
test ({ -2.5, 12.5 }, 10);
}
beginTest ("Denormalised parameter values can be retrieved");
{
const auto test = [&] (NormalisableRange<float> range, float value)
{
AudioParameterFloat param ({}, {}, range, {});
AudioProcessorValueTreeState::ParameterAdapter adapter (param);
adapter.setDenormalisedValue (value);
expectEquals (adapter.getDenormalisedValue(), value);
expectEquals (adapter.getRawDenormalisedValue().load(), value);
};
test ({ -20, -10 }, -15);
test ({ 0, 7.5 }, 2.5);
}
beginTest ("Floats can be converted to text");
{
const auto test = [&] (NormalisableRange<float> range, float value, String expected)
{
AudioParameterFloat param ({}, {}, range, {});
AudioProcessorValueTreeState::ParameterAdapter adapter (param);
expectEquals (adapter.getTextForDenormalisedValue (value), expected);
};
test ({ -100, 100 }, 0, "0.0000000");
test ({ -2.5, 12.5 }, 10, "10.0000000");
test ({ -20, -10 }, -15, "-15.0000000");
test ({ 0, 7.5 }, 2.5, "2.5000000");
}
beginTest ("Text can be converted to floats");
{
const auto test = [&] (NormalisableRange<float> range, String text, float expected)
{
AudioParameterFloat param ({}, {}, range, {});
AudioProcessorValueTreeState::ParameterAdapter adapter (param);
expectEquals (adapter.getDenormalisedValueForText (text), expected);
};
test ({ -100, 100 }, "0.0", 0);
test ({ -2.5, 12.5 }, "10.0", 10);
test ({ -20, -10 }, "-15.0", -15);
test ({ 0, 7.5 }, "2.5", 2.5);
}
}
};
static ParameterAdapterTests parameterAdapterTests;
namespace
{
template <typename ValueType>
inline bool operator== (const NormalisableRange<ValueType>& a,
const NormalisableRange<ValueType>& b)
{
return std::tie (a.start, a.end, a.interval, a.skew, a.symmetricSkew)
== std::tie (b.start, b.end, b.interval, b.skew, b.symmetricSkew);
}
template <typename ValueType>
inline bool operator!= (const NormalisableRange<ValueType>& a,
const NormalisableRange<ValueType>& b)
{
return ! (a == b);
}
} // namespace
class AudioProcessorValueTreeStateTests final : public UnitTest
{
private:
using Parameter = AudioProcessorValueTreeState::Parameter;
using ParameterGroup = AudioProcessorParameterGroup;
using ParameterLayout = AudioProcessorValueTreeState::ParameterLayout;
using Attributes = AudioProcessorValueTreeStateParameterAttributes;
class TestAudioProcessor final : public AudioProcessor
{
public:
TestAudioProcessor() = default;
explicit TestAudioProcessor (ParameterLayout layout)
: state (*this, nullptr, "state", std::move (layout)) {}
const String getName() const override { return {}; }
void prepareToPlay (double, int) override {}
void releaseResources() override {}
void processBlock (AudioBuffer<float>&, MidiBuffer&) override {}
using AudioProcessor::processBlock;
double getTailLengthSeconds() const override { return {}; }
bool acceptsMidi() const override { return {}; }
bool producesMidi() const override { return {}; }
AudioProcessorEditor* createEditor() override { return {}; }
bool hasEditor() const override { return {}; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return {}; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return {}; }
void changeProgramName (int, const String&) override {}
void getStateInformation (MemoryBlock&) override {}
void setStateInformation (const void*, int) override {}
AudioProcessorValueTreeState state { *this, nullptr };
};
struct Listener final : public AudioProcessorValueTreeState::Listener
{
void parameterChanged (const String& idIn, float valueIn) override
{
id = idIn;
value = valueIn;
}
String id;
float value{};
};
public:
AudioProcessorValueTreeStateTests()
: UnitTest ("Audio Processor Value Tree State", UnitTestCategories::audioProcessorParameters)
{}
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6262)
void runTest() override
{
ScopedJuceInitialiser_GUI scopedJuceInitialiser_gui;
beginTest ("After calling createAndAddParameter, the number of parameters increases by one");
{
TestAudioProcessor proc;
proc.state.createAndAddParameter (std::make_unique<Parameter> (
String(),
String(),
NormalisableRange<float>(),
0.0f));
expectEquals (proc.getParameters().size(), 1);
}
beginTest ("After creating a normal named parameter, we can later retrieve that parameter");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f));
expect (proc.state.getParameter (key) == param);
}
beginTest ("After construction, the value tree has the expected format");
{
TestAudioProcessor proc ({
std::make_unique<AudioProcessorParameterGroup> ("A", "", "",
std::make_unique<AudioParameterBool> ("a", "", false),
std::make_unique<AudioParameterFloat> ("b", "", NormalisableRange<float>{}, 0.0f)),
std::make_unique<AudioProcessorParameterGroup> ("B", "", "",
std::make_unique<AudioParameterInt> ("c", "", 0, 1, 0),
std::make_unique<AudioParameterChoice> ("d", "", StringArray { "foo", "bar" }, 0)) });
const auto valueTree = proc.state.copyState();
expectEquals (valueTree.getNumChildren(), 4);
for (auto child : valueTree)
{
expect (child.hasType ("PARAM"));
expect (child.hasProperty ("id"));
expect (child.hasProperty ("value"));
}
}
beginTest ("Meta parameters can be created");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f,
Attributes().withMeta (true)));
expect (param->isMetaParameter());
}
beginTest ("Automatable parameters can be created");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f,
Attributes().withAutomatable (true)));
expect (param->isAutomatable());
}
beginTest ("Discrete parameters can be created");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f,
Attributes().withDiscrete (true)));
expect (param->isDiscrete());
}
beginTest ("Custom category parameters can be created");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f,
Attributes().withCategory (AudioProcessorParameter::Category::inputMeter)));
expect (param->category == AudioProcessorParameter::Category::inputMeter);
}
beginTest ("Boolean parameters can be created");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f,
Attributes().withBoolean (true)));
expect (param->isBoolean());
}
beginTest ("After creating a custom named parameter, we can later retrieve that parameter");
{
const auto key = "id";
auto param = std::make_unique<AudioParameterBool> (key, "", false);
const auto paramPtr = param.get();
TestAudioProcessor proc (std::move (param));
expect (proc.state.getParameter (key) == paramPtr);
}
beginTest ("After adding a normal parameter that already exists, the AudioProcessor parameters are unchanged");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f));
proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f));
expectEquals (proc.getParameters().size(), 1);
expect (proc.getParameters().getFirst() == param);
}
beginTest ("After setting a parameter value, that value is reflected in the state");
{
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f));
const auto value = 0.5f;
param->setValueNotifyingHost (value);
expectEquals (proc.state.getRawParameterValue (key)->load(), value);
}
beginTest ("After adding an APVTS::Parameter, its value is the default value");
{
TestAudioProcessor proc;
const auto key = "id";
const auto value = 5.0f;
proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float> (0.0f, 100.0f, 10.0f),
value));
expectEquals (proc.state.getRawParameterValue (key)->load(), value);
}
beginTest ("Listeners receive notifications when parameters change");
{
Listener listener;
TestAudioProcessor proc;
const auto key = "id";
const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f));
proc.state.addParameterListener (key, &listener);
const auto value = 0.5f;
param->setValueNotifyingHost (value);
expectEquals (listener.id, String { key });
expectEquals (listener.value, value);
}
beginTest ("Bool parameters have a range of 0-1");
{
const auto key = "id";
TestAudioProcessor proc (std::make_unique<AudioParameterBool> (key, "", false));
expect (proc.state.getParameterRange (key) == NormalisableRange<float> (0.0f, 1.0f, 1.0f));
}
beginTest ("Float parameters retain their specified range");
{
const auto key = "id";
const auto range = NormalisableRange<float> { -100, 100, 0.7f, 0.2f, true };
TestAudioProcessor proc (std::make_unique<AudioParameterFloat> (key, "", range, 0.0f));
expect (proc.state.getParameterRange (key) == range);
}
beginTest ("Int parameters retain their specified range");
{
const auto key = "id";
const auto min = -27;
const auto max = 53;
TestAudioProcessor proc (std::make_unique<AudioParameterInt> (key, "", min, max, 0));
expect (proc.state.getParameterRange (key) == NormalisableRange<float> (float (min), float (max), 1.0f));
}
beginTest ("Choice parameters retain their specified range");
{
const auto key = "id";
const auto choices = StringArray { "", "", "" };
TestAudioProcessor proc (std::make_unique<AudioParameterChoice> (key, "", choices, 0));
expect (proc.state.getParameterRange (key) == NormalisableRange<float> (0.0f, (float) (choices.size() - 1), 1.0f));
expect (proc.state.getParameter (key)->getNumSteps() == choices.size());
}
beginTest ("When the parameter value is changed, normal parameter values are updated");
{
TestAudioProcessor proc;
const auto key = "id";
const auto initialValue = 0.2f;
auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
initialValue));
proc.state.state = ValueTree { "state" };
auto value = proc.state.getParameterAsValue (key);
expectEquals (float (value.getValue()), initialValue);
const auto newValue = 0.75f;
value = newValue;
expectEquals (param->getValue(), newValue);
expectEquals (proc.state.getRawParameterValue (key)->load(), newValue);
}
beginTest ("When the parameter value is changed, custom parameter values are updated");
{
const auto key = "id";
const auto choices = StringArray ("foo", "bar", "baz");
auto param = std::make_unique<AudioParameterChoice> (key, "", choices, 0);
const auto paramPtr = param.get();
TestAudioProcessor proc (std::move (param));
const auto newValue = 2.0f;
auto value = proc.state.getParameterAsValue (key);
value = newValue;
expectEquals (paramPtr->getCurrentChoiceName(), choices[int (newValue)]);
expectEquals (proc.state.getRawParameterValue (key)->load(), newValue);
}
beginTest ("When the parameter value is changed, listeners are notified");
{
Listener listener;
TestAudioProcessor proc;
const auto key = "id";
proc.state.createAndAddParameter (std::make_unique<Parameter> (
key,
String(),
NormalisableRange<float>(),
0.0f));
proc.state.addParameterListener (key, &listener);
proc.state.state = ValueTree { "state" };
const auto newValue = 0.75f;
proc.state.getParameterAsValue (key) = newValue;
expectEquals (listener.value, newValue);
expectEquals (listener.id, String { key });
}
beginTest ("When the parameter value is changed, listeners are notified");
{
const auto key = "id";
const auto choices = StringArray { "foo", "bar", "baz" };
Listener listener;
TestAudioProcessor proc (std::make_unique<AudioParameterChoice> (key, "", choices, 0));
proc.state.addParameterListener (key, &listener);
const auto newValue = 2.0f;
proc.state.getParameterAsValue (key) = newValue;
expectEquals (listener.value, newValue);
expectEquals (listener.id, String (key));
}
}
JUCE_END_IGNORE_WARNINGS_MSVC
};
static AudioProcessorValueTreeStateTests audioProcessorValueTreeStateTests;
#endif
} // namespace juce
@@ -0,0 +1,660 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/** Advanced properties of an AudioProcessorValueTreeState::Parameter.
The members here have the same meaning as the similarly-named member functions of
AudioParameterFloatAttributes.
@see AudioParameterFloatAttributes, RangedAudioParameterAttributes
@tags{Audio}
*/
class AudioProcessorValueTreeStateParameterAttributes
{
using This = AudioProcessorValueTreeStateParameterAttributes;
using StringFromValue = AudioParameterFloatAttributes::StringFromValue;
using ValueFromString = AudioParameterFloatAttributes::ValueFromString;
using Category = AudioParameterFloatAttributes::Category;
public:
/** @see RangedAudioParameterAttributes::withStringFromValueFunction() */
[[nodiscard]] auto withStringFromValueFunction (StringFromValue x) const { return withMember (*this, &This::attributes, attributes.withStringFromValueFunction (std::move (x))); }
/** @see RangedAudioParameterAttributes::withValueFromStringFunction() */
[[nodiscard]] auto withValueFromStringFunction (ValueFromString x) const { return withMember (*this, &This::attributes, attributes.withValueFromStringFunction (std::move (x))); }
/** @see RangedAudioParameterAttributes::withLabel() */
[[nodiscard]] auto withLabel (String x) const { return withMember (*this, &This::attributes, attributes.withLabel (std::move (x))); }
/** @see RangedAudioParameterAttributes::withCategory() */
[[nodiscard]] auto withCategory (Category x) const { return withMember (*this, &This::attributes, attributes.withCategory (std::move (x))); }
/** @see RangedAudioParameterAttributes::withMeta() */
[[nodiscard]] auto withMeta (bool x) const { return withMember (*this, &This::attributes, attributes.withMeta (std::move (x))); }
/** @see RangedAudioParameterAttributes::withAutomatable() */
[[nodiscard]] auto withAutomatable (bool x) const { return withMember (*this, &This::attributes, attributes.withAutomatable (std::move (x))); }
/** @see RangedAudioParameterAttributes::withInverted() */
[[nodiscard]] auto withInverted (bool x) const { return withMember (*this, &This::attributes, attributes.withInverted (std::move (x))); }
/** Pass 'true' if this parameter has discrete steps, or 'false' if the parameter is continuous.
Using an AudioParameterChoice or AudioParameterInt might be a better choice than setting this flag.
*/
[[nodiscard]] auto withDiscrete (bool x) const { return withMember (*this, &This::discrete, std::move (x)); }
/** Pass 'true' if this parameter only has two valid states.
Using an AudioParameterBool might be a better choice than setting this flag.
*/
[[nodiscard]] auto withBoolean (bool x) const { return withMember (*this, &This::boolean, std::move (x)); }
/** @returns all attributes that might also apply to an AudioParameterFloat */
[[nodiscard]] const auto& getAudioParameterFloatAttributes() const { return attributes; }
/** @returns 'true' if this parameter has discrete steps, or 'false' if the parameter is continuous. */
[[nodiscard]] const auto& getDiscrete() const { return discrete; }
/** @returns 'true' if this parameter only has two valid states. */
[[nodiscard]] const auto& getBoolean() const { return boolean; }
private:
AudioParameterFloatAttributes attributes;
bool discrete = false, boolean = false;
};
/**
This class contains a ValueTree that is used to manage an AudioProcessor's entire state.
It has its own internal class of parameter object that is linked to values
within its ValueTree, and which are each identified by a string ID.
You can get access to the underlying ValueTree object via the state member variable,
so you can add extra properties to it as necessary.
It also provides some utility child classes for connecting parameters directly to
GUI controls like sliders.
The favoured constructor of this class takes a collection of RangedAudioParameters or
AudioProcessorParameterGroups of RangedAudioParameters and adds them to the attached
AudioProcessor directly.
The deprecated way of using this class is as follows:
1) Create an AudioProcessorValueTreeState, and give it some parameters using createAndAddParameter().
2) Initialise the state member variable with a type name.
The deprecated constructor will be removed from the API in a future version of JUCE!
@tags{Audio}
*/
class JUCE_API AudioProcessorValueTreeState : private Timer,
private ValueTree::Listener
{
public:
//==============================================================================
/** A class to contain a set of RangedAudioParameters and AudioProcessorParameterGroups
containing RangedAudioParameters.
This class is used in the AudioProcessorValueTreeState constructor to allow
arbitrarily grouped RangedAudioParameters to be passed to an AudioProcessor.
*/
class JUCE_API ParameterLayout final
{
private:
//==============================================================================
template <typename It>
using ValidIfIterator = decltype (std::next (std::declval<It>()));
public:
//==============================================================================
template <typename... Items>
ParameterLayout (std::unique_ptr<Items>... items) { add (std::move (items)...); }
template <typename It, typename = ValidIfIterator<It>>
ParameterLayout (It begin, It end) { add (begin, end); }
template <typename... Items>
void add (std::unique_ptr<Items>... items)
{
parameters.reserve (parameters.size() + sizeof... (items));
(parameters.push_back (makeParameterStorage (std::move (items))), ...);
}
template <typename It, typename = ValidIfIterator<It>>
void add (It begin, It end)
{
parameters.reserve (parameters.size() + std::size_t (std::distance (begin, end)));
std::transform (std::make_move_iterator (begin),
std::make_move_iterator (end),
std::back_inserter (parameters),
[] (auto item) { return makeParameterStorage (std::move (item)); });
}
ParameterLayout (const ParameterLayout& other) = delete;
ParameterLayout (ParameterLayout&& other) noexcept { swap (other); }
ParameterLayout& operator= (const ParameterLayout& other) = delete;
ParameterLayout& operator= (ParameterLayout&& other) noexcept { swap (other); return *this; }
void swap (ParameterLayout& other) noexcept { std::swap (other.parameters, parameters); }
private:
//==============================================================================
struct Visitor
{
virtual ~Visitor() = default;
// If you have a compiler error telling you that there is no matching
// member function to call for 'visit', then you are probably attempting
// to add a parameter that is not derived from RangedAudioParameter to
// the AudioProcessorValueTreeState.
virtual void visit (std::unique_ptr<RangedAudioParameter>) const = 0;
virtual void visit (std::unique_ptr<AudioProcessorParameterGroup>) const = 0;
};
struct ParameterStorageBase
{
virtual ~ParameterStorageBase() = default;
virtual void accept (const Visitor& visitor) = 0;
};
template <typename Contents>
struct ParameterStorage : ParameterStorageBase
{
explicit ParameterStorage (std::unique_ptr<Contents> input) : contents (std::move (input)) {}
void accept (const Visitor& visitor) override { visitor.visit (std::move (contents)); }
std::unique_ptr<Contents> contents;
};
template <typename Contents>
static std::unique_ptr<ParameterStorage<Contents>> makeParameterStorage (std::unique_ptr<Contents> contents)
{
return std::make_unique<ParameterStorage<Contents>> (std::move (contents));
}
void add() {}
friend class AudioProcessorValueTreeState;
std::vector<std::unique_ptr<ParameterStorageBase>> parameters;
};
//==============================================================================
/** Creates a state object for a given processor, and sets up all the parameters
that will control that processor.
You should *not* assign a new ValueTree to the state, or call
createAndAddParameter, after using this constructor.
Note that each AudioProcessorValueTreeState should be attached to only one
processor, and must have the same lifetime as the processor, as they will
have dependencies on each other.
The ParameterLayout parameter has a set of constructors that allow you to
add multiple RangedAudioParameters and AudioProcessorParameterGroups containing
RangedAudioParameters to the AudioProcessorValueTreeState inside this constructor.
@code
YourAudioProcessor()
: apvts (*this, &undoManager, "PARAMETERS",
{ std::make_unique<AudioParameterFloat> ("a", "Parameter A", NormalisableRange<float> (-100.0f, 100.0f), 0),
std::make_unique<AudioParameterInt> ("b", "Parameter B", 0, 5, 2) })
@endcode
To add parameters programmatically you can call `add` repeatedly on a
ParameterLayout instance:
@code
AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
{
AudioProcessorValueTreeState::ParameterLayout layout;
for (int i = 1; i < 9; ++i)
layout.add (std::make_unique<AudioParameterInt> (String (i), String (i), 0, i, 0));
return layout;
}
YourAudioProcessor()
: apvts (*this, &undoManager, "PARAMETERS", createParameterLayout())
{
}
@endcode
@param processorToConnectTo The Processor that will be managed by this object
@param undoManagerToUse An optional UndoManager to use; pass nullptr if no UndoManager is required
@param valueTreeType The identifier used to initialise the internal ValueTree
@param parameterLayout An object that holds all parameters and parameter groups that the
AudioProcessor should use.
*/
AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo,
UndoManager* undoManagerToUse,
const Identifier& valueTreeType,
ParameterLayout parameterLayout);
/** This constructor is discouraged and will be deprecated in a future version of JUCE!
Use the other constructor instead.
Creates a state object for a given processor.
The UndoManager is optional and can be a nullptr. After creating your state object,
you should add parameters with the createAndAddParameter() method. Note that each
AudioProcessorValueTreeState should be attached to only one processor, and must have
the same lifetime as the processor, as they will have dependencies on each other.
*/
AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo, UndoManager* undoManagerToUse);
/** Destructor. */
~AudioProcessorValueTreeState() override;
//==============================================================================
#ifndef DOXYGEN
/** Previous calls to
@code
createAndAddParameter (paramID1, paramName1, ...);
@endcode
can be replaced with
@code
using Parameter = AudioProcessorValueTreeState::Parameter;
createAndAddParameter (std::make_unique<Parameter> (paramID1, paramName1, ...));
@endcode
However, a much better approach is to use the AudioProcessorValueTreeState
constructor directly
@code
using Parameter = AudioProcessorValueTreeState::Parameter;
YourAudioProcessor()
: apvts (*this, &undoManager, "PARAMETERS", { std::make_unique<Parameter> (paramID1, paramName1, ...),
std::make_unique<Parameter> (paramID2, paramName2, ...),
... })
@endcode
@see AudioProcessorValueTreeState::AudioProcessorValueTreeState
This function creates and returns a new parameter object for controlling a
parameter with the given ID.
Calling this will create and add a special type of AudioProcessorParameter to the
AudioProcessor to which this state is attached.
*/
[[deprecated ("This function is deprecated and will be removed in a future version of JUCE! "
"See the method docs for a code example of the replacement methods.")]]
RangedAudioParameter* createAndAddParameter (const String& parameterID,
const String& parameterName,
const String& labelText,
NormalisableRange<float> valueRange,
float defaultValue,
std::function<String (float)> valueToTextFunction,
std::function<float (const String&)> textToValueFunction,
bool isMetaParameter = false,
bool isAutomatableParameter = true,
bool isDiscrete = false,
AudioProcessorParameter::Category parameterCategory = AudioProcessorParameter::genericParameter,
bool isBoolean = false);
#endif
/** This function adds a parameter to the attached AudioProcessor and that parameter will
be managed by this AudioProcessorValueTreeState object.
*/
RangedAudioParameter* createAndAddParameter (std::unique_ptr<RangedAudioParameter> parameter);
//==============================================================================
/** Returns a parameter by its ID string. */
RangedAudioParameter* getParameter (StringRef parameterID) const noexcept;
/** Returns a pointer to a floating point representation of a particular parameter which a realtime
process can read to find out its current value.
Note that calling this method from within AudioProcessorValueTreeState::Listener::parameterChanged()
is not guaranteed to return an up-to-date value for the parameter.
*/
std::atomic<float>* getRawParameterValue (StringRef parameterID) const noexcept;
//==============================================================================
/** A listener class that can be attached to an AudioProcessorValueTreeState.
Use AudioProcessorValueTreeState::addParameterListener() to register a callback.
*/
struct JUCE_API Listener
{
virtual ~Listener() = default;
/** This callback method is called by the AudioProcessorValueTreeState when a parameter changes.
Within this call, retrieving the value of the parameter that has changed via the getRawParameterValue()
or getParameter() methods is not guaranteed to return the up-to-date value. If you need this you should
instead use the newValue parameter.
*/
virtual void parameterChanged (const String& parameterID, float newValue) = 0;
};
/** Attaches a callback to one of the parameters, which will be called when the parameter changes. */
void addParameterListener (StringRef parameterID, Listener* listener);
/** Removes a callback that was previously added with addParameterCallback(). */
void removeParameterListener (StringRef parameterID, Listener* listener);
//==============================================================================
/** Returns a Value object that can be used to control a particular parameter. */
Value getParameterAsValue (StringRef parameterID) const;
/** Returns the range that was set when the given parameter was created. */
NormalisableRange<float> getParameterRange (StringRef parameterID) const noexcept;
//==============================================================================
/** Returns a copy of the state value tree.
The AudioProcessorValueTreeState's ValueTree is updated internally on the
message thread, but there may be cases when you may want to access the state
from a different thread (getStateInformation is a good example). This method
flushes all pending audio parameter value updates and returns a copy of the
state in a thread safe way.
Note: This method uses locks to synchronise thread access, so whilst it is
thread-safe, it is not realtime-safe. Do not call this method from within
your audio processing code!
*/
ValueTree copyState();
/** Replaces the state value tree.
The AudioProcessorValueTreeState's ValueTree is updated internally on the
message thread, but there may be cases when you may want to modify the state
from a different thread (setStateInformation is a good example). This method
allows you to replace the state in a thread safe way.
Note: This method uses locks to synchronise thread access, so whilst it is
thread-safe, it is not realtime-safe. Do not call this method from within
your audio processing code!
*/
void replaceState (const ValueTree& newState);
//==============================================================================
/** A reference to the processor with which this state is associated. */
AudioProcessor& processor;
/** The state of the whole processor.
This must be initialised after all calls to createAndAddParameter().
You can replace this with your own ValueTree object, and can add properties and
children to the tree. This class will automatically add children for each of the
parameter objects that are created by createAndAddParameter().
*/
ValueTree state;
/** Provides access to the undo manager that this object is using. */
UndoManager* const undoManager;
private:
//==============================================================================
class ParameterAdapter;
public:
//==============================================================================
/** A parameter class that maintains backwards compatibility with deprecated
AudioProcessorValueTreeState functionality.
Previous calls to
@code
createAndAddParameter (paramID1, paramName1, ...);
@endcode
can be replaced with
@code
using Parameter = AudioProcessorValueTreeState::Parameter;
createAndAddParameter (std::make_unique<Parameter> (paramID1, paramName1, ...));
@endcode
However, a much better approach is to use the AudioProcessorValueTreeState
constructor directly
@code
using Parameter = AudioProcessorValueTreeState::Parameter;
YourAudioProcessor()
: apvts (*this, &undoManager, "PARAMETERS", { std::make_unique<Parameter> (paramID1, paramName1, ...),
std::make_unique<Parameter> (paramID2, paramName2, ...),
... })
@endcode
*/
class Parameter final : public AudioParameterFloat
{
public:
/** Constructs a parameter instance.
Example usage:
@code
using Parameter = AudioProcessorValueTreeState::Parameter;
using Attributes = AudioProcessorValueTreeStateParameterAttributes;
auto parameter = std::make_unique<Parameter> (ParameterID { "uniqueID", 1 },
"Name",
NormalisableRange<float> { 0.0f, 100.0f },
50.0f,
Attributes().withStringFromValueFunction (myStringFromValueFunction)
.withValueFromStringFunction (myValueFromStringFunction)
.withLabel ("%"));
@endcode
@param parameterID The globally-unique identifier of this parameter
@param parameterName The user-facing name of this parameter
@param valueRange The valid range of values for this parameter
@param defaultValue The initial parameter value
@param attributes Further advanced settings to customise the behaviour of this parameter
*/
Parameter (const ParameterID& parameterID,
const String& parameterName,
NormalisableRange<float> valueRange,
float defaultValue,
const AudioProcessorValueTreeStateParameterAttributes& attributes = {});
[[deprecated ("Prefer the signature taking an Attributes argument")]]
Parameter (const ParameterID& parameterID,
const String& parameterName,
const String& labelText,
NormalisableRange<float> valueRange,
float defaultParameterValue,
std::function<String (float)> valueToTextFunction,
std::function<float (const String&)> textToValueFunction,
bool isMetaParameter = false,
bool isAutomatableParameter = true,
bool isDiscrete = false,
AudioProcessorParameter::Category parameterCategory = AudioProcessorParameter::genericParameter,
bool isBoolean = false)
: Parameter (parameterID,
parameterName,
valueRange,
defaultParameterValue,
AudioProcessorValueTreeStateParameterAttributes().withLabel (labelText)
.withStringFromValueFunction (adaptSignature (std::move (valueToTextFunction)))
.withValueFromStringFunction (std::move (textToValueFunction))
.withMeta (isMetaParameter)
.withAutomatable (isAutomatableParameter)
.withDiscrete (isDiscrete)
.withCategory (parameterCategory)
.withBoolean (isBoolean))
{
}
float getDefaultValue() const override;
int getNumSteps() const override;
bool isDiscrete() const override;
bool isBoolean() const override;
private:
static std::function<String (float, int)> adaptSignature (std::function<String (float)> func)
{
if (func == nullptr)
return nullptr;
return [f = std::move (func)] (float v, int) { return f (v); };
}
void valueChanged (float) override;
std::function<void()> onValueChanged;
const float unsnappedDefault;
const bool discrete, boolean;
std::atomic<float> lastValue { -1.0f };
friend class AudioProcessorValueTreeState::ParameterAdapter;
};
//==============================================================================
/** An object of this class maintains a connection between a Slider and a parameter
in an AudioProcessorValueTreeState.
During the lifetime of this SliderAttachment object, it keeps the two things in
sync, making it easy to connect a slider to a parameter. When this object is
deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState
and Slider aren't deleted before this object!
*/
class JUCE_API SliderAttachment
{
public:
SliderAttachment (AudioProcessorValueTreeState& stateToUse,
const String& parameterID,
Slider& slider);
private:
std::unique_ptr<SliderParameterAttachment> attachment;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAttachment)
};
//==============================================================================
/** An object of this class maintains a connection between a ComboBox and a parameter
in an AudioProcessorValueTreeState.
Combobox items will be spaced linearly across the range of the parameter. For
example if the range is specified by NormalisableRange<float> (-0.5f, 0.5f, 0.5f)
and you add three items then the first will be mapped to a value of -0.5, the
second to 0, and the third to 0.5.
During the lifetime of this ComboBoxAttachment object, it keeps the two things in
sync, making it easy to connect a combo box to a parameter. When this object is
deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState
and ComboBox aren't deleted before this object!
*/
class JUCE_API ComboBoxAttachment
{
public:
ComboBoxAttachment (AudioProcessorValueTreeState& stateToUse,
const String& parameterID,
ComboBox& combo);
private:
std::unique_ptr<ComboBoxParameterAttachment> attachment;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAttachment)
};
//==============================================================================
/** An object of this class maintains a connection between a Button and a parameter
in an AudioProcessorValueTreeState.
During the lifetime of this ButtonAttachment object, it keeps the two things in
sync, making it easy to connect a button to a parameter. When this object is
deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState
and Button aren't deleted before this object!
*/
class JUCE_API ButtonAttachment
{
public:
ButtonAttachment (AudioProcessorValueTreeState& stateToUse,
const String& parameterID,
Button& button);
private:
std::unique_ptr<ButtonParameterAttachment> attachment;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAttachment)
};
private:
//==============================================================================
/** Code that looks like this:
@code
auto paramA = apvts.createParameter ("a", "Parameter A", {}, { -100, 100 }, ...);
auto paramB = apvts.createParameter ("b", "Parameter B", {}, { 0, 5 }, ...);
addParameterGroup (std::make_unique<AudioProcessorParameterGroup> ("g1", "Group 1", " | ", std::move (paramA), std::move (paramB)));
apvts.state = ValueTree (Identifier ("PARAMETERS"));
@endcode
can instead create the APVTS like this, avoiding the two-step initialization process and leveraging one of JUCE's
pre-built parameter types (or your own custom type derived from RangedAudioParameter):
@code
using Parameter = AudioProcessorValueTreeState::Parameter;
YourAudioProcessor()
: apvts (*this, &undoManager, "PARAMETERS",
{ std::make_unique<AudioProcessorParameterGroup> ("g1", "Group 1", " | ",
std::make_unique<Parameter> ("a", "Parameter A", "", NormalisableRange<float> (-100, 100), ...),
std::make_unique<Parameter> ("b", "Parameter B", "", NormalisableRange<float> (0, 5), ...)) })
@endcode
*/
[[deprecated ("This method was introduced to allow you to use AudioProcessorValueTreeState parameters in "
"an AudioProcessorParameterGroup, but there is now a much nicer way to achieve this. See the "
"method docs for a code example.")]]
std::unique_ptr<RangedAudioParameter> createParameter (const String&, const String&, const String&, NormalisableRange<float>,
float, std::function<String (float)>, std::function<float (const String&)>,
bool, bool, bool, AudioProcessorParameter::Category, bool);
//==============================================================================
#if JUCE_UNIT_TESTS
friend struct ParameterAdapterTests;
#endif
void addParameterAdapter (RangedAudioParameter&);
ParameterAdapter* getParameterAdapter (StringRef) const;
bool flushParameterValuesToValueTree();
void setNewState (ValueTree);
void timerCallback() override;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
void valueTreeRedirected (ValueTree&) override;
void updateParameterConnectionsToChildTrees();
const Identifier valueType { "PARAM" }, valuePropertyID { "value" }, idPropertyID { "id" };
struct StringRefLessThan final
{
bool operator() (StringRef a, StringRef b) const noexcept { return a.text.compare (b.text) < 0; }
};
std::map<StringRef, std::unique_ptr<ParameterAdapter>, StringRefLessThan> adapterTable;
CriticalSection valueTreeChanging;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorValueTreeState)
};
} // namespace juce
@@ -0,0 +1,146 @@
/*
==============================================================================
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.
==============================================================================
*/
#ifndef DOXYGEN
// Forward declarations to avoid leaking implementation details.
namespace Steinberg::Vst
{
class IComponent;
} // namespace Steinberg::Vst
#endif
//==============================================================================
#if TARGET_OS_IPHONE
struct OpaqueAudioComponentInstance;
typedef struct OpaqueAudioComponentInstance* AudioComponentInstance;
#else
struct ComponentInstanceRecord;
typedef struct ComponentInstanceRecord* AudioComponentInstance;
#endif
typedef AudioComponentInstance AudioUnit;
//==============================================================================
/* If you are including the VST headers inside a namespace this forward
declaration may cause a collision with the contents of `aeffect.h`.
If that is the case you can avoid the collision by placing a `struct AEffect;`
forward declaration inside the namespace and before the inclusion of the VST
headers, e.g. @code
namespace Vst2
{
struct AEffect;
#include <pluginterfaces/vst2.x/aeffect.h>
#include <pluginterfaces/vst2.x/aeffectx.h>
}
@endcode
*/
struct AEffect;
//==============================================================================
namespace juce
{
//==============================================================================
/** Create a derived implementation of this class and pass it to
AudioPluginInstance::getExtensions() to retrieve format-specific
information about a plugin instance.
Note that the references passed to the visit member functions are only
guaranteed to live for the duration of the function call, so don't
store pointers to these objects! If you need to store and reuse
format-specific information, it is recommended to copy the result
of the function calls that you care about. For example, you should
store the result of VST::getAEffectPtr() rather than storing a pointer
to the VST instance.
@tags{Audio}
*/
struct ExtensionsVisitor
{
/** Indicates that there is no platform specific information available. */
struct Unknown {};
/** Can be used to retrieve information about a VST3 that is wrapped by an AudioProcessor. */
struct VST3Client
{
virtual ~VST3Client() = default;
virtual Steinberg::Vst::IComponent* getIComponentPtr() const noexcept = 0;
virtual MemoryBlock getPreset() const = 0;
virtual bool setPreset (const MemoryBlock&) const = 0;
};
/** Can be used to retrieve information about an AudioUnit that is wrapped by an AudioProcessor. */
struct AudioUnitClient
{
virtual ~AudioUnitClient() = default;
virtual AudioUnit getAudioUnitHandle() const noexcept = 0;
};
/** Can be used to retrieve information about a VST that is wrapped by an AudioProcessor. */
struct VSTClient
{
virtual ~VSTClient() = default;
virtual AEffect* getAEffectPtr() const noexcept = 0;
};
/** Can be used to retrieve information about a plugin that provides ARA extensions. */
struct ARAClient
{
virtual ~ARAClient() = default;
virtual void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)>) const = 0;
};
ExtensionsVisitor() = default;
ExtensionsVisitor (const ExtensionsVisitor&) = default;
ExtensionsVisitor (ExtensionsVisitor&&) = default;
ExtensionsVisitor& operator= (const ExtensionsVisitor&) = default;
ExtensionsVisitor& operator= (ExtensionsVisitor&&) = default;
virtual ~ExtensionsVisitor() = default;
/** Will be called if there is no platform specific information available. */
virtual void visitUnknown (const Unknown&) {}
/** Called with VST3-specific information. */
virtual void visitVST3Client (const VST3Client&) {}
/** Called with VST-specific information. */
virtual void visitVSTClient (const VSTClient&) {}
/** Called with AU-specific information. */
virtual void visitAudioUnitClient (const AudioUnitClient&) {}
/** Called with ARA-specific information. */
virtual void visitARAClient (const ARAClient&) {}
};
} // namespace juce
@@ -0,0 +1,182 @@
/*
==============================================================================
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.
==============================================================================
*/
#if ! DOXYGEN
namespace juce
{
template <size_t requiredFlagBitsPerItem>
class FlagCache
{
using FlagType = uint32_t;
public:
FlagCache() = default;
explicit FlagCache (size_t items)
: flags (divCeil (items, groupsPerWord))
{
std::fill (flags.begin(), flags.end(), 0);
}
void set (size_t index, FlagType bits)
{
const auto flagIndex = index / groupsPerWord;
jassert (flagIndex < flags.size());
const auto groupIndex = index - (flagIndex * groupsPerWord);
flags[flagIndex].fetch_or (moveToGroupPosition (bits, groupIndex), std::memory_order_acq_rel);
}
/* Calls the supplied callback for any entries with non-zero flags, and
sets all flags to zero.
*/
template <typename Callback>
void ifSet (Callback&& callback)
{
for (size_t flagIndex = 0; flagIndex < flags.size(); ++flagIndex)
{
const auto prevFlags = flags[flagIndex].exchange (0, std::memory_order_acq_rel);
for (size_t group = 0; group < groupsPerWord; ++group)
{
const auto masked = moveFromGroupPosition (prevFlags, group);
if (masked != 0)
callback ((flagIndex * groupsPerWord) + group, masked);
}
}
}
void clear()
{
std::fill (flags.begin(), flags.end(), 0);
}
private:
/* Given the flags for a single item, and a group index, shifts the flags
so that they are positioned at the appropriate location for that group
index.
e.g. If the flag type is a uint32_t, and there are 2 flags per item,
then each uint32_t will hold flags for 16 items. The flags for item 0
are the least significant two bits; the flags for item 15 are the most
significant two bits.
*/
static constexpr FlagType moveToGroupPosition (FlagType ungrouped, size_t groupIndex)
{
return (ungrouped & groupMask) << (groupIndex * bitsPerFlagGroup);
}
/* Given a set of grouped flags for multiple items, and a group index,
extracts the flags set for an item at that group index.
e.g. If the flag type is a uint32_t, and there are 2 flags per item,
then each uint32_t will hold flags for 16 items. Asking for groupIndex
0 will return the least significant two bits; asking for groupIndex 15
will return the most significant two bits.
*/
static constexpr FlagType moveFromGroupPosition (FlagType grouped, size_t groupIndex)
{
return (grouped >> (groupIndex * bitsPerFlagGroup)) & groupMask;
}
static constexpr size_t findNextPowerOfTwoImpl (size_t n, size_t shift)
{
return shift == 32 ? n : findNextPowerOfTwoImpl (n | (n >> shift), shift * 2);
}
static constexpr size_t findNextPowerOfTwo (size_t value)
{
return findNextPowerOfTwoImpl (value - 1, 1) + 1;
}
static constexpr size_t divCeil (size_t a, size_t b)
{
return (a / b) + ((a % b) != 0);
}
static constexpr size_t bitsPerFlagGroup = findNextPowerOfTwo (requiredFlagBitsPerItem);
static constexpr size_t groupsPerWord = (8 * sizeof (FlagType)) / bitsPerFlagGroup;
static constexpr FlagType groupMask = ((FlagType) 1 << requiredFlagBitsPerItem) - 1;
std::vector<std::atomic<FlagType>> flags;
};
template <size_t requiredFlagBitsPerItem>
class FlaggedFloatCache
{
public:
FlaggedFloatCache() = default;
explicit FlaggedFloatCache (size_t sizeIn)
: values (sizeIn),
flags (sizeIn)
{
std::fill (values.begin(), values.end(), 0.0f);
}
size_t size() const noexcept { return values.size(); }
float exchangeValue (size_t index, float value)
{
jassert (index < size());
return values[index].exchange (value, std::memory_order_relaxed);
}
void setBits (size_t index, uint32_t bits) { flags.set (index, bits); }
void setValueAndBits (size_t index, float value, uint32_t bits)
{
exchangeValue (index, value);
setBits (index, bits);
}
float get (size_t index) const noexcept
{
jassert (index < size());
return values[index].load (std::memory_order_relaxed);
}
/* Calls the supplied callback for any entries which have been modified
since the last call to this function.
*/
template <typename Callback>
void ifSet (Callback&& callback)
{
flags.ifSet ([this, &callback] (size_t groupIndex, uint32_t bits)
{
callback (groupIndex, values[groupIndex].load (std::memory_order_relaxed), bits);
});
}
private:
std::vector<std::atomic<float>> values;
FlagCache<requiredFlagBitsPerItem> flags;
};
} // namespace juce
#endif
@@ -0,0 +1,268 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
ParameterAttachment::ParameterAttachment (RangedAudioParameter& param,
std::function<void (float)> parameterChangedCallback,
UndoManager* um)
: parameter (param),
undoManager (um),
setValue (std::move (parameterChangedCallback))
{
parameter.addListener (this);
}
ParameterAttachment::~ParameterAttachment()
{
parameter.removeListener (this);
cancelPendingUpdate();
}
void ParameterAttachment::sendInitialUpdate()
{
parameterValueChanged ({}, parameter.getValue());
}
void ParameterAttachment::setValueAsCompleteGesture (float newDenormalisedValue)
{
callIfParameterValueChanged (newDenormalisedValue, [this] (float f)
{
beginGesture();
parameter.setValueNotifyingHost (f);
endGesture();
});
}
void ParameterAttachment::beginGesture()
{
if (undoManager != nullptr)
undoManager->beginNewTransaction();
parameter.beginChangeGesture();
}
void ParameterAttachment::setValueAsPartOfGesture (float newDenormalisedValue)
{
callIfParameterValueChanged (newDenormalisedValue, [this] (float f)
{
parameter.setValueNotifyingHost (f);
});
}
void ParameterAttachment::endGesture()
{
parameter.endChangeGesture();
}
template <typename Callback>
void ParameterAttachment::callIfParameterValueChanged (float newDenormalisedValue,
Callback&& callback)
{
const auto newValue = normalise (newDenormalisedValue);
if (! approximatelyEqual (parameter.getValue(), newValue))
callback (newValue);
}
void ParameterAttachment::parameterValueChanged (int, float newValue)
{
lastValue = newValue;
if (MessageManager::getInstance()->isThisTheMessageThread())
{
cancelPendingUpdate();
handleAsyncUpdate();
}
else
{
triggerAsyncUpdate();
}
}
void ParameterAttachment::handleAsyncUpdate()
{
NullCheckedInvocation::invoke (setValue, parameter.convertFrom0to1 (lastValue));
}
//==============================================================================
SliderParameterAttachment::SliderParameterAttachment (RangedAudioParameter& param,
Slider& s,
UndoManager* um)
: slider (s),
attachment (param, [this] (float f) { setValue (f); }, um)
{
slider.valueFromTextFunction = [&param] (const String& text) { return (double) param.convertFrom0to1 (param.getValueForText (text)); };
slider.textFromValueFunction = [&param] (double value) { return param.getText (param.convertTo0to1 ((float) value), 0); };
slider.setDoubleClickReturnValue (true, param.convertFrom0to1 (param.getDefaultValue()));
auto range = param.getNormalisableRange();
auto convertFrom0To1Function = [range] (double currentRangeStart,
double currentRangeEnd,
double normalisedValue) mutable
{
range.start = (float) currentRangeStart;
range.end = (float) currentRangeEnd;
return (double) range.convertFrom0to1 ((float) normalisedValue);
};
auto convertTo0To1Function = [range] (double currentRangeStart,
double currentRangeEnd,
double mappedValue) mutable
{
range.start = (float) currentRangeStart;
range.end = (float) currentRangeEnd;
return (double) range.convertTo0to1 ((float) mappedValue);
};
auto snapToLegalValueFunction = [range] (double currentRangeStart,
double currentRangeEnd,
double mappedValue) mutable
{
range.start = (float) currentRangeStart;
range.end = (float) currentRangeEnd;
return (double) range.snapToLegalValue ((float) mappedValue);
};
NormalisableRange<double> newRange { (double) range.start,
(double) range.end,
std::move (convertFrom0To1Function),
std::move (convertTo0To1Function),
std::move (snapToLegalValueFunction) };
newRange.interval = range.interval;
newRange.skew = range.skew;
newRange.symmetricSkew = range.symmetricSkew;
slider.setNormalisableRange (newRange);
sendInitialUpdate();
slider.valueChanged();
slider.addListener (this);
}
SliderParameterAttachment::~SliderParameterAttachment()
{
slider.removeListener (this);
}
void SliderParameterAttachment::sendInitialUpdate() { attachment.sendInitialUpdate(); }
void SliderParameterAttachment::setValue (float newValue)
{
const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
slider.setValue (newValue, sendNotificationSync);
}
void SliderParameterAttachment::sliderValueChanged (Slider*)
{
if (! ignoreCallbacks)
attachment.setValueAsPartOfGesture ((float) slider.getValue());
}
//==============================================================================
ComboBoxParameterAttachment::ComboBoxParameterAttachment (RangedAudioParameter& param,
ComboBox& c,
UndoManager* um)
: comboBox (c),
storedParameter (param),
attachment (param, [this] (float f) { setValue (f); }, um)
{
sendInitialUpdate();
comboBox.addListener (this);
}
ComboBoxParameterAttachment::~ComboBoxParameterAttachment()
{
comboBox.removeListener (this);
}
void ComboBoxParameterAttachment::sendInitialUpdate()
{
attachment.sendInitialUpdate();
}
void ComboBoxParameterAttachment::setValue (float newValue)
{
const auto normValue = storedParameter.convertTo0to1 (newValue);
const auto index = roundToInt (normValue * (float) (comboBox.getNumItems() - 1));
if (index == comboBox.getSelectedItemIndex())
return;
const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
comboBox.setSelectedItemIndex (index, sendNotificationSync);
}
void ComboBoxParameterAttachment::comboBoxChanged (ComboBox*)
{
if (ignoreCallbacks)
return;
const auto numItems = comboBox.getNumItems();
const auto selected = (float) comboBox.getSelectedItemIndex();
const auto newValue = numItems > 1 ? selected / (float) (numItems - 1)
: 0.0f;
attachment.setValueAsCompleteGesture (storedParameter.convertFrom0to1 (newValue));
}
//==============================================================================
ButtonParameterAttachment::ButtonParameterAttachment (RangedAudioParameter& param,
Button& b,
UndoManager* um)
: button (b),
attachment (param, [this] (float f) { setValue (f); }, um)
{
sendInitialUpdate();
button.addListener (this);
}
ButtonParameterAttachment::~ButtonParameterAttachment()
{
button.removeListener (this);
}
void ButtonParameterAttachment::sendInitialUpdate()
{
attachment.sendInitialUpdate();
}
void ButtonParameterAttachment::setValue (float newValue)
{
const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
button.setToggleState (newValue >= 0.5f, sendNotificationSync);
}
void ButtonParameterAttachment::buttonClicked (Button*)
{
if (ignoreCallbacks)
return;
attachment.setValueAsCompleteGesture (button.getToggleState() ? 1.0f : 0.0f);
}
} // namespace juce
@@ -0,0 +1,251 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/** Used to implement 'attachments' or 'controllers' that link a plug-in
parameter to a UI element.
To implement a new attachment type, create a new class which includes an
instance of this class as a data member. Your class should pass a function
to the constructor of the ParameterAttachment, which will then be called on
the message thread when the parameter changes. You can use this function to
update the state of the UI control. Your class should also register as a
listener of the UI control and respond to respond to changes in the UI element
by calling either setValueAsCompleteGesture or beginGesture,
setValueAsPartOfGesture and endGesture.
Make sure to call `sendInitialUpdate` at the end of your new attachment's
constructor, so that the UI immediately reflects the state of the parameter.
@tags{Audio}
*/
class ParameterAttachment : private AudioProcessorParameter::Listener,
private AsyncUpdater
{
public:
/** Listens to a parameter and calls the the provided function in response to
parameter changes. If an undoManager is supplied `beginNewTransaction` will
be called on it whenever the UI requests a parameter change via this attachment.
@param parameter The parameter to which this attachment will listen
@param parameterChangedCallback The function that will be called on the message thread in response
to parameter changes
@param undoManager The UndoManager that will be used to begin transactions when the UI
requests a parameter change.
*/
ParameterAttachment (RangedAudioParameter& parameter,
std::function<void (float)> parameterChangedCallback,
UndoManager* undoManager = nullptr);
/** Destructor. */
~ParameterAttachment() override;
/** Calls the parameterChangedCallback function that was registered in
the constructor, making the UI reflect the current parameter state.
This function should be called after doing any necessary setup on
the UI control that is being managed (e.g. adding ComboBox entries,
making buttons toggle-able).
*/
void sendInitialUpdate();
/** Triggers a full gesture message on the managed parameter.
Call this in the listener callback of the UI control in response
to a one-off change in the UI like a button-press.
*/
void setValueAsCompleteGesture (float newDenormalisedValue);
/** Begins a gesture on the managed parameter.
Call this when the UI is about to begin a continuous interaction,
like when the mouse button is pressed on a slider.
*/
void beginGesture();
/** Updates the parameter value during a gesture.
Call this during a continuous interaction, like a slider value
changed callback.
*/
void setValueAsPartOfGesture (float newDenormalisedValue);
/** Ends a gesture on the managed parameter.
Call this when the UI has finished a continuous interaction,
like when the mouse button is released on a slider.
*/
void endGesture();
private:
float normalise (float f) const { return parameter.convertTo0to1 (f); }
template <typename Callback>
void callIfParameterValueChanged (float newDenormalisedValue, Callback&& callback);
void parameterValueChanged (int, float) override;
void parameterGestureChanged (int, bool) override {}
void handleAsyncUpdate() override;
RangedAudioParameter& parameter;
std::atomic<float> lastValue { 0.0f };
UndoManager* undoManager = nullptr;
std::function<void (float)> setValue;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterAttachment)
};
//==============================================================================
/** An object of this class maintains a connection between a Slider and a
plug-in parameter.
During the lifetime of this object it keeps the two things in sync, making
it easy to connect a slider to a parameter. When this object is deleted, the
connection is broken. Make sure that your parameter and Slider are not
deleted before this object!
@tags{Audio}
*/
class SliderParameterAttachment : private Slider::Listener
{
public:
/** Creates a connection between a plug-in parameter and a Slider.
@param parameter The parameter to use
@param slider The Slider to use
@param undoManager An optional UndoManager
*/
SliderParameterAttachment (RangedAudioParameter& parameter, Slider& slider,
UndoManager* undoManager = nullptr);
/** Destructor. */
~SliderParameterAttachment() override;
/** Call this after setting up your slider in the case where you need to do
extra setup after constructing this attachment.
*/
void sendInitialUpdate();
private:
void setValue (float newValue);
void sliderValueChanged (Slider*) override;
void sliderDragStarted (Slider*) override { attachment.beginGesture(); }
void sliderDragEnded (Slider*) override { attachment.endGesture(); }
Slider& slider;
ParameterAttachment attachment;
bool ignoreCallbacks = false;
};
//==============================================================================
/** An object of this class maintains a connection between a ComboBox and a
plug-in parameter.
ComboBox items will be spaced linearly across the range of the parameter. For
example if the range is specified by NormalisableRange<float> (-0.5f, 0.5f, 0.5f)
and you add three items then the first will be mapped to a value of -0.5, the
second to 0, and the third to 0.5.
During the lifetime of this object it keeps the two things in sync, making it
easy to connect a combo box to a parameter. When this object is deleted, the
connection is broken. Make sure that your parameter and ComboBox are not deleted
before this object!
@tags{Audio}
*/
class ComboBoxParameterAttachment : private ComboBox::Listener
{
public:
/** Creates a connection between a plug-in parameter and a ComboBox.
@param parameter The parameter to use
@param combo The ComboBox to use
@param undoManager An optional UndoManager
*/
ComboBoxParameterAttachment (RangedAudioParameter& parameter, ComboBox& combo,
UndoManager* undoManager = nullptr);
/** Destructor. */
~ComboBoxParameterAttachment() override;
/** Call this after setting up your combo box in the case where you need to do
extra setup after constructing this attachment.
*/
void sendInitialUpdate();
private:
void setValue (float newValue);
void comboBoxChanged (ComboBox*) override;
ComboBox& comboBox;
RangedAudioParameter& storedParameter;
ParameterAttachment attachment;
bool ignoreCallbacks = false;
};
//==============================================================================
/** An object of this class maintains a connection between a Button and a
plug-in parameter.
During the lifetime of this object it keeps the two things in sync, making it
easy to connect a button to a parameter. When this object is deleted, the
connection is broken. Make sure that your parameter and Button are not deleted
before this object!
@tags{Audio}
*/
class ButtonParameterAttachment : private Button::Listener
{
public:
/** Creates a connection between a plug-in parameter and a Button.
@param parameter The parameter to use
@param button The Button to use
@param undoManager An optional UndoManager
*/
ButtonParameterAttachment (RangedAudioParameter& parameter, Button& button,
UndoManager* undoManager = nullptr);
/** Destructor. */
~ButtonParameterAttachment() override;
/** Call this after setting up your button in the case where you need to do
extra setup after constructing this attachment.
*/
void sendInitialUpdate();
private:
void setValue (float newValue);
void buttonClicked (Button*) override;
Button& button;
ParameterAttachment attachment;
bool ignoreCallbacks = false;
};
} // namespace juce
@@ -0,0 +1,319 @@
/*
==============================================================================
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.
==============================================================================
*/
#if JucePlugin_Enable_IAA && JucePlugin_Build_Standalone && JUCE_IOS && (! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP)
bool JUCE_CALLTYPE juce_isInterAppAudioConnected();
void JUCE_CALLTYPE juce_switchToHostApplication();
juce::Image JUCE_CALLTYPE juce_getIAAHostIcon (int);
#endif
namespace juce
{
Image JUCE_API getIconFromApplication (const String&, const int);
AudioProcessor::WrapperType PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_Undefined;
std::function<bool (AudioProcessor&)> PluginHostType::jucePlugInIsRunningInAudioSuiteFn = nullptr;
String PluginHostType::hostIdReportedByWrapper;
bool PluginHostType::isInterAppAudioConnected() const
{
#if JucePlugin_Enable_IAA && JucePlugin_Build_Standalone && JUCE_IOS && (! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP)
if (getPluginLoadedAs() == AudioProcessor::wrapperType_Standalone)
return juce_isInterAppAudioConnected();
#endif
return false;
}
void PluginHostType::switchToHostApplication() const
{
#if JucePlugin_Enable_IAA && JucePlugin_Build_Standalone && JUCE_IOS && (! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP)
if (getPluginLoadedAs() == AudioProcessor::wrapperType_Standalone)
juce_switchToHostApplication();
#endif
}
bool PluginHostType::isInAAXAudioSuite ([[maybe_unused]] AudioProcessor& processor)
{
#if JucePlugin_Build_AAX
if (PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_AAX
&& jucePlugInIsRunningInAudioSuiteFn != nullptr)
{
return jucePlugInIsRunningInAudioSuiteFn (processor);
}
#endif
return false;
}
Image PluginHostType::getHostIcon ([[maybe_unused]] int size) const
{
#if JucePlugin_Enable_IAA && JucePlugin_Build_Standalone && JUCE_IOS && (! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP)
if (isInterAppAudioConnected())
return juce_getIAAHostIcon (size);
#endif
#if JUCE_MAC
String bundlePath (getHostPath().upToLastOccurrenceOf (".app", true, true));
return getIconFromApplication (bundlePath, size);
#endif
return Image();
}
const char* PluginHostType::getHostDescription() const noexcept
{
switch (type)
{
case AbletonLive6: return "Ableton Live 6";
case AbletonLive7: return "Ableton Live 7";
case AbletonLive8: return "Ableton Live 8";
case AbletonLive9: return "Ableton Live 9";
case AbletonLive10: return "Ableton Live 10";
case AbletonLive11: return "Ableton Live 11";
case AbletonLiveGeneric: return "Ableton Live";
case AdobeAudition: return "Adobe Audition";
case AdobePremierePro: return "Adobe Premiere";
case AppleGarageBand: return "Apple GarageBand";
case AppleInfoHelper: return "com.apple.audio.InfoHelper";
case AppleLogic: return "Apple Logic";
case AppleMainStage: return "Apple MainStage";
case Ardour: return "Ardour";
case AULab: return "AU Lab";
case AUVal: return "auval";
case AvidProTools: return "ProTools";
case BitwigStudio: return "Bitwig Studio";
case CakewalkSonar8: return "Cakewalk Sonar 8";
case CakewalkSonarGeneric: return "Cakewalk Sonar";
case CakewalkByBandlab: return "Cakewalk by Bandlab";
case DaVinciResolve: return "DaVinci Resolve";
case DigitalPerformer: return "DigitalPerformer";
case FinalCut: return "Final Cut";
case FruityLoops: return "FruityLoops";
case JUCEPluginHost: return "JUCE AudioPluginHost";
case MagixSamplitude: return "Magix Samplitude";
case MagixSequoia: return "Magix Sequoia";
case pluginval: return "pluginval";
case MergingPyramix: return "Pyramix";
case MuseReceptorGeneric: return "Muse Receptor";
case Maschine: return "NI Maschine";
case Reaper: return "Reaper";
case Reason: return "Reason";
case Renoise: return "Renoise";
case SADiE: return "SADiE";
case SteinbergCubase4: return "Steinberg Cubase 4";
case SteinbergCubase5: return "Steinberg Cubase 5";
case SteinbergCubase5Bridged: return "Steinberg Cubase 5 Bridged";
case SteinbergCubase6: return "Steinberg Cubase 6";
case SteinbergCubase7: return "Steinberg Cubase 7";
case SteinbergCubase8: return "Steinberg Cubase 8";
case SteinbergCubase8_5: return "Steinberg Cubase 8.5";
case SteinbergCubase9: return "Steinberg Cubase 9";
case SteinbergCubase9_5: return "Steinberg Cubase 9.5";
case SteinbergCubase10: return "Steinberg Cubase 10";
case SteinbergCubase10_5: return "Steinberg Cubase 10.5";
case SteinbergCubaseGeneric: return "Steinberg Cubase";
case SteinbergNuendo3: return "Steinberg Nuendo 3";
case SteinbergNuendo4: return "Steinberg Nuendo 4";
case SteinbergNuendo5: return "Steinberg Nuendo 5";
case SteinbergNuendoGeneric: return "Steinberg Nuendo";
case SteinbergWavelab5: return "Steinberg Wavelab 5";
case SteinbergWavelab6: return "Steinberg Wavelab 6";
case SteinbergWavelab7: return "Steinberg Wavelab 7";
case SteinbergWavelab8: return "Steinberg Wavelab 8";
case SteinbergWavelabGeneric: return "Steinberg Wavelab";
case SteinbergTestHost: return "Steinberg TestHost";
case StudioOne: return "Studio One";
case Tracktion3: return "Tracktion 3";
case TracktionGeneric: return "Tracktion";
case TracktionWaveform: return "Tracktion Waveform";
case VBVSTScanner: return "VBVSTScanner";
case ViennaEnsemblePro: return "Vienna Ensemble Pro";
case WaveBurner: return "WaveBurner";
case UnknownHost:
default: break;
}
return "Unknown";
}
PluginHostType::HostType PluginHostType::getHostType()
{
auto hostPath = getHostPath();
auto hostFilename = File (hostPath).getFileName();
#if JUCE_MAC
if (hostPath.containsIgnoreCase ("Final Cut Pro.app")) return FinalCut;
if (hostPath.containsIgnoreCase ("Final Cut Pro Trial.app")) return FinalCut;
if (hostPath.containsIgnoreCase ("Live 6")) return AbletonLive6;
if (hostPath.containsIgnoreCase ("Live 7")) return AbletonLive7;
if (hostPath.containsIgnoreCase ("Live 8")) return AbletonLive8;
if (hostPath.containsIgnoreCase ("Live 9")) return AbletonLive9;
if (hostPath.containsIgnoreCase ("Live 10")) return AbletonLive10;
if (hostPath.containsIgnoreCase ("Live 11")) return AbletonLive11;
if (hostFilename.containsIgnoreCase ("Live")) return AbletonLiveGeneric;
if (hostFilename.containsIgnoreCase ("Audition")) return AdobeAudition;
if (hostFilename.containsIgnoreCase ("Adobe Premiere")) return AdobePremierePro;
if (hostFilename.containsIgnoreCase ("GarageBand")) return AppleGarageBand;
if (hostFilename.containsIgnoreCase ("Logic")) return AppleLogic;
if (hostFilename.containsIgnoreCase ("MainStage")) return AppleMainStage;
if (hostFilename.containsIgnoreCase ("AU Lab")) return AULab;
if (hostFilename.containsIgnoreCase ("Pro Tools")) return AvidProTools;
if (hostFilename.containsIgnoreCase ("Nuendo 3")) return SteinbergNuendo3;
if (hostFilename.containsIgnoreCase ("Nuendo 4")) return SteinbergNuendo4;
if (hostFilename.containsIgnoreCase ("Nuendo 5")) return SteinbergNuendo5;
if (hostFilename.containsIgnoreCase ("Nuendo")) return SteinbergNuendoGeneric;
if (hostFilename.containsIgnoreCase ("Cubase 4")) return SteinbergCubase4;
if (hostFilename.containsIgnoreCase ("Cubase 5")) return SteinbergCubase5;
if (hostFilename.containsIgnoreCase ("Cubase 6")) return SteinbergCubase6;
if (hostFilename.containsIgnoreCase ("Cubase 7")) return SteinbergCubase7;
if (hostPath.containsIgnoreCase ("Cubase 8.app")) return SteinbergCubase8;
if (hostPath.containsIgnoreCase ("Cubase 8.5.app")) return SteinbergCubase8_5;
if (hostPath.containsIgnoreCase ("Cubase 9.app")) return SteinbergCubase9;
if (hostPath.containsIgnoreCase ("Cubase 9.5.app")) return SteinbergCubase9_5;
if (hostPath.containsIgnoreCase ("Cubase 10.app")) return SteinbergCubase10;
if (hostPath.containsIgnoreCase ("Cubase 10.5.app")) return SteinbergCubase10_5;
if (hostFilename.containsIgnoreCase ("Cubase")) return SteinbergCubaseGeneric;
if (hostPath.containsIgnoreCase ("Wavelab 7")) return SteinbergWavelab7;
if (hostPath.containsIgnoreCase ("Wavelab 8")) return SteinbergWavelab8;
if (hostFilename.containsIgnoreCase ("Wavelab")) return SteinbergWavelabGeneric;
if (hostFilename.containsIgnoreCase ("WaveBurner")) return WaveBurner;
if (hostPath.containsIgnoreCase ("Digital Performer")) return DigitalPerformer;
if (hostFilename.containsIgnoreCase ("reaper")) return Reaper;
if (hostFilename.containsIgnoreCase ("Reason")) return Reason;
if (hostPath.containsIgnoreCase ("Studio One")) return StudioOne;
if (hostFilename.startsWithIgnoreCase ("Waveform")) return TracktionWaveform;
if (hostPath.containsIgnoreCase ("Tracktion 3")) return Tracktion3;
if (hostFilename.containsIgnoreCase ("Tracktion")) return TracktionGeneric;
if (hostFilename.containsIgnoreCase ("Renoise")) return Renoise;
if (hostFilename.containsIgnoreCase ("Resolve")) return DaVinciResolve;
if (hostFilename.startsWith ("Bitwig")) return BitwigStudio;
if (hostFilename.containsIgnoreCase ("OsxFL")) return FruityLoops;
if (hostFilename.containsIgnoreCase ("pluginval")) return pluginval;
if (hostFilename.containsIgnoreCase ("AudioPluginHost")) return JUCEPluginHost;
if (hostFilename.containsIgnoreCase ("Maschine")) return Maschine;
if (hostFilename.containsIgnoreCase ("Vienna Ensemble Pro")) return ViennaEnsemblePro;
if (hostFilename.containsIgnoreCase ("auvaltool")) return AUVal;
if (hostFilename.containsIgnoreCase ("com.apple.audio.infohelper")) return AppleInfoHelper;
if (hostIdReportedByWrapper == "com.apple.logic.pro") return AppleLogic;
if (hostIdReportedByWrapper == "com.apple.garageband") return AppleGarageBand;
if (hostIdReportedByWrapper == "com.apple.mainstage") return AppleMainStage;
const auto procName = nsStringToJuce ([[NSRunningApplication currentApplication] localizedName]);
const auto matchesInOrder = [&] (const StringArray& strings)
{
return procName.matchesWildcard ("AUHostingService*(" + strings.joinIntoString ("*") + ")", false);
};
// Depending on localization settings, spaces are not always plain ascii spaces
if (matchesInOrder ({ "Logic", "Pro" })) return AppleLogic;
if (matchesInOrder ({ "GarageBand" })) return AppleGarageBand;
if (matchesInOrder ({ "MainStage" })) return AppleMainStage;
if (matchesInOrder ({ "Final", "Cut", "Pro" })) return FinalCut;
#elif JUCE_WINDOWS
if (hostFilename.containsIgnoreCase ("Live 6")) return AbletonLive6;
if (hostFilename.containsIgnoreCase ("Live 7")) return AbletonLive7;
if (hostFilename.containsIgnoreCase ("Live 8")) return AbletonLive8;
if (hostFilename.containsIgnoreCase ("Live 9")) return AbletonLive9;
if (hostFilename.containsIgnoreCase ("Live 10")) return AbletonLive10;
if (hostFilename.containsIgnoreCase ("Live 11")) return AbletonLive11;
if (hostFilename.containsIgnoreCase ("Live ")) return AbletonLiveGeneric;
if (hostFilename.containsIgnoreCase ("Audition")) return AdobeAudition;
if (hostFilename.containsIgnoreCase ("Adobe Premiere")) return AdobePremierePro;
if (hostFilename.containsIgnoreCase ("ProTools")) return AvidProTools;
if (hostPath.containsIgnoreCase ("SONAR 8")) return CakewalkSonar8;
if (hostFilename.containsIgnoreCase ("SONAR")) return CakewalkSonarGeneric;
if (hostFilename.containsIgnoreCase ("Cakewalk.exe")) return CakewalkByBandlab;
if (hostFilename.containsIgnoreCase ("GarageBand")) return AppleGarageBand;
if (hostFilename.containsIgnoreCase ("Logic")) return AppleLogic;
if (hostFilename.containsIgnoreCase ("MainStage")) return AppleMainStage;
if (hostFilename.startsWithIgnoreCase ("Waveform")) return TracktionWaveform;
if (hostPath.containsIgnoreCase ("Tracktion 3")) return Tracktion3;
if (hostFilename.containsIgnoreCase ("Tracktion")) return TracktionGeneric;
if (hostFilename.containsIgnoreCase ("reaper")) return Reaper;
if (hostFilename.containsIgnoreCase ("Cubase4")) return SteinbergCubase4;
if (hostFilename.containsIgnoreCase ("Cubase5")) return SteinbergCubase5;
if (hostFilename.containsIgnoreCase ("Cubase6")) return SteinbergCubase6;
if (hostFilename.containsIgnoreCase ("Cubase7")) return SteinbergCubase7;
if (hostFilename.containsIgnoreCase ("Cubase8.exe")) return SteinbergCubase8;
if (hostFilename.containsIgnoreCase ("Cubase8.5.exe")) return SteinbergCubase8_5;
// Later version of Cubase scan plug-ins with a separate executable "vst2xscanner"
if (hostFilename.containsIgnoreCase ("Cubase9.5.exe")
|| hostPath.containsIgnoreCase ("Cubase 9.5")) return SteinbergCubase9_5;
if (hostFilename.containsIgnoreCase ("Cubase9.exe")
|| hostPath.containsIgnoreCase ("Cubase 9")) return SteinbergCubase9;
if (hostFilename.containsIgnoreCase ("Cubase10.5.exe")
|| hostPath.containsIgnoreCase ("Cubase 10.5")) return SteinbergCubase10_5;
if (hostFilename.containsIgnoreCase ("Cubase10.exe")
|| hostPath.containsIgnoreCase ("Cubase 10")) return SteinbergCubase10;
if (hostFilename.containsIgnoreCase ("Cubase")) return SteinbergCubaseGeneric;
if (hostFilename.containsIgnoreCase ("VSTBridgeApp")) return SteinbergCubase5Bridged;
if (hostPath.containsIgnoreCase ("Wavelab 5")) return SteinbergWavelab5;
if (hostPath.containsIgnoreCase ("Wavelab 6")) return SteinbergWavelab6;
if (hostPath.containsIgnoreCase ("Wavelab 7")) return SteinbergWavelab7;
if (hostPath.containsIgnoreCase ("Wavelab 8")) return SteinbergWavelab8;
if (hostPath.containsIgnoreCase ("Nuendo")) return SteinbergNuendoGeneric;
if (hostFilename.containsIgnoreCase ("Wavelab")) return SteinbergWavelabGeneric;
if (hostFilename.containsIgnoreCase ("TestHost")) return SteinbergTestHost;
if (hostFilename.containsIgnoreCase ("rm-host")) return MuseReceptorGeneric;
if (hostFilename.containsIgnoreCase ("Maschine")) return Maschine;
if (hostFilename.startsWith ("FL")) return FruityLoops;
if (hostFilename.contains ("ilbridge.")) return FruityLoops;
if (hostPath.containsIgnoreCase ("Studio One")) return StudioOne;
if (hostPath.containsIgnoreCase ("Digital Performer")) return DigitalPerformer;
if (hostFilename.containsIgnoreCase ("VST_Scanner")) return VBVSTScanner;
if (hostPath.containsIgnoreCase ("Merging Technologies")) return MergingPyramix;
if (hostFilename.startsWithIgnoreCase ("Sam")) return MagixSamplitude;
if (hostFilename.startsWithIgnoreCase ("Sequoia")) return MagixSequoia;
if (hostFilename.containsIgnoreCase ("Reason")) return Reason;
if (hostFilename.containsIgnoreCase ("Renoise")) return Renoise;
if (hostFilename.containsIgnoreCase ("Resolve")) return DaVinciResolve;
if (hostPath.containsIgnoreCase ("Bitwig Studio")) return BitwigStudio;
if (hostFilename.containsIgnoreCase ("Sadie")) return SADiE;
if (hostFilename.containsIgnoreCase ("pluginval")) return pluginval;
if (hostFilename.containsIgnoreCase ("AudioPluginHost")) return JUCEPluginHost;
if (hostFilename.containsIgnoreCase ("Vienna Ensemble Pro")) return ViennaEnsemblePro;
#elif JUCE_LINUX || JUCE_BSD
if (hostFilename.containsIgnoreCase ("Ardour")) return Ardour;
if (hostFilename.startsWithIgnoreCase ("Waveform")) return TracktionWaveform;
if (hostFilename.containsIgnoreCase ("Tracktion")) return TracktionGeneric;
if (hostFilename.startsWith ("Bitwig")) return BitwigStudio;
if (hostFilename.containsIgnoreCase ("pluginval")) return pluginval;
if (hostFilename.containsIgnoreCase ("AudioPluginHost")) return JUCEPluginHost;
#elif JUCE_IOS
#elif JUCE_ANDROID
#else
#error
#endif
return UnknownHost;
}
} // namespace juce
@@ -0,0 +1,256 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A useful utility class to determine the host or DAW in which your plugin is
loaded.
Declare a PluginHostType object in your class to use it.
@tags{Audio}
*/
class PluginHostType
{
public:
//==============================================================================
PluginHostType() : type (getHostType()) {}
PluginHostType (const PluginHostType& other) = default;
PluginHostType& operator= (const PluginHostType& other) = default;
//==============================================================================
/** Represents the host type and also its version for some hosts. */
enum HostType
{
UnknownHost, /**< Represents an unknown host. */
AbletonLive6, /**< Represents Ableton Live 6. */
AbletonLive7, /**< Represents Ableton Live 7. */
AbletonLive8, /**< Represents Ableton Live 8. */
AbletonLive9, /**< Represents Ableton Live 9. */
AbletonLive10, /**< Represents Ableton Live 10. */
AbletonLive11, /**< Represents Ableton Live 11. */
AbletonLiveGeneric, /**< Represents Ableton Live. */
AdobeAudition, /**< Represents Adobe Audition. */
AdobePremierePro, /**< Represents Adobe Premiere Pro. */
AppleGarageBand, /**< Represents Apple GarageBand. */
AppleInfoHelper, /**< Represents Apple com.apple.audio.InfoHelper. */
AppleLogic, /**< Represents Apple Logic Pro. */
AppleMainStage, /**< Represents Apple Main Stage. */
Ardour, /**< Represents Ardour. */
AULab, /**< Represents AU Lab. */
AUVal, /**< Represents Apple AU validator. */
AvidProTools, /**< Represents Avid Pro Tools. */
BitwigStudio, /**< Represents Bitwig Studio. */
CakewalkSonar8, /**< Represents Cakewalk Sonar 8. */
CakewalkSonarGeneric, /**< Represents Cakewalk Sonar. */
CakewalkByBandlab, /**< Represents Cakewalk by Bandlab. */
DaVinciResolve, /**< Represents DaVinci Resolve. */
DigitalPerformer, /**< Represents Digital Performer. */
FinalCut, /**< Represents Apple Final Cut Pro. */
FruityLoops, /**< Represents Fruity Loops. */
JUCEPluginHost, /**< Represents the JUCE AudioPluginHost */
MagixSamplitude, /**< Represents Magix Samplitude. */
MagixSequoia, /**< Represents Magix Sequoia. */
MergingPyramix, /**< Represents Merging Pyramix. */
MuseReceptorGeneric, /**< Represents Muse Receptor. */
Maschine, /**< Represents Native Instruments Maschine. */
pluginval, /**< Represents pluginval. */
Reaper, /**< Represents Cockos Reaper. */
Reason, /**< Represents Reason. */
Renoise, /**< Represents Renoise. */
SADiE, /**< Represents SADiE. */
SteinbergCubase4, /**< Represents Steinberg Cubase 4. */
SteinbergCubase5, /**< Represents Steinberg Cubase 5. */
SteinbergCubase5Bridged, /**< Represents Steinberg Cubase 5 Bridged. */
SteinbergCubase6, /**< Represents Steinberg Cubase 6. */
SteinbergCubase7, /**< Represents Steinberg Cubase 7. */
SteinbergCubase8, /**< Represents Steinberg Cubase 8. */
SteinbergCubase8_5, /**< Represents Steinberg Cubase 8.5. */
SteinbergCubase9, /**< Represents Steinberg Cubase 9. */
SteinbergCubase9_5, /**< Represents Steinberg Cubase 9.5. */
SteinbergCubase10, /**< Represents Steinberg Cubase 10. */
SteinbergCubase10_5, /**< Represents Steinberg Cubase 10.5. */
SteinbergCubaseGeneric, /**< Represents Steinberg Cubase. */
SteinbergNuendo3, /**< Represents Steinberg Nuendo 3. */
SteinbergNuendo4, /**< Represents Steinberg Nuendo 4. */
SteinbergNuendo5, /**< Represents Steinberg Nuendo 5. */
SteinbergNuendoGeneric, /**< Represents Steinberg Nuendo. */
SteinbergWavelab5, /**< Represents Steinberg Wavelab 5. */
SteinbergWavelab6, /**< Represents Steinberg Wavelab 6. */
SteinbergWavelab7, /**< Represents Steinberg Wavelab 7. */
SteinbergWavelab8, /**< Represents Steinberg Wavelab 8. */
SteinbergWavelabGeneric, /**< Represents Steinberg Wavelab. */
SteinbergTestHost, /**< Represents Steinberg's VST3 Test Host. */
StudioOne, /**< Represents PreSonus Studio One. */
Tracktion3, /**< Represents Tracktion 3. */
TracktionGeneric, /**< Represents Tracktion. */
TracktionWaveform, /**< Represents Tracktion Waveform. */
VBVSTScanner, /**< Represents VB Audio VST Scanner. */
ViennaEnsemblePro, /**< Represents Vienna Ensemble Pro. */
WaveBurner /**< Represents Apple WaveBurner. */
};
HostType type;
//==============================================================================
/** Returns true if the host is any version of Ableton Live. */
bool isAbletonLive() const noexcept { return type == AbletonLive6 || type == AbletonLive7 || type == AbletonLive8
|| type == AbletonLive9 || type == AbletonLive10 || type == AbletonLive11
|| type == AbletonLiveGeneric; }
/** Returns true if the host is Adobe Audition. */
bool isAdobeAudition() const noexcept { return type == AdobeAudition; }
/** Returns true if the host is com.apple.audio.InfoHelper. */
bool isAppleInfoHelper() const noexcept { return type == AppleInfoHelper; }
/** Returns true if the host is Ardour. */
bool isArdour() const noexcept { return type == Ardour; }
/** Returns true if the host is AU Lab. */
bool isAULab() const noexcept { return type == AULab; }
/** Returns true if the host is auval. */
bool isAUVal() const noexcept { return type == AUVal; }
/** Returns true if the host is Bitwig Studio. */
bool isBitwigStudio() const noexcept { return type == BitwigStudio; }
/** Returns true if the host is any version of Steinberg Cubase. */
bool isCubase() const noexcept { return type == SteinbergCubase4 || type == SteinbergCubase5 || type == SteinbergCubase5Bridged || type == SteinbergCubase6
|| type == SteinbergCubase7 || type == SteinbergCubase8 || type == SteinbergCubase8_5 || type == SteinbergCubase9
|| type == SteinbergCubase9_5 || type == SteinbergCubase10 || type == SteinbergCubase10_5 || type == SteinbergCubaseGeneric; }
/** Returns true if the host is Steinberg Cubase 7 or later. */
bool isCubase7orLater() const noexcept { return isCubase() && ! (type == SteinbergCubase4 || type == SteinbergCubase5 || type == SteinbergCubase6); }
/** Returns true if the host is Steinberg Cubase 5 Bridged. */
bool isCubaseBridged() const noexcept { return type == SteinbergCubase5Bridged; }
/** Returns true if the host is DaVinci Resolve. */
bool isDaVinciResolve() const noexcept { return type == DaVinciResolve; }
/** Returns true if the host is Digital Performer. */
bool isDigitalPerformer() const noexcept { return type == DigitalPerformer; }
/** Returns true if the host is Apple Final Cut Pro. */
bool isFinalCut() const noexcept { return type == FinalCut; }
/** Returns true if the host is Fruity Loops. */
bool isFruityLoops() const noexcept { return type == FruityLoops; }
/** Returns true if the host is Apple GarageBand. */
bool isGarageBand() const noexcept { return type == AppleGarageBand; }
/** Returns true if the host is the JUCE AudioPluginHost */
bool isJUCEPluginHost() const noexcept { return type == JUCEPluginHost; }
/** Returns true if the host is Apple Logic Pro. */
bool isLogic() const noexcept { return type == AppleLogic; }
/** Returns true if the host is Apple MainStage. */
bool isMainStage() const noexcept { return type == AppleMainStage; }
/** Returns true if the host is any version of Steinberg Nuendo. */
bool isNuendo() const noexcept { return type == SteinbergNuendo3 || type == SteinbergNuendo4 || type == SteinbergNuendo5 || type == SteinbergNuendoGeneric; }
/** Returns true if the host is pluginval. */
bool isPluginval() const noexcept { return type == pluginval; }
/** Returns true if the host is Adobe Premiere Pro. */
bool isPremiere() const noexcept { return type == AdobePremierePro; }
/** Returns true if the host is Avid Pro Tools. */
bool isProTools() const noexcept { return type == AvidProTools; }
/** Returns true if the host is Merging Pyramix. */
bool isPyramix() const noexcept { return type == MergingPyramix; }
/** Returns true if the host is Muse Receptor. */
bool isReceptor() const noexcept { return type == MuseReceptorGeneric; }
/** Returns true if the host is Cockos Reaper. */
bool isReaper() const noexcept { return type == Reaper; }
/** Returns true if the host is Reason. */
bool isReason() const noexcept { return type == Reason; }
/** Returns true if the host is Renoise. */
bool isRenoise() const noexcept { return type == Renoise; }
/** Returns true if the host is SADiE. */
bool isSADiE() const noexcept { return type == SADiE; }
/** Returns true if the host is Magix Samplitude. */
bool isSamplitude() const noexcept { return type == MagixSamplitude; }
/** Returns true if the host is Magix Sequoia. */
bool isSequoia() const noexcept { return type == MagixSequoia; }
/** Returns true if the host is any version of Cakewalk Sonar. */
bool isSonar() const noexcept { return type == CakewalkSonar8 || type == CakewalkSonarGeneric || type == CakewalkByBandlab; }
/** Returns true if the host is Steinberg's VST3 Test Host. */
bool isSteinbergTestHost() const noexcept { return type == SteinbergTestHost; }
/** Returns true if the host is any product from Steinberg. */
bool isSteinberg() const noexcept { return isCubase() || isNuendo() || isWavelab() || isSteinbergTestHost(); }
/** Returns true if the host is PreSonus Studio One. */
bool isStudioOne() const noexcept { return type == StudioOne; }
/** Returns true if the host is any version of Tracktion. */
bool isTracktion() const noexcept { return type == Tracktion3 || type == TracktionGeneric || isTracktionWaveform(); }
/** Returns true if the host is Tracktion Waveform. */
bool isTracktionWaveform() const noexcept { return type == TracktionWaveform; }
/** Returns true if the host is VB Audio VST Scanner. */
bool isVBVSTScanner() const noexcept { return type == VBVSTScanner; }
/** Returns true if the host is Vienna Ensemble Pro. */
bool isViennaEnsemblePro() const noexcept { return type == ViennaEnsemblePro; }
/** Returns true if the host is Apple WaveBurner. */
bool isWaveBurner() const noexcept { return type == WaveBurner; }
/** Returns true if the host is any version of Steinberg WaveLab. */
bool isWavelab() const noexcept { return isWavelabLegacy() || type == SteinbergWavelab7 || type == SteinbergWavelab8 || type == SteinbergWavelabGeneric; }
/** Returns true if the host is Steinberg WaveLab 6 or below. */
bool isWavelabLegacy() const noexcept { return type == SteinbergWavelab5 || type == SteinbergWavelab6; }
/** Returns true if the host is Native Instruments Maschine. */
bool isMaschine() const noexcept { return type == Maschine; }
//==============================================================================
/** Returns a human-readable description of the host. */
const char* getHostDescription() const noexcept;
//==============================================================================
/** Returns true if the plugin is connected with Inter-App Audio on iOS. */
bool isInterAppAudioConnected() const;
/** Switches to the host application when Inter-App Audio is used on iOS. */
void switchToHostApplication() const;
/** Gets the host app's icon when Inter-App Audio is used on iOS. */
Image getHostIcon (int size) const;
//==============================================================================
/** Returns the complete absolute path of the host application executable. */
static String getHostPath()
{
return File::getSpecialLocation (File::hostApplicationPath).getFullPathName();
}
//==============================================================================
/**
Returns the plug-in format via which the plug-in file was loaded. This value is
identical to AudioProcessor::wrapperType of the main audio processor of this
plug-in. This function is useful for code that does not have access to the
plug-in's main audio processor.
@see AudioProcessor::wrapperType
*/
static AudioProcessor::WrapperType getPluginLoadedAs() noexcept { return jucePlugInClientCurrentWrapperType; }
/** Returns true if the AudioProcessor instance is an AAX plug-in running in AudioSuite. */
static bool isInAAXAudioSuite (AudioProcessor&);
//==============================================================================
#ifndef DOXYGEN
// @internal
static AudioProcessor::WrapperType jucePlugInClientCurrentWrapperType;
static std::function<bool (AudioProcessor&)> jucePlugInIsRunningInAudioSuiteFn;
static String hostIdReportedByWrapper;
#endif
private:
static HostType getHostType();
};
} // namespace juce
@@ -0,0 +1,51 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
int RangedAudioParameter::getNumSteps() const
{
const auto& range = getNormalisableRange();
if (range.interval > 0)
return (static_cast<int> ((range.end - range.start) / range.interval) + 1);
return AudioProcessor::getDefaultNumParameterSteps();
}
float RangedAudioParameter::convertTo0to1 (float v) const noexcept
{
const auto& range = getNormalisableRange();
return range.convertTo0to1 (range.snapToLegalValue (v));
}
float RangedAudioParameter::convertFrom0to1 (float v) const noexcept
{
const auto& range = getNormalisableRange();
return range.snapToLegalValue (range.convertFrom0to1 (jlimit (0.0f, 1.0f, v)));
}
} // namespace juce
@@ -0,0 +1,120 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/**
@internal
Holds common attributes of audio parameters.
CRTP is used here because we want the Attributes types for each parameter
(Float, Bool, Choice, Int) to be distinct and extensible in the future.
i.e. the identifiers AudioParameterFloatAttributes and RangedAudioParameterAttributes<float>
should not be interchangable because we might need to add float-specific attributes in
the future. Users should not refer directly to RangedAudioParameterAttributes.
@tags{Audio}
*/
template <typename Derived, typename Value>
class RangedAudioParameterAttributes
{
using This = RangedAudioParameterAttributes;
public:
using Category = AudioProcessorParameter::Category;
using StringFromValue = std::function<String (Value, int)>;
using ValueFromString = std::function<Value (const String&)>;
/** An optional lambda function that converts a non-normalised value to a string with a maximum length. This may be used by hosts to display the parameter's value. */
[[nodiscard]] auto withStringFromValueFunction (StringFromValue x) const { return withMember (asDerived(), &Derived::stringFromValue, std::move (x)); }
/** An optional lambda function that parses a string and converts it into a non-normalised value. Some hosts use this to allow users to type in parameter values. */
[[nodiscard]] auto withValueFromStringFunction (ValueFromString x) const { return withMember (asDerived(), &Derived::valueFromString, std::move (x)); }
/** See AudioProcessorParameterWithIDAttributes::withLabel() */
[[nodiscard]] auto withLabel (String x) const { return withMember (asDerived(), &Derived::attributes, attributes.withLabel (std::move (x))); }
/** See AudioProcessorParameterWithIDAttributes::withCategory() */
[[nodiscard]] auto withCategory (Category x) const { return withMember (asDerived(), &Derived::attributes, attributes.withCategory (std::move (x))); }
/** See AudioProcessorParameter::isMetaParameter() */
[[nodiscard]] auto withMeta (bool x) const { return withMember (asDerived(), &Derived::attributes, attributes.withMeta (std::move (x))); }
/** See AudioProcessorParameter::isAutomatable() */
[[nodiscard]] auto withAutomatable (bool x) const { return withMember (asDerived(), &Derived::attributes, attributes.withAutomatable (std::move (x))); }
/** See AudioProcessorParameter::isOrientationInverted() */
[[nodiscard]] auto withInverted (bool x) const { return withMember (asDerived(), &Derived::attributes, attributes.withInverted (std::move (x))); }
/** An optional lambda function that converts a non-normalised value to a string with a maximum length. This may be used by hosts to display the parameter's value. */
[[nodiscard]] const auto& getStringFromValueFunction() const { return stringFromValue; }
/** An optional lambda function that parses a string and converts it into a non-normalised value. Some hosts use this to allow users to type in parameter values. */
[[nodiscard]] const auto& getValueFromStringFunction() const { return valueFromString; }
/** Gets attributes that would also apply to an AudioProcessorParameterWithID */
[[nodiscard]] const auto& getAudioProcessorParameterWithIDAttributes() const { return attributes; }
private:
auto& asDerived() const { return *static_cast<const Derived*> (this); }
AudioProcessorParameterWithIDAttributes attributes;
StringFromValue stringFromValue;
ValueFromString valueFromString;
};
//==============================================================================
/**
This abstract base class is used by some AudioProcessorParameter helper classes.
@see AudioParameterFloat, AudioParameterInt, AudioParameterBool, AudioParameterChoice
@tags{Audio}
*/
class JUCE_API RangedAudioParameter : public AudioProcessorParameterWithID
{
public:
using AudioProcessorParameterWithID::AudioProcessorParameterWithID;
/** Returns the range of values that the parameter can take. */
virtual const NormalisableRange<float>& getNormalisableRange() const = 0;
/** Returns the number of steps for this parameter based on the normalisable range's interval.
If you are using lambda functions to define the normalisable range's snapping behaviour
then you should override this function so that it returns the number of snapping points.
*/
int getNumSteps() const override;
/** Normalises and snaps a value based on the normalisable range. */
float convertTo0to1 (float v) const noexcept;
/** Denormalises and snaps a value based on the normalisable range. */
float convertFrom0to1 (float v) const noexcept;
};
} // namespace juce
@@ -0,0 +1,39 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
pointer_sized_int VST2ClientExtensions::handleVstPluginCanDo ([[maybe_unused]] int32 index,
[[maybe_unused]] pointer_sized_int value,
[[maybe_unused]] void* ptr,
[[maybe_unused]] float opt)
{
return 0;
}
void VST2ClientExtensions::handleVstHostCallbackAvailable ([[maybe_unused]] std::function<VstHostCallbackType>&& callback) {}
} // namespace juce
@@ -0,0 +1,74 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
/**
An interface to allow an AudioProcessor to implement extended VST2-specific functionality.
To use this class, create an object that inherits from it, implement the methods, then return
a pointer to the object in your AudioProcessor::getVST2ClientExtensions() method.
@see AudioProcessor, AAXClientExtensions, VST3ClientExtensions
@tags{Audio}
*/
struct VST2ClientExtensions
{
virtual ~VST2ClientExtensions() = default;
/** This is called by the VST plug-in wrapper when it receives unhandled
plug-in "can do" calls from the host.
*/
virtual pointer_sized_int handleVstPluginCanDo (int32 index,
pointer_sized_int value,
void* ptr,
float opt);
/** This is called by the VST plug-in wrapper when it receives unhandled
vendor specific calls from the host.
*/
virtual pointer_sized_int handleVstManufacturerSpecific (int32 index,
pointer_sized_int value,
void* ptr,
float opt) = 0;
/** The host callback function type. */
using VstHostCallbackType = pointer_sized_int (int32 opcode,
int32 index,
pointer_sized_int value,
void* ptr,
float opt);
/** This is called once by the VST plug-in wrapper after its constructor.
You can use the supplied function to query the VST host.
*/
virtual void handleVstHostCallbackAvailable (std::function<VstHostCallbackType>&& callback);
};
using VSTCallbackHandler [[deprecated ("replace with VST2ClientExtensions")]] = VST2ClientExtensions;
} // namespace juce
@@ -0,0 +1,110 @@
/*
==============================================================================
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.
==============================================================================
*/
#ifndef DOXYGEN
// Forward declaration to avoid leaking implementation details.
namespace Steinberg
{
class FUnknown;
using TUID = char[16];
} // namespace Steinberg
#endif
namespace juce
{
/**
An interface to allow an AudioProcessor to implement extended VST3-specific functionality.
To use this class, create an object that inherits from it, implement the methods, then return
a pointer to the object in your AudioProcessor::getVST3ClientExtensions() method.
@see AudioProcessor, AAXClientExtensions, VST2ClientExtensions
@tags{Audio}
*/
struct VST3ClientExtensions
{
virtual ~VST3ClientExtensions() = default;
/** This function may be used by implementations of queryInterface()
in the VST3's implementation of IEditController to return
additional supported interfaces.
*/
virtual int32_t queryIEditController (const Steinberg::TUID, void** obj)
{
*obj = nullptr;
return -1;
}
/** This function may be used by implementations of queryInterface()
in the VST3's implementation of IAudioProcessor to return
additional supported interfaces.
*/
virtual int32_t queryIAudioProcessor (const Steinberg::TUID, void** obj)
{
*obj = nullptr;
return -1;
}
/** This may be called by the VST3 wrapper when the host sets an
IComponentHandler for the plugin to use.
You should not make any assumptions about how and when this will be
called - this function may not be called at all!
*/
virtual void setIComponentHandler (Steinberg::FUnknown*) {}
/** This may be called shortly after the AudioProcessor is constructed
with the current IHostApplication.
You should not make any assumptions about how and when this will be
called - this function may not be called at all!
*/
virtual void setIHostApplication (Steinberg::FUnknown*) {}
/** This function will be called to check whether the first input bus
should be designated as "kMain" or "kAux". Return true if the
first bus should be kMain, or false if the bus should be kAux.
All other input buses will always be designated kAux.
*/
virtual bool getPluginHasMainInput() const { return true; }
/** This function should return the UIDs of any compatible VST2 plug-ins.
Each item in the vector should be a 32-character string consisting only
of the characters 0-9 and A-F.
This information will be used to implement the IPluginCompatibility
interface. Hosts can use this interface to determine whether this VST3
is capable of replacing a given VST2.
*/
virtual std::vector<String> getCompatibleClasses() const { return {}; }
};
} // namespace juce