Audacity 3.2.0
Namespaces | Functions
LibImportExport Namespace Reference

Namespaces

namespace  Test
 

Functions

std::optional< LibFileFormats::AcidizerTagsGetAcidizerTags (SNDFILE &file, const std::vector< std::string > &trustedDistributors)
 Get the Acidizer tags from a file if from a trusted distributor. More...
 
 TEST_CASE ("GetAcidizerTags")
 

Detailed Description


Audacity: A Digital Audio Editor

GetAcidizerTags.cpp

Matthieu Hodgkinson


Audacity: A Digital Audio Editor

LibsndfileTagger.h

Matthieu Hodgkinson


Audacity: A Digital Audio Editor

GetAcidizerTagsTests.cpp

Matthieu Hodgkinson

Function Documentation

◆ GetAcidizerTags()

std::optional< LibFileFormats::AcidizerTags > IMPORT_EXPORT_API LibImportExport::GetAcidizerTags ( SNDFILE &  file,
const std::vector< std::string > &  trustedDistributors 
)

Get the Acidizer tags from a file if from a trusted distributor.

Acidizer tags are looked for in the header ( https://exiftool.org/TagNames/RIFF.html#Acidizer). Metadata with incorrect BPM values are nevertheless very common, so the function will return non-null only if the distributor is trusted. The distributor is looked for as the "DistributedBy" RIFF info tag (https://exiftool.org/TagNames/RIFF.html#Info).

Definition at line 21 of file GetAcidizerTags.cpp.

23{
24 SF_LOOP_INFO loopInfo;
25 if (
26 sf_command(&file, SFC_GET_LOOP_INFO, &loopInfo, sizeof(loopInfo)) ==
27 SF_FALSE)
28 return {};
29
30 if (
31 loopInfo.loop_mode == SF_LOOP_BACKWARD ||
32 loopInfo.loop_mode == SF_LOOP_ALTERNATING)
33 // Don't know what that is:
34 return {};
35
36 if (loopInfo.loop_mode == SF_LOOP_NONE)
38
39 if (loopInfo.num_beats != 0)
40 {
41 // Forward loop with number of beats set: all files like these I have seen
42 // so far were correctly tagged.
43 SF_INFO info;
44 std::memset(&info, 0, sizeof(info));
45 sf_command(&file, SFC_GET_CURRENT_SF_INFO, &info, sizeof(info));
46 if (info.samplerate == 0 || info.frames == 0)
47 return {};
48 const auto duration = 1. * info.frames / info.samplerate;
49 return LibFileFormats::AcidizerTags::Loop { 60. * loopInfo.num_beats /
50 duration };
51 }
52
53 // There is loop info, but in some unexpected combination. We don't trust it
54 // unless it is from a trusted distributor.
55 SF_CHUNK_INFO info;
56 constexpr std::array<char, 4> listId = { 'L', 'I', 'S', 'T' };
57 std::copy(listId.begin(), listId.end(), info.id);
58 std::fill(info.id + sizeof(listId), info.id + sizeof(info.id), '\0');
59 info.id_size = sizeof(listId);
60 auto chunkIt = sf_get_chunk_iterator(&file, &info);
61 while (chunkIt)
62 {
63 if (sf_get_chunk_size(chunkIt, &info) != SF_ERR_NO_ERROR)
64 break;
65 constexpr std::array<char, 4> INFO = { 'I', 'N', 'F', 'O' };
66 constexpr std::array<char, 4> IDST = { 'I', 'D', 'S', 'T' };
67 // Another 4 bytes after INFO and IDST that indicates the size of the data
68 constexpr auto dataPos = sizeof(INFO) + sizeof(IDST) + 4;
69 if (info.datalen < dataPos)
70 // Not the expected data
71 continue;
72 const auto chars = std::make_unique<char[]>(info.datalen);
73 info.data = chars.get();
74 if (sf_get_chunk_data(chunkIt, &info) != SF_ERR_NO_ERROR)
75 break;
76 chunkIt = sf_next_chunk_iterator(chunkIt);
77
78 auto pos = 0;
79 const auto firstFour =
80 std::string { chars.get() + pos, chars.get() + pos + sizeof(INFO) };
81 if (firstFour != std::string { INFO.data(), INFO.size() })
82 continue;
83
84 pos += sizeof(INFO);
85 const auto nextFour =
86 std::string { chars.get() + pos, chars.get() + pos + sizeof(IDST) };
87 if (nextFour != std::string { IDST.data(), IDST.size() })
88 continue;
89
90 // Ignore trailing nulls, which could be the result of byte-padding for
91 // word alignment:
92 const auto charsEnd = std::find_if(
93 chars.get() + dataPos, chars.get() + info.datalen,
94 [](const char c) { return c == '\0'; });
95 const auto distributor = std::string { chars.get() + 12, charsEnd };
96 const auto isTrusted =
97 std::find(
98 trustedDistributors.begin(), trustedDistributors.end(),
99 distributor) != trustedDistributors.end();
100 if (isTrusted)
101 // Later we may want to get the key, too, but for now we're only
102 // interested in BPM.
103 return LibFileFormats::AcidizerTags::Loop { loopInfo.bpm };
104 }
105
106 // No luck:
107 return {};
108}
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40

References LibFileFormats::AcidizerTags::Loop::bpm, and staffpad::vo::copy().

Referenced by PCMImportFileHandle::Import(), and TEST_CASE().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ TEST_CASE()

LibImportExport::TEST_CASE ( "GetAcidizerTags"  )

Definition at line 27 of file GetAcidizerTagsTests.cpp.

28{
29 SECTION("returns null if")
30 {
31 SECTION("there is no loop info")
32 {
34 const auto actual = GetAcidizerTags(tagger.ReopenInReadMode(), {});
35 REQUIRE(!actual.has_value());
36 }
37 SECTION("tempo is set but the distributor isn't whitelisted")
38 {
39 Test::LibsndfileTagger tagger;
40 tagger.AddAcidizerTags(AcidizerTags::Loop { 120. });
41 tagger.AddDistributorInfo("Distributor Zen");
42 const auto actual = GetAcidizerTags(
43 tagger.ReopenInReadMode(),
44 { "foo", "Distributor Z", "Distributor Zen 2" });
45 REQUIRE(!actual.has_value());
46 }
47 }
48
49 SECTION("returns valid info if")
50 {
51 SECTION("OneShot is set")
52 {
53 Test::LibsndfileTagger tagger;
54 tagger.AddAcidizerTags(AcidizerTags::OneShot {});
55 const auto actual = GetAcidizerTags(tagger.ReopenInReadMode(), {});
56 REQUIRE(actual.has_value());
57 REQUIRE(actual->isOneShot);
58 }
59 SECTION("Beats is set")
60 {
61 // 20 beats in 10 seconds -> 120 bpm.
62 Test::LibsndfileTagger tagger { 10. };
63 constexpr auto numBeats = 20;
64 tagger.AddAcidizerTags(Test::AcidizerTags::Beats { numBeats });
65 auto& file = tagger.ReopenInReadMode();
66 const auto actual = GetAcidizerTags(file, {});
67 REQUIRE(actual.has_value());
68 SF_INFO info;
69 sf_command(&file, SFC_GET_CURRENT_SF_INFO, &info, sizeof(SF_INFO));
70 const auto durationAfterClose = 1. * info.frames / info.samplerate;
71 const auto expectedBpm = numBeats * 60 / durationAfterClose;
72 REQUIRE(actual->bpm == Approx(expectedBpm));
73 REQUIRE(actual->isOneShot == false);
74 };
75 SECTION("Tempo is set and the distributor is whitelisted")
76 {
77 Test::LibsndfileTagger tagger;
78 tagger.AddAcidizerTags(AcidizerTags::Loop { 120. });
79 tagger.AddDistributorInfo("Trusted Distributor");
80 const auto actual = GetAcidizerTags(
81 tagger.ReopenInReadMode(), { "Trusted Distributor" });
82 REQUIRE(actual.has_value());
83 REQUIRE(actual->bpm == 120.);
84 REQUIRE(actual->isOneShot == false);
85 }
86 }
87}
When adding tags, the allocated memory must be preserved until the file is closed....
std::optional< LibFileFormats::AcidizerTags > GetAcidizerTags(SNDFILE &file, const std::vector< std::string > &trustedDistributors)
Get the Acidizer tags from a file if from a trusted distributor.

References LibImportExport::Test::LibsndfileTagger::AddAcidizerTags(), LibImportExport::Test::LibsndfileTagger::AddDistributorInfo(), GetAcidizerTags(), and LibImportExport::Test::LibsndfileTagger::ReopenInReadMode().

Here is the call graph for this function: