@@ -213,8 +213,8 @@ auto validate_catalogue(boost::asio::any_io_executor executor, vamp_zero::config
|
||||
}
|
||||
}
|
||||
for (auto const &exercise : catalog.practice.exercises) {
|
||||
for (auto const &variant : exercise.variants) {
|
||||
auto reference = vamp_zero::score_reference{.category = vamp_zero::score_category::PRACTICE, .id = variant.id};
|
||||
for (auto const &practice_part : exercise.parts) {
|
||||
auto reference = vamp_zero::score_reference{.category = vamp_zero::score_category::PRACTICE, .id = practice_part.id};
|
||||
auto valid = co_await validate_score_pages(executor, config, std::move(reference));
|
||||
if (!valid) {
|
||||
co_return valid;
|
||||
|
||||
@@ -22,7 +22,7 @@ auto score_active(vamp_zero::app_state const &state) -> std::bool_t {
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
auto const *exercise = vamp_zero::active_practice(state);
|
||||
return exercise != nullptr && vamp_zero::selected_practice_variant(state, *exercise) != nullptr;
|
||||
return exercise != nullptr && vamp_zero::selected_practice_part(state, *exercise) != nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
namespace {
|
||||
|
||||
constexpr auto application_id = std::int64_t{0x56414D50};
|
||||
constexpr auto catalogue_schema_version = std::int64_t{2};
|
||||
constexpr auto catalogue_schema_version = std::int64_t{3};
|
||||
|
||||
auto connection_config(vamp_zero::database_config const &config) -> noto::connection_config {
|
||||
auto result = noto::file_connection_config(config.path, "product-vamp");
|
||||
@@ -183,7 +183,7 @@ auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executo
|
||||
if (!connected) {
|
||||
co_return std::unexpected(connected.error());
|
||||
}
|
||||
auto selected = co_await connection.async_prepare("SELECT id, title, composer, key_signature, source_path, measure_count, total_pages, authored_key_signature, exercise_id "
|
||||
auto selected = co_await connection.async_prepare("SELECT id, title, composer, key_signature, source_path, measure_count, total_pages, part, exercise_id "
|
||||
"FROM practice_exercises ORDER BY exercise_id, id");
|
||||
if (!selected) {
|
||||
auto close = co_await connection.async_close();
|
||||
@@ -213,9 +213,12 @@ auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executo
|
||||
status = std::unexpected(source.error());
|
||||
break;
|
||||
}
|
||||
auto variant = vamp_zero::practice_exercise::score_variant{
|
||||
auto practice_part = vamp_zero::library_part{
|
||||
.id = text_value(row[0]),
|
||||
.title = text_value(row[1]),
|
||||
.composer = text_value(row[2]),
|
||||
.key_signature = text_value(row[3]),
|
||||
.part = text_value(row[7]),
|
||||
.source_path = std::move(*source),
|
||||
.measure_count = count_value(row[5]),
|
||||
.total_pages = count_value(row[6]),
|
||||
@@ -224,8 +227,7 @@ auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executo
|
||||
.id = text_value(row[8]),
|
||||
.title = text_value(row[1]),
|
||||
.composer = text_value(row[2]),
|
||||
.authored_key_signature = text_value(row[7]),
|
||||
.variants = {std::move(variant)},
|
||||
.parts = {std::move(practice_part)},
|
||||
});
|
||||
}
|
||||
auto closed = co_await close_catalogue_query(connection, *selected, std::move(status));
|
||||
|
||||
+16
-14
@@ -19,10 +19,9 @@ auto invalid(std::string message) -> modulus::error {
|
||||
return vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, std::move(message));
|
||||
}
|
||||
|
||||
auto key_rank(std::string const &key) -> std::size_t {
|
||||
auto const &keys = vamp_zero::practice_key_names();
|
||||
auto tonic = key.substr(0, key.find(' '));
|
||||
return static_cast<std::size_t>(std::distance(keys.begin(), std::find(keys.begin(), keys.end(), tonic)));
|
||||
auto part_rank(std::string const &part) -> std::size_t {
|
||||
auto const &parts = vamp_zero::practice_part_names();
|
||||
return static_cast<std::size_t>(std::distance(parts.begin(), std::find(parts.begin(), parts.end(), part)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,24 +52,27 @@ auto vamp_zero::group_practice_exercises(std::vector<vamp_zero::practice_exercis
|
||||
auto merged = std::vector<vamp_zero::practice_exercise>{};
|
||||
for (auto &exercise : exercises) {
|
||||
if (!merged.empty() && merged.back().id == exercise.id) {
|
||||
if (merged.back().authored_key_signature != exercise.authored_key_signature) {
|
||||
return std::unexpected(invalid("practice variants disagree on authored key: " + exercise.id));
|
||||
if (merged.back().title != exercise.title || merged.back().composer != exercise.composer) {
|
||||
return std::unexpected(invalid("practice parts disagree on exercise identity: " + exercise.id));
|
||||
}
|
||||
merged.back().variants.insert(merged.back().variants.end(), std::make_move_iterator(exercise.variants.begin()), std::make_move_iterator(exercise.variants.end()));
|
||||
merged.back().parts.insert(merged.back().parts.end(), std::make_move_iterator(exercise.parts.begin()), std::make_move_iterator(exercise.parts.end()));
|
||||
} else {
|
||||
merged.push_back(std::move(exercise));
|
||||
}
|
||||
}
|
||||
for (auto &exercise : merged) {
|
||||
std::sort(exercise.variants.begin(), exercise.variants.end(), [](auto const &left, auto const &right) {
|
||||
return std::tuple{key_rank(left.key_signature), left.id} < std::tuple{key_rank(right.key_signature), right.id};
|
||||
std::sort(exercise.parts.begin(), exercise.parts.end(), [](auto const &left, auto const &right) {
|
||||
return std::tuple{part_rank(left.part), left.id} < std::tuple{part_rank(right.part), right.id};
|
||||
});
|
||||
if (exercise.variants.size() != vamp_zero::practice_key_names().size()) {
|
||||
return std::unexpected(invalid("practice exercise requires twelve key variants: " + exercise.id));
|
||||
if (exercise.parts.size() != vamp_zero::practice_part_names().size()) {
|
||||
return std::unexpected(invalid("practice exercise requires acoustic, electric, and vocal parts: " + exercise.id));
|
||||
}
|
||||
for (auto index = std::size_t{}; index < exercise.variants.size(); ++index) {
|
||||
if (key_rank(exercise.variants[index].key_signature) != index) {
|
||||
return std::unexpected(invalid("practice exercise has missing or duplicate keys: " + exercise.id));
|
||||
for (auto index = std::size_t{}; index < exercise.parts.size(); ++index) {
|
||||
if (exercise.parts[index].key_signature != exercise.parts.front().key_signature) {
|
||||
return std::unexpected(invalid("practice parts disagree on key: " + exercise.id));
|
||||
}
|
||||
if (part_rank(exercise.parts[index].part) != index) {
|
||||
return std::unexpected(invalid("practice exercise has missing or duplicate parts: " + exercise.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,42 +66,26 @@ auto vamp_zero::active_practice(vamp_zero::app_state const &state) -> vamp_zero:
|
||||
return found == state.catalog->practice.exercises.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_practice_variant(vamp_zero::app_state const &state, vamp_zero::practice_exercise const &exercise) -> vamp_zero::practice_exercise::score_variant const * {
|
||||
if (exercise.variants.empty()) {
|
||||
auto vamp_zero::selected_practice_part(vamp_zero::app_state const &state, vamp_zero::practice_exercise const &exercise) -> vamp_zero::library_part const * {
|
||||
if (exercise.parts.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (state.selected_practice_key_index == 0U) {
|
||||
if (state.selected_practice_part_index == 0U) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = state.selected_practice_key_index - 1U;
|
||||
if (index >= exercise.variants.size()) {
|
||||
auto index = state.selected_practice_part_index - 1U;
|
||||
if (index >= exercise.parts.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &exercise.variants[index];
|
||||
return &exercise.parts[index];
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_practice_key(vamp_zero::app_state const &state, vamp_zero::practice_exercise const &exercise) -> std::optional<std::string_view> {
|
||||
auto const *variant = vamp_zero::selected_practice_variant(state, exercise);
|
||||
if (variant == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return variant->key_signature;
|
||||
}
|
||||
|
||||
auto vamp_zero::practice_key_row_count(vamp_zero::app_state const &state) -> std::size_t {
|
||||
auto vamp_zero::practice_part_row_count(vamp_zero::app_state const &state) -> std::size_t {
|
||||
auto const *exercise = vamp_zero::selected_practice_exercise(state);
|
||||
if (exercise == nullptr) {
|
||||
return 1U;
|
||||
}
|
||||
return menu_row_count(exercise->variants.size());
|
||||
}
|
||||
|
||||
auto vamp_zero::effective_practice_key(vamp_zero::app_state const &state, vamp_zero::practice_exercise const &exercise) -> std::string {
|
||||
if (auto const *variant = vamp_zero::selected_practice_variant(state, exercise);
|
||||
variant != nullptr && !variant->key_signature.empty()) {
|
||||
return variant->key_signature;
|
||||
}
|
||||
return exercise.variants.empty() ? std::string{} : exercise.variants.front().key_signature;
|
||||
return menu_row_count(exercise->parts.size());
|
||||
}
|
||||
|
||||
auto vamp_zero::active_pages(vamp_zero::app_state const &state) -> std::size_t {
|
||||
@@ -111,8 +95,8 @@ auto vamp_zero::active_pages(vamp_zero::app_state const &state) -> std::size_t {
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
auto const *exercise = vamp_zero::active_practice(state);
|
||||
auto const *variant = exercise == nullptr ? nullptr : vamp_zero::selected_practice_variant(state, *exercise);
|
||||
return variant == nullptr ? 0U : variant->total_pages;
|
||||
auto const *practice_part = exercise == nullptr ? nullptr : vamp_zero::selected_practice_part(state, *exercise);
|
||||
return practice_part == nullptr ? 0U : practice_part->total_pages;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
@@ -86,13 +86,6 @@ auto vamp_zero::parse_notation_document(std::string_view json, std::string sourc
|
||||
return invalid(std::string{part.error().message()});
|
||||
}
|
||||
document.part = std::move(*part);
|
||||
if (metadata->if_contains("authored_key_signature")) {
|
||||
auto key = string_field(*metadata, "authored_key_signature");
|
||||
if (!key || key->empty()) {
|
||||
return invalid("authored_key_signature must be a nonempty string");
|
||||
}
|
||||
document.authored_key_signature = std::move(*key);
|
||||
}
|
||||
if (staff.if_contains("arrangement_part_type")) {
|
||||
auto type = string_field(staff, "arrangement_part_type");
|
||||
if (!type) {
|
||||
@@ -100,13 +93,6 @@ auto vamp_zero::parse_notation_document(std::string_view json, std::string sourc
|
||||
}
|
||||
document.arrangement_part_type = std::move(*type);
|
||||
}
|
||||
if (document.arrangement_part_type == "practice" && document.key_signature == "No key" && metadata->if_contains("variant_tonic")) {
|
||||
auto tonic = string_field(*metadata, "variant_tonic");
|
||||
if (!tonic) {
|
||||
return invalid(std::string{tonic.error().message()});
|
||||
}
|
||||
document.key_signature = *tonic + " (no key)";
|
||||
}
|
||||
for (auto const &item : *pages) {
|
||||
if (!item.is_object()) {
|
||||
return invalid("page must be an object");
|
||||
|
||||
@@ -78,15 +78,15 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
state.selected_part_index = 0U;
|
||||
}
|
||||
state.selected_practice_exercise_index = std::min(state.selected_practice_exercise_index, state.catalog->practice.exercises.size());
|
||||
state.selected_practice_key_index = std::min(state.selected_practice_key_index, practice_key_row_count(state) - 1U);
|
||||
state.selected_practice_part_index = std::min(state.selected_practice_part_index, practice_part_row_count(state) - 1U);
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU && selected_group(state) == nullptr) {
|
||||
state.mode = vamp_zero::app_mode::PERFORMANCE_SONG_MENU;
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_PART_MENU) {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
if (exercise == nullptr || exercise->variants.empty()) {
|
||||
if (exercise == nullptr || exercise->parts.empty()) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.selected_practice_key_index = 0U;
|
||||
state.selected_practice_part_index = 0U;
|
||||
}
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE && active_part(state) == nullptr) {
|
||||
@@ -100,8 +100,8 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (selected_practice_variant(state, *exercise) == nullptr) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_KEY_MENU;
|
||||
} else if (selected_practice_part(state, *exercise) == nullptr) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_PART_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
}
|
||||
@@ -155,30 +155,16 @@ auto part_json(vamp_zero::library_part const &part) -> boost::json::object {
|
||||
}
|
||||
|
||||
auto exercise_json(vamp_zero::practice_exercise const &exercise) -> boost::json::object {
|
||||
auto variants = boost::json::array{};
|
||||
for (auto const &variant : exercise.variants) {
|
||||
auto item = boost::json::object{};
|
||||
item["id"] = variant.id;
|
||||
item["key_signature"] = variant.key_signature;
|
||||
item["source_path"] = variant.source_path;
|
||||
item["measures"] = static_cast<std::uint64_t>(variant.measure_count);
|
||||
item["pages"] = static_cast<std::uint64_t>(variant.total_pages);
|
||||
variants.push_back(std::move(item));
|
||||
auto parts = boost::json::array{};
|
||||
for (auto const &part : exercise.parts) {
|
||||
parts.push_back(part_json(part));
|
||||
}
|
||||
auto result = boost::json::object{};
|
||||
result["id"] = exercise.id;
|
||||
result["title"] = exercise.title;
|
||||
result["composer"] = exercise.composer;
|
||||
result["authored_key_signature"] = exercise.authored_key_signature;
|
||||
if (!exercise.variants.empty()) {
|
||||
auto const &primary = exercise.variants.front();
|
||||
result["key_signature"] = primary.key_signature;
|
||||
result["source_path"] = primary.source_path;
|
||||
result["measures"] = static_cast<std::uint64_t>(primary.measure_count);
|
||||
result["pages"] = static_cast<std::uint64_t>(primary.total_pages);
|
||||
}
|
||||
result["variant_count"] = static_cast<std::uint64_t>(exercise.variants.size());
|
||||
result["variants"] = std::move(variants);
|
||||
result["part_count"] = static_cast<std::uint64_t>(exercise.parts.size());
|
||||
result["parts"] = std::move(parts);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -207,36 +193,19 @@ auto practice_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
for (auto const &exercise : state.catalog->practice.exercises) {
|
||||
exercises.push_back(exercise_json(exercise));
|
||||
}
|
||||
auto keys = boost::json::array{};
|
||||
for (auto key : vamp_zero::practice_key_names()) {
|
||||
keys.push_back(boost::json::value{std::string{key}});
|
||||
}
|
||||
auto selection = boost::json::object{};
|
||||
selection["exercise_row_index"] = static_cast<std::uint64_t>(state.selected_practice_exercise_index);
|
||||
selection["key_row_index"] = static_cast<std::uint64_t>(state.selected_practice_key_index);
|
||||
selection["part_row_index"] = static_cast<std::uint64_t>(state.selected_practice_part_index);
|
||||
if (auto const *exercise = selected_practice_exercise(state); exercise != nullptr) {
|
||||
selection["exercise"] = exercise_json(*exercise);
|
||||
auto variant = selected_practice_variant(state, *exercise);
|
||||
if (variant != nullptr) {
|
||||
auto item = boost::json::object{};
|
||||
item["id"] = variant->id;
|
||||
item["key_signature"] = variant->key_signature;
|
||||
item["source_path"] = variant->source_path;
|
||||
item["measures"] = static_cast<std::uint64_t>(variant->measure_count);
|
||||
item["pages"] = static_cast<std::uint64_t>(variant->total_pages);
|
||||
selection["variant"] = std::move(item);
|
||||
}
|
||||
}
|
||||
if (auto const *exercise = selected_practice_exercise(state); exercise != nullptr) {
|
||||
if (auto key = selected_practice_key(state, *exercise)) {
|
||||
selection["key"] = std::string{*key};
|
||||
auto practice_part = selected_practice_part(state, *exercise);
|
||||
if (practice_part != nullptr) {
|
||||
selection["part"] = part_json(*practice_part);
|
||||
}
|
||||
}
|
||||
auto practice = boost::json::object{};
|
||||
practice["exercise_count"] = static_cast<std::uint64_t>(state.catalog->practice.exercises.size());
|
||||
practice["exercises"] = std::move(exercises);
|
||||
practice["key_count"] = static_cast<std::uint64_t>(vamp_zero::practice_key_names().size());
|
||||
practice["keys"] = std::move(keys);
|
||||
practice["selection"] = std::move(selection);
|
||||
return practice;
|
||||
}
|
||||
@@ -285,7 +254,7 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
} else if (state.selected_main_menu_index == main_menu_practice) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.selected_practice_exercise_index = 0;
|
||||
state.selected_practice_key_index = 0;
|
||||
state.selected_practice_part_index = 0;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (state.selected_main_menu_index == main_menu_playback) {
|
||||
@@ -347,10 +316,10 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
case vamp_zero::app_mode::PRACTICE_EXERCISE_MENU:
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_practice_exercise_index = wrap_next(state.selected_practice_exercise_index, menu_row_count(state.catalog->practice.exercises.size()));
|
||||
state.selected_practice_key_index = 0;
|
||||
state.selected_practice_part_index = 0;
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.selected_practice_exercise_index = wrap_previous(state.selected_practice_exercise_index, menu_row_count(state.catalog->practice.exercises.size()));
|
||||
state.selected_practice_key_index = 0;
|
||||
state.selected_practice_part_index = 0;
|
||||
} else if (state.selected_practice_exercise_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::MAIN_MENU;
|
||||
state.active_practice_id.clear();
|
||||
@@ -359,24 +328,24 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
state.active_part_id.clear();
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_KEY_MENU;
|
||||
state.selected_practice_key_index = 0;
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_PART_MENU;
|
||||
state.selected_practice_part_index = 0;
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_KEY_MENU:
|
||||
case vamp_zero::app_mode::PRACTICE_PART_MENU:
|
||||
if (auto const *exercise = selected_practice_exercise(state);
|
||||
exercise != nullptr) {
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_practice_key_index =
|
||||
wrap_next(state.selected_practice_key_index, menu_row_count(exercise->variants.size()));
|
||||
state.selected_practice_part_index =
|
||||
wrap_next(state.selected_practice_part_index, menu_row_count(exercise->parts.size()));
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.selected_practice_key_index =
|
||||
wrap_previous(state.selected_practice_key_index, menu_row_count(exercise->variants.size()));
|
||||
} else if (state.selected_practice_key_index == 0U) {
|
||||
state.selected_practice_part_index =
|
||||
wrap_previous(state.selected_practice_part_index, menu_row_count(exercise->parts.size()));
|
||||
} else if (state.selected_practice_part_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (selected_practice_variant(state, *exercise) != nullptr) {
|
||||
} else if (selected_practice_part(state, *exercise) != nullptr) {
|
||||
state.active_practice_id = exercise->id;
|
||||
state.active_part_id.clear();
|
||||
state.page_index = 0;
|
||||
@@ -388,14 +357,14 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_SCORE:
|
||||
if (auto const *exercise = active_practice(state); exercise != nullptr) {
|
||||
auto const *variant = selected_practice_variant(state, *exercise);
|
||||
auto const *practice_part = selected_practice_part(state, *exercise);
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
auto pages = variant == nullptr ? 0U : variant->total_pages;
|
||||
auto pages = practice_part == nullptr ? 0U : practice_part->total_pages;
|
||||
state.page_index = std::min(state.page_index + 1U, pages == 0U ? 0U : pages - 1U);
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.page_index = state.page_index == 0U ? 0U : state.page_index - 1U;
|
||||
} else {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_KEY_MENU;
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_PART_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
}
|
||||
@@ -429,14 +398,16 @@ auto vamp_zero::app_status_json(vamp_zero::app_state const &state) -> boost::jso
|
||||
}
|
||||
} else if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
if (auto const *exercise = active_practice(state); exercise != nullptr) {
|
||||
auto const *variant = selected_practice_variant(state, *exercise);
|
||||
auto const *practice_part = selected_practice_part(state, *exercise);
|
||||
active["selected"] = true;
|
||||
active["kind"] = "practice";
|
||||
active["exercise"] = exercise_json(*exercise);
|
||||
active["key"] = effective_practice_key(state, *exercise);
|
||||
if (practice_part != nullptr) {
|
||||
active["part"] = part_json(*practice_part);
|
||||
}
|
||||
active["page_index"] = static_cast<std::uint64_t>(state.page_index);
|
||||
active["page_number"] = static_cast<std::uint64_t>(state.page_index + 1U);
|
||||
active["page_total"] = static_cast<std::uint64_t>(variant == nullptr ? 0U : variant->total_pages);
|
||||
active["page_total"] = static_cast<std::uint64_t>(practice_part == nullptr ? 0U : practice_part->total_pages);
|
||||
}
|
||||
}
|
||||
if (active.empty()) {
|
||||
@@ -478,12 +449,12 @@ auto vamp_zero::remote_display_text(vamp_zero::app_state const &state) -> std::s
|
||||
}
|
||||
} else if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
if (auto const *selected = active_practice(state); selected != nullptr) {
|
||||
auto const *variant = selected_practice_variant(state, *selected);
|
||||
auto const *practice_part = selected_practice_part(state, *selected);
|
||||
active = true;
|
||||
title = selected->title;
|
||||
part = "Key: " + effective_practice_key(state, *selected);
|
||||
part = practice_part == nullptr ? std::string{} : practice_part->part;
|
||||
page = state.page_index + 1U;
|
||||
pages = variant == nullptr ? 0U : variant->total_pages;
|
||||
pages = practice_part == nullptr ? 0U : practice_part->total_pages;
|
||||
}
|
||||
}
|
||||
auto output = std::ostringstream{};
|
||||
@@ -529,13 +500,13 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
|
||||
if (state.selected_practice_exercise_index >= menu_row_count(state.catalog->practice.exercises.size())) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected practice exercise row is out of range"));
|
||||
}
|
||||
if (state.selected_practice_key_index >= practice_key_row_count(state)) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected practice key row is out of range"));
|
||||
if (state.selected_practice_part_index >= practice_part_row_count(state)) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected practice part row is out of range"));
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_PART_MENU) {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
if (exercise == nullptr || exercise->variants.empty()) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice key mode requires an exercise with key variants"));
|
||||
if (exercise == nullptr || exercise->parts.empty()) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice part mode requires an exercise with parts"));
|
||||
}
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
@@ -552,11 +523,11 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
|
||||
if (exercise == nullptr) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice score mode requires an active exercise"));
|
||||
}
|
||||
auto const *variant = selected_practice_variant(state, *exercise);
|
||||
if (variant == nullptr) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice score mode requires a selected generated variant"));
|
||||
auto const *practice_part = selected_practice_part(state, *exercise);
|
||||
if (practice_part == nullptr) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice score mode requires a selected generated part"));
|
||||
}
|
||||
if (variant->total_pages == 0U || state.page_index >= variant->total_pages) {
|
||||
if (practice_part->total_pages == 0U || state.page_index >= practice_part->total_pages) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice score page index is out of range"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +135,8 @@ auto vamp_zero::selected_score_reference(vamp_zero::app_state const &state) -> s
|
||||
}
|
||||
} else if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
if (auto exercise = vamp_zero::active_practice(state)) {
|
||||
if (auto variant = vamp_zero::selected_practice_variant(state, *exercise)) {
|
||||
return vamp_zero::score_reference{.category = vamp_zero::score_category::PRACTICE, .id = variant->id};
|
||||
if (auto practice_part = vamp_zero::selected_practice_part(state, *exercise)) {
|
||||
return vamp_zero::score_reference{.category = vamp_zero::score_category::PRACTICE, .id = practice_part->id};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-18
@@ -31,28 +31,15 @@ auto vamp_zero::to_string(vamp_zero::app_mode value) -> std::string {
|
||||
return "performance_score";
|
||||
case vamp_zero::app_mode::PRACTICE_EXERCISE_MENU:
|
||||
return "practice_exercise_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_KEY_MENU:
|
||||
return "practice_key_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_PART_MENU:
|
||||
return "practice_part_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_SCORE:
|
||||
return "practice_score";
|
||||
}
|
||||
return "main_menu";
|
||||
}
|
||||
|
||||
auto vamp_zero::practice_key_names() -> std::vector<std::string_view> const & {
|
||||
static auto const keys = std::vector<std::string_view>{
|
||||
"C",
|
||||
"Db",
|
||||
"D",
|
||||
"Eb",
|
||||
"E",
|
||||
"F",
|
||||
"Gb",
|
||||
"G",
|
||||
"Ab",
|
||||
"A",
|
||||
"Bb",
|
||||
"B",
|
||||
};
|
||||
return keys;
|
||||
auto vamp_zero::practice_part_names() -> std::vector<std::string_view> const & {
|
||||
static auto const parts = std::vector<std::string_view>{"Acoustic", "Electric", "Vocal"};
|
||||
return parts;
|
||||
}
|
||||
|
||||
+15
-16
@@ -314,22 +314,22 @@ auto draw_practice_exercise_menu(luma::draw_context &context, luma::rect bounds,
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_key_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto draw_practice_part_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
exercise == nullptr ? std::string{"KEY"} : exercise->title,
|
||||
exercise == nullptr ? std::string{"PART"} : exercise->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT PRACTICE KEY", 1, vamp_style::muted, 360.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT PRACTICE PART", 1, vamp_style::muted, 360.0F);
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
auto total_rows = (exercise == nullptr ? 0U : exercise->variants.size()) + 1U;
|
||||
auto total_rows = (exercise == nullptr ? 0U : exercise->parts.size()) + 1U;
|
||||
auto max_rows = std::min<std::size_t>(total_rows, 7U);
|
||||
auto first = first_visible_row(state.selected_practice_key_index, total_rows, max_rows);
|
||||
auto first = first_visible_row(state.selected_practice_part_index, total_rows, max_rows);
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
@@ -340,20 +340,19 @@ auto draw_practice_key_menu(luma::draw_context &context, luma::rect bounds, vamp
|
||||
.height = row_height},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_practice_key_index == 0U);
|
||||
state.selected_practice_part_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto key = exercise == nullptr ? std::string{"C"} : exercise->variants[index - 1U].key_signature;
|
||||
auto source = exercise == nullptr || exercise->authored_key_signature.empty() ? std::string{}
|
||||
: "SOURCE " + exercise->authored_key_signature;
|
||||
auto part = exercise == nullptr ? std::string{} : exercise->parts[index - 1U].part;
|
||||
auto key = exercise == nullptr ? std::string{} : exercise->parts[index - 1U].key_signature;
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
part,
|
||||
key,
|
||||
source,
|
||||
index == state.selected_practice_key_index);
|
||||
index == state.selected_practice_part_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,9 +369,9 @@ auto draw_score(luma::draw_context &context, luma::rect bounds, vamp_zero::app_s
|
||||
draw_practice_exercise_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
auto const *variant = selected_practice_variant(state, *exercise);
|
||||
if (variant == nullptr) {
|
||||
draw_practice_key_menu(context, bounds, state);
|
||||
auto const *practice_part = selected_practice_part(state, *exercise);
|
||||
if (practice_part == nullptr) {
|
||||
draw_practice_part_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -435,8 +434,8 @@ public:
|
||||
draw_performance_part_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_EXERCISE_MENU) {
|
||||
draw_practice_exercise_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
|
||||
draw_practice_key_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_PART_MENU) {
|
||||
draw_practice_part_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PERFORMANCE_SCORE ||
|
||||
state.app.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
draw_score(context, content, state.app, loaded, score_cache_);
|
||||
|
||||
Reference in New Issue
Block a user