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/elements.hpp b/include/adm/elements.hpp index 9fdf9366..cfea6c39 100644 --- a/include/adm/elements.hpp +++ b/include/adm/elements.hpp @@ -48,6 +48,7 @@ #include "adm/elements/time.hpp" #include "adm/elements/audio_programme_ref_screen.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/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/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/private/document_parser.cpp b/src/private/document_parser.cpp index cb2635b8..58c0603d 100644 --- a/src/private/document_parser.cpp +++ b/src/private/document_parser.cpp @@ -599,6 +599,7 @@ namespace adm { setOptionalAttribute(node, "audioBlockFormatID", audioBlockFormat, &parseAudioBlockFormatId); addTimeParametersToBlock(node, audioBlockFormat, timeReference); setOptionalAttribute(node, "initializeBlock", audioBlockFormat); + setOptionalElement(node, "cartesian", audioBlockFormat); setMultiElement(node, "position", audioBlockFormat, &parseSpeakerPosition); addOptionalElements(node, "speakerLabel", audioBlockFormat, &parseSpeakerLabel); setOptionalElement(node, "headLocked", audioBlockFormat); diff --git a/src/private/rapidxml_formatter.cpp b/src/private/rapidxml_formatter.cpp index eed2d7bf..2b0028f0 100644 --- a/src/private/rapidxml_formatter.cpp +++ b/src/private/rapidxml_formatter.cpp @@ -349,6 +349,7 @@ namespace adm { if(audioBlock.has()) { node.addMultiElement(&audioBlock, "position", &formatCartesianSpeakerPosition); } + node.addOptionalElement(&audioBlock, "cartesian"); node.addOptionalElement(&audioBlock, "headLocked"); node.addOptionalElement(&audioBlock, "headphoneVirtualise", &formatHeadphoneVirtualise); diff --git a/tests/audio_block_format_direct_speakers_tests.cpp b/tests/audio_block_format_direct_speakers_tests.cpp index b0b422a4..5804c19d 100644 --- a/tests/audio_block_format_direct_speakers_tests.cpp +++ b/tests/audio_block_format_direct_speakers_tests.cpp @@ -10,11 +10,14 @@ TEST_CASE("DirectSpeakers block format common subelements") { REQUIRE(blockFormat.has() == true); REQUIRE(blockFormat.has() == false); REQUIRE(blockFormat.has() == false); + REQUIRE(blockFormat.has() == true); REQUIRE(blockFormat.isDefault() == true); + REQUIRE(blockFormat.isDefault() == true); auto defaultRtime = std::chrono::seconds{0}; REQUIRE(blockFormat.get().get() == defaultRtime); + REQUIRE(blockFormat.get() == false); auto rTime = std::chrono::seconds{1}; auto duration = std::chrono::seconds{10}; @@ -60,6 +63,8 @@ TEST_CASE("DirectSpeakers block format with Spherical coordinates") { defaultPosition.get()); REQUIRE(blockFormat.get().get() == defaultPosition.get()); + REQUIRE(blockFormat.get() == false); + REQUIRE(blockFormat.isDefault() == true); auto speakerPosition = SphericalSpeakerPosition(Azimuth(30), Elevation(10), Distance(0.5)); @@ -70,6 +75,8 @@ TEST_CASE("DirectSpeakers block format with Spherical coordinates") { Approx(10)); REQUIRE(blockFormat.get().get() == Approx(0.5)); + REQUIRE(blockFormat.get() == false); + REQUIRE(blockFormat.isDefault() == true); } } @@ -85,4 +92,65 @@ TEST_CASE("DirectSpeakers block format with Cartesian coordinates") { REQUIRE(retrievedPosition.get() == speakerPosition.get()); REQUIRE(retrievedPosition.get() == speakerPosition.get()); REQUIRE(retrievedPosition.get() == speakerPosition.get()); + REQUIRE(blockFormat.get() == true); + REQUIRE(blockFormat.isDefault() == false); +} + +TEST_CASE("DirectSpeakers block format cartesian interactions") { + using namespace adm; + + SECTION("spherical speaker position does not set cartesian when unset") { + auto blockFormat = AudioBlockFormatDirectSpeakers{}; + blockFormat.set(SphericalSpeakerPosition{Azimuth{30.0f}, Elevation{5.0f}}); + + REQUIRE(blockFormat.has() == true); + REQUIRE(blockFormat.has() == false); + REQUIRE(blockFormat.get() == false); + REQUIRE(blockFormat.isDefault() == true); + } + + SECTION( + "unsetting cartesian with cartesian position sets default spherical") { + auto blockFormat = AudioBlockFormatDirectSpeakers{}; + blockFormat.set(CartesianSpeakerPosition{X{0.8f}, Y{-0.3f}, Z{0.2f}}); + + REQUIRE(blockFormat.has() == true); + REQUIRE(blockFormat.get() == true); + + blockFormat.unset(); + + REQUIRE(blockFormat.has() == true); + REQUIRE(blockFormat.has() == false); + REQUIRE(blockFormat.get().get() == + Approx(0.0f)); + REQUIRE(blockFormat.get().get() == + Approx(0.0f)); + REQUIRE(blockFormat.get().has() == + false); + REQUIRE(blockFormat.get() == false); + REQUIRE(blockFormat.isDefault() == true); + } + + SECTION( + "setting cartesian true with spherical position sets default cartesian") { + auto blockFormat = AudioBlockFormatDirectSpeakers{}; + blockFormat.set(SphericalSpeakerPosition{Azimuth{10.0f}, Elevation{15.0f}, + Distance{1.0f}}); + + REQUIRE(blockFormat.has() == true); + REQUIRE(blockFormat.get().get() == + Approx(10.0f)); + + blockFormat.set(Cartesian{true}); + + REQUIRE(blockFormat.has() == true); + REQUIRE(blockFormat.has() == false); + REQUIRE(blockFormat.get().get() == + Approx(0.0f)); + REQUIRE(blockFormat.get().get() == + Approx(0.0f)); + REQUIRE(blockFormat.get().has() == false); + REQUIRE(blockFormat.get() == true); + REQUIRE(blockFormat.isDefault() == false); + } } diff --git a/tests/test_data/write_default_cartesian_speaker.accepted.xml b/tests/test_data/write_default_cartesian_speaker.accepted.xml index 44013271..269af8c9 100644 --- a/tests/test_data/write_default_cartesian_speaker.accepted.xml +++ b/tests/test_data/write_default_cartesian_speaker.accepted.xml @@ -7,6 +7,7 @@ 0.000000 0.000000 + 1 diff --git a/tests/test_data/write_specified_cartesian_speaker.accepted.xml b/tests/test_data/write_specified_cartesian_speaker.accepted.xml index 99959e97..bbce659e 100644 --- a/tests/test_data/write_specified_cartesian_speaker.accepted.xml +++ b/tests/test_data/write_specified_cartesian_speaker.accepted.xml @@ -15,6 +15,7 @@ 0.500000 0.400000 0.600000 + 1 1 0.500000 5 diff --git a/tests/test_data/write_specified_headphone_virtualise.accepted.xml b/tests/test_data/write_specified_headphone_virtualise.accepted.xml index daee63a0..eb81eda1 100644 --- a/tests/test_data/write_specified_headphone_virtualise.accepted.xml +++ b/tests/test_data/write_specified_headphone_virtualise.accepted.xml @@ -16,6 +16,7 @@ 0.000000 0.000000 + 1 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/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/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