Audacity 3.2.0
LibsndfileTagger.cpp
Go to the documentation of this file.
1#include "LibsndfileTagger.h"
2#include "AcidizerTags.h"
3
4#include <array>
5#include <cassert>
6#include <cmath>
7#include <cstdio>
8#include <cstring>
9#include <memory>
10#include <stdexcept>
11
12namespace LibImportExport
13{
14namespace Test
15{
16LibsndfileTagger::LibsndfileTagger(double duration, const std::string& filename)
17 : mFilename { filename.empty() ? std::tmpnam(nullptr) : filename }
18{
19 SF_INFO sfInfo;
20 std::memset(&sfInfo, 0, sizeof(sfInfo));
21 sfInfo.samplerate = 44100;
22 sfInfo.channels = 1;
23 sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
24 sfInfo.sections = 1;
25 sfInfo.seekable = 1;
26 mFile = sf_open(mFilename.c_str(), SFM_WRITE, &sfInfo);
27 assert(mFile != nullptr);
28 if (duration > 0)
29 {
30 // Write zeros using sf_write_float
31 sfInfo.frames =
32 static_cast<sf_count_t>(std::round(duration * sfInfo.samplerate));
33 const auto numItems = sfInfo.channels * sfInfo.frames;
34 std::unique_ptr<short[]> zeros { new short[numItems] };
35 std::fill(zeros.get(), zeros.get() + numItems, 0);
36 const auto written = sf_write_short(mFile, zeros.get(), numItems);
37 if (written != numItems)
38 throw std::runtime_error("Failed to write audio to file");
39 }
40}
41
43{
44 sf_close(mFile);
45}
46
47LibsndfileTagger::operator bool() const
48{
49 return mFile != nullptr;
50}
51
53{
54 if (!mFile)
55 throw std::runtime_error("File is not open");
56
57 sf_close(mFile);
58 mDistributorData.reset();
59 mAcidData.reset();
60
61 SF_INFO sfInfo;
62 mFile = sf_open(mFilename.c_str(), SFM_READ, &sfInfo);
63 if (!mFile)
64 throw std::runtime_error("Failed to re-open file");
65 return *mFile;
66}
67
69{
70 // Adapted from the ACID chunk readout code in libsndfile and its comment:
71 // clang-format off
72 /*
73 ** The acid chunk goes a little something like this:
74 **
75 ** 4 bytes 'acid'
76 ** 4 bytes (int) length of chunk starting at next byte
77 **
78 ** 4 bytes (int) type of file:
79 ** this appears to be a bit mask,however some combinations
80 ** are probably impossible and/or qualified as "errors"
81 **
82 ** 0x01 On: One Shot Off: Loop
83 ** 0x02 On: Root note is Set Off: No root
84 ** 0x04 On: Stretch is On, Off: Strech is OFF
85 ** 0x08 On: Disk Based Off: Ram based
86 ** 0x10 On: ?????????? Off: ????????? (Acidizer puts that ON)
87 **
88 ** 2 bytes (short) root note
89 ** if type 0x10 is OFF : [C,C#,(...),B] -> [0x30 to 0x3B]
90 ** if type 0x10 is ON : [C,C#,(...),B] -> [0x3C to 0x47]
91 ** (both types fit on same MIDI pitch albeit different octaves, so who cares)
92 **
93 ** 2 bytes (short) ??? always set to 0x8000
94 ** 4 bytes (float) ??? seems to be always 0
95 ** 4 bytes (int) number of beats
96 ** 2 bytes (short) meter denominator //always 4 in SF/ACID
97 ** 2 bytes (short) meter numerator //always 4 in SF/ACID
98 ** //are we sure about the order?? usually its num/denom
99 ** 4 bytes (float) tempo
100 **
101 */
102 // clang-format on
103
104 SF_LOOP_INFO loopInfo {};
105 loopInfo.bpm = acidTags.bpm.value_or(0.);
106 loopInfo.loop_mode = acidTags.isOneShot ? SF_LOOP_NONE : SF_LOOP_FORWARD;
107
108 SF_CHUNK_INFO chunk;
109 std::memset(&chunk, 0, sizeof(chunk));
110 std::snprintf(chunk.id, sizeof(chunk.id), "acid");
111 chunk.id_size = 4;
112 // All sizes listed above except the first two:
113 chunk.datalen = 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4;
114 mAcidData = std::make_unique<uint8_t[]>(chunk.datalen);
115 std::memset(mAcidData.get(), 0, chunk.datalen);
116 chunk.data = mAcidData.get();
117
118 // The type has 4 bytes, of which we may only set the 1st bit to 1 if the
119 // loop is one-shot:
120 if (acidTags.isOneShot)
121 {
122 auto type = reinterpret_cast<uint32_t*>(mAcidData.get());
123 *type |= 0x00000001;
124 }
125 else if (acidTags.beats.has_value())
126 {
127 auto numBeats = reinterpret_cast<uint32_t*>(mAcidData.get() + 12);
128 *numBeats = *acidTags.beats;
129 }
130 else
131 {
132 assert(acidTags.bpm.has_value());
133 auto tempo = reinterpret_cast<float*>(mAcidData.get() + 20);
134 *tempo = *acidTags.bpm;
135 }
136
137 // Set the meter denominator 2 bytes to 4:
138 auto numerator = reinterpret_cast<uint16_t*>(mAcidData.get() + 16);
139 *numerator |= 0x0004;
140 auto denominator = reinterpret_cast<uint16_t*>(mAcidData.get() + 18);
141 *denominator |= 0x0004;
142
143 const auto result = sf_set_chunk(mFile, &chunk);
144 assert(result == SF_ERR_NO_ERROR);
145}
146
147void LibsndfileTagger::AddDistributorInfo(const std::string& distributor)
148{
149 const uint32_t distributorSize = distributor.size();
150 // Why we didn't use `auto` the line above:
151 static_assert(sizeof(distributorSize) == 4);
152 SF_CHUNK_INFO chunk;
153 std::snprintf(chunk.id, sizeof(chunk.id), "LIST");
154 chunk.id_size = 4;
155 constexpr std::array<char, 4> listTypeID = { 'I', 'N', 'F', 'O' };
156 constexpr std::array<char, 4> distributorTypeID = { 'I', 'D', 'S', 'T' };
157 chunk.datalen = sizeof(listTypeID) + sizeof(distributorTypeID) +
158 sizeof(distributorSize) + distributorSize;
159 // A trick taken from libsndfile's source code, probably to ensure that
160 // the rest of the data stays word-aligned:
161 while (chunk.datalen & 3)
162 ++chunk.datalen;
163 mDistributorData = std::make_unique<uint8_t[]>(chunk.datalen);
164 chunk.data = mDistributorData.get();
165 auto data = mDistributorData.get();
166 std::memset(chunk.data, 0, chunk.datalen);
167 auto pos = 0;
168
169 std::memcpy(data + pos, listTypeID.data(), sizeof(listTypeID));
170
171 pos += sizeof(listTypeID);
172 std::memcpy(data + pos, distributorTypeID.data(), sizeof(distributorTypeID));
173
174 pos += sizeof(distributorTypeID);
175 std::memcpy(data + pos, &distributorSize, sizeof(distributorSize));
176
177 pos += sizeof(distributorSize);
178 std::memcpy(data + pos, distributor.data(), distributorSize);
179
180 const auto result = sf_set_chunk(mFile, &chunk);
181 assert(result == SF_ERR_NO_ERROR);
182}
183} // namespace Test
184} // namespace LibImportExport
static wxCharBuffer mFilename
Definition: SelectFile.cpp:38
void AddAcidizerTags(const Test::AcidizerTags &acidTags)
std::unique_ptr< uint8_t[]> mDistributorData
void AddDistributorInfo(const std::string &distributor)
std::unique_ptr< uint8_t[]> mAcidData
LibsndfileTagger(double duration=0., const std::string &filename="")
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
STL namespace.
const std::optional< double > bpm
Definition: AcidizerTags.h:54
In the Audacity code, we only are interested in LibFileFormats::AcidizerTags and the information it c...
const std::optional< int > beats