+13
-22
@@ -25,7 +25,7 @@
|
||||
namespace {
|
||||
|
||||
constexpr auto application_id = std::int64_t{0x56414D50};
|
||||
constexpr auto catalogue_schema_version = std::int64_t{1};
|
||||
constexpr auto catalogue_schema_version = std::int64_t{2};
|
||||
|
||||
auto connection_config(vamp_zero::database_config const &config) -> noto::connection_config {
|
||||
auto result = noto::file_connection_config(config.path, "product-vamp");
|
||||
@@ -183,9 +183,8 @@ 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, practice_type_id, practice_type_title, lesson_group_id, lesson_group_title, title, composer, "
|
||||
"key_signature, source_path, measure_count, total_pages, authored_key_signature, exercise_id FROM practice_exercises ORDER BY practice_type_id COLLATE NOCASE, "
|
||||
"lesson_group_id COLLATE NOCASE, source_path COLLATE NOCASE, title COLLATE NOCASE, id COLLATE NOCASE");
|
||||
auto selected = co_await connection.async_prepare("SELECT id, title, composer, key_signature, source_path, measure_count, total_pages, authored_key_signature, exercise_id "
|
||||
"FROM practice_exercises ORDER BY exercise_id, id");
|
||||
if (!selected) {
|
||||
auto close = co_await connection.async_close();
|
||||
if (!close) {
|
||||
@@ -205,35 +204,27 @@ auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executo
|
||||
break;
|
||||
}
|
||||
auto const &row = **next;
|
||||
if (row.size() != 13U || count_value(row[9]) == 0U || count_value(row[10]) == 0U || text_value(row[12]).empty()) {
|
||||
if (row.size() != 9U || count_value(row[5]) == 0U || count_value(row[6]) == 0U || text_value(row[8]).empty() || text_value(row[3]).empty()) {
|
||||
status = std::unexpected(vamp_zero::make_error(vamp_zero::error_code::PERSISTENCE, "invalid practice metadata"));
|
||||
break;
|
||||
}
|
||||
auto type_id = text_value(row[1]);
|
||||
auto lesson_id = text_value(row[3]);
|
||||
auto source = resolve_source_path(assets_path, text_value(row[8]));
|
||||
auto source = resolve_source_path(assets_path, text_value(row[4]));
|
||||
if (!source) {
|
||||
status = std::unexpected(source.error());
|
||||
break;
|
||||
}
|
||||
auto source_path = std::move(*source);
|
||||
auto key_signature = text_value(row[7]).empty() ? std::string{"C"} : text_value(row[7]);
|
||||
auto variant = vamp_zero::practice_exercise::score_variant{
|
||||
.id = text_value(row[0]),
|
||||
.key_signature = key_signature,
|
||||
.source_path = source_path,
|
||||
.measure_count = count_value(row[9]),
|
||||
.total_pages = count_value(row[10]),
|
||||
.key_signature = text_value(row[3]),
|
||||
.source_path = std::move(*source),
|
||||
.measure_count = count_value(row[5]),
|
||||
.total_pages = count_value(row[6]),
|
||||
};
|
||||
exercises.push_back({
|
||||
.id = text_value(row[12]),
|
||||
.practice_type_id = std::move(type_id),
|
||||
.practice_type_title = text_value(row[2]),
|
||||
.lesson_group_id = std::move(lesson_id),
|
||||
.lesson_group_title = text_value(row[4]),
|
||||
.title = text_value(row[5]),
|
||||
.composer = text_value(row[6]),
|
||||
.authored_key_signature = text_value(row[11]),
|
||||
.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)},
|
||||
});
|
||||
}
|
||||
|
||||
+10
-12
@@ -48,11 +48,11 @@ auto vamp_zero::group_library_parts(std::vector<vamp_zero::library_part> parts)
|
||||
|
||||
auto vamp_zero::group_practice_exercises(std::vector<vamp_zero::practice_exercise> exercises) -> std::expected<vamp_zero::practice_library, modulus::error> {
|
||||
std::sort(exercises.begin(), exercises.end(), [](auto const &left, auto const &right) {
|
||||
return std::tie(left.practice_type_id, left.lesson_group_id, left.id) < std::tie(right.practice_type_id, right.lesson_group_id, right.id);
|
||||
return left.id < right.id;
|
||||
});
|
||||
auto merged = std::vector<vamp_zero::practice_exercise>{};
|
||||
for (auto &exercise : exercises) {
|
||||
if (!merged.empty() && std::tie(merged.back().practice_type_id, merged.back().lesson_group_id, merged.back().id) == std::tie(exercise.practice_type_id, exercise.lesson_group_id, exercise.id)) {
|
||||
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));
|
||||
}
|
||||
@@ -61,20 +61,18 @@ auto vamp_zero::group_practice_exercises(std::vector<vamp_zero::practice_exercis
|
||||
merged.push_back(std::move(exercise));
|
||||
}
|
||||
}
|
||||
auto types = std::vector<vamp_zero::practice_type_group>{};
|
||||
for (auto index = std::size_t{}; index < merged.size(); ++index) {
|
||||
auto &exercise = merged[index];
|
||||
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};
|
||||
});
|
||||
if (types.empty() || types.back().id != exercise.practice_type_id) {
|
||||
types.push_back({.id = exercise.practice_type_id, .title = exercise.practice_type_title});
|
||||
if (exercise.variants.size() != vamp_zero::practice_key_names().size()) {
|
||||
return std::unexpected(invalid("practice exercise requires twelve key variants: " + exercise.id));
|
||||
}
|
||||
auto &lessons = types.back().lesson_groups;
|
||||
if (lessons.empty() || lessons.back().id != exercise.lesson_group_id) {
|
||||
lessons.push_back({.id = exercise.lesson_group_id, .title = exercise.lesson_group_title});
|
||||
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));
|
||||
}
|
||||
}
|
||||
lessons.back().exercises.push_back(index);
|
||||
}
|
||||
return vamp_zero::practice_library{.exercises = std::move(merged), .types = std::move(types)};
|
||||
return vamp_zero::practice_library{.exercises = std::move(merged)};
|
||||
}
|
||||
|
||||
@@ -45,40 +45,15 @@ auto vamp_zero::active_part(vamp_zero::app_state const &state) -> vamp_zero::lib
|
||||
return found == state.catalog->performance.parts.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_practice_type(vamp_zero::app_state const &state) -> vamp_zero::practice_type_group const * {
|
||||
if (state.selected_practice_type_index == 0U || state.catalog->practice.types.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = state.selected_practice_type_index - 1U;
|
||||
if (index >= state.catalog->practice.types.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &state.catalog->practice.types[index];
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_practice_lesson(vamp_zero::app_state const &state) -> vamp_zero::practice_lesson_group const * {
|
||||
auto const *type = vamp_zero::selected_practice_type(state);
|
||||
if (state.selected_practice_lesson_index == 0U || type == nullptr || type->lesson_groups.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = state.selected_practice_lesson_index - 1U;
|
||||
if (index >= type->lesson_groups.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &type->lesson_groups[index];
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_practice_exercise(vamp_zero::app_state const &state) -> vamp_zero::practice_exercise const * {
|
||||
auto const *lesson = vamp_zero::selected_practice_lesson(state);
|
||||
if (state.selected_practice_exercise_index == 0U || lesson == nullptr || lesson->exercises.empty()) {
|
||||
if (state.selected_practice_exercise_index == 0U) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = state.selected_practice_exercise_index - 1U;
|
||||
if (index >= lesson->exercises.size()) {
|
||||
if (index >= state.catalog->practice.exercises.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto exercise_index = lesson->exercises[index];
|
||||
return exercise_index < state.catalog->practice.exercises.size() ? &state.catalog->practice.exercises[exercise_index] : nullptr;
|
||||
return &state.catalog->practice.exercises[index];
|
||||
}
|
||||
|
||||
auto vamp_zero::active_practice(vamp_zero::app_state const &state) -> vamp_zero::practice_exercise const * {
|
||||
@@ -91,17 +66,10 @@ 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::supports_key_selection(vamp_zero::practice_exercise const &exercise) -> std::bool_t {
|
||||
return vamp_zero::practice_type_supports_key_selection(exercise.practice_type_id);
|
||||
}
|
||||
|
||||
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()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!vamp_zero::supports_key_selection(exercise)) {
|
||||
return &exercise.variants.front();
|
||||
}
|
||||
if (state.selected_practice_key_index == 0U) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -122,7 +90,7 @@ auto vamp_zero::selected_practice_key(vamp_zero::app_state const &state, vamp_ze
|
||||
|
||||
auto vamp_zero::practice_key_row_count(vamp_zero::app_state const &state) -> std::size_t {
|
||||
auto const *exercise = vamp_zero::selected_practice_exercise(state);
|
||||
if (exercise == nullptr || !vamp_zero::supports_key_selection(*exercise)) {
|
||||
if (exercise == nullptr) {
|
||||
return 1U;
|
||||
}
|
||||
return menu_row_count(exercise->variants.size());
|
||||
@@ -148,13 +116,3 @@ auto vamp_zero::active_pages(vamp_zero::app_state const &state) -> std::size_t {
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
auto vamp_zero::practice_fallback_menu(vamp_zero::app_state const &state) -> vamp_zero::app_mode {
|
||||
if (vamp_zero::selected_practice_lesson(state) != nullptr) {
|
||||
return vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
}
|
||||
if (vamp_zero::selected_practice_type(state) != nullptr) {
|
||||
return vamp_zero::app_mode::PRACTICE_LESSON_MENU;
|
||||
}
|
||||
return vamp_zero::app_mode::PRACTICE_TYPE_MENU;
|
||||
}
|
||||
|
||||
+19
-169
@@ -77,36 +77,14 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
} else {
|
||||
state.selected_part_index = 0U;
|
||||
}
|
||||
state.selected_practice_type_index =
|
||||
std::min(state.selected_practice_type_index, menu_row_count(state.catalog->practice.types.size()) - 1U);
|
||||
if (auto const *type = selected_practice_type(state); type != nullptr) {
|
||||
state.selected_practice_lesson_index =
|
||||
std::min(state.selected_practice_lesson_index, menu_row_count(type->lesson_groups.size()) - 1U);
|
||||
} else {
|
||||
state.selected_practice_lesson_index = 0U;
|
||||
}
|
||||
if (auto const *lesson = selected_practice_lesson(state); lesson != nullptr) {
|
||||
state.selected_practice_exercise_index =
|
||||
std::min(state.selected_practice_exercise_index, menu_row_count(lesson->exercises.size()) - 1U);
|
||||
} else {
|
||||
state.selected_practice_exercise_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);
|
||||
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_LESSON_MENU && selected_practice_type(state) == nullptr) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_TYPE_MENU;
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_EXERCISE_MENU && selected_practice_lesson(state) == nullptr) {
|
||||
state.mode = selected_practice_type(state) == nullptr ? vamp_zero::app_mode::PRACTICE_TYPE_MENU
|
||||
: vamp_zero::app_mode::PRACTICE_LESSON_MENU;
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
if (exercise == nullptr) {
|
||||
state.mode = practice_fallback_menu(state);
|
||||
} else if (!supports_key_selection(*exercise) || exercise->variants.empty()) {
|
||||
if (exercise == nullptr || exercise->variants.empty()) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.selected_practice_key_index = 0U;
|
||||
}
|
||||
@@ -119,7 +97,7 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
} else if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
auto const *exercise = active_practice(state);
|
||||
if (exercise == nullptr) {
|
||||
state.mode = practice_fallback_menu(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) {
|
||||
@@ -189,11 +167,6 @@ auto exercise_json(vamp_zero::practice_exercise const &exercise) -> boost::json:
|
||||
}
|
||||
auto result = boost::json::object{};
|
||||
result["id"] = exercise.id;
|
||||
result["practice_type_id"] = exercise.practice_type_id;
|
||||
result["practice_type_title"] = exercise.practice_type_title;
|
||||
result["lesson_group_id"] = exercise.lesson_group_id;
|
||||
result["lesson_group_title"] = exercise.lesson_group_title;
|
||||
result["supports_key_selection"] = supports_key_selection(exercise);
|
||||
result["title"] = exercise.title;
|
||||
result["composer"] = exercise.composer;
|
||||
result["authored_key_signature"] = exercise.authored_key_signature;
|
||||
@@ -209,41 +182,6 @@ auto exercise_json(vamp_zero::practice_exercise const &exercise) -> boost::json:
|
||||
return result;
|
||||
}
|
||||
|
||||
auto lesson_json(vamp_zero::practice_lesson_group const &lesson, vamp_zero::practice_library const &library) -> boost::json::object {
|
||||
auto exercises = boost::json::array{};
|
||||
for (auto const &exercise : lesson.exercises) {
|
||||
exercises.push_back(exercise_json(library.exercises[exercise]));
|
||||
}
|
||||
auto result = boost::json::object{};
|
||||
result["id"] = lesson.id;
|
||||
result["title"] = lesson.title;
|
||||
result["exercise_count"] = static_cast<std::uint64_t>(lesson.exercises.size());
|
||||
result["exercises"] = std::move(exercises);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto practice_type_exercise_count(vamp_zero::practice_type_group const &type) -> std::size_t {
|
||||
auto count = std::size_t{};
|
||||
for (auto const &lesson : type.lesson_groups) {
|
||||
count += lesson.exercises.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
auto type_json(vamp_zero::practice_type_group const &type, vamp_zero::practice_library const &library) -> boost::json::object {
|
||||
auto lessons = boost::json::array{};
|
||||
for (auto const &lesson : type.lesson_groups) {
|
||||
lessons.push_back(lesson_json(lesson, library));
|
||||
}
|
||||
auto result = boost::json::object{};
|
||||
result["id"] = type.id;
|
||||
result["title"] = type.title;
|
||||
result["lesson_group_count"] = static_cast<std::uint64_t>(type.lesson_groups.size());
|
||||
result["exercise_count"] = static_cast<std::uint64_t>(practice_type_exercise_count(type));
|
||||
result["lesson_groups"] = std::move(lessons);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto performance_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
auto groups = boost::json::array{};
|
||||
for (auto const &group : state.catalog->performance.groups) {
|
||||
@@ -265,10 +203,6 @@ auto performance_json(vamp_zero::app_state const &state) -> boost::json::object
|
||||
}
|
||||
|
||||
auto practice_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
auto types = boost::json::array{};
|
||||
for (auto const &type : state.catalog->practice.types) {
|
||||
types.push_back(type_json(type, state.catalog->practice));
|
||||
}
|
||||
auto exercises = boost::json::array{};
|
||||
for (auto const &exercise : state.catalog->practice.exercises) {
|
||||
exercises.push_back(exercise_json(exercise));
|
||||
@@ -278,16 +212,8 @@ auto practice_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
keys.push_back(boost::json::value{std::string{key}});
|
||||
}
|
||||
auto selection = boost::json::object{};
|
||||
selection["type_row_index"] = static_cast<std::uint64_t>(state.selected_practice_type_index);
|
||||
selection["lesson_row_index"] = static_cast<std::uint64_t>(state.selected_practice_lesson_index);
|
||||
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);
|
||||
if (auto const *type = selected_practice_type(state); type != nullptr) {
|
||||
selection["type"] = type_json(*type, state.catalog->practice);
|
||||
}
|
||||
if (auto const *lesson = selected_practice_lesson(state); lesson != nullptr) {
|
||||
selection["lesson_group"] = lesson_json(*lesson, state.catalog->practice);
|
||||
}
|
||||
if (auto const *exercise = selected_practice_exercise(state); exercise != nullptr) {
|
||||
selection["exercise"] = exercise_json(*exercise);
|
||||
auto variant = selected_practice_variant(state, *exercise);
|
||||
@@ -307,9 +233,7 @@ auto practice_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
}
|
||||
}
|
||||
auto practice = boost::json::object{};
|
||||
practice["type_count"] = static_cast<std::uint64_t>(state.catalog->practice.types.size());
|
||||
practice["exercise_count"] = static_cast<std::uint64_t>(state.catalog->practice.exercises.size());
|
||||
practice["types"] = std::move(types);
|
||||
practice["exercises"] = std::move(exercises);
|
||||
practice["key_count"] = static_cast<std::uint64_t>(vamp_zero::practice_key_names().size());
|
||||
practice["keys"] = std::move(keys);
|
||||
@@ -359,9 +283,7 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
state.active_part_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (state.selected_main_menu_index == main_menu_practice) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_TYPE_MENU;
|
||||
state.selected_practice_type_index = 0;
|
||||
state.selected_practice_lesson_index = 0;
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.selected_practice_exercise_index = 0;
|
||||
state.selected_practice_key_index = 0;
|
||||
state.active_practice_id.clear();
|
||||
@@ -422,85 +344,28 @@ 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_TYPE_MENU:
|
||||
case vamp_zero::app_mode::PRACTICE_EXERCISE_MENU:
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_practice_type_index =
|
||||
wrap_next(state.selected_practice_type_index, menu_row_count(state.catalog->practice.types.size()));
|
||||
state.selected_practice_lesson_index = 0;
|
||||
state.selected_practice_exercise_index = 0;
|
||||
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;
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.selected_practice_type_index =
|
||||
wrap_previous(state.selected_practice_type_index, menu_row_count(state.catalog->practice.types.size()));
|
||||
state.selected_practice_lesson_index = 0;
|
||||
state.selected_practice_exercise_index = 0;
|
||||
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;
|
||||
} else if (state.selected_practice_type_index == 0U) {
|
||||
} else if (state.selected_practice_exercise_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::MAIN_MENU;
|
||||
} else {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_LESSON_MENU;
|
||||
state.selected_practice_lesson_index = 0;
|
||||
state.selected_practice_exercise_index = 0;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (selected_practice_exercise(state) != nullptr) {
|
||||
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;
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_LESSON_MENU:
|
||||
if (auto const *type = selected_practice_type(state); type != nullptr) {
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_practice_lesson_index =
|
||||
wrap_next(state.selected_practice_lesson_index, menu_row_count(type->lesson_groups.size()));
|
||||
state.selected_practice_exercise_index = 0;
|
||||
state.selected_practice_key_index = 0;
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.selected_practice_lesson_index =
|
||||
wrap_previous(state.selected_practice_lesson_index, menu_row_count(type->lesson_groups.size()));
|
||||
state.selected_practice_exercise_index = 0;
|
||||
state.selected_practice_key_index = 0;
|
||||
} else if (state.selected_practice_lesson_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_TYPE_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.selected_practice_exercise_index = 0;
|
||||
state.selected_practice_key_index = 0;
|
||||
}
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_EXERCISE_MENU:
|
||||
if (auto const *lesson = selected_practice_lesson(state); lesson != nullptr) {
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_practice_exercise_index =
|
||||
wrap_next(state.selected_practice_exercise_index, menu_row_count(lesson->exercises.size()));
|
||||
state.selected_practice_key_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(lesson->exercises.size()));
|
||||
state.selected_practice_key_index = 0;
|
||||
} else if (state.selected_practice_exercise_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_LESSON_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (auto const *exercise = selected_practice_exercise(state); exercise != nullptr) {
|
||||
state.active_part_id.clear();
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
if (supports_key_selection(*exercise) && !exercise->variants.empty()) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_KEY_MENU;
|
||||
state.selected_practice_key_index = 0;
|
||||
} else {
|
||||
state.active_practice_id = exercise->id;
|
||||
state.selected_practice_key_index = 0;
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_SCORE;
|
||||
++state.score_session;
|
||||
state.audio.playback_error.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_KEY_MENU:
|
||||
if (auto const *exercise = selected_practice_exercise(state);
|
||||
exercise != nullptr && supports_key_selection(*exercise)) {
|
||||
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()));
|
||||
@@ -530,8 +395,7 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.page_index = state.page_index == 0U ? 0U : state.page_index - 1U;
|
||||
} else {
|
||||
state.mode = supports_key_selection(*exercise) ? vamp_zero::app_mode::PRACTICE_KEY_MENU
|
||||
: vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_KEY_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
}
|
||||
@@ -662,29 +526,15 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
|
||||
group != nullptr && state.selected_part_index >= menu_row_count(group->parts.size())) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected performance part row is out of range"));
|
||||
}
|
||||
if (state.selected_practice_type_index >= menu_row_count(state.catalog->practice.types.size())) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected practice type row is out of range"));
|
||||
}
|
||||
if (auto const *type = selected_practice_type(state);
|
||||
type != nullptr && state.selected_practice_lesson_index >= menu_row_count(type->lesson_groups.size())) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected practice lesson row is out of range"));
|
||||
}
|
||||
if (auto const *lesson = selected_practice_lesson(state);
|
||||
lesson != nullptr && state.selected_practice_exercise_index >= menu_row_count(lesson->exercises.size())) {
|
||||
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.mode == vamp_zero::app_mode::PRACTICE_LESSON_MENU && selected_practice_type(state) == nullptr) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice lesson mode requires a selected practice type"));
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_EXERCISE_MENU && selected_practice_lesson(state) == nullptr) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice exercise mode requires a selected lesson group"));
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
if (exercise == nullptr || !supports_key_selection(*exercise)) {
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,6 @@ auto vamp_zero::to_string(vamp_zero::app_mode value) -> std::string {
|
||||
return "performance_part_menu";
|
||||
case vamp_zero::app_mode::PERFORMANCE_SCORE:
|
||||
return "performance_score";
|
||||
case vamp_zero::app_mode::PRACTICE_TYPE_MENU:
|
||||
return "practice_type_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_LESSON_MENU:
|
||||
return "practice_lesson_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_EXERCISE_MENU:
|
||||
return "practice_exercise_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_KEY_MENU:
|
||||
@@ -60,7 +56,3 @@ auto vamp_zero::practice_key_names() -> std::vector<std::string_view> const & {
|
||||
};
|
||||
return keys;
|
||||
}
|
||||
|
||||
auto vamp_zero::practice_type_supports_key_selection(std::string_view practice_type_id) -> std::bool_t {
|
||||
return practice_type_id == "melody" || practice_type_id == "chord";
|
||||
}
|
||||
|
||||
+12
-114
@@ -275,110 +275,14 @@ auto draw_performance_part_menu(luma::draw_context &context, luma::rect bounds,
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_type_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "PRACTICE", 4, vamp_style::ink, 300.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(state.catalog->practice.types.size()) + " TYPES " + std::to_string(state.catalog->practice.exercises.size()) + " EXERCISES", 1, vamp_style::muted, 420.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 = state.catalog->practice.types.size() + 1U;
|
||||
auto max_rows = std::min<std::size_t>(total_rows, 7U);
|
||||
auto first = first_visible_row(state.selected_practice_type_index, total_rows, max_rows);
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
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},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_practice_type_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const &type = state.catalog->practice.types[index - 1U];
|
||||
auto exercise_count = std::size_t{};
|
||||
for (auto const &lesson : type.lesson_groups) {
|
||||
exercise_count += lesson.exercises.size();
|
||||
}
|
||||
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},
|
||||
type.title,
|
||||
std::to_string(exercise_count) + " EXERCISES",
|
||||
index == state.selected_practice_type_index);
|
||||
}
|
||||
if (state.catalog->practice.types.empty()) {
|
||||
draw_text(context, bounds.x + 38.0F, list_y + row_height + 28.0F, "NO PRACTICE EXERCISES FOUND", 3, vamp_style::retrying, 560.0F);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_lesson_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *type = selected_practice_type(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
type == nullptr ? std::string{"PRACTICE"} : type->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT LESSON GROUP", 1, vamp_style::muted, 360.0F);
|
||||
if (type == nullptr) {
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 118.0F, "NO LESSON GROUPS FOUND", 3, vamp_style::retrying, 520.0F);
|
||||
return;
|
||||
}
|
||||
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 = type->lesson_groups.size() + 1U;
|
||||
auto max_rows = std::min<std::size_t>(total_rows, 7U);
|
||||
auto first = first_visible_row(state.selected_practice_lesson_index, total_rows, max_rows);
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
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},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_practice_lesson_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const &lesson = type->lesson_groups[index - 1U];
|
||||
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},
|
||||
lesson.title,
|
||||
std::to_string(lesson.exercises.size()) + " EXERCISES",
|
||||
index == state.selected_practice_lesson_index);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_exercise_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *lesson = selected_practice_lesson(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
lesson == nullptr ? std::string{"EXERCISES"} : lesson->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT EXERCISE", 1, vamp_style::muted, 320.0F);
|
||||
if (lesson == nullptr) {
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 118.0F, "NO EXERCISES FOUND", 3, vamp_style::retrying, 440.0F);
|
||||
return;
|
||||
}
|
||||
auto const &exercises = state.catalog->practice.exercises;
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "PRACTICE", 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(exercises.size()) + " EXERCISES", 1, vamp_style::muted, 320.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 = lesson->exercises.size() + 1U;
|
||||
auto total_rows = exercises.size() + 1U;
|
||||
auto max_rows = std::min<std::size_t>(total_rows, 7U);
|
||||
auto first = first_visible_row(state.selected_practice_exercise_index, total_rows, max_rows);
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
@@ -394,22 +298,20 @@ auto draw_practice_exercise_menu(luma::draw_context &context, luma::rect bounds,
|
||||
state.selected_practice_exercise_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto exercise_index = lesson->exercises[index - 1U];
|
||||
if (exercise_index >= state.catalog->practice.exercises.size()) {
|
||||
continue;
|
||||
}
|
||||
auto const &exercise = state.catalog->practice.exercises[exercise_index];
|
||||
auto secondary =
|
||||
supports_key_selection(exercise) ? std::string{"KEY SELECT"} : "KEY " + effective_practice_key(state, exercise);
|
||||
auto const &exercise = exercises[index - 1U];
|
||||
auto title = exercise.title == exercise.id ? exercise.id : exercise.id + " - " + exercise.title;
|
||||
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},
|
||||
exercise.title,
|
||||
secondary,
|
||||
title,
|
||||
"",
|
||||
index == state.selected_practice_exercise_index);
|
||||
}
|
||||
if (exercises.empty()) {
|
||||
draw_text(context, bounds.x + 38.0F, list_y + row_height + 28.0F, "NO EXERCISES FOUND", 3, vamp_style::retrying, 440.0F);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_key_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
@@ -465,7 +367,7 @@ auto draw_score(luma::draw_context &context, luma::rect bounds, vamp_zero::app_s
|
||||
} else {
|
||||
auto const *exercise = active_practice(state);
|
||||
if (exercise == nullptr) {
|
||||
draw_practice_type_menu(context, bounds, state);
|
||||
draw_practice_exercise_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
auto const *variant = selected_practice_variant(state, *exercise);
|
||||
@@ -531,10 +433,6 @@ public:
|
||||
draw_performance_song_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU) {
|
||||
draw_performance_part_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_TYPE_MENU) {
|
||||
draw_practice_type_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_LESSON_MENU) {
|
||||
draw_practice_lesson_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) {
|
||||
|
||||
Reference in New Issue
Block a user