625 lines
28 KiB
C++
625 lines
28 KiB
C++
#include <vamp_zero/page_control.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <expected>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include <boost/json/array.hpp>
|
|
#include <boost/json/object.hpp>
|
|
#include <boost/json/value.hpp>
|
|
|
|
#include <vamp_zero/errors.hpp>
|
|
#include <vamp_zero/navigation.hpp>
|
|
|
|
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<std::uint64_t>(part.measure_count);
|
|
result["pages"] = static_cast<std::uint64_t>(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<std::uint64_t>(variant.measure_count);
|
|
item["pages"] = static_cast<std::uint64_t>(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<std::uint64_t>(exercise.measure_count);
|
|
result["pages"] = static_cast<std::uint64_t>(exercise.total_pages);
|
|
result["variant_count"] = static_cast<std::uint64_t>(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<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) -> 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<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.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<std::uint64_t>(state.performance.groups.size());
|
|
library["part_count"] = static_cast<std::uint64_t>(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<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);
|
|
}
|
|
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<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 = boost::json::object{};
|
|
practice["type_count"] = static_cast<std::uint64_t>(state.practice.types.size());
|
|
practice["exercise_count"] = static_cast<std::uint64_t>(state.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);
|
|
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<std::uint64_t>(state.selected_main_menu_index);
|
|
selection["song_row_index"] = static_cast<std::uint64_t>(state.selected_song_index);
|
|
selection["part_row_index"] = static_cast<std::uint64_t>(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<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>(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<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);
|
|
}
|
|
}
|
|
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<void, modulus::error> {
|
|
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 {};
|
|
}
|