Shared Catalog

This commit is contained in:
khodowany
2026-09-17 00:44:19 -05:00
parent aa1273b07d
commit 5941a4cd49
44 changed files with 3613 additions and 733 deletions
+38 -28
View File
@@ -66,16 +66,19 @@ auto decrease_bpm(vamp_zero::audio_state &audio) -> void {
}
auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
if (!state.catalog) {
state.catalog = vamp_zero::empty_catalogue();
}
state.audio = clamp_audio_state(state.audio);
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);
state.selected_song_index = std::min(state.selected_song_index, menu_row_count(state.catalog->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);
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);
@@ -192,19 +195,23 @@ auto exercise_json(vamp_zero::practice_exercise const &exercise) -> boost::json:
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["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);
return result;
}
auto lesson_json(vamp_zero::practice_lesson_group const &lesson) -> boost::json::object {
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(exercise));
exercises.push_back(exercise_json(library.exercises[exercise]));
}
auto result = boost::json::object{};
result["id"] = lesson.id;
@@ -222,10 +229,10 @@ auto practice_type_exercise_count(vamp_zero::practice_type_group const &type) ->
return count;
}
auto type_json(vamp_zero::practice_type_group const &type) -> boost::json::object {
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));
lessons.push_back(lesson_json(lesson, library));
}
auto result = boost::json::object{};
result["id"] = type.id;
@@ -238,10 +245,10 @@ auto type_json(vamp_zero::practice_type_group const &type) -> boost::json::objec
auto performance_json(vamp_zero::app_state const &state) -> boost::json::object {
auto groups = boost::json::array{};
for (auto const &group : state.performance.groups) {
for (auto const &group : state.catalog->performance.groups) {
auto parts = boost::json::array{};
for (auto const &part : group.parts) {
parts.push_back(part_json(part));
parts.push_back(part_json(state.catalog->performance.parts[part]));
}
auto item = boost::json::object{};
item["title"] = group.title;
@@ -250,19 +257,19 @@ auto performance_json(vamp_zero::app_state const &state) -> boost::json::object
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["song_count"] = static_cast<std::uint64_t>(state.catalog->performance.groups.size());
library["part_count"] = static_cast<std::uint64_t>(state.catalog->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));
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.practice.exercises) {
for (auto const &exercise : state.catalog->practice.exercises) {
exercises.push_back(exercise_json(exercise));
}
auto keys = boost::json::array{};
@@ -275,10 +282,10 @@ auto practice_json(vamp_zero::app_state const &state) -> 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);
if (auto const *type = selected_practice_type(state); type != nullptr) {
selection["type"] = type_json(*type);
selection["type"] = type_json(*type, state.catalog->practice);
}
if (auto const *lesson = selected_practice_lesson(state); lesson != nullptr) {
selection["lesson_group"] = lesson_json(*lesson);
selection["lesson_group"] = lesson_json(*lesson, state.catalog->practice);
}
if (auto const *exercise = selected_practice_exercise(state); exercise != nullptr) {
selection["exercise"] = exercise_json(*exercise);
@@ -299,8 +306,8 @@ 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.practice.types.size());
practice["exercise_count"] = static_cast<std::uint64_t>(state.practice.exercises.size());
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());
@@ -368,11 +375,11 @@ 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::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_song_index = wrap_next(state.selected_song_index, menu_row_count(state.catalog->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()));
wrap_previous(state.selected_song_index, menu_row_count(state.catalog->performance.groups.size()));
state.selected_part_index = 0;
} else if (state.selected_song_index == 0U) {
state.mode = vamp_zero::app_mode::MAIN_MENU;
@@ -415,13 +422,13 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
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()));
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_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()));
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_key_index = 0;
@@ -619,6 +626,9 @@ auto vamp_zero::remote_display_text(vamp_zero::app_state const &state) -> std::s
}
auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::expected<void, modulus::error> {
if (!state.catalog) {
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "catalogue is missing"));
}
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"));
}
@@ -638,14 +648,14 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
if (state.audio.tempo_editing && state.mode != vamp_zero::app_mode::MAIN_MENU) {
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "tempo edit mode requires the main menu"));
}
if (state.selected_song_index >= menu_row_count(state.performance.groups.size())) {
if (state.selected_song_index >= menu_row_count(state.catalog->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())) {
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);
@@ -668,7 +678,7 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
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"));
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice key mode requires an exercise with key variants"));
}
}
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {