@@ -10,6 +10,8 @@
|
||||
|
||||
#include <vamp_zero/errors.hpp>
|
||||
#include <vamp_zero/notation.hpp>
|
||||
#include <vamp_zero/performance.hpp>
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
@@ -90,6 +92,44 @@ auto vamp_zero::parse_notation_document(std::string_view json, std::string sourc
|
||||
}
|
||||
document.arrangement_part_type = std::move(*type);
|
||||
}
|
||||
auto times = std::vector<boost::multiprecision::cpp_rational>{0};
|
||||
auto rational = [](vamp_zero::beat_position beat) -> boost::multiprecision::cpp_rational { return boost::multiprecision::cpp_rational{beat.numerator} / beat.denominator; };
|
||||
try {
|
||||
for (auto const &measure : *measures) {
|
||||
auto duration = vamp_zero::parse_beat(measure.at("duration"));
|
||||
if (!duration || duration->numerator == 0) {
|
||||
return invalid("invalid measure duration");
|
||||
}
|
||||
times.push_back(times.back() + rational(*duration));
|
||||
}
|
||||
auto const &performance = root.at("performance").as_object();
|
||||
document.song_id = std::string{performance.at("song_id").as_string()};
|
||||
auto duration = vamp_zero::parse_beat(performance.at("duration"));
|
||||
if (!duration || rational(*duration) != times.back() || !integer_equals(performance, "count_in_beats", 4)) {
|
||||
return invalid("invalid performance duration or count-in");
|
||||
}
|
||||
document.duration = *duration;
|
||||
auto const &clicks = performance.at("clicks").as_array();
|
||||
auto click = std::size_t{};
|
||||
for (auto measure = std::size_t{}; measure + 1 < times.size(); ++measure) {
|
||||
for (auto beat = times[measure]; beat < times[measure + 1]; ++beat) {
|
||||
if (click >= clicks.size()) {
|
||||
return invalid("missing metronome beat");
|
||||
}
|
||||
auto position = vamp_zero::parse_beat(clicks[click].at("beat"));
|
||||
if (!position || rational(*position) != beat || clicks[click].at("downbeat").as_bool() != (beat == times[measure])) {
|
||||
return invalid("invalid metronome accent or beat");
|
||||
}
|
||||
++click;
|
||||
}
|
||||
}
|
||||
if (click != clicks.size()) {
|
||||
return invalid("unexpected metronome beats");
|
||||
}
|
||||
} catch (...) {
|
||||
return invalid("missing performance timing; regenerate score assets");
|
||||
}
|
||||
auto cursor = std::size_t{};
|
||||
for (auto const &item : *pages) {
|
||||
if (!item.is_object()) {
|
||||
return invalid("page must be an object");
|
||||
@@ -110,7 +150,21 @@ auto vamp_zero::parse_notation_document(std::string_view json, std::string sourc
|
||||
})) {
|
||||
return invalid("page sha256 must be 64 lowercase hexadecimal digits");
|
||||
}
|
||||
document.pages.push_back({std::move(*path), std::move(*hash)});
|
||||
try {
|
||||
auto first = item.at("first_measure").to_number<std::size_t>();
|
||||
auto count = item.at("measure_count").to_number<std::size_t>();
|
||||
auto start = vamp_zero::parse_beat(item.at("start_beat")), end = vamp_zero::parse_beat(item.at("end_beat"));
|
||||
if (first != cursor + 1 || count == 0 || count > measures->size() - cursor || !start || !end || rational(*start) != times[cursor] || rational(*end) != times[cursor + count]) {
|
||||
return invalid("invalid page measure coverage");
|
||||
}
|
||||
document.pages.push_back({std::move(*path), std::move(*hash), first, count, *start, *end});
|
||||
cursor += count;
|
||||
} catch (...) {
|
||||
return invalid("missing page timing; regenerate score assets");
|
||||
}
|
||||
}
|
||||
if (cursor != measures->size()) {
|
||||
return invalid("incomplete page measure coverage");
|
||||
}
|
||||
document.id = std::filesystem::path{source_path}.stem().string();
|
||||
document.source_path = std::move(source_path);
|
||||
|
||||
Reference in New Issue
Block a user