/*
This file is part of libdjinterop.
libdjinterop is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libdjinterop is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libdjinterop. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
typedef std::vector::size_type data_size_t;
namespace djinterop
{
namespace enginelibrary
{
namespace
{
template
stdx::optional > prohibit(const U& sentinel, T&& data)
{
if (data == sentinel)
{
return stdx::nullopt;
}
return stdx::make_optional(std::forward(data));
}
template
stdx::optional opt_static_cast(const stdx::optional& u)
{
if (!u)
{
return stdx::nullopt;
}
return stdx::make_optional(static_cast(*u));
}
char* encode_beatgrid(const std::vector& beatgrid, char* ptr)
{
typedef std::vector::size_type vec_size_t;
ptr = encode_int64_be(beatgrid.size(), ptr);
for (vec_size_t i = 0; i < beatgrid.size(); ++i)
{
ptr = encode_double_le(beatgrid[i].sample_offset, ptr);
ptr = encode_int64_le(beatgrid[i].index, ptr);
int32_t diff = 0;
if (i < beatgrid.size() - 1)
{
diff = beatgrid[i + 1].index - beatgrid[i].index;
}
ptr = encode_int32_le(diff, ptr);
ptr = encode_int32_le(0, ptr); // unknown field
}
return ptr;
}
std::pair, const char*> decode_beatgrid(
const char* ptr, const char* end)
{
int64_t count;
std::tie(count, ptr) = decode_int64_be(ptr);
if (count == 0)
{
return {{}, ptr};
}
if (count < 2)
{
throw std::invalid_argument{
"Beat data grid has an invalid number of markers"};
}
if (count > 32768)
{
throw std::invalid_argument{
"Beat data grid has unsupportedly many markers"};
}
if (end - ptr < 24 * count)
{
throw std::invalid_argument{"Beat data grid is missing data"};
}
std::vector result(count);
int32_t beats_until_next_marker;
typedef std::vector::size_type vec_size_t;
for (vec_size_t i = 0; i < result.size(); ++i)
{
std::tie(result[i].sample_offset, ptr) = decode_double_le(ptr);
std::tie(result[i].index, ptr) = decode_int64_le(ptr);
if (i != 0)
{
if (result[i].index <= result[i - 1].index)
{
throw std::invalid_argument{
"Beat data grid has unsorted indices"};
}
if (result[i].sample_offset <= result[i - 1].sample_offset)
{
throw std::invalid_argument{
"Beat data grid has unsorted sample offsets"};
}
if (result[i].index - result[i - 1].index !=
beats_until_next_marker)
{
throw std::invalid_argument{
"Beat data grid has conflicting markers"};
}
}
std::tie(beats_until_next_marker, ptr) = decode_int32_le(ptr);
std::tie(std::ignore, ptr) = decode_int32_le(ptr); // unknown field
}
if (beats_until_next_marker != 0)
{
throw std::invalid_argument{
"Beat data grid promised non-existent marker"};
}
return {std::move(result), ptr};
}
} // namespace
// Encode beat data into a byte array
std::vector beat_data::encode() const
{
std::vector uncompressed(
33 + 24 * (default_beatgrid.size() + adjusted_beatgrid.size()));
auto ptr = uncompressed.data();
const auto end = ptr + uncompressed.size();
if (sampling)
{
ptr = encode_double_be(sampling->sample_rate, ptr);
ptr = encode_double_be(sampling->sample_count, ptr);
}
else
{
ptr = encode_double_be(0, ptr); // TODO (haslersn): is 0 ok?
ptr = encode_double_be(0, ptr);
}
ptr = encode_uint8(1, ptr);
ptr = encode_beatgrid(default_beatgrid, ptr);
ptr = encode_beatgrid(adjusted_beatgrid, ptr);
if (ptr != end)
{
throw std::runtime_error{"Internal error in beat_data::encode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return zlib_compress(uncompressed);
}
// Extract beat data from a byte array
beat_data beat_data::decode(const std::vector& compressed_data)
{
const auto raw_data = zlib_uncompress(compressed_data);
auto ptr = raw_data.data();
const auto end = ptr + raw_data.size();
if (raw_data.size() < 33)
{
throw std::invalid_argument{
"Beat data has less than the minimum length of 33 bytes"};
}
beat_data result;
sampling_info sampling;
std::tie(sampling.sample_rate, ptr) = decode_double_be(ptr);
std::tie(sampling.sample_count, ptr) = decode_double_be(ptr);
result.sampling = sampling.sample_rate != 0 ? stdx::make_optional(sampling)
: stdx::nullopt;
uint8_t is_beat_data_set;
std::tie(is_beat_data_set, ptr) = decode_uint8(ptr);
if (is_beat_data_set != 1)
{
// TODO (haslersn): print a warning that "Is beat data set" is not 1
}
try
{
std::vector default_beatgrid;
std::vector adjusted_beatgrid;
std::tie(default_beatgrid, ptr) = decode_beatgrid(ptr, end);
std::tie(adjusted_beatgrid, ptr) = decode_beatgrid(ptr, end);
// If there's an exception, then the following will intentially not be
// executed.
result.default_beatgrid = std::move(default_beatgrid);
result.adjusted_beatgrid = std::move(adjusted_beatgrid);
}
catch (const std::invalid_argument& e)
{
// TODO (haslersn): print a warning with e.what().
}
if (ptr != end)
{
throw std::invalid_argument{"Beat data has too much data"};
}
return result;
}
// Encode high-resolution waveform data into a byte array
std::vector high_res_waveform_data::encode() const
{
std::vector uncompressed(30 + 6 * waveform.size());
auto ptr = uncompressed.data();
const auto end = ptr + uncompressed.size();
ptr = encode_int64_be(waveform.size(), ptr);
ptr = encode_int64_be(waveform.size(), ptr);
ptr = encode_double_be(samples_per_entry, ptr);
uint8_t max_low = 0;
uint8_t max_mid = 0;
uint8_t max_high = 0;
uint8_t max_low_opc = 0;
uint8_t max_mid_opc = 0;
uint8_t max_high_opc = 0;
for (auto& entry : waveform)
{
max_low = std::max(max_low, entry.low.value);
max_mid = std::max(max_mid, entry.mid.value);
max_high = std::max(max_high, entry.high.value);
max_low_opc = std::max(max_low_opc, entry.low.opacity);
max_mid_opc = std::max(max_mid_opc, entry.mid.opacity);
max_high_opc = std::max(max_high_opc, entry.high.opacity);
ptr = encode_uint8(entry.low.value, ptr);
ptr = encode_uint8(entry.mid.value, ptr);
ptr = encode_uint8(entry.high.value, ptr);
ptr = encode_uint8(entry.low.opacity, ptr);
ptr = encode_uint8(entry.mid.opacity, ptr);
ptr = encode_uint8(entry.high.opacity, ptr);
}
// Encode the maximum values across all entries
ptr = encode_uint8(max_low, ptr);
ptr = encode_uint8(max_mid, ptr);
ptr = encode_uint8(max_high, ptr);
ptr = encode_uint8(max_low_opc, ptr);
ptr = encode_uint8(max_mid_opc, ptr);
ptr = encode_uint8(max_high_opc, ptr);
if (ptr != end)
{
throw std::runtime_error{
"Internal error in high_res_waveform_data::encode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return zlib_compress(uncompressed);
}
// Extract high-resolution waveform from a byte array
high_res_waveform_data high_res_waveform_data::decode(
const std::vector& compressed_data)
{
const auto raw_data = zlib_uncompress(compressed_data);
auto ptr = raw_data.data();
const auto end = ptr + raw_data.size();
if (raw_data.size() < 30)
{
throw std::invalid_argument{
"High-resolution waveform data has less than the minimum length of "
"30 bytes"};
}
// Work out how many entries we have
high_res_waveform_data result;
int64_t num_entries_1, num_entries_2;
std::tie(num_entries_1, ptr) = decode_int64_be(ptr);
std::tie(num_entries_2, ptr) = decode_int64_be(ptr);
std::tie(result.samples_per_entry, ptr) = decode_double_be(ptr);
if (num_entries_1 != num_entries_2)
{
throw std::invalid_argument{
"High-resolution waveform data has conflicting length fields"};
}
if (end - ptr != 6 * (num_entries_1 + 1))
{
throw std::invalid_argument{
"High-resolution waveform data has incorrect length"};
}
result.waveform.resize(num_entries_1);
for (auto& entry : result.waveform)
{
std::tie(entry.low.value, ptr) = decode_uint8(ptr);
std::tie(entry.mid.value, ptr) = decode_uint8(ptr);
std::tie(entry.high.value, ptr) = decode_uint8(ptr);
std::tie(entry.low.opacity, ptr) = decode_uint8(ptr);
std::tie(entry.mid.opacity, ptr) = decode_uint8(ptr);
std::tie(entry.high.opacity, ptr) = decode_uint8(ptr);
}
// Ignore additional entry at the end
ptr += 6;
if (ptr != end)
{
throw std::runtime_error{
"Internal error in high_res_waveform_data::decode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return result;
}
// Encode loops into a byte array
std::vector loops_data::encode() const
{
auto total_label_length = std::accumulate(
loops.begin(), loops.end(), int64_t{0},
[](int64_t x, const stdx::optional& loop) {
return x + (loop ? loop->label.length() : 0);
});
std::vector uncompressed(192 + total_label_length);
auto ptr = uncompressed.data();
const auto end = ptr + uncompressed.size();
ptr = encode_int64_le(loops.size(), ptr); // 8
for (auto& loop : loops)
{
if (loop)
{
if (loop->label.length() == 0)
{
throw std::logic_error{"Loop labels must not be empty"};
}
ptr = encode_uint8(loop->label.length(), ptr);
for (auto& chr : loop->label)
{
ptr = encode_uint8(static_cast(chr), ptr);
}
ptr = encode_double_le(loop->start_sample_offset, ptr);
ptr = encode_double_le(loop->end_sample_offset, ptr);
ptr = encode_uint8(1, ptr);
ptr = encode_uint8(1, ptr);
ptr = encode_uint8(loop->color.a, ptr);
ptr = encode_uint8(loop->color.r, ptr);
ptr = encode_uint8(loop->color.g, ptr);
ptr = encode_uint8(loop->color.b, ptr);
}
else
{
ptr = encode_uint8(0, ptr);
ptr = encode_double_le(-1, ptr);
ptr = encode_double_le(-1, ptr);
for (int i = 0; i < 6; ++i)
{
ptr = encode_uint8(0, ptr);
}
}
}
if (ptr != end)
{
throw std::runtime_error{"Internal error in loops_data::encode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
// Note that 'loops' is not compressed
return uncompressed;
}
// Extract loops from a byte array
loops_data loops_data::decode(const std::vector& raw_data)
{
// Note that loops are not compressed, unlike all the other fields
auto ptr = raw_data.data();
const auto end = ptr + raw_data.size();
if (raw_data.size() < 192)
{
throw std::invalid_argument{
"Loops data has less than the minimum length of 192 bytes"};
}
{ // Check that there are exactly 8 loops
int64_t num_loops;
std::tie(num_loops, ptr) = decode_int64_le(ptr);
if (num_loops != 8)
{
throw std::invalid_argument{
"Loops data has an unsupported number of loops"};
}
}
loops_data result;
for (auto& loop : result.loops)
{
uint8_t label_length;
std::tie(label_length, ptr) = decode_uint8(ptr);
if (end - ptr < 22 + label_length)
{
throw std::invalid_argument{"Loop data has loop with missing data"};
}
if (label_length > 0)
{
loop.emplace();
loop->label.assign(ptr, label_length);
ptr += label_length;
std::tie(loop->start_sample_offset, ptr) = decode_double_le(ptr);
if (loop->start_sample_offset < 0)
{
// TODO (haslersn): warning
}
std::tie(loop->end_sample_offset, ptr) = decode_double_le(ptr);
if (loop->end_sample_offset < 0)
{
// TODO (haslersn): warning
}
uint8_t is_set;
std::tie(is_set, ptr) = decode_uint8(ptr);
if (!is_set)
{
// TODO (haslersn): warning
}
std::tie(is_set, ptr) = decode_uint8(ptr);
if (!is_set)
{
// TODO (haslersn): warning
}
std::tie(loop->color.a, ptr) = decode_uint8(ptr);
std::tie(loop->color.r, ptr) = decode_uint8(ptr);
std::tie(loop->color.g, ptr) = decode_uint8(ptr);
std::tie(loop->color.b, ptr) = decode_uint8(ptr);
}
else
{
ptr += 22;
}
}
if (ptr != end)
{
throw std::invalid_argument{"Loops data has too much data"};
}
return result;
}
// Encode overview waveform data into a byte array
std::vector overview_waveform_data::encode() const
{
std::vector uncompressed(27 + 3 * waveform.size());
auto ptr = uncompressed.data();
const auto end = ptr + uncompressed.size();
// Encode
ptr = encode_int64_be(waveform.size(), ptr);
ptr = encode_int64_be(waveform.size(), ptr);
ptr = encode_double_be(samples_per_entry, ptr);
uint8_t max_low = 0;
uint8_t max_mid = 0;
uint8_t max_high = 0;
for (auto& entry : waveform)
{
max_low = std::max(max_low, entry.low.value);
max_mid = std::max(max_mid, entry.mid.value);
max_high = std::max(max_high, entry.high.value);
ptr = encode_uint8(entry.low.value, ptr);
ptr = encode_uint8(entry.mid.value, ptr);
ptr = encode_uint8(entry.high.value, ptr);
}
// Encode the maximum values across all entries
ptr = encode_uint8(max_low, ptr);
ptr = encode_uint8(max_mid, ptr);
ptr = encode_uint8(max_high, ptr);
if (ptr != end)
{
throw std::runtime_error{
"Internal error in overview_waveform_data::encode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return zlib_compress(uncompressed);
}
// Extract overview waveform from a byte array
overview_waveform_data overview_waveform_data::decode(
const std::vector& compressed_data)
{
const auto raw_data = zlib_uncompress(compressed_data);
auto ptr = raw_data.data();
const auto end = ptr + raw_data.size();
if (raw_data.size() < 27)
{
throw std::invalid_argument{
"Overview waveform data has less than the minimum length of "
"27 bytes"};
}
// Work out how many entries we have
overview_waveform_data result;
int64_t num_entries_1, num_entries_2;
std::tie(num_entries_1, ptr) = decode_int64_be(ptr);
std::tie(num_entries_2, ptr) = decode_int64_be(ptr);
std::tie(result.samples_per_entry, ptr) = decode_double_be(ptr);
if (num_entries_1 != num_entries_2)
{
throw std::invalid_argument{
"High-resolution waveform data has conflicting length fields"};
}
if (end - ptr != 3 * (num_entries_1 + 1))
{
throw std::invalid_argument{
"High-resolution waveform data has incorrect length"};
}
result.waveform.resize(num_entries_1);
for (auto& entry : result.waveform)
{
std::tie(entry.low.value, ptr) = decode_uint8(ptr);
std::tie(entry.mid.value, ptr) = decode_uint8(ptr);
std::tie(entry.high.value, ptr) = decode_uint8(ptr);
}
// Ignore additional entry at the end
ptr += 3;
if (ptr != end)
{
throw std::runtime_error{
"Internal error in overview_waveform_data::decode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return result;
}
// Encode quick cues data into a byte array
std::vector quick_cues_data::encode() const
{
auto total_label_length = std::accumulate(
hot_cues.begin(), hot_cues.end(), int64_t{0},
[](int64_t x, const stdx::optional& hot_cue) {
return x + (hot_cue ? hot_cue->label.length() : 0);
});
// Work out total length of all cue labels
std::vector uncompressed(129 + total_label_length);
auto ptr = uncompressed.data();
const auto end = ptr + uncompressed.size();
ptr = encode_int64_be(hot_cues.size(), ptr);
for (auto& hot_cue : hot_cues)
{
if (hot_cue)
{
if (hot_cue->label.length() == 0)
{
throw std::invalid_argument{"Hot cue labels must not be empty"};
}
ptr = encode_uint8(hot_cue->label.length(), ptr);
for (auto& chr : hot_cue->label)
{
ptr = encode_uint8(static_cast(chr), ptr);
}
ptr = encode_double_be(hot_cue->sample_offset, ptr);
ptr = encode_uint8(hot_cue->color.a, ptr);
ptr = encode_uint8(hot_cue->color.r, ptr);
ptr = encode_uint8(hot_cue->color.g, ptr);
ptr = encode_uint8(hot_cue->color.b, ptr);
}
else
{
ptr = encode_uint8(0, ptr);
ptr = encode_double_be(0, ptr);
for (int i = 0; i < 4; ++i)
{
ptr = encode_uint8(0, ptr);
}
}
}
ptr = encode_double_be(adjusted_main_cue, ptr);
ptr = encode_uint8(adjusted_main_cue == default_main_cue ? 0 : 1, ptr);
ptr = encode_double_be(default_main_cue, ptr);
if (ptr != end)
{
throw std::runtime_error{"Internal error in quick_cues_data::encode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return zlib_compress(uncompressed);
}
// Extract quick cues data from a byte array
quick_cues_data quick_cues_data::decode(
const std::vector& compressed_data)
{
const auto raw_data = zlib_uncompress(compressed_data);
auto ptr = raw_data.data();
const auto end = ptr + raw_data.size();
if (raw_data.size() < 129)
{
throw std::invalid_argument{
"Quick cues data has less than the minimum length of 129 bytes"};
}
{ // Check that there are exactly 8 loops
int64_t num_hot_cues;
std::tie(num_hot_cues, ptr) = decode_int64_be(ptr);
if (num_hot_cues != 8)
{
throw std::invalid_argument{
"Quick cues data has an unsupported number of hot cues"};
}
}
quick_cues_data result;
for (auto& hot_cue : result.hot_cues)
{
uint8_t label_length;
std::tie(label_length, ptr) = decode_uint8(ptr);
if (end - ptr < 29 + label_length) // 12 (here) + 17 (after the loop)
{
throw std::invalid_argument{
"Quick cues data has hot cue with missing data"};
}
if (label_length > 0)
{
hot_cue.emplace();
hot_cue->label.assign(ptr, label_length);
ptr += label_length;
std::tie(hot_cue->sample_offset, ptr) = decode_double_be(ptr);
if (hot_cue->sample_offset < 0)
{
// TODO (haslersn): warning
}
std::tie(hot_cue->color.a, ptr) = decode_uint8(ptr);
std::tie(hot_cue->color.r, ptr) = decode_uint8(ptr);
std::tie(hot_cue->color.g, ptr) = decode_uint8(ptr);
std::tie(hot_cue->color.b, ptr) = decode_uint8(ptr);
}
else
{
ptr += 12;
}
}
std::tie(result.adjusted_main_cue, ptr) = decode_double_be(ptr);
uint8_t is_adjusted;
std::tie(is_adjusted, ptr) = decode_uint8(ptr);
std::tie(result.default_main_cue, ptr) = decode_double_be(ptr);
if (is_adjusted > 1 || (is_adjusted == 0 && result.adjusted_main_cue !=
result.default_main_cue))
{
// TODO (haslersn): This is not fatal. Make this a warning.
throw std::invalid_argument{
"Quick cues data has invalid main cue data"};
}
if (ptr != end)
{
throw std::invalid_argument{"Quick cues data has too much data"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return result;
}
// Encode track data into a byte array
std::vector track_data::encode() const
{
std::vector uncompressed(28); // Track data has fixed size
auto ptr = uncompressed.data();
const auto end = ptr + uncompressed.size();
if (sampling)
{
ptr = encode_double_be(sampling->sample_rate, ptr);
ptr = encode_int64_be(sampling->sample_count, ptr);
}
else
{
ptr = encode_double_be(0, ptr); // TODO (haslersn): is 0 ok?
ptr = encode_int64_be(0, ptr);
}
ptr = encode_double_be(average_loudness.value_or(0), ptr);
ptr = encode_int32_be(key ? static_cast(*key) : 0, ptr);
if (ptr != end)
{
throw std::runtime_error{"Internal error in track_data::encode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return zlib_compress(uncompressed);
}
// Extract track data from a byte array
track_data track_data::decode(const std::vector& compressed_track_data)
{
const auto raw_data = zlib_uncompress(compressed_track_data);
auto ptr = raw_data.data();
const auto end = ptr + raw_data.size();
if (raw_data.size() != 28)
{
throw std::invalid_argument{
"Track data doesn't have expected length of 28 bytes"};
}
track_data result;
sampling_info sampling;
std::tie(sampling.sample_rate, ptr) = decode_double_be(ptr);
std::tie(sampling.sample_count, ptr) = decode_int64_be(ptr);
result.sampling = sampling.sample_rate != 0 ? stdx::make_optional(sampling)
: stdx::nullopt;
double raw_average_loudness;
std::tie(raw_average_loudness, ptr) = decode_double_be(ptr);
result.average_loudness = prohibit(0, raw_average_loudness);
int32_t raw_key;
std::tie(raw_key, ptr) = decode_int32_be(ptr);
result.key = opt_static_cast(prohibit(0, raw_key));
if (ptr != end)
{
throw std::runtime_error{"Internal error in track_data::decode()"};
// TODO (haslersn): This shouldn't be possible to happen. How to handle?
}
return result;
}
} // namespace enginelibrary
} // namespace djinterop