diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d00c6fa..0e23cc46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,13 +41,14 @@ jobs: uses: lukka/get-cmake@latest - name: Restore artifacts, or run vcpkg, build and cache artifacts - uses: lukka/run-vcpkg@v7 + uses: lukka/run-vcpkg@v11 id: runvcpkg with: - vcpkgArguments: 'boost-variant boost-optional boost-format boost-functional boost-range boost-iterator boost-rational' - vcpkgTriplet: '${{ matrix.triplet }}' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' - vcpkgGitCommitId: '7ad236f60f5f7197e93c4d7f0807622f4899076d' + vcpkgGitCommitId: 'b46d9050a9d40d54d24cac3ef8d50402d421f598' + + # Setup MSVC environment on windows, otherwise we get minsys2 / gcc + - uses: ilammy/msvc-dev-cmd@v1 - name: 'Install ubuntu dependencies' if: matrix.os == 'ubuntu-latest' diff --git a/include/adm/document.hpp b/include/adm/document.hpp index 3e605782..34d52436 100644 --- a/include/adm/document.hpp +++ b/include/adm/document.hpp @@ -20,19 +20,29 @@ namespace adm { namespace detail { extern template class ADM_EXPORT_TEMPLATE_METHODS OptionalParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; - using DocumentBase = HasParameters>; + using DocumentBase = + HasParameters, OptionalParameter, + OptionalParameter>; } // namespace detail /** * @brief Class representation of a whole ADM document * * \rst - * +---------------+-----------------+----------------------------+ - * | ADM Parameter | Parameter Type | Pattern Type | - * +===============+=================+============================+ - * | version | :type:`Version` | :class:`OptionalParameter` | - * +---------------+-----------------+----------------------------+ + * +---------------+---------------------+----------------------------+ + * | ADM Parameter | Parameter Type | Pattern Type | + * +===============+=====================+============================+ + * | version | :type:`Version` | :class:`OptionalParameter` | + * +---------------+---------------------+----------------------------+ + * | tagList | :type:`TagList` | :class:`OptionalParameter` | + * +---------------+---------------------+----------------------------+ + * | profileList | :type:`ProfileList` | :class:`OptionalParameter` | + * +---------------+---------------------+----------------------------+ * \endrst * * Note that: @@ -86,6 +96,15 @@ namespace adm { * * References from and to the ADM element will automatically be removed * too. + * + * Additional side effects: + * - removing an AudioProgramme, AudioContent, or AudioObject prunes + * TagGroup entries in tagList that reference the removed element + * - If the TagList becomes empty due to pruning, the list itself is + * removed as it is only valid when it contains 1 or more TagGroups + * - removing an AudioPackFormat or AudioObject also removes matching + * renderer/referenceLayout ID references in nested + * authoringInformation/loudnessMetadata structures. */ ///@{ /// @brief Remove an AudioProgramme @@ -236,6 +255,23 @@ namespace adm { using detail::AddWrapperMethods::isDefault; using detail::AddWrapperMethods::unset; + /** + * @brief Set the document's tagList. + * + * Each TagGroup's audioProgramme/audioContent/audioObject references are + * validated against the document: + * * if a referenced element already belongs to a *different* document, + * the document is left unmodified and `false` is returned; + * * if a referenced element is not yet attached to any document, it is + * added to this document (mirroring the auto-add behaviour of + * `Document::add(...)` for nested references); + * * elements already belonging to this document are left untouched. + * + * @return `true` on success, `false` if any reference belongs to another + * document. + */ + ADM_EXPORT bool set(TagList tagList); + private: ADM_EXPORT Document(); ADM_EXPORT Document(const Document &) = default; @@ -316,5 +352,4 @@ namespace adm { typedef typename detail::ParameterTraits::tag Tag; return getElements(Tag()); } - } // namespace adm diff --git a/include/adm/elements.hpp b/include/adm/elements.hpp index 9fdf9366..87aa24b1 100644 --- a/include/adm/elements.hpp +++ b/include/adm/elements.hpp @@ -27,6 +27,7 @@ #include "adm/elements/audio_stream_format.hpp" #include "adm/elements/audio_track_uid.hpp" #include "adm/elements/profile_list.hpp" +#include "adm/elements/tag_list.hpp" #include "adm/elements/audio_block_format_direct_speakers.hpp" #include "adm/elements/audio_block_format_matrix.hpp" @@ -48,6 +49,8 @@ #include "adm/elements/time.hpp" #include "adm/elements/audio_programme_ref_screen.hpp" +#include "adm/elements/authoring_information.hpp" +#include "adm/elements/cartesian.hpp" #include "adm/elements/channel_lock.hpp" #include "adm/elements/dialogue.hpp" #include "adm/elements/format_descriptor.hpp" diff --git a/include/adm/elements/audio_block_format_direct_speakers.hpp b/include/adm/elements/audio_block_format_direct_speakers.hpp index 59faba92..bfd1618e 100644 --- a/include/adm/elements/audio_block_format_direct_speakers.hpp +++ b/include/adm/elements/audio_block_format_direct_speakers.hpp @@ -68,6 +68,8 @@ namespace adm { * +---------------------+------------------------------------+----------------------------+ * | initializeBlock | :type:`InitializeBlock` | :class:`OptionalParameter` | * +---------------------+------------------------------------+----------------------------+ + * | cartesian | :type:`Cartesian` | custom, see below | + * +---------------------+------------------------------------+----------------------------+ * | position | - :type:`SpeakerPosition` | :class:`VariantParameter` | * | | - :type:`SphericalSpeakerPosition` | | * | | - :type:`CartesianSpeakerPosition` | :class:`RequiredParameter` | @@ -83,6 +85,11 @@ namespace adm { * | speakerLabel | :type:`SpeakerLabels` | :class:`VectorParameter` | * +---------------------+------------------------------------+----------------------------+ * \endrst + * + * ``cartesian`` and ``position`` attributes are linked; see + * :func:`void set(Cartesian)`, :func:`void set(SpeakerPosition)`, + * :func:`void set(CartesianSpeakerPosition)` and + * :func:`void set(SphericalSpeakerPosition)`. * * @warning not all methods are implemented for speakerLabel */ @@ -138,6 +145,8 @@ namespace adm { ADM_EXPORT void set(Rtime rtime); /// @brief Duration setter ADM_EXPORT void set(Duration duration); + /// @brief Cartesian setter + ADM_EXPORT void set(Cartesian cartesian); /// @brief CartesianSpeakerPosition setter ADM_EXPORT void set(CartesianSpeakerPosition speakerPosition); /// @brief SphericalSpeakerPosition setter @@ -174,6 +183,7 @@ namespace adm { ADM_EXPORT Duration get(detail::ParameterTraits::tag) const; ADM_EXPORT SpeakerLabels get(detail::ParameterTraits::tag) const; + ADM_EXPORT Cartesian get(detail::ParameterTraits::tag) const; ADM_EXPORT CartesianSpeakerPosition get(detail::ParameterTraits::tag) const; ADM_EXPORT SphericalSpeakerPosition @@ -183,6 +193,7 @@ namespace adm { ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; + ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has( detail::ParameterTraits::tag) const; ADM_EXPORT bool has( @@ -192,15 +203,18 @@ namespace adm { bool isDefault(Tag) const { return false; } + ADM_EXPORT bool isDefault(detail::ParameterTraits::tag) const; ADM_EXPORT void unset(detail::ParameterTraits::tag); ADM_EXPORT void unset(detail::ParameterTraits::tag); ADM_EXPORT void unset(detail::ParameterTraits::tag); + ADM_EXPORT void unset(detail::ParameterTraits::tag); AudioBlockFormatId id_; boost::optional rtime_; boost::optional duration_; SpeakerLabels speakerLabels_; + boost::optional cartesian_; SpeakerPosition speakerPosition_; }; diff --git a/include/adm/elements/audio_block_format_objects.hpp b/include/adm/elements/audio_block_format_objects.hpp index 4c57faeb..afdd4a8e 100644 --- a/include/adm/elements/audio_block_format_objects.hpp +++ b/include/adm/elements/audio_block_format_objects.hpp @@ -18,11 +18,6 @@ namespace adm { class Document; - - /// @brief Tag for NamedType ::Cartesian - struct CartesianTag {}; - /// @brief NamedType for cartesian parameter - using Cartesian = detail::NamedType; /// @brief Tag for NamedType ::Width struct WidthTag {}; /// @brief NamedType for width parameter diff --git a/include/adm/elements/audio_channel_format.hpp b/include/adm/elements/audio_channel_format.hpp index 45df467e..aa474157 100644 --- a/include/adm/elements/audio_channel_format.hpp +++ b/include/adm/elements/audio_channel_format.hpp @@ -381,7 +381,7 @@ namespace adm { return previous == 0u || current == previous.get() + 1u; } } - + template void AudioChannelFormat::assignId(BlockFormat &blockFormat, BlockFormat *previousBlock) { diff --git a/include/adm/elements/audio_programme.hpp b/include/adm/elements/audio_programme.hpp index a1728aa2..f29dd88f 100644 --- a/include/adm/elements/audio_programme.hpp +++ b/include/adm/elements/audio_programme.hpp @@ -9,6 +9,7 @@ #include "adm/elements/audio_content.hpp" #include "adm/elements/audio_programme_id.hpp" #include "adm/elements/audio_programme_ref_screen.hpp" +#include "adm/elements/authoring_information.hpp" #include "adm/elements/loudness_metadata.hpp" #include "adm/elements_fwd.hpp" #include "adm/helper/element_range.hpp" @@ -158,6 +159,8 @@ namespace adm { ADM_EXPORT void set(MaxDuckingDepth depth); /// @brief AudioProgrammeReferenceScreen setter ADM_EXPORT void set(AudioProgrammeReferenceScreen refScreen); + /// @brief AuthoringInformation setter + ADM_EXPORT void set(AuthoringInformation authoringInformation); /** * @brief ADM parameter unset template @@ -237,6 +240,8 @@ namespace adm { get(detail::ParameterTraits::tag) const; ADM_EXPORT AudioProgrammeReferenceScreen get(detail::ParameterTraits::tag) const; + ADM_EXPORT AuthoringInformation + get(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; @@ -247,6 +252,8 @@ namespace adm { ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has( detail::ParameterTraits::tag) const; + ADM_EXPORT bool has( + detail::ParameterTraits::tag) const; template bool isDefault(Tag) const { @@ -261,6 +268,7 @@ namespace adm { ADM_EXPORT void unset(detail::ParameterTraits::tag); ADM_EXPORT void unset( detail::ParameterTraits::tag); + ADM_EXPORT void unset(detail::ParameterTraits::tag); ADM_EXPORT ElementRange getReferences( detail::ParameterTraits::tag) const; @@ -283,6 +291,7 @@ namespace adm { std::vector> audioContents_; boost::optional maxDuckingDepth_; boost::optional refScreen_; + boost::optional authoringInformation_; }; ///@} diff --git a/include/adm/elements/audio_programme_ref_screen.hpp b/include/adm/elements/audio_programme_ref_screen.hpp index 5b44673e..8a0b6bbc 100644 --- a/include/adm/elements/audio_programme_ref_screen.hpp +++ b/include/adm/elements/audio_programme_ref_screen.hpp @@ -1,20 +1,51 @@ #pragma once #include +#include "adm/detail/auto_base.hpp" +#include "adm/detail/named_option_helper.hpp" +#include "adm/elements/coordinate_mode.hpp" +#include "adm/export.h" namespace adm { struct AudioProgrammeReferenceScreenTag {}; - class AudioProgrammeReferenceScreen { + namespace detail { + using AudioProgrammeReferenceScreenBase = + HasParameters>; + } // namespace detail + + class AudioProgrammeReferenceScreen + : private detail::AudioProgrammeReferenceScreenBase, + private detail::AddWrapperMethods { public: - typedef AudioProgrammeReferenceScreenTag tag; + using tag = AudioProgrammeReferenceScreenTag; + + template + explicit AudioProgrammeReferenceScreen(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + using detail::AudioProgrammeReferenceScreenBase::set; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + + void print(std::ostream &os) const { + os << "("; + if (has()) { + os << "coordinateMode=" << get(); + } + os << ")"; + } - AudioProgrammeReferenceScreen() {} + private: + using detail::AudioProgrammeReferenceScreenBase::get; + using detail::AudioProgrammeReferenceScreenBase::has; + using detail::AudioProgrammeReferenceScreenBase::isDefault; + using detail::AudioProgrammeReferenceScreenBase::unset; - /** - * @brief Print overview to ostream - */ - void print(std::ostream &os) const { os << "()"; }; + friend class detail::AddWrapperMethods; }; } // namespace adm diff --git a/include/adm/elements/authoring_information.hpp b/include/adm/elements/authoring_information.hpp new file mode 100644 index 00000000..e274ca71 --- /dev/null +++ b/include/adm/elements/authoring_information.hpp @@ -0,0 +1,184 @@ +/// @file authoring_information.hpp +#pragma once + +#include +#include + +#include "adm/detail/auto_base.hpp" +#include "adm/detail/named_option_helper.hpp" +#include "adm/detail/named_type.hpp" +#include "adm/detail/optional_comparison.hpp" +#include "adm/elements/renderer_common_types.hpp" +#include "adm/export.h" + +namespace adm { + + class LoudnessRenderer; + + /// @brief Tag for ::ReferenceLayout named-type + struct ReferenceLayoutTag {}; + /** + * @brief Reference to audioPackFormat used by a referenceLayout + * sub-element of authoringInformation (BS.2076-3 Table A1-51). + */ + class ReferenceLayout { + public: + using tag = ReferenceLayoutTag; + + ReferenceLayout() = default; + explicit ReferenceLayout(std::shared_ptr packFormat) + : packFormat_(std::move(packFormat)) {} + + std::shared_ptr const &get() const { return packFormat_; } + + private: + std::shared_ptr packFormat_; + }; + + ADD_TRAIT(ReferenceLayout, ReferenceLayoutTag); + + inline bool operator==(ReferenceLayout const &lhs, + ReferenceLayout const &rhs) { + return lhs.get() == rhs.get(); + } + inline bool operator!=(ReferenceLayout const &lhs, + ReferenceLayout const &rhs) { + return !(lhs == rhs); + } + + /// @brief Vector of ReferenceLayout + using ReferenceLayouts = std::vector; + ADD_TRAIT(ReferenceLayouts, ReferenceLayoutsTag); + + /// @brief Tag for Renderer class + struct RendererTag {}; + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS + VectorParameter; + + using RendererBase = HasParameters< + RequiredParameter, OptionalParameter, + OptionalParameter, OptionalParameter, + VectorParameter>; + } // namespace detail + + /** + * @brief Class representation of the renderer sub-element of an + * authoringInformation element (BS.2076-3 Tables A1-52 / A1-53). + */ + class AuthoringRenderer + : private detail::RendererBase, + private detail::AddWrapperMethods { + public: + using tag = RendererTag; + + template + explicit AuthoringRenderer(RendererUri uri, Parameters... namedArgs) { + this->set(std::move(uri)); + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + using detail::RendererBase::add; + using detail::RendererBase::remove; + using detail::RendererBase::set; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + + /// @brief Convert to LoudnessRenderer preserving all shared parameters. + ADM_EXPORT LoudnessRenderer toLoudnessRenderer() const; + + ADM_EXPORT void print(std::ostream &os) const; + + private: + using detail::RendererBase::get; + using detail::RendererBase::has; + using detail::RendererBase::isDefault; + using detail::RendererBase::unset; + + friend class detail::AddWrapperMethods; + }; + + ADD_TRAIT(AuthoringRenderer, RendererTag); + + inline bool operator==(const AuthoringRenderer &a, + const AuthoringRenderer &b) { + if (!detail::optionalsEqual(a, b)) { + return false; + } + + if (a.has() != + b.has()) { + return false; + } + + if (!a.has()) { + return true; + } + + return a.get() == + b.get(); + } + inline bool operator!=(const AuthoringRenderer &a, + const AuthoringRenderer &b) { + return !(a == b); + } + + /// @brief Vector of Renderer + using Renderers = std::vector; + ADD_TRAIT(Renderers, RenderersTag); + + /// @brief Tag for AuthoringInformation class + struct AuthoringInformationTag {}; + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS + VectorParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + VectorParameter; + + using AuthoringInformationBase = + HasParameters, + VectorParameter>; + } // namespace detail + + /** + * @brief Class representation of the authoringInformation sub-element of an + * audioProgramme element (BS.2076-3 §5.8.6). + */ + class AuthoringInformation + : private detail::AuthoringInformationBase, + private detail::AddWrapperMethods { + public: + using tag = AuthoringInformationTag; + + template + explicit AuthoringInformation(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + using detail::AuthoringInformationBase::add; + using detail::AuthoringInformationBase::remove; + using detail::AuthoringInformationBase::set; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + + ADM_EXPORT void print(std::ostream &os) const; + + private: + using detail::AuthoringInformationBase::get; + using detail::AuthoringInformationBase::has; + using detail::AuthoringInformationBase::isDefault; + using detail::AuthoringInformationBase::unset; + + friend class detail::AddWrapperMethods; + }; + + ADD_TRAIT(AuthoringInformation, AuthoringInformationTag); + +} // namespace adm diff --git a/include/adm/elements/cartesian.hpp b/include/adm/elements/cartesian.hpp new file mode 100644 index 00000000..bce91911 --- /dev/null +++ b/include/adm/elements/cartesian.hpp @@ -0,0 +1,11 @@ +/// @file cartesian.hpp +#pragma once + +#include "adm/detail/named_type.hpp" + +namespace adm { + /// @brief Tag for NamedType ::Cartesian + struct CartesianTag {}; + /// @brief NamedType for cartesian parameter + using Cartesian = detail::NamedType; +} // namespace adm diff --git a/include/adm/elements/common_parameters.hpp b/include/adm/elements/common_parameters.hpp index 84a03c9e..ebf58446 100644 --- a/include/adm/elements/common_parameters.hpp +++ b/include/adm/elements/common_parameters.hpp @@ -1,6 +1,7 @@ #pragma once #include "adm/detail/auto_base.hpp" #include "adm/elements/audio_block_format_id.hpp" +#include "adm/elements/cartesian.hpp" #include "adm/elements/gain.hpp" #include "adm/elements/headphone_virtualise.hpp" #include "adm/elements/head_locked.hpp" diff --git a/include/adm/elements/coordinate_mode.hpp b/include/adm/elements/coordinate_mode.hpp new file mode 100644 index 00000000..2a6cce85 --- /dev/null +++ b/include/adm/elements/coordinate_mode.hpp @@ -0,0 +1,16 @@ +/// @file coordinate_mode.hpp +#pragma once + +#include +#include "adm/detail/named_type.hpp" + +namespace adm { + + /// @brief Tag for NamedType ::CoordinateMode + struct CoordinateModeTag {}; + /// @brief NamedType for the coordinateMode attribute + /// + /// Allowable values are "polar" or "cartesian". + using CoordinateMode = detail::NamedType; + +} // namespace adm diff --git a/include/adm/elements/dialogue.hpp b/include/adm/elements/dialogue.hpp index 337dc470..c7974e9c 100644 --- a/include/adm/elements/dialogue.hpp +++ b/include/adm/elements/dialogue.hpp @@ -36,7 +36,7 @@ namespace adm { */ using NonDialogueContentKind = detail::NamedType>; + detail::RangeValidator<0, 3>>; /// NonDialogueContentKind definitions namespace NonDialogueContent { @@ -45,6 +45,8 @@ namespace adm { const NonDialogueContentKind MUSIC = NonDialogueContentKind(1); /// NonDialogueContentKind for effects const NonDialogueContentKind EFFECT = NonDialogueContentKind(2); + /// NonDialogueContentKind for music and effects + const NonDialogueContentKind MUSIC_AND_EFFECTS = NonDialogueContentKind(3); } // namespace NonDialogueContent /// @brief Tag for NamedType ::DialogueContentKind @@ -80,10 +82,10 @@ namespace adm { /** * @brief NamedType for the mixedContentKind type * - * Valid values are in the range [0, 3] + * Valid values are in the range [0, 4] */ using MixedContentKind = detail::NamedType>; + detail::RangeValidator<0, 4>>; /// MixedContent definitions namespace MixedContent { @@ -94,6 +96,9 @@ namespace adm { const MixedContentKind MIXED = MixedContentKind(2); /// MixedContentKind for hearing impaired const MixedContentKind HEARING_IMPAIRED = MixedContentKind(3); + /// MixedContentKind for complete main with audio description/hearing impaired + const MixedContentKind COMPLETE_MAIN_HEARING_IMPAIRED_AD = + MixedContentKind(4); } // namespace MixedContent ///@brief Type to hold a NonDialogueContentKind, DialogueContentKind or diff --git a/include/adm/elements/loudness_metadata.hpp b/include/adm/elements/loudness_metadata.hpp index e0ef37e1..90e3727e 100644 --- a/include/adm/elements/loudness_metadata.hpp +++ b/include/adm/elements/loudness_metadata.hpp @@ -3,6 +3,7 @@ #include "adm/detail/named_type.hpp" #include "adm/detail/auto_base.hpp" #include "adm/detail/optional_comparison.hpp" +#include "adm/elements/loudness_renderer.hpp" #include "adm/export.h" #include #include @@ -11,6 +12,8 @@ namespace adm { + class AuthoringRenderer; + /// @brief Tag for NamedType ::LoudnessMethod struct loudnessMethodTag {}; /// @brief NamedType for loudnessMethod parameter @@ -115,6 +118,10 @@ namespace adm { ADM_EXPORT void set(MaxShortTerm maxShortTerm); /// @brief DialogueLoudness setter ADM_EXPORT void set(DialogueLoudness dialogueLoudness); + /// @brief LoudnessRenderer setter + ADM_EXPORT void set(LoudnessRenderer renderer); + /// @brief Renderer setter (converted to LoudnessRenderer) + ADM_EXPORT void set(AuthoringRenderer renderer); /** * @brief ADM parameter unset template @@ -149,6 +156,8 @@ namespace adm { get(detail::ParameterTraits::tag) const; ADM_EXPORT DialogueLoudness get(detail::ParameterTraits::tag) const; + ADM_EXPORT LoudnessRenderer + get(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; @@ -160,6 +169,7 @@ namespace adm { ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; ADM_EXPORT bool has(detail::ParameterTraits::tag) const; + ADM_EXPORT bool has(detail::ParameterTraits::tag) const; template bool isDefault(Tag) const { @@ -175,6 +185,7 @@ namespace adm { ADM_EXPORT void unset(detail::ParameterTraits::tag); ADM_EXPORT void unset(detail::ParameterTraits::tag); ADM_EXPORT void unset(detail::ParameterTraits::tag); + ADM_EXPORT void unset(detail::ParameterTraits::tag); boost::optional loudnessMethod_; boost::optional loudnessRecType_; @@ -185,6 +196,7 @@ namespace adm { boost::optional maxMomentary_; boost::optional maxShortTerm_; boost::optional dialogueLoudness_; + boost::optional renderer_; }; // ---- Implementation ---- // diff --git a/include/adm/elements/loudness_renderer.hpp b/include/adm/elements/loudness_renderer.hpp new file mode 100644 index 00000000..3f9cb43f --- /dev/null +++ b/include/adm/elements/loudness_renderer.hpp @@ -0,0 +1,83 @@ +/// @file loudness_renderer.hpp +#pragma once + +#include + +#include "adm/detail/auto_base.hpp" +#include "adm/detail/named_option_helper.hpp" +#include "adm/elements/renderer_common_types.hpp" +#include "adm/export.h" + +namespace adm { + + class AuthoringRenderer; + + /// @brief Tag for LoudnessRenderer class + struct LoudnessRendererTag {}; + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + VectorParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + VectorParameter; + + using LoudnessRendererBase = HasParameters< + OptionalParameter, OptionalParameter, + OptionalParameter, OptionalParameter, + VectorParameter, + VectorParameter>; + } // namespace detail + + /** + * @brief Class representation of the renderer sub-element of a + * loudnessMetadata element (BS.2076-3 Tables A1-39 / A1-40). + */ + class LoudnessRenderer : private detail::LoudnessRendererBase, + private detail::AddWrapperMethods { + public: + using tag = LoudnessRendererTag; + + template + explicit LoudnessRenderer(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + using detail::LoudnessRendererBase::add; + using detail::LoudnessRendererBase::remove; + using detail::LoudnessRendererBase::set; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + + /// @brief Create a LoudnessRenderer from an authoring Renderer. + ADM_EXPORT static LoudnessRenderer fromRenderer( + AuthoringRenderer const& renderer); + + /// @brief Convert to Renderer, using provided uri and explicitly dropping audioObjectIDRef values. + /// uri is required as it is optional in a Loudness renderer but required in authoring renderer + ADM_EXPORT AuthoringRenderer + toRendererDroppingObjectRefs(RendererUri uri) const; + + ADM_EXPORT void print(std::ostream& os) const; + + private: + using detail::LoudnessRendererBase::get; + using detail::LoudnessRendererBase::has; + using detail::LoudnessRendererBase::isDefault; + using detail::LoudnessRendererBase::unset; + + friend class detail::AddWrapperMethods; + }; + + ADD_TRAIT(LoudnessRenderer, LoudnessRendererTag); + +} // namespace adm diff --git a/include/adm/elements/profile_list.hpp b/include/adm/elements/profile_list.hpp index a49faf0d..ec19a79e 100644 --- a/include/adm/elements/profile_list.hpp +++ b/include/adm/elements/profile_list.hpp @@ -85,12 +85,12 @@ namespace adm { using ProfileListBase = HasParameters>; } // namespace detail - struct ProfileceListTag {}; + struct ProfileListTag {}; class ProfileList : private detail::ProfileListBase, private detail::AddWrapperMethods { public: - using tag = ProfileceListTag; + using tag = ProfileListTag; template explicit ProfileList(Parameters... namedArgs) { diff --git a/include/adm/elements/renderer_common_types.hpp b/include/adm/elements/renderer_common_types.hpp new file mode 100644 index 00000000..e141c090 --- /dev/null +++ b/include/adm/elements/renderer_common_types.hpp @@ -0,0 +1,58 @@ +/// @file renderer_common_types.hpp +#pragma once + +#include +#include +#include +#include + +#include "adm/detail/auto_base.hpp" +#include "adm/detail/named_type.hpp" +#include "adm/elements/coordinate_mode.hpp" +#include "adm/elements_fwd.hpp" + +namespace adm { + + /// @brief Tag for NamedType ::RendererUri + struct RendererUriTag {}; + /// @brief NamedType for the renderer uri attribute + using RendererUri = detail::NamedType; + + /// @brief Tag for NamedType ::RendererName + struct RendererNameTag {}; + /// @brief NamedType for the renderer name attribute + using RendererName = detail::NamedType; + + /// @brief Tag for NamedType ::RendererVersion + struct RendererVersionTag {}; + /// @brief NamedType for the renderer version attribute + using RendererVersion = detail::NamedType; + + /// @brief Vector of audioPackFormat references used by a renderer + using RendererPackFormatIdRefs = + std::vector>; + ADD_TRAIT(RendererPackFormatIdRefs, RendererPackFormatIdRefsTag); + + /// @brief Vector of audioObject references used by a renderer + using RendererObjectIdRefs = std::vector>; + ADD_TRAIT(RendererObjectIdRefs, RendererObjectIdRefsTag); + + namespace detail { + template <> + struct ParameterCompare { + static bool compare(RendererPackFormatIdRefs const& lhs, + RendererPackFormatIdRefs const& rhs) { + return lhs == rhs; + } + }; + + template <> + struct ParameterCompare { + static bool compare(RendererObjectIdRefs const& lhs, + RendererObjectIdRefs const& rhs) { + return lhs == rhs; + } + }; + } // namespace detail + +} // namespace adm diff --git a/include/adm/elements/tag_list.hpp b/include/adm/elements/tag_list.hpp new file mode 100644 index 00000000..136fe469 --- /dev/null +++ b/include/adm/elements/tag_list.hpp @@ -0,0 +1,300 @@ +#pragma once +#include +#include "adm/detail/auto_base.hpp" +#include "adm/elements/audio_programme.hpp" +#include "adm/elements_fwd.hpp" +#include "adm/detail/named_option_helper.hpp" +#include "adm/detail/optional_comparison.hpp" +#include "adm/errors.hpp" + +namespace adm { + + class TagList; + + struct TagValueTag {}; + using TagValue = detail::NamedType; + + struct TagClassTag {}; + using TagClass = detail::NamedType; + + struct TagTag {}; + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS + RequiredParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; + + using TagBase = + HasParameters, OptionalParameter>; + } // namespace detail + + class Tag : private detail::TagBase, private detail::AddWrapperMethods { + public: + using tag = TagTag; + + template + explicit Tag(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + ADM_EXPORT explicit Tag(std::string str) : Tag(TagValue(std::move(str))) {} + ADM_EXPORT explicit Tag(const char *s); + + ADM_EXPORT void print(std::ostream &os) const; + + using detail::TagBase::set; + using detail::TagBase::unset; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + + private: + using detail::TagBase::get; + using detail::TagBase::has; + + friend class detail::AddWrapperMethods; + }; + + struct TagsTag {}; + + using Tags = std::vector; + ADD_TRAIT(Tags, TagsTag); + + inline bool operator==(const Tag &a, const Tag &b) { + return detail::optionalsEqual(a, b); + } + + inline bool operator!=(const Tag &a, const Tag &b) { return !(a == b); } + + struct TagGroupTag {}; + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS VectorParameter; + + using TagGroupBase = HasParameters>; + } // namespace detail + + class TagGroup : private detail::TagGroupBase, + private detail::AddWrapperMethods { + public: + enum class RemoveResult { + Success, + LastReferenceError, // A TagGroup must always have at least one reference + NotFound + }; + using tag = TagGroupTag; + + TagGroup() = default; + + template + explicit TagGroup(std::shared_ptr const &reference, + Parameters... namedArgs) { + addReference(reference); + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + template + explicit TagGroup(std::shared_ptr const &reference, + Parameters... namedArgs) { + addReference(reference); + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + template + explicit TagGroup(std::shared_ptr const &reference, + Parameters... namedArgs) { + addReference(reference); + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + /// @brief Add reference to an AudioProgramme + ADM_EXPORT bool addReference(std::shared_ptr programme); + + /// @brief Add reference to an AudioContent + ADM_EXPORT bool addReference(std::shared_ptr content); + + /// @brief Add reference to an AudioObject + ADM_EXPORT bool addReference(std::shared_ptr object); + + template + ElementRange getReferences(); + + template + ElementRange getReferences() const; + + /// @brief Remove reference to an AudioProgramme + ADM_EXPORT RemoveResult + removeReference(std::shared_ptr programme); + + /// @brief Remove reference to an AudioContent + ADM_EXPORT RemoveResult + removeReference(std::shared_ptr content); + + /// @brief Remove reference to an AudioObject + ADM_EXPORT RemoveResult + removeReference(std::shared_ptr object); + + template + void clearReferences(); + + using AddWrapperMethods::get; + using AddWrapperMethods::has; + using AddWrapperMethods::isDefault; + using AddWrapperMethods::unset; + using detail::TagGroupBase::add; + using detail::TagGroupBase::remove; + using detail::TagGroupBase::set; + + template + bool has() const { + return has(typename detail::ParameterTraits::tag{}); + } + + private: + using detail::TagGroupBase::get; + using detail::TagGroupBase::has; + using detail::TagGroupBase::isDefault; + using detail::TagGroupBase::unset; + + friend class detail::AddWrapperMethods; + friend class Document; + friend class TagList; + + bool invalid() const; + + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag) const; + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag); + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag) const; + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag); + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag) const; + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag); + + std::vector> audioProgrammes_; + std::vector> audioContents_; + std::vector> audioObjects_; + }; + + inline bool operator==(const TagGroup &a, const TagGroup &b) { + return detail::optionalsEqual(a, b) && + detail::elementRangeEqual( + a.getReferences(), + b.getReferences()) && + detail::elementRangeEqual( + a.getReferences(), + b.getReferences()) && + detail::elementRangeEqual( + a.getReferences(), + b.getReferences()); + } + + inline bool operator!=(const TagGroup &a, const TagGroup &b) { + return !(a == b); + } + + template + ElementRange TagGroup::getReferences() const { + typedef typename detail::ParameterTraits::tag Tag; + return getReferences(Tag()); + } + + template + ElementRange TagGroup::getReferences() { + typedef typename detail::ParameterTraits::tag Tag; + return getReferences(Tag()); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) const { + return ElementRange(audioProgrammes_.begin(), + audioProgrammes_.end()); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) const { + return ElementRange(audioContents_.begin(), + audioContents_.end()); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) const { + return ElementRange(audioObjects_.begin(), + audioObjects_.end()); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) { + return ElementRange(audioProgrammes_.begin(), + audioProgrammes_.end()); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) { + return ElementRange(audioContents_.begin(), + audioContents_.end()); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) { + return ElementRange(audioObjects_.begin(), + audioObjects_.end()); + } + + template + void TagGroup::clearReferences() { + typedef typename detail::ParameterTraits::tag Tag; + clearReferences(Tag()); + } + + struct TagGroupsTag {}; + + using TagGroups = std::vector; + ADD_TRAIT(TagGroups, TagGroupsTag); + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS + VectorParameter; + + using TagListBase = HasParameters>; + } // namespace detail + + struct TagListTag {}; + + class TagList : private detail::TagListBase, + private detail::AddWrapperMethods, + public std::enable_shared_from_this { + public: + template + std::shared_ptr create(Parameters... namedArgs) { + return std::make_shared( + std::forward(namedArgs...)); + } + using tag = TagListTag; + using detail::TagListBase::set; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + using detail::TagListBase::remove; + + ADM_EXPORT bool add(TagGroup group); + + template + explicit TagList(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + private: + using detail::TagListBase::get; + using detail::TagListBase::has; + using detail::TagListBase::isDefault; + using detail::TagListBase::unset; + + friend class detail::AddWrapperMethods; + }; +} // namespace adm diff --git a/include/adm/elements_fwd.hpp b/include/adm/elements_fwd.hpp index 518bdb2e..45c3f57f 100644 --- a/include/adm/elements_fwd.hpp +++ b/include/adm/elements_fwd.hpp @@ -47,5 +47,8 @@ namespace adm { class AudioTrackUidId; class LoudnessMetadata; + class LoudnessRenderer; + class AuthoringRenderer; + class AuthoringInformation; class AudioProgrammeReferenceScreen; } // namespace adm diff --git a/include/adm/helper/element_range.hpp b/include/adm/helper/element_range.hpp index 1cd1678a..8d170942 100644 --- a/include/adm/helper/element_range.hpp +++ b/include/adm/helper/element_range.hpp @@ -197,6 +197,18 @@ namespace adm { [](std::weak_ptr w) { return w.lock(); }); return result; } + template + bool elementRangeEqual(ElementRange a, + ElementRange b) { + if (a.size() != b.size()) { + return false; + } + bool equal = true; + for (std::size_t i = 0; i != a.size() && equal; ++i) { + equal = a[i] == b[i]; + } + return equal; + } } // namespace detail diff --git a/include/adm/private/copy.hpp b/include/adm/private/copy.hpp index 9fa6e544..3c464d22 100644 --- a/include/adm/private/copy.hpp +++ b/include/adm/private/copy.hpp @@ -9,9 +9,48 @@ namespace adm { + /** + * @brief Per-element-kind shared_ptr mapping from a source Document's + * elements to their copies. Populated by `copyAllElements` and consumed + * by reference-resolution helpers such as `resolveReferences` and + * `copyAuxiliary`. + */ + struct ElementMapping { + // clang-format off + std::unordered_map, std::shared_ptr> audioProgramme; + std::unordered_map, std::shared_ptr> audioContent; + std::unordered_map, std::shared_ptr> audioObject; + std::unordered_map, std::shared_ptr> audioPackFormat; + std::unordered_map, std::shared_ptr> audioChannelFormat; + std::unordered_map, std::shared_ptr> audioStreamFormat; + std::unordered_map, std::shared_ptr> audioTrackFormat; + std::unordered_map, std::shared_ptr> audioTrackUid; + // clang-format on + }; + std::vector copyAllElements( std::shared_ptr document); + /** + * @brief Like `copyAllElements`, but also reports the source-to-copy + * mapping for every element kind, so callers can translate references + * stored in document-level parameters (e.g. tagList). + */ + std::vector copyAllElements( + std::shared_ptr document, ElementMapping& mapping); + + /** + * @brief Copy document-level auxiliary parameters (ProfileList, TagList) + * from `src` to `dest`, translating any element references in the + * TagList through `mapping`. + * + * Must be called *after* the copied elements have been added to `dest`, + * so that `Document::set(TagList)` can adopt them via paternity checks. + */ + void copyAuxiliary(std::shared_ptr src, + std::shared_ptr dest, + ElementMapping const& mapping); + template class AddTo : public boost::static_visitor<> { public: diff --git a/include/adm/private/document_parser.hpp b/include/adm/private/document_parser.hpp index ee2ac5c2..fd3e1380 100644 --- a/include/adm/private/document_parser.hpp +++ b/include/adm/private/document_parser.hpp @@ -45,6 +45,8 @@ namespace adm { LoudnessMetadatas parseLoudnessMetadatas(const std::vector& nodes); AudioProgrammeReferenceScreen parseAudioProgrammeReferenceScreen( NodePtr node); + AuthoringRenderer parseAuthoringRenderer(NodePtr node); + AuthoringInformation parseAuthoringInformation(NodePtr node); Label parseLabel(NodePtr node); AudioBlockFormatObjects parseAudioBlockFormatObjects( NodePtr node, boost::optional timeReference); @@ -70,6 +72,7 @@ namespace adm { NodePtr node, boost::optional timeReference); Profile parseProfile(NodePtr node); ProfileList parseProfileList(NodePtr node); + Tag parseTTag(NodePtr node); NodePtr findAudioFormatExtendedNodeEbuCore(NodePtr root); NodePtr findAudioFormatExtendedNodeFullRecursive(NodePtr root); @@ -105,6 +108,8 @@ namespace adm { std::shared_ptr parseAudioPackFormat(NodePtr node); std::shared_ptr parseAudioTrackUid(NodePtr node); std::shared_ptr parseAudioChannelFormat(NodePtr node); + std::shared_ptr parseTagGroup(NodePtr node); + TagList parseTagList(NodePtr node); rapidxml::file<> xmlFile_; ParserOptions options_; @@ -127,6 +132,9 @@ namespace adm { std::map, AudioChannelFormatId> streamFormatChannelFormatRef_; std::map, AudioPackFormatId> streamFormatPackFormatRef_; std::map, std::vector> streamFormatTrackFormatRefs_; + std::map, std::vector> tagGroupProgrammeRefs_; + std::map, std::vector> tagGroupContentRefs_; + std::map, std::vector> tagGroupObjectRefs_; // clang-format on /// used to keep track of element IDs ourselves to avoid having it @@ -154,6 +162,10 @@ namespace adm { const std::map, std::vector>& map); + void resolveProgrammeAuthoringRendererReferences(); + void resolveProgrammeLoudnessRendererReferences(); + void resolveContentLoudnessRendererReferences(); + template void resolveReference(const std::map& map) { for (const auto& entry : map) { @@ -167,6 +179,21 @@ namespace adm { } void setCommonProperties(std::shared_ptr audioPackFormat, NodePtr node); + + struct RendererNestedIds { + std::vector packFormatIds; + std::vector objectIds; + }; + + std::map, std::vector> + programmeAuthoringReferenceLayoutPackFormatRefs_; + std::map, + std::vector>> + programmeAuthoringRendererPackFormatRefs_; + std::map, std::vector> + programmeLoudnessRendererRefs_; + std::map, std::vector> + contentLoudnessRendererRefs_; }; } // namespace xml diff --git a/include/adm/private/rapidxml_formatter.hpp b/include/adm/private/rapidxml_formatter.hpp index df06671e..4ba22a3e 100644 --- a/include/adm/private/rapidxml_formatter.hpp +++ b/include/adm/private/rapidxml_formatter.hpp @@ -55,6 +55,9 @@ namespace adm { XmlNode &node, const std::shared_ptr trackUid); void formatProfileList(XmlNode &node, const ProfileList &profileList); void formatProfile(XmlNode &node, const Profile &profile); + void formatTagList(XmlNode &node, const TagList &tagList); + void formatTagGroup(XmlNode &node, const TagGroup &tagGroup); + void formatTag(XmlNode &node, const Tag &tag); void formatBlockFormatDirectSpeakers( XmlNode &node, const AudioBlockFormatDirectSpeakers &audioBlock, diff --git a/include/adm/private/rapidxml_wrapper.hpp b/include/adm/private/rapidxml_wrapper.hpp index 5d4ce89a..65a9af99 100644 --- a/include/adm/private/rapidxml_wrapper.hpp +++ b/include/adm/private/rapidxml_wrapper.hpp @@ -102,6 +102,10 @@ namespace adm { void addBaseElements(const Source &src, const std::string &name, Callable formatter); + template + void addBaseElement(const Source &src, const std::string &name, + Callable formatter); + template void addReference(const Source &src, const std::string &name); @@ -262,6 +266,16 @@ namespace adm { } } + template + void XmlNode::addBaseElement(const Source &src, const std::string &name, + Callable formatter) { + auto admElement = src->template getElement(); + if (admElement) { + auto node = addNode(name); + formatter(node, *admElement); + } + } + template void XmlNode::addReference(const Source &src, const std::string &name) { addElement(src->template getReference(), name); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b11f800f..37e71fa0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(adm elements/audio_block_format_binaural.cpp elements/audio_object_interaction.cpp elements/audio_pack_format_hoa.cpp + elements/authoring_information.cpp elements/common_parameters.cpp elements/time.cpp elements/channel_lock.cpp @@ -44,6 +45,7 @@ add_library(adm elements/jump_position.cpp elements/label.cpp elements/loudness_metadata.cpp + elements/loudness_renderer.cpp elements/object_divergence.cpp elements/position.cpp elements/position_offset.cpp @@ -54,6 +56,7 @@ add_library(adm elements/type_descriptor.cpp elements/format_descriptor.cpp elements/headphone_virtualise.cpp + elements/tag_list.cpp utilities/block_duration_assignment.cpp utilities/copy.cpp utilities/id_assignment.cpp diff --git a/src/document.cpp b/src/document.cpp index 85759cdd..c08d0bcc 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -8,12 +8,218 @@ #include "adm/private/copy.hpp" #include +#include namespace adm { + namespace { + template + bool pruneIf(ContainerT& container, Predicate predicate) { + auto end = std::remove_if(container.begin(), container.end(), predicate); + if (end == container.end()) { + return false; + } + container.erase(end, container.end()); + return true; + } + + template + void pruneTagGroupsReferencing( + Document& document, + std::shared_ptr const& removedElement) { + if (!document.has()) return; + auto list = document.get(); + auto groups = list.get(); + auto pruned = pruneIf(groups, [&](TagGroup const& group) { + auto refs = group.template getReferences(); + return std::find(refs.begin(), refs.end(), removedElement) != + refs.end(); + }); + if (!pruned) return; + if (groups.empty()) { + document.unset(); + return; + } + TagList newList; + for (auto& group : groups) newList.add(group); + document.set(newList); + } + } // namespace + namespace detail { template class OptionalParameter; + template class OptionalParameter; + template class OptionalParameter; } // namespace detail + namespace { + // Bound the recursion depth of Document::add() to prevent a stack + // overflow when the cross-reference graph is pathologically deep + // (e.g. an adversarial XML with thousands of nested + // audioPackFormatIDRefs). Re-entering an already-added element is + // already short-circuited by checkParent(), so this only fires on + // genuinely deep, unique chains. The limit is far above any realistic + // ADM document. + constexpr int kMaxAddRecursionDepth = 1000; + thread_local int g_addRecursionDepth = 0; + + struct AddRecursionGuard { + AddRecursionGuard() { + if (g_addRecursionDepth >= kMaxAddRecursionDepth) { + throw std::runtime_error( + "Document::add: cross-reference recursion depth exceeded " + "(possible deeply-nested or malformed input)"); + } + ++g_addRecursionDepth; + } + ~AddRecursionGuard() { --g_addRecursionDepth; } + AddRecursionGuard(const AddRecursionGuard&) = delete; + AddRecursionGuard& operator=(const AddRecursionGuard&) = delete; + }; + + template + bool pruneRendererRefs( + RendererType& renderer, + std::shared_ptr const& removedElement) { + if (!renderer.template has()) { + return false; + } + auto const removedId = removedElement->template get(); + auto refs = renderer.template get(); + pruneIf(refs, [&removedId, &removedElement](auto const& ref) { + if (ref == removedElement) { + return true; + } + bool removed = ref->template get() == + removedId; + return removed; + }); + + if (refs.empty()) { + renderer.template unset(); + } else { + renderer.set(std::move(refs)); + } + return true; + } + + template + bool pruneLoudnessMetadataIdRefs( + LoudnessMetadatas& data, + std::shared_ptr removedElement) { + bool changed = false; + for (auto& loudnessMetadata : data) { + if (!loudnessMetadata.has()) { + continue; + } + auto renderer = loudnessMetadata.get(); + if (!pruneRendererRefs>>( + renderer, + removedElement)) { + continue; + } + loudnessMetadata.set(std::move(renderer)); + changed = true; + } + return changed; + + } + + template + void pruneElementLoudnessIdRefs( + std::shared_ptr const& element, + std::shared_ptr const& removedElement) { + if (!element->template has()) { + return; + } + auto loudnessMetadatas = element->template get(); + if (!pruneLoudnessMetadataIdRefs(loudnessMetadatas, removedElement)) { + return; + } + element->set(std::move(loudnessMetadatas)); + } + + bool pruneAuthoringInformationPackFormatIdRefs( + AuthoringInformation& info, + AudioPackFormatId const& removedId, + std::shared_ptr const& removedPackFormat) { + bool changed = false; + + if (info.has()) { + auto renderers = info.get(); + bool renderersChanged = false; + for (auto& renderer : renderers) { + renderersChanged |= + pruneRendererRefs( + renderer, + removedPackFormat); + } + if (renderersChanged) { + if (renderers.empty()) { + info.unset(); + } else { + info.set(std::move(renderers)); + } + changed = true; + } + } + + if (info.has()) { + auto referenceLayouts = info.get(); + auto pruned = pruneIf(referenceLayouts, [&](ReferenceLayout const& layout) { + auto const& ref = layout.get(); + if (ref == removedPackFormat) { + return true; + } + return ref->get() == removedId; + }); + if (pruned) { + if (referenceLayouts.empty()) { + info.unset(); + } else { + info.set(std::move(referenceLayouts)); + } + changed = true; + } + } + + return changed; + } + + void prunePackFormatIdRefs( + Document& document, + AudioPackFormatId const& removedId, + std::shared_ptr const& removedPackFormat) { + for (auto const& programme : document.getElements()) { + if (programme->has()) { + auto info = programme->get(); + if (pruneAuthoringInformationPackFormatIdRefs( + info, + removedId, + removedPackFormat)) { + programme->set(std::move(info)); + } + } + pruneElementLoudnessIdRefs(programme, removedPackFormat); + } + + for (auto const& content : document.getElements()) { + pruneElementLoudnessIdRefs(content, removedPackFormat); + } + } + + void pruneObjectIdRefs( + Document& document, + std::shared_ptr const& removedObject) { + for (auto const& programme : document.getElements()) { + pruneElementLoudnessIdRefs(programme, removedObject); + } + + for (auto const& content : document.getElements()) { + pruneElementLoudnessIdRefs(content, removedObject); + } + } + } // namespace + Document::Document() { idAssigner_.document(this); } std::shared_ptr Document::create() { @@ -31,8 +237,8 @@ namespace adm { copy->audioTrackFormats_.reserve(audioTrackFormats_.size()); copy->audioTrackUids_.reserve(audioTrackUids_.size()); - auto elements = copyAllElements(shared_from_this()); - if (has()) copy->set(get()); + ElementMapping mapping; + auto elements = copyAllElements(shared_from_this(), mapping); for (auto& e : elements) { if (auto v = boost::get>(&e)) { AudioProgrammeAttorney::setParent(*v, copy); @@ -60,11 +266,13 @@ namespace adm { copy->audioTrackUids_.push_back(*v); } } + copyAuxiliary(shared_from_this(), copy, mapping); return copy; } // ---- add elements ---- // bool Document::add(std::shared_ptr programme) { + AddRecursionGuard guard; if (!checkParent(programme, "AudioProgramme")) { idAssigner_.assignId(*programme); AudioProgrammeAttorney::setParent(programme, shared_from_this()); @@ -79,6 +287,7 @@ namespace adm { } bool Document::add(std::shared_ptr content) { + AddRecursionGuard guard; if (!checkParent(content, "AudioContent")) { idAssigner_.assignId(*content); AudioContentAttorney::setParent(content, shared_from_this()); @@ -93,6 +302,7 @@ namespace adm { } bool Document::add(std::shared_ptr object) { + AddRecursionGuard guard; if (!checkParent(object, "AudioObject")) { idAssigner_.assignId(*object); AudioObjectAttorney::setParent(object, shared_from_this()); @@ -115,6 +325,7 @@ namespace adm { } } bool Document::add(std::shared_ptr packFormat) { + AddRecursionGuard guard; if (!checkParent(packFormat, "AudioPackFormat")) { idAssigner_.assignId(*packFormat); AudioPackFormatAttorney::setParent(packFormat, shared_from_this()); @@ -132,6 +343,7 @@ namespace adm { } bool Document::add(std::shared_ptr channelFormat) { + AddRecursionGuard guard; if (!checkParent(channelFormat, "AudioChannelFormat")) { idAssigner_.assignId(*channelFormat); AudioChannelFormatAttorney::setParent(channelFormat, shared_from_this()); @@ -143,6 +355,7 @@ namespace adm { } bool Document::add(std::shared_ptr streamFormat) { + AddRecursionGuard guard; if (!checkParent(streamFormat, "AudioStreamFormat")) { idAssigner_.assignId(*streamFormat); AudioStreamFormatAttorney::setParent(streamFormat, shared_from_this()); @@ -170,6 +383,7 @@ namespace adm { } bool Document::add(std::shared_ptr trackFormat) { + AddRecursionGuard guard; if (!checkParent(trackFormat, "AudioTrackFormat")) { // NOTE: That the id assignment works properly the AudioStreamFormats // have to be added before the AudioTrackFormat. @@ -192,6 +406,7 @@ namespace adm { } bool Document::add(std::shared_ptr trackUid) { + AddRecursionGuard guard; if (!checkParent(trackUid, "AudioTrackUid")) { idAssigner_.assignId(*trackUid); AudioTrackUidAttorney::setParent(trackUid, shared_from_this()); @@ -222,6 +437,7 @@ namespace adm { if (it != audioProgrammes_.end()) { audioProgrammes_.erase(it); AudioProgrammeAttorney::setParent(programme, {}); + pruneTagGroupsReferencing(*this, programme); return true; } return false; @@ -235,6 +451,7 @@ namespace adm { for (auto& audioProgramme : audioProgrammes_) { audioProgramme->removeReference(content); } + pruneTagGroupsReferencing(*this, content); return true; } return false; @@ -251,15 +468,53 @@ namespace adm { for (auto& audioContent : audioContents_) { audioContent->removeReference(object); } + pruneTagGroupsReferencing(*this, object); + pruneObjectIdRefs(*this, object); return true; } return false; } + namespace { + template + bool tagGroupRefsBelongToOtherDoc( + Document const& doc, + std::vector> const& refs) { + for (auto const& ref : refs) { + auto parent = ref->getParent().lock(); + if (parent && parent.get() != &doc) return true; + } + return false; + } + } // namespace + + bool Document::set(TagList tagList) { + // Validate every TagGroup reference against this document up-front so a + // failure leaves the document unmodified. + for (auto const& group : tagList.get()) { + if (tagGroupRefsBelongToOtherDoc(*this, group.audioProgrammes_) || + tagGroupRefsBelongToOtherDoc(*this, group.audioContents_) || + tagGroupRefsBelongToOtherDoc(*this, group.audioObjects_)) { + return false; + } + } + // Adopt any unparented references into this document. Elements already + // belonging to this document are short-circuited by checkParent() inside + // add(). + for (auto const& group : tagList.get()) { + for (auto const& p : group.audioProgrammes_) add(p); + for (auto const& c : group.audioContents_) add(c); + for (auto const& o : group.audioObjects_) add(o); + } + detail::DocumentBase::set(std::move(tagList)); + return true; + } + bool Document::remove(std::shared_ptr packFormat) { auto it = std::find(audioPackFormats_.begin(), audioPackFormats_.end(), packFormat); if (it != audioPackFormats_.end()) { + auto removedPackId = packFormat->get(); audioPackFormats_.erase(it); AudioPackFormatAttorney::setParent(packFormat, {}); for (auto& audioPackFormat : audioPackFormats_) { @@ -278,6 +533,7 @@ namespace adm { audioTrackUid->removeReference(); } } + prunePackFormatIdRefs(*this, removedPackId, packFormat); return true; } return false; diff --git a/src/elements/audio_block_format_direct_speakers.cpp b/src/elements/audio_block_format_direct_speakers.cpp index 86ad5352..ede9fec8 100644 --- a/src/elements/audio_block_format_direct_speakers.cpp +++ b/src/elements/audio_block_format_direct_speakers.cpp @@ -24,6 +24,18 @@ namespace adm { detail::ParameterTraits::tag) const { return speakerLabels_; } + Cartesian AudioBlockFormatDirectSpeakers::get( + detail::ParameterTraits::tag) const { + if (cartesian_ != boost::none) { + return cartesian_.get(); + } else { + if (has()) { + return Cartesian(false); + } else { + return Cartesian(true); + } + } + } CartesianSpeakerPosition AudioBlockFormatDirectSpeakers::get( detail::ParameterTraits::tag) const { return boost::get(speakerPosition_); @@ -50,6 +62,10 @@ namespace adm { detail::ParameterTraits::tag) const { return speakerLabels_.size() > 0; } + bool AudioBlockFormatDirectSpeakers::has( + detail::ParameterTraits::tag) const { + return true; + } bool AudioBlockFormatDirectSpeakers::has( detail::ParameterTraits::tag) const { return (boost::get(&speakerPosition_)); @@ -64,6 +80,10 @@ namespace adm { detail::ParameterTraits::tag) const { return duration_ == boost::none; } + bool AudioBlockFormatDirectSpeakers::isDefault( + detail::ParameterTraits::tag) const { + return cartesian_ == boost::none; + } // ---- Setter ---- // void AudioBlockFormatDirectSpeakers::set(AudioBlockFormatId id) { id_ = id; } @@ -71,16 +91,37 @@ namespace adm { void AudioBlockFormatDirectSpeakers::set(Duration duration) { duration_ = duration; } + void AudioBlockFormatDirectSpeakers::set(Cartesian cartesian) { + cartesian_ = cartesian; + + if (cartesian.get()) { + if (has()) { + speakerPosition_ = CartesianSpeakerPosition{}; + } + } else { + if (has()) { + speakerPosition_ = SphericalSpeakerPosition{}; + } + } + } void AudioBlockFormatDirectSpeakers::set( CartesianSpeakerPosition speakerPosition) { speakerPosition_ = speakerPosition; + cartesian_ = Cartesian(true); } void AudioBlockFormatDirectSpeakers::set( SphericalSpeakerPosition speakerPosition) { speakerPosition_ = speakerPosition; + if (cartesian_ != boost::none) { + cartesian_ = Cartesian(false); + } } void AudioBlockFormatDirectSpeakers::set(SpeakerPosition speakerPosition) { - speakerPosition_ = speakerPosition; + if (speakerPosition.which() == 0) { + set(boost::get(speakerPosition)); + } else if (speakerPosition.which() == 1) { + set(boost::get(speakerPosition)); + } } // ---- Unsetter ---- // @@ -96,6 +137,13 @@ namespace adm { detail::ParameterTraits::tag) { speakerLabels_.clear(); } + void AudioBlockFormatDirectSpeakers::unset( + detail::ParameterTraits::tag) { + cartesian_ = boost::none; + if (!has()) { + set(SphericalSpeakerPosition{}); + } + } // ---- Add ---- // bool AudioBlockFormatDirectSpeakers::add(SpeakerLabel speakerLabel) { diff --git a/src/elements/audio_programme.cpp b/src/elements/audio_programme.cpp index cbb59b31..5ee6ef47 100644 --- a/src/elements/audio_programme.cpp +++ b/src/elements/audio_programme.cpp @@ -45,6 +45,10 @@ namespace adm { detail::ParameterTraits::tag) const { return refScreen_.get(); } + AuthoringInformation AudioProgramme::get( + detail::ParameterTraits::tag) const { + return authoringInformation_.get(); + } // ---- Has ---- // bool AudioProgramme::has( @@ -73,6 +77,10 @@ namespace adm { detail::ParameterTraits::tag) const { return refScreen_ != boost::none; } + bool AudioProgramme::has( + detail::ParameterTraits::tag) const { + return authoringInformation_ != boost::none; + } // ---- isDefault ---- // bool AudioProgramme::isDefault(detail::ParameterTraits::tag) const { @@ -101,6 +109,9 @@ namespace adm { void AudioProgramme::set(AudioProgrammeReferenceScreen refScreen) { refScreen_ = refScreen; } + void AudioProgramme::set(AuthoringInformation authoringInformation) { + authoringInformation_ = std::move(authoringInformation); + } // ---- Unsetter ---- // void AudioProgramme::unset( @@ -120,6 +131,10 @@ namespace adm { detail::ParameterTraits::tag) { refScreen_ = boost::none; } + void AudioProgramme::unset( + detail::ParameterTraits::tag) { + authoringInformation_ = boost::none; + } // ---- References ---- // bool AudioProgramme::addReference(std::shared_ptr content) { diff --git a/src/elements/authoring_information.cpp b/src/elements/authoring_information.cpp new file mode 100644 index 00000000..5b79e0a0 --- /dev/null +++ b/src/elements/authoring_information.cpp @@ -0,0 +1,46 @@ +#include "adm/elements/authoring_information.hpp" +#include "adm/elements/loudness_renderer.hpp" + +namespace adm { + + LoudnessRenderer AuthoringRenderer::toLoudnessRenderer() const { + return LoudnessRenderer::fromRenderer(*this); + } + + void AuthoringRenderer::print(std::ostream& os) const { + os << "("; + bool first = true; + auto sep = [&]() { + if (!first) os << ", "; + first = false; + }; + if (has()) { + sep(); + os << "uri=" << get(); + } + if (has()) { + sep(); + os << "name=" << get(); + } + if (has()) { + sep(); + os << "version=" << get(); + } + if (has()) { + sep(); + os << "coordinateMode=" << get(); + } + os << ")"; + } + + void AuthoringInformation::print(std::ostream& os) const { + os << "(referenceLayouts=" << get().size() + << ", renderers=" << get().size() << ")"; + } + + namespace detail { + template class VectorParameter; + template class VectorParameter; + } // namespace detail + +} // namespace adm diff --git a/src/elements/loudness_metadata.cpp b/src/elements/loudness_metadata.cpp index c441b939..224dcc40 100644 --- a/src/elements/loudness_metadata.cpp +++ b/src/elements/loudness_metadata.cpp @@ -1,4 +1,5 @@ #include "adm/elements/loudness_metadata.hpp" +#include "adm/elements/authoring_information.hpp" #include @@ -45,6 +46,10 @@ namespace adm { detail::ParameterTraits::tag) const { return dialogueLoudness_.get(); } + LoudnessRenderer LoudnessMetadata::get( + detail::ParameterTraits::tag) const { + return renderer_.get(); + } // ---- Has ---- // bool LoudnessMetadata::has( @@ -80,6 +85,10 @@ namespace adm { detail::ParameterTraits::tag) const { return dialogueLoudness_ != boost::none; } + bool LoudnessMetadata::has( + detail::ParameterTraits::tag) const { + return renderer_ != boost::none; + } // ---- Setter ---- // void LoudnessMetadata::set(LoudnessMethod loudnessMethod) { @@ -109,6 +118,12 @@ namespace adm { void LoudnessMetadata::set(DialogueLoudness dialogueLoudness) { dialogueLoudness_ = dialogueLoudness; } + void LoudnessMetadata::set(LoudnessRenderer renderer) { + renderer_ = std::move(renderer); + } + void LoudnessMetadata::set(AuthoringRenderer renderer) { + renderer_ = renderer.toLoudnessRenderer(); + } // ---- Unsetter ---- // void LoudnessMetadata::unset(detail::ParameterTraits::tag) { @@ -140,6 +155,9 @@ namespace adm { void LoudnessMetadata::unset(detail::ParameterTraits::tag) { dialogueLoudness_ = boost::none; } + void LoudnessMetadata::unset(detail::ParameterTraits::tag) { + renderer_ = boost::none; + } void LoudnessMetadata::print(std::ostream& os) const { os << "("; diff --git a/src/elements/loudness_renderer.cpp b/src/elements/loudness_renderer.cpp new file mode 100644 index 00000000..aeced284 --- /dev/null +++ b/src/elements/loudness_renderer.cpp @@ -0,0 +1,83 @@ +#include "adm/elements/loudness_renderer.hpp" +#include "adm/elements/authoring_information.hpp" + +namespace adm { + + LoudnessRenderer LoudnessRenderer::fromRenderer( + AuthoringRenderer const& renderer) { + LoudnessRenderer loudnessRenderer; + if (renderer.has()) { + loudnessRenderer.set(renderer.get()); + } + if (renderer.has()) { + loudnessRenderer.set(renderer.get()); + } + if (renderer.has()) { + loudnessRenderer.set(renderer.get()); + } + if (renderer.has()) { + loudnessRenderer.set(renderer.get()); + } + if (renderer.has()) { + loudnessRenderer.set(renderer.get()); + } + return loudnessRenderer; + } + + AuthoringRenderer LoudnessRenderer::toRendererDroppingObjectRefs( + RendererUri uri) const { + AuthoringRenderer renderer{std::move(uri)}; + if (has()) { + renderer.set(get()); + } + if (has()) { + renderer.set(get()); + } + if (has()) { + renderer.set(get()); + } + if (has()) { + renderer.set(get()); + } + if (has()) { + renderer.set(get()); + } + return renderer; + } + + void LoudnessRenderer::print(std::ostream& os) const { + os << "("; + bool first = true; + auto sep = [&]() { + if (!first) os << ", "; + first = false; + }; + if (has()) { + sep(); + os << "uri=" << get(); + } + if (has()) { + sep(); + os << "name=" << get(); + } + if (has()) { + sep(); + os << "version=" << get(); + } + if (has()) { + sep(); + os << "coordinateMode=" << get(); + } + os << ")"; + } + + namespace detail { + template class OptionalParameter; + template class OptionalParameter; + template class OptionalParameter; + template class OptionalParameter; + template class VectorParameter; + template class VectorParameter; + } // namespace detail + +} // namespace adm diff --git a/src/elements/tag_list.cpp b/src/elements/tag_list.cpp new file mode 100644 index 00000000..f76c16bf --- /dev/null +++ b/src/elements/tag_list.cpp @@ -0,0 +1,102 @@ +#include "adm/elements/tag_list.hpp" +#include + +namespace adm { + Tag::Tag(const char* s) { + // to avoid UB from std::string + if (!s) { + throw error::AdmGenericRuntimeError{ + "Cannot construct Tag from null const char*"}; + } + set(TagValue{std::string{s}}); + } + + template + bool add_reference(std::vector>& references, + std::shared_ptr ref) { + auto it = std::find(references.begin(), references.end(), ref); + if (it == references.end()) { + references.push_back(ref); + return true; + } + return false; + } + + // ---- References ---- // + bool TagGroup::addReference(std::shared_ptr programme) { + return add_reference(audioProgrammes_, std::move(programme)); + } + + bool TagGroup::addReference(std::shared_ptr content) { + return add_reference(audioContents_, std::move(content)); + } + + bool TagGroup::addReference(std::shared_ptr object) { + return add_reference(audioObjects_, std::move(object)); + } + + template + TagGroup::RemoveResult remove_reference( + std::vector>& references, + std::shared_ptr const& ref) { + auto it = std::find(references.begin(), references.end(), ref); + if (it == references.end()) { + return TagGroup::RemoveResult::NotFound; + } + references.erase(it); + return TagGroup::RemoveResult::Success; + } + + TagGroup::RemoveResult TagGroup::removeReference( + std::shared_ptr programme) { + if (remove_reference(audioProgrammes_, programme) == + RemoveResult::NotFound) { + return RemoveResult::NotFound; + } + if (invalid()) { + addReference(std::move(programme)); + return RemoveResult::LastReferenceError; + } + return RemoveResult::Success; + } + + TagGroup::RemoveResult TagGroup::removeReference( + std::shared_ptr content) { + if (remove_reference(audioContents_, content) == RemoveResult::NotFound) { + return RemoveResult::NotFound; + } + if (invalid()) { + addReference(std::move(content)); + return RemoveResult::LastReferenceError; + } + return RemoveResult::Success; + } + + TagGroup::RemoveResult TagGroup::removeReference( + std::shared_ptr object) { + if (remove_reference(audioObjects_, object) == RemoveResult::NotFound) { + return RemoveResult::NotFound; + } + if (invalid()) { + addReference(std::move(object)); + return RemoveResult::LastReferenceError; + } + return RemoveResult::Success; + } + + bool TagGroup::invalid() const { + return audioContents_.empty() && audioObjects_.empty() && + audioProgrammes_.empty(); + } + + bool TagList::add(TagGroup group) { + return detail::TagListBase::add(std::move(group)); + } + + namespace detail { + template class RequiredParameter; + template class OptionalParameter; + template class VectorParameter; + template class VectorParameter; + } // namespace detail +} // namespace adm diff --git a/src/private/copy.cpp b/src/private/copy.cpp index cf56b01e..295eb5a2 100644 --- a/src/private/copy.cpp +++ b/src/private/copy.cpp @@ -3,22 +3,142 @@ namespace adm { - struct ElementMapping { - // clang-format off - std::unordered_map, std::shared_ptr> audioProgramme; - std::unordered_map, std::shared_ptr> audioContent; - std::unordered_map, std::shared_ptr> audioObject; - std::unordered_map, std::shared_ptr> audioPackFormat; - std::unordered_map, std::shared_ptr> audioChannelFormat; - std::unordered_map, std::shared_ptr> audioStreamFormat; - std::unordered_map, std::shared_ptr> audioTrackFormat; - std::unordered_map, std::shared_ptr> audioTrackUid; - // clang-format on - }; + namespace { + RendererPackFormatIdRefs remapPackFormatRefs( + RendererPackFormatIdRefs const& refs, ElementMapping const& mapping) { + RendererPackFormatIdRefs remapped; + remapped.reserve(refs.size()); + for (auto const& ref : refs) { + auto it = mapping.audioPackFormat.find(ref); + if (it != mapping.audioPackFormat.end()) { + remapped.push_back(it->second); + } else { + remapped.push_back(ref); + } + } + return remapped; + } + + RendererObjectIdRefs remapObjectRefs(RendererObjectIdRefs const& refs, + ElementMapping const& mapping) { + RendererObjectIdRefs remapped; + remapped.reserve(refs.size()); + for (auto const& ref : refs) { + auto it = mapping.audioObject.find(ref); + if (it != mapping.audioObject.end()) { + remapped.push_back(it->second); + } else { + remapped.push_back(ref); + } + } + return remapped; + } + + void remapAuthoringInformationReferences( + std::shared_ptr const& programme, + ElementMapping const& mapping) { + if (!programme->has()) { + return; + } + + auto info = programme->get(); + bool changed = false; + if (info.has()) { + auto layouts = info.get(); + ReferenceLayouts remappedLayouts; + remappedLayouts.reserve(layouts.size()); + for (auto const& refLayout : layouts) { + auto const& ref = refLayout.get(); + auto it = mapping.audioPackFormat.find(ref); + if (it != mapping.audioPackFormat.end()) { + remappedLayouts.push_back(ReferenceLayout{it->second}); + } else { + remappedLayouts.push_back(ReferenceLayout{ref}); + } + } + + if (remappedLayouts.empty()) { + info.unset(); + } else { + info.set(std::move(remappedLayouts)); + } + changed = true; + } + + if (info.has()) { + auto renderers = info.get(); + for (auto& renderer : renderers) { + if (!renderer.has()) { + continue; + } + + auto remapped = remapPackFormatRefs( + renderer.get(), mapping); + if (remapped.empty()) { + renderer.unset(); + } else { + renderer.set(std::move(remapped)); + } + changed = true; + } + + if (changed) { + info.set(std::move(renderers)); + } + } + + if (changed) programme->set(std::move(info)); + } + + template + void remapLoudnessRendererReferences(std::shared_ptr const& owner, + ElementMapping const& mapping) { + if (!owner->template has()) { + return; + } + + auto loudnessMetadatas = owner->template get(); + bool changed = false; + for (auto& loudnessMetadata : loudnessMetadatas) { + if (!loudnessMetadata.template has()) { + continue; + } + + auto renderer = loudnessMetadata.template get(); + + if (renderer.template has()) { + auto remapped = remapPackFormatRefs( + renderer.template get(), mapping); + if (remapped.empty()) { + renderer.template unset(); + } else { + renderer.set(std::move(remapped)); + } + changed = true; + } + + if (renderer.template has()) { + auto remapped = remapObjectRefs( + renderer.template get(), mapping); + if (remapped.empty()) { + renderer.template unset(); + } else { + renderer.set(std::move(remapped)); + } + changed = true; + } + + loudnessMetadata.set(std::move(renderer)); + } + + if (changed) { + owner->set(std::move(loudnessMetadatas)); + } + } + } // namespace std::vector copyAllElements( - std::shared_ptr document) { - ElementMapping mapping; + std::shared_ptr document, ElementMapping& mapping) { std::vector copiedElements; // copy for (const auto& element : document->getElements()) { @@ -92,7 +212,62 @@ namespace adm { resolveReference(element, mapping.audioTrackUid, mapping.audioChannelFormat); } + + for (const auto& element : document->getElements()) { + auto copiedProgramme = mapping.audioProgramme.at(element); + remapAuthoringInformationReferences(copiedProgramme, mapping); + remapLoudnessRendererReferences(copiedProgramme, mapping); + } + + for (const auto& element : document->getElements()) { + auto copiedContent = mapping.audioContent.at(element); + remapLoudnessRendererReferences(copiedContent, mapping); + } + return copiedElements; } + std::vector copyAllElements( + std::shared_ptr document) { + ElementMapping mapping; + return copyAllElements(std::move(document), mapping); + } + + void copyAuxiliary(std::shared_ptr src, + std::shared_ptr dest, + ElementMapping const& mapping) { + if (src->has()) dest->set(src->get()); + if (src->has()) dest->set(src->get()); + if (!src->has()) return; + + auto srcTagList = src->get(); + TagList newTagList; + for (auto const& srcGroup : srcTagList.get()) { + // Translate each ref through the mapping. The source document is + // assumed valid: Document::set(TagList) and Document::remove() keep + // every TagGroup ref attached to the document, so it is guaranteed + // to be in the mapping (mirrors the assumption used by + // resolveReferences for ordinary cross-references). TagGroup has no + // default ctor, so the first translated ref seeds the new group. + std::unique_ptr newGroup; + auto translate = [&](auto const& srcRefs, auto const& mappingMap) { + for (auto const& r : srcRefs) { + auto const& mapped = mappingMap.at(r); + if (!newGroup) + newGroup.reset(new TagGroup(mapped)); + else + newGroup->addReference(mapped); + } + }; + translate(srcGroup.getReferences(), mapping.audioObject); + translate(srcGroup.getReferences(), mapping.audioContent); + translate(srcGroup.getReferences(), + mapping.audioProgramme); + if (!newGroup) continue; // not possible for a valid source document + for (auto const& tag : srcGroup.get()) newGroup->add(tag); + newTagList.add(*newGroup); + } + dest->set(std::move(newTagList)); + } + } // namespace adm diff --git a/src/private/document_parser.cpp b/src/private/document_parser.cpp index cb2635b8..cbfc006f 100644 --- a/src/private/document_parser.cpp +++ b/src/private/document_parser.cpp @@ -3,6 +3,9 @@ #include "adm/private/xml_parser_helper.hpp" #include "adm/detail/named_type_validators.hpp" #include "adm/errors.hpp" + +#include + namespace adm { namespace xml { @@ -110,7 +113,22 @@ namespace adm { resolveReference(streamFormatChannelFormatRef_); resolveReference(streamFormatPackFormatRef_); resolveReferences(streamFormatTrackFormatRefs_); + resolveProgrammeAuthoringRendererReferences(); + resolveProgrammeLoudnessRendererReferences(); + resolveContentLoudnessRendererReferences(); + // add other ADM elements to ADM document + for (NodePtr node = root->first_node(); node; + node = node->next_sibling()) { + std::string nodeName(node->name(), node->name_size()); + if (nodeName == "profileList") { + // Can't use the local add function as that contains an ID setting + document_->set(parseProfileList(node)); + } else if (nodeName == "tagList") { + // Can't use the local add function as that contains an ID setting + document_->set(parseTagList(node)); + } + } } else { throw error::XmlParsingError("audioFormatExtended node not found"); } @@ -222,10 +240,61 @@ namespace adm { setOptionalMultiElement(node, "loudnessMetadata", audioProgramme, &parseLoudnessMetadatas); setOptionalElement(node, "audioProgrammeReferenceScreen", audioProgramme, &parseAudioProgrammeReferenceScreen); + setOptionalElement(node, "authoringInformation", audioProgramme, &parseAuthoringInformation); addOptionalReferences(node, "audioContentIDRef", audioProgramme, programmeContentRefs_, &parseAudioContentId); addOptionalElements diff --git a/tests/test_data/xml_parser/audio_block_format_direct_speakers_cartesian.xml b/tests/test_data/xml_parser/audio_block_format_direct_speakers_cartesian.xml index c4ee285d..fc7fafdb 100644 --- a/tests/test_data/xml_parser/audio_block_format_direct_speakers_cartesian.xml +++ b/tests/test_data/xml_parser/audio_block_format_direct_speakers_cartesian.xml @@ -15,6 +15,7 @@ 0.500000 0.400000 0.600000 + 1 @@ -22,3 +23,4 @@ + diff --git a/tests/test_data/xml_parser/audio_block_format_direct_speakers_cartesian_spherical_mismatch.xml b/tests/test_data/xml_parser/audio_block_format_direct_speakers_cartesian_spherical_mismatch.xml new file mode 100644 index 00000000..45f422a4 --- /dev/null +++ b/tests/test_data/xml_parser/audio_block_format_direct_speakers_cartesian_spherical_mismatch.xml @@ -0,0 +1,17 @@ + + + + + + + + 0.0 + 0.0 + 0.5 + 0 + + + + + + diff --git a/tests/test_data/xml_parser/audio_block_format_direct_speakers_spherical_cartesian_mismatch.xml b/tests/test_data/xml_parser/audio_block_format_direct_speakers_spherical_cartesian_mismatch.xml new file mode 100644 index 00000000..60370e99 --- /dev/null +++ b/tests/test_data/xml_parser/audio_block_format_direct_speakers_spherical_cartesian_mismatch.xml @@ -0,0 +1,17 @@ + + + + + + + + 30.0 + 0.0 + 1.0 + 1 + + + + + + diff --git a/tests/test_data/xml_parser/audio_stream_format_forward_track_refs.xml b/tests/test_data/xml_parser/audio_stream_format_forward_track_refs.xml new file mode 100644 index 00000000..42802057 --- /dev/null +++ b/tests/test_data/xml_parser/audio_stream_format_forward_track_refs.xml @@ -0,0 +1,16 @@ + + + + + + + AT_00031001_01 + AT_00031002_01 + + + + + + + + diff --git a/tests/test_data/xml_parser/authoring_information.xml b/tests/test_data/xml_parser/authoring_information.xml new file mode 100644 index 00000000..e26acccd --- /dev/null +++ b/tests/test_data/xml_parser/authoring_information.xml @@ -0,0 +1,21 @@ + + + + + + + + + AP_00010003 + + + AP_00010003 + AP_00010017 + + + + + + + + diff --git a/tests/test_data/xml_parser/authoring_information_forward_pack_refs.xml b/tests/test_data/xml_parser/authoring_information_forward_pack_refs.xml new file mode 100644 index 00000000..10175460 --- /dev/null +++ b/tests/test_data/xml_parser/authoring_information_forward_pack_refs.xml @@ -0,0 +1,23 @@ + + + + + + + + + AP_00031001 + + + AP_00031001 + AP_00031002 + + + + + + + + + + diff --git a/tests/test_data/xml_parser/authoring_information_identical_renderers.xml b/tests/test_data/xml_parser/authoring_information_identical_renderers.xml new file mode 100644 index 00000000..d6e68fe2 --- /dev/null +++ b/tests/test_data/xml_parser/authoring_information_identical_renderers.xml @@ -0,0 +1,19 @@ + + + + + + + + + AP_00010001 + + + AP_00010001 + + + + + + + diff --git a/tests/test_data/xml_parser/authoring_information_multiple_renderers.xml b/tests/test_data/xml_parser/authoring_information_multiple_renderers.xml new file mode 100644 index 00000000..3c5c0d2e --- /dev/null +++ b/tests/test_data/xml_parser/authoring_information_multiple_renderers.xml @@ -0,0 +1,19 @@ + + + + + + + + + AP_00010001 + + + AP_00010002 + + + + + + + diff --git a/tests/test_data/xml_parser/loudness_renderer.xml b/tests/test_data/xml_parser/loudness_renderer.xml new file mode 100644 index 00000000..f17df930 --- /dev/null +++ b/tests/test_data/xml_parser/loudness_renderer.xml @@ -0,0 +1,21 @@ + + + + + + + + -23.0 + + AP_00010002 + AO_1001 + AO_1002 + + + + + + + + + diff --git a/tests/test_data/xml_parser/loudness_renderer_forward_refs.xml b/tests/test_data/xml_parser/loudness_renderer_forward_refs.xml new file mode 100644 index 00000000..0882cb3f --- /dev/null +++ b/tests/test_data/xml_parser/loudness_renderer_forward_refs.xml @@ -0,0 +1,35 @@ + + + + + + + + + AP_00031001 + AP_00031002 + AO_1001 + AO_1002 + + + + + + + + AP_00031001 + AP_00031002 + AO_1001 + AO_1002 + + + + + + + + + + + + diff --git a/tests/xml_authoring_information_tests.cpp b/tests/xml_authoring_information_tests.cpp new file mode 100644 index 00000000..a406dd20 --- /dev/null +++ b/tests/xml_authoring_information_tests.cpp @@ -0,0 +1,115 @@ +#include +#include +#include "adm/document.hpp" +#include "adm/elements/audio_programme.hpp" +#include "adm/elements/audio_programme_ref_screen.hpp" +#include "adm/elements/authoring_information.hpp" +#include "adm/elements/coordinate_mode.hpp" +#include "adm/parse.hpp" +#include "adm/write.hpp" +#include "helper/file_comparator.hpp" + +using namespace adm; + +TEST_CASE("xml/authoring_information") { + auto document = parseXml("xml_parser/authoring_information.xml"); + + auto programme = document->lookup(parseAudioProgrammeId("APR_1001")); + REQUIRE(programme->has()); + auto info = programme->get(); + auto layouts = info.get(); + REQUIRE(layouts.size() == 1); + auto layoutPack = layouts.at(0).get(); + REQUIRE(layoutPack->get() == + parseAudioPackFormatId("AP_00010003")); + auto renderers = info.get(); + REQUIRE(renderers.size() == 1); + auto const& r = renderers.at(0); + REQUIRE(r.get() == + std::string{"urn:itu:bs:2127:0:itu_adm_renderer"}); + REQUIRE(r.get() == std::string{"polar"}); + REQUIRE(r.get().size() == 2); + + REQUIRE(programme->has()); + auto screen = programme->get(); + REQUIRE(screen.has()); + REQUIRE(screen.get() == std::string{"cartesian"}); + + std::stringstream xml; + writeXml(xml, document); + CHECK_THAT(xml.str(), EqualsXmlFile("authoring_information")); +} + +TEST_CASE("xml/authoring_information_forward_pack_refs") { + auto document = + parseXml("xml_parser/authoring_information_forward_pack_refs.xml"); + + auto programme = document->lookup(parseAudioProgrammeId("APR_1001")); + REQUIRE(programme->has()); + auto info = programme->get(); + REQUIRE(info.has()); + auto layouts = info.get(); + REQUIRE(layouts.size() == 1); + auto layoutPack = layouts.at(0).get(); + REQUIRE(layoutPack->get() == + parseAudioPackFormatId("AP_00031001")); + + auto renderers = info.get(); + REQUIRE(renderers.size() == 1); + + auto const& renderer = renderers.at(0); + REQUIRE(renderer.has()); + auto packRefs = renderer.get(); + REQUIRE(packRefs.size() == 2); + + auto packA = document->lookup(parseAudioPackFormatId("AP_00031001")); + REQUIRE(packA); + auto packB = document->lookup(parseAudioPackFormatId("AP_00031002")); + REQUIRE(packB); + + auto refA = packRefs.at(0); + auto refB = packRefs.at(1); + REQUIRE(refA == packA); + REQUIRE(refB == packB); + + std::stringstream xml; + writeXml(xml, document); + REQUIRE(xml.str().find( + "AP_00031001") != + std::string::npos); +} + +TEST_CASE("xml/authoring_information_multiple_renderers") { + auto document = + parseXml("xml_parser/authoring_information_multiple_renderers.xml"); + + auto programme = document->lookup(parseAudioProgrammeId("APR_1001")); + REQUIRE(programme->has()); + auto renderers = programme->get().get(); + REQUIRE(renderers.size() == 2); + + auto firstPackRefs = renderers.at(0).get(); + REQUIRE(firstPackRefs.size() == 1); + REQUIRE(firstPackRefs.at(0) == + document->lookup(parseAudioPackFormatId("AP_00010001"))); + + auto secondPackRefs = renderers.at(1).get(); + REQUIRE(secondPackRefs.size() == 1); + REQUIRE(secondPackRefs.at(0) == + document->lookup(parseAudioPackFormatId("AP_00010002"))); +} + +TEST_CASE("xml/authoring_information_identical_renderers") { + auto document = + parseXml("xml_parser/authoring_information_identical_renderers.xml"); + + auto programme = document->lookup(parseAudioProgrammeId("APR_1001")); + REQUIRE(programme->has()); + auto renderers = programme->get().get(); + REQUIRE(renderers.size() == 1); + + auto packRefs = renderers.at(0).get(); + REQUIRE(packRefs.size() == 1); + REQUIRE(packRefs.at(0) == + document->lookup(parseAudioPackFormatId("AP_00010001"))); +} diff --git a/tests/xml_loudness_renderer_tests.cpp b/tests/xml_loudness_renderer_tests.cpp new file mode 100644 index 00000000..986dd6d0 --- /dev/null +++ b/tests/xml_loudness_renderer_tests.cpp @@ -0,0 +1,72 @@ +#include +#include +#include "adm/document.hpp" +#include "adm/elements/audio_content.hpp" +#include "adm/elements/audio_programme.hpp" +#include "adm/elements/loudness_metadata.hpp" +#include "adm/elements/loudness_renderer.hpp" +#include "adm/parse.hpp" +#include "adm/write.hpp" +#include "helper/file_comparator.hpp" + +using namespace adm; + +TEST_CASE("xml/loudness_renderer") { + auto document = parseXml("xml_parser/loudness_renderer.xml"); + + auto programme = document->lookup(parseAudioProgrammeId("APR_1001")); + REQUIRE(programme->has()); + auto const& lms = programme->get(); + REQUIRE(lms.size() == 1); + auto const& lm = lms.at(0); + REQUIRE(lm.has()); + auto renderer = lm.get(); + REQUIRE(renderer.get() == + std::string{"urn:itu:bs:2127:0:itu_adm_renderer"}); + REQUIRE(renderer.get() == std::string{"Rec. ITU-R BS.2127"}); + REQUIRE(renderer.get() == std::string{"1.0.0"}); + REQUIRE(renderer.get() == std::string{"polar"}); + REQUIRE(renderer.get().size() == 1); + REQUIRE(renderer.get().size() == 2); + + std::stringstream xml; + writeXml(xml, document); + CHECK_THAT(xml.str(), EqualsXmlFile("loudness_renderer")); +} + +TEST_CASE("xml/loudness_renderer_forward_refs") { + auto document = parseXml("xml_parser/loudness_renderer_forward_refs.xml"); + + auto packA = document->lookup(parseAudioPackFormatId("AP_00031001")); + auto packB = document->lookup(parseAudioPackFormatId("AP_00031002")); + auto objectA = document->lookup(parseAudioObjectId("AO_1001")); + auto objectB = document->lookup(parseAudioObjectId("AO_1002")); + + auto verifyRendererRefs = [&](LoudnessRenderer const& renderer) { + REQUIRE(renderer.has()); + auto packRefs = renderer.get(); + REQUIRE(packRefs.size() == 2); + REQUIRE(packRefs.at(0) == packA); + REQUIRE(packRefs.at(1) == packB); + + REQUIRE(renderer.has()); + auto objectRefs = renderer.get(); + REQUIRE(objectRefs.size() == 2); + REQUIRE(objectRefs.at(0) == objectA); + REQUIRE(objectRefs.at(1) == objectB); + }; + + auto programme = document->lookup(parseAudioProgrammeId("APR_1001")); + REQUIRE(programme->has()); + auto programmeLms = programme->get(); + REQUIRE(programmeLms.size() == 1); + REQUIRE(programmeLms.at(0).has()); + verifyRendererRefs(programmeLms.at(0).get()); + + auto content = document->lookup(parseAudioContentId("ACO_1001")); + REQUIRE(content->has()); + auto contentLms = content->get(); + REQUIRE(contentLms.size() == 1); + REQUIRE(contentLms.at(0).has()); + verifyRendererRefs(contentLms.at(0).get()); +} diff --git a/tests/xml_parser_audio_block_format_direct_speakers_tests.cpp b/tests/xml_parser_audio_block_format_direct_speakers_tests.cpp index 6d3b8151..6e5b266a 100644 --- a/tests/xml_parser_audio_block_format_direct_speakers_tests.cpp +++ b/tests/xml_parser_audio_block_format_direct_speakers_tests.cpp @@ -27,6 +27,8 @@ TEST_CASE("xml_parser/audio_block_format_direct_speakers") { 30.0f); REQUIRE(firstBlockFormat.get().get() == 0.0f); + REQUIRE(firstBlockFormat.get() == false); + REQUIRE(firstBlockFormat.isDefault() == true); REQUIRE(firstBlockFormat.get().get() == false); REQUIRE(firstBlockFormat.get() .get() == Approx(60)); @@ -84,9 +86,58 @@ TEST_CASE("xml_parser/audio_block_format_direct_speakers_cartesian") { REQUIRE(speakerPosition.has()); auto edgeLock = speakerPosition.get(); REQUIRE(edgeLock.get().get() == "left"); + REQUIRE(firstBlockFormat.get() == true); + REQUIRE(firstBlockFormat.isDefault() == false); } } +TEST_CASE( + "xml_parser/" + "audio_block_format_direct_speakers_spherical_cartesian_mismatch") { + using namespace adm; + auto document = parseXml( + "xml_parser/audio_block_format_direct_speakers_spherical_cartesian_" + "mismatch.xml"); + auto channelFormat = + document->lookup(parseAudioChannelFormatId("AC_00011001")); + REQUIRE(channelFormat); + + auto firstBlockFormat = + *(channelFormat->getElements().begin()); + REQUIRE(firstBlockFormat.has() == true); + REQUIRE(firstBlockFormat.has() == false); + auto speakerPosition = firstBlockFormat.get(); + REQUIRE(speakerPosition.get() == Approx(30.0f)); + REQUIRE(speakerPosition.get() == Approx(0.0f)); + REQUIRE(speakerPosition.get() == Approx(1.0f)); + REQUIRE(firstBlockFormat.get() == false); + REQUIRE(firstBlockFormat.isDefault() == false); +} + +TEST_CASE( + "xml_parser/" + "audio_block_format_direct_speakers_cartesian_spherical_mismatch") { + using namespace adm; + auto document = parseXml( + "xml_parser/audio_block_format_direct_speakers_cartesian_spherical_" + "mismatch.xml"); + auto channelFormat = + document->lookup(parseAudioChannelFormatId("AC_00011001")); + REQUIRE(channelFormat); + + auto firstBlockFormat = + *(channelFormat->getElements().begin()); + REQUIRE(firstBlockFormat.has() == true); + REQUIRE(firstBlockFormat.has() == false); + auto speakerPosition = firstBlockFormat.get(); + REQUIRE(speakerPosition.get() == Approx(0.0f)); + REQUIRE(speakerPosition.get() == Approx(0.0f)); + REQUIRE(speakerPosition.has()); + REQUIRE(speakerPosition.get() == Approx(0.5f)); + REQUIRE(firstBlockFormat.get() == true); + REQUIRE(firstBlockFormat.isDefault() == false); +} + TEST_CASE("xml_parser/audio_block_format_direct_speakers_cartesian_bad_bound") { using namespace adm; REQUIRE_THROWS_AS( diff --git a/tests/xml_parser_audio_stream_format_tests.cpp b/tests/xml_parser_audio_stream_format_tests.cpp index 384e151b..f4b6f579 100644 --- a/tests/xml_parser_audio_stream_format_tests.cpp +++ b/tests/xml_parser_audio_stream_format_tests.cpp @@ -23,3 +23,18 @@ TEST_CASE("xml_parser/audio_stream_format_duplicate_id") { adm::parseXml("xml_parser/audio_stream_format_duplicate_id.xml"), adm::error::XmlParsingDuplicateId); } + +TEST_CASE("xml_parser/audio_stream_format_forward_track_format_references") { + using namespace adm; + auto document = + parseXml("xml_parser/audio_stream_format_forward_track_refs.xml"); + auto streamFormat = document->lookup(parseAudioStreamFormatId("AS_00031001")); + + auto trackA = document->lookup(parseAudioTrackFormatId("AT_00031001_01")); + auto trackB = document->lookup(parseAudioTrackFormatId("AT_00031002_01")); + + auto trackRefs = streamFormat->getAudioTrackFormatReferences(); + REQUIRE(trackRefs.size() == 2); + REQUIRE(trackRefs[0].lock() == trackA); + REQUIRE(trackRefs[1].lock() == trackB); +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..9f9482f0 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,15 @@ +{ + "name": "libadm", + "version": "0.14.0", + "homepage": "https://github.com/ebu/libadm", + "license": "Apache-2.0", + "dependencies": [ + "boost-variant", + "boost-optional", + "boost-format", + "boost-functional", + "boost-range", + "boost-iterator", + "boost-rational" + ] +} \ No newline at end of file