#include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr auto main_menu_count = std::size_t{2}; auto wrap_next(std::size_t value, std::size_t count) -> std::size_t { if (count == 0U) { return 0U; } return (value + 1U) % count; } auto wrap_previous(std::size_t value, std::size_t count) -> std::size_t { if (count == 0U) { return 0U; } return value == 0U ? count - 1U : value - 1U; } auto menu_row_count(std::size_t item_count) -> std::size_t { return item_count + 1U; } auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state { state.selected_main_menu_index = std::min(state.selected_main_menu_index, main_menu_count - 1U); state.selected_song_index = std::min(state.selected_song_index, menu_row_count(state.performance.groups.size()) - 1U); if (auto const* group = selected_group(state); group != nullptr) { state.selected_part_index = std::min(state.selected_part_index, menu_row_count(group->parts.size()) - 1U); } else { state.selected_part_index = 0U; } state.selected_practice_type_index = std::min(state.selected_practice_type_index, menu_row_count(state.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_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()) { state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU; state.selected_practice_key_index = 0U; } } if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE && active_part(state) == nullptr) { state.mode = selected_group(state) == nullptr ? vamp_zero::app_mode::PERFORMANCE_SONG_MENU : vamp_zero::app_mode::PERFORMANCE_PART_MENU; state.active_part_id.clear(); state.page_index = 0; } 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.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; state.active_practice_id.clear(); state.page_index = 0; } } if (auto pages = active_pages(state); pages != 0U) { state.page_index = std::min(state.page_index, pages - 1U); } else if (state.mode != vamp_zero::app_mode::PERFORMANCE_SCORE && state.mode != vamp_zero::app_mode::PRACTICE_SCORE) { state.page_index = 0; } return state; } auto command_json(vamp_zero::app_state const& state) -> boost::json::object { auto commands = boost::json::object{}; commands["last"] = state.last_command; commands["next_count"] = state.next_count; commands["previous_count"] = state.previous_count; commands["select_count"] = state.select_count; commands["heartbeat_count"] = state.heartbeat_count; return commands; } auto part_json(vamp_zero::library_part const& part) -> boost::json::object { auto result = boost::json::object{}; result["id"] = part.id; result["title"] = part.title; result["composer"] = part.composer; result["key_signature"] = part.key_signature; result["part"] = part.part; result["source_path"] = part.source_path; result["measures"] = static_cast(part.measure_count); result["pages"] = static_cast(part.total_pages); return result; } 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(variant.measure_count); item["pages"] = static_cast(variant.total_pages); variants.push_back(std::move(item)); } 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["key_signature"] = exercise.key_signature; result["source_path"] = exercise.source_path; result["measures"] = static_cast(exercise.measure_count); result["pages"] = static_cast(exercise.total_pages); result["variant_count"] = static_cast(exercise.variants.size()); result["variants"] = std::move(variants); return result; } auto lesson_json(vamp_zero::practice_lesson_group const& lesson) -> boost::json::object { auto exercises = boost::json::array{}; for (auto const& exercise : lesson.exercises) { exercises.push_back(exercise_json(exercise)); } auto result = boost::json::object{}; result["id"] = lesson.id; result["title"] = lesson.title; result["exercise_count"] = static_cast(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) -> boost::json::object { auto lessons = boost::json::array{}; for (auto const& lesson : type.lesson_groups) { lessons.push_back(lesson_json(lesson)); } auto result = boost::json::object{}; result["id"] = type.id; result["title"] = type.title; result["lesson_group_count"] = static_cast(type.lesson_groups.size()); result["exercise_count"] = static_cast(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.performance.groups) { auto parts = boost::json::array{}; for (auto const& part : group.parts) { parts.push_back(part_json(part)); } auto item = boost::json::object{}; item["title"] = group.title; item["composer"] = group.composer; item["parts"] = std::move(parts); groups.push_back(std::move(item)); } auto library = boost::json::object{}; library["song_count"] = static_cast(state.performance.groups.size()); library["part_count"] = static_cast(state.performance.parts.size()); library["groups"] = std::move(groups); return library; } auto practice_json(vamp_zero::app_state const& state) -> boost::json::object { auto types = boost::json::array{}; for (auto const& type : state.practice.types) { types.push_back(type_json(type)); } auto exercises = boost::json::array{}; for (auto const& exercise : state.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["type_row_index"] = static_cast(state.selected_practice_type_index); selection["lesson_row_index"] = static_cast(state.selected_practice_lesson_index); selection["exercise_row_index"] = static_cast(state.selected_practice_exercise_index); selection["key_row_index"] = static_cast(state.selected_practice_key_index); if (auto const* type = selected_practice_type(state); type != nullptr) { selection["type"] = type_json(*type); } if (auto const* lesson = selected_practice_lesson(state); lesson != nullptr) { selection["lesson_group"] = lesson_json(*lesson); } 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(variant->measure_count); item["pages"] = static_cast(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 = boost::json::object{}; practice["type_count"] = static_cast(state.practice.types.size()); practice["exercise_count"] = static_cast(state.practice.exercises.size()); practice["types"] = std::move(types); practice["exercises"] = std::move(exercises); practice["key_count"] = static_cast(vamp_zero::practice_key_names().size()); practice["keys"] = std::move(keys); practice["selection"] = std::move(selection); return practice; } auto sanitize_line_value(std::string value) -> std::string { for (auto& ch : value) { if (ch == '\r' || ch == '\n') { ch = ' '; } } return value; } } auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::page_command command) -> vamp_zero::app_state { state = clamp_state(std::move(state)); if (command == vamp_protocol::page_command::NEXT) { ++state.next_count; } else if (command == vamp_protocol::page_command::PREVIOUS) { ++state.previous_count; } else { ++state.select_count; } state.last_command = vamp_protocol::page_command_name(command); switch (state.mode) { case vamp_zero::app_mode::MAIN_MENU: if (command == vamp_protocol::page_command::NEXT) { state.selected_main_menu_index = wrap_next(state.selected_main_menu_index, main_menu_count); } else if (command == vamp_protocol::page_command::PREVIOUS) { state.selected_main_menu_index = wrap_previous(state.selected_main_menu_index, main_menu_count); } else if (state.selected_main_menu_index == 0U) { state.mode = vamp_zero::app_mode::PERFORMANCE_SONG_MENU; state.selected_song_index = 0; state.selected_part_index = 0; state.active_part_id.clear(); state.page_index = 0; } else { state.mode = vamp_zero::app_mode::PRACTICE_TYPE_MENU; state.selected_practice_type_index = 0; state.selected_practice_lesson_index = 0; state.selected_practice_exercise_index = 0; state.selected_practice_key_index = 0; state.active_practice_id.clear(); state.page_index = 0; } return clamp_state(std::move(state)); case vamp_zero::app_mode::PERFORMANCE_SONG_MENU: if (command == vamp_protocol::page_command::NEXT) { state.selected_song_index = wrap_next(state.selected_song_index, menu_row_count(state.performance.groups.size())); state.selected_part_index = 0; } else if (command == vamp_protocol::page_command::PREVIOUS) { state.selected_song_index = wrap_previous(state.selected_song_index, menu_row_count(state.performance.groups.size())); state.selected_part_index = 0; } else if (state.selected_song_index == 0U) { state.mode = vamp_zero::app_mode::MAIN_MENU; } else { state.mode = vamp_zero::app_mode::PERFORMANCE_PART_MENU; state.selected_part_index = 0; } return clamp_state(std::move(state)); case vamp_zero::app_mode::PERFORMANCE_PART_MENU: if (auto const* group = selected_group(state); group != nullptr) { if (command == vamp_protocol::page_command::NEXT) { state.selected_part_index = wrap_next(state.selected_part_index, menu_row_count(group->parts.size())); } else if (command == vamp_protocol::page_command::PREVIOUS) { state.selected_part_index = wrap_previous(state.selected_part_index, menu_row_count(group->parts.size())); } else if (state.selected_part_index == 0U) { state.mode = vamp_zero::app_mode::PERFORMANCE_SONG_MENU; state.active_part_id.clear(); state.page_index = 0; } else if (auto const* part = selected_part(state); part != nullptr) { state.active_part_id = part->id; state.active_practice_id.clear(); state.page_index = 0; state.mode = vamp_zero::app_mode::PERFORMANCE_SCORE; } } return clamp_state(std::move(state)); case vamp_zero::app_mode::PERFORMANCE_SCORE: if (auto const* part = active_part(state); part != nullptr) { if (command == vamp_protocol::page_command::NEXT) { state.page_index = std::min(state.page_index + 1U, part->total_pages == 0U ? 0U : part->total_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::PERFORMANCE_PART_MENU; state.active_part_id.clear(); state.page_index = 0; } } return clamp_state(std::move(state)); case vamp_zero::app_mode::PRACTICE_TYPE_MENU: if (command == vamp_protocol::page_command::NEXT) { state.selected_practice_type_index = wrap_next(state.selected_practice_type_index, menu_row_count(state.practice.types.size())); state.selected_practice_lesson_index = 0; state.selected_practice_exercise_index = 0; 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.practice.types.size())); state.selected_practice_lesson_index = 0; state.selected_practice_exercise_index = 0; state.selected_practice_key_index = 0; } else if (state.selected_practice_type_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.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; } } } 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)) { 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())); } 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.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.active_practice_id = exercise->id; state.active_part_id.clear(); state.page_index = 0; state.mode = vamp_zero::app_mode::PRACTICE_SCORE; } } 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); if (command == vamp_protocol::page_command::NEXT) { auto pages = variant == nullptr ? 0U : variant->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 = supports_key_selection(*exercise) ? vamp_zero::app_mode::PRACTICE_KEY_MENU : vamp_zero::app_mode::PRACTICE_EXERCISE_MENU; state.active_practice_id.clear(); state.page_index = 0; } } return clamp_state(std::move(state)); } return clamp_state(std::move(state)); } auto vamp_zero::app_status_json(vamp_zero::app_state const& state) -> boost::json::value { auto selection = boost::json::object{}; selection["main_menu_index"] = static_cast(state.selected_main_menu_index); selection["song_row_index"] = static_cast(state.selected_song_index); selection["part_row_index"] = static_cast(state.selected_part_index); if (auto const* group = selected_group(state); group != nullptr) { selection["title"] = group->title; selection["composer"] = group->composer; } if (auto const* part = selected_part(state); part != nullptr) { selection["part"] = part_json(*part); } auto active = boost::json::object{}; if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) { if (auto const* part = active_part(state); part != nullptr) { active["selected"] = true; active["kind"] = "performance"; active["part"] = part_json(*part); active["page_index"] = static_cast(state.page_index); active["page_number"] = static_cast(state.page_index + 1U); active["page_total"] = static_cast(part->total_pages); } } 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); active["selected"] = true; active["kind"] = "practice"; active["exercise"] = exercise_json(*exercise); active["key"] = effective_practice_key(state, *exercise); active["page_index"] = static_cast(state.page_index); active["page_number"] = static_cast(state.page_index + 1U); active["page_total"] = static_cast(variant == nullptr ? 0U : variant->total_pages); } } if (active.empty()) { active["selected"] = false; active["page_index"] = 0; active["page_number"] = 0; active["page_total"] = 0; } auto remote = boost::json::object{}; remote["pico_connected"] = state.pico_connected; remote["heartbeat_count"] = state.heartbeat_count; auto performance = performance_json(state); auto root = boost::json::object{}; root["mode"] = vamp_zero::to_string(state.mode); root["library"] = performance; root["performance"] = std::move(performance); root["practice"] = practice_json(state); root["selection"] = std::move(selection); root["active"] = std::move(active); root["commands"] = command_json(state); root["remote"] = std::move(remote); return root; } auto vamp_zero::remote_display_text(vamp_zero::app_state const& state) -> std::string { auto title = std::string{}; auto part = std::string{}; auto page = std::size_t{}; auto pages = std::size_t{}; auto active = false; if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) { if (auto const* selected = active_part(state); selected != nullptr) { active = true; title = selected->title; part = selected->part; page = state.page_index + 1U; pages = selected->total_pages; } } 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); active = true; title = selected->title; part = "Key: " + effective_practice_key(state, *selected); page = state.page_index + 1U; pages = variant == nullptr ? 0U : variant->total_pages; } } auto output = std::ostringstream{}; output << "version=1\n"; output << "mode=" << vamp_zero::to_string(state.mode) << "\n"; output << "active=" << (active ? "1" : "0") << "\n"; output << "title=" << sanitize_line_value(title) << "\n"; output << "part=" << sanitize_line_value(part) << "\n"; output << "page=" << page << "\n"; output << "pages=" << pages << "\n"; return output.str(); } auto vamp_zero::validate_app_state(vamp_zero::app_state const& state) -> std::expected { if (state.selected_main_menu_index >= main_menu_count) { return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected main menu index is out of range")); } if (state.selected_song_index >= menu_row_count(state.performance.groups.size())) { return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected performance song row is out of range")); } if (auto const* group = selected_group(state); 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.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())) { 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)) { return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice key mode requires a melody exercise")); } } if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) { auto const* part = active_part(state); if (part == nullptr) { return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "performance score mode requires an active part")); } if (part->total_pages == 0U || state.page_index >= part->total_pages) { return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "performance score page index is out of range")); } } if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) { auto const* exercise = active_practice(state); 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")); } if (variant->total_pages == 0U || state.page_index >= variant->total_pages) { return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice score page index is out of range")); } } return {}; }