Cleanup
ci / checks (push) Successful in 38m56s

This commit is contained in:
khodowany
2026-09-08 08:53:41 -05:00
parent 62abb2d3f6
commit d7919f148d
21 changed files with 1322 additions and 479 deletions
+112 -15
View File
@@ -21,6 +21,7 @@
#include <vamp_zero/errors.hpp>
#include <vamp_zero/library.hpp>
#include <vamp_zero/score.hpp>
namespace {
@@ -108,6 +109,31 @@ auto now_ms() -> std::int64_t {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
auto execute_delete_stale_catalog_rows(noto::connection& connection, std::string_view table, std::vector<std::string> const& ids) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
auto sql = std::string{"DELETE FROM "};
sql += table;
sql += " WHERE catalog_managed = 1";
auto params = std::vector<noto::sqlite_value>{};
params.reserve(ids.size());
if (!ids.empty()) {
sql += " AND id NOT IN (";
for (auto index = std::size_t{}; index < ids.size(); ++index) {
if (index != 0U) {
sql += ", ";
}
sql += "?";
sql += std::to_string(index + 1U);
params.emplace_back(ids[index]);
}
sql += ")";
}
auto deleted = co_await connection.async_execute(std::move(sql), std::move(params));
if (!deleted) {
co_return std::unexpected(deleted.error());
}
co_return std::expected<void, modulus::error>{};
}
}
auto vamp_zero::split_migration_statements(std::string const& input) -> std::expected<std::vector<std::string>, modulus::error> {
@@ -203,6 +229,10 @@ auto vamp_zero::async_migrate_database(boost::asio::any_io_executor executor, va
if (!practice_hierarchy_statements) {
co_return std::unexpected(practice_hierarchy_statements.error());
}
auto catalog_managed_statements = read_migration(config, "005_catalog_managed_rows.sql");
if (!catalog_managed_statements) {
co_return std::unexpected(catalog_managed_statements.error());
}
auto migrator = noto::migrator{connection};
auto migrations = std::vector<noto::migration>{};
auto foundation = noto::migration{};
@@ -237,12 +267,20 @@ auto vamp_zero::async_migrate_database(boost::asio::any_io_executor executor, va
co_return co_await execute_statements(connection, statements);
};
migrations.push_back(std::move(practice_hierarchy));
auto catalog_managed = noto::migration{};
catalog_managed.id = "005_catalog_managed_rows";
catalog_managed.description = "mark rows managed by bundled catalog import";
catalog_managed.checksum = "005_catalog_managed_rows_v1";
catalog_managed.up = [statements = std::move(*catalog_managed_statements)](noto::connection& connection) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
co_return co_await execute_statements(connection, statements);
};
migrations.push_back(std::move(catalog_managed));
auto migrated = co_await migrator.async_migrate(std::move(migrations));
if (!migrated) {
co_return std::unexpected(migrated.error());
}
auto version = co_await connection.async_set_user_version(4);
auto version = co_await connection.async_set_user_version(5);
if (!version) {
co_return std::unexpected(version.error());
}
@@ -268,21 +306,14 @@ auto vamp_zero::async_import_catalogs(boost::asio::any_io_executor executor, vam
co_return std::unexpected(begin.error());
}
auto imported = std::expected<void, modulus::error>{};
auto cleared = co_await connection.async_execute("DELETE FROM songs");
if (!cleared) {
imported = std::unexpected(cleared.error());
}
if (imported) {
auto cleared_practice = co_await connection.async_execute("DELETE FROM practice_exercises");
if (!cleared_practice) {
imported = std::unexpected(cleared_practice.error());
}
}
auto timestamp = now_ms();
auto song_ids = std::vector<std::string>{};
song_ids.reserve(performance->parts.size());
for (auto const& part : performance->parts) {
if (!imported) {
break;
}
song_ids.push_back(part.id);
auto params = std::vector<noto::sqlite_value>{
noto::sqlite_value{part.id},
noto::sqlite_value{part.title},
@@ -294,16 +325,41 @@ auto vamp_zero::async_import_catalogs(boost::asio::any_io_executor executor, vam
noto::sqlite_value{part.source_path},
noto::sqlite_value{part.key_signature},
};
auto inserted = co_await connection.async_execute("INSERT INTO songs(id, title, composer, source_json, created_at_ms, updated_at_ms, part, source_path, key_signature) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", std::move(params));
auto inserted = co_await connection.async_execute(
R"sql(
INSERT INTO songs(id, title, composer, source_json, created_at_ms, updated_at_ms, part, source_path, key_signature, catalog_managed)
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 1)
ON CONFLICT(id) DO UPDATE SET
title = excluded.title,
composer = excluded.composer,
source_json = excluded.source_json,
part = excluded.part,
source_path = excluded.source_path,
key_signature = excluded.key_signature,
catalog_managed = 1,
updated_at_ms = CASE
WHEN songs.title != excluded.title
OR songs.composer != excluded.composer
OR songs.source_json != excluded.source_json
OR songs.part != excluded.part
OR songs.source_path != excluded.source_path
OR songs.key_signature != excluded.key_signature
THEN excluded.updated_at_ms
ELSE songs.updated_at_ms
END
)sql",
std::move(params));
if (!inserted) {
imported = std::unexpected(inserted.error());
}
}
auto practice_ids = std::vector<std::string>{};
for (auto const& exercise : practice->exercises) {
if (!imported) {
break;
}
for (auto const& variant : exercise.variants) {
practice_ids.push_back(variant.id);
auto params = std::vector<noto::sqlite_value>{
noto::sqlite_value{variant.id},
noto::sqlite_value{exercise.practice_type_id},
@@ -318,13 +374,54 @@ auto vamp_zero::async_import_catalogs(boost::asio::any_io_executor executor, vam
noto::sqlite_value{timestamp},
noto::sqlite_value{timestamp},
};
auto inserted = co_await connection.async_execute("INSERT INTO practice_exercises(id, practice_type_id, practice_type_title, lesson_group_id, lesson_group_title, title, composer, key_signature, source_json, source_path, created_at_ms, updated_at_ms) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)", std::move(params));
auto inserted = co_await connection.async_execute(
R"sql(
INSERT INTO practice_exercises(id, practice_type_id, practice_type_title, lesson_group_id, lesson_group_title, title, composer, key_signature, source_json, source_path, created_at_ms, updated_at_ms, catalog_managed)
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, 1)
ON CONFLICT(id) DO UPDATE SET
practice_type_id = excluded.practice_type_id,
practice_type_title = excluded.practice_type_title,
lesson_group_id = excluded.lesson_group_id,
lesson_group_title = excluded.lesson_group_title,
title = excluded.title,
composer = excluded.composer,
key_signature = excluded.key_signature,
source_json = excluded.source_json,
source_path = excluded.source_path,
catalog_managed = 1,
updated_at_ms = CASE
WHEN practice_exercises.practice_type_id != excluded.practice_type_id
OR practice_exercises.practice_type_title != excluded.practice_type_title
OR practice_exercises.lesson_group_id != excluded.lesson_group_id
OR practice_exercises.lesson_group_title != excluded.lesson_group_title
OR practice_exercises.title != excluded.title
OR practice_exercises.composer != excluded.composer
OR practice_exercises.key_signature != excluded.key_signature
OR practice_exercises.source_json != excluded.source_json
OR practice_exercises.source_path != excluded.source_path
THEN excluded.updated_at_ms
ELSE practice_exercises.updated_at_ms
END
)sql",
std::move(params));
if (!inserted) {
imported = std::unexpected(inserted.error());
break;
}
}
}
if (imported) {
auto deleted = co_await execute_delete_stale_catalog_rows(connection, "songs", song_ids);
if (!deleted) {
imported = std::unexpected(deleted.error());
}
}
if (imported) {
auto deleted = co_await execute_delete_stale_catalog_rows(connection, "practice_exercises", practice_ids);
if (!deleted) {
imported = std::unexpected(deleted.error());
}
}
if (imported) {
auto committed = co_await connection.async_execute("COMMIT");
if (!committed) {
@@ -380,7 +477,7 @@ auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor exec
.source_path = text_value(row[4]),
.source_json = std::move(source_json),
.measure_count = parsed->measures.size(),
.total_pages = parsed->measures.empty() ? 1U : (parsed->measures.size() + 11U) / 12U,
.total_pages = vamp_zero::score_page_count(*parsed),
});
}
auto closed = co_await connection.async_close();
@@ -428,7 +525,7 @@ auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executo
.source_path = source_path,
.source_json = source_json,
.measure_count = parsed->measures.size(),
.total_pages = parsed->measures.empty() ? 1U : (parsed->measures.size() + 11U) / 12U,
.total_pages = vamp_zero::score_page_count(*parsed),
};
exercises.push_back({
.id = logical_practice_exercise_id(type_id, lesson_id, source_path, text_value(row[5])),
+3 -7
View File
@@ -19,6 +19,7 @@
#include <boost/system/error_code.hpp>
#include <vamp_zero/errors.hpp>
#include <vamp_zero/score.hpp>
namespace {
@@ -273,11 +274,6 @@ auto parse_measure(boost::json::value const& value) -> std::expected<vamp_zero::
return measure;
}
auto score_page_count(std::size_t measure_count) -> std::size_t {
constexpr auto measures_per_page = std::size_t{12};
return measure_count == 0U ? 1U : (measure_count + measures_per_page - 1U) / measures_per_page;
}
auto load_score_documents(std::filesystem::path const& directory, std::bool_t required) -> std::expected<std::vector<vamp_zero::score_document>, modulus::error> {
auto error = std::error_code{};
if (!std::filesystem::exists(directory, error)) {
@@ -559,7 +555,7 @@ auto vamp_zero::load_performance_library(std::string const& assets_path) -> std:
.source_path = document.source_path,
.source_json = std::move(document.source_json),
.measure_count = document.measures.size(),
.total_pages = score_page_count(document.measures.size()),
.total_pages = vamp_zero::score_page_count(document),
});
}
return vamp_zero::group_library_parts(std::move(parts));
@@ -616,7 +612,7 @@ auto vamp_zero::load_practice_library(std::string const& assets_path) -> std::ex
.source_path = document->source_path,
.source_json = document->source_json,
.measure_count = document->measures.size(),
.total_pages = score_page_count(document->measures.size()),
.total_pages = vamp_zero::score_page_count(*document),
};
exercises.push_back({
.id = std::move(exercise_id),
+156
View File
@@ -0,0 +1,156 @@
#include <vamp_zero/navigation.hpp>
#include <algorithm>
namespace {
auto menu_row_count(std::size_t item_count) -> std::size_t {
return item_count + 1U;
}
}
auto vamp_zero::selected_group(vamp_zero::app_state const& state) -> vamp_zero::song_group const* {
if (state.selected_song_index == 0U || state.performance.groups.empty()) {
return nullptr;
}
auto index = state.selected_song_index - 1U;
if (index >= state.performance.groups.size()) {
return nullptr;
}
return &state.performance.groups[index];
}
auto vamp_zero::selected_part(vamp_zero::app_state const& state) -> vamp_zero::library_part const* {
auto const* group = vamp_zero::selected_group(state);
if (state.selected_part_index == 0U || group == nullptr || group->parts.empty()) {
return nullptr;
}
auto index = state.selected_part_index - 1U;
if (index >= group->parts.size()) {
return nullptr;
}
return &group->parts[index];
}
auto vamp_zero::active_part(vamp_zero::app_state const& state) -> vamp_zero::library_part const* {
if (state.active_part_id.empty()) {
return nullptr;
}
auto found = std::find_if(state.performance.parts.begin(), state.performance.parts.end(), [&](auto const& part) {
return part.id == state.active_part_id;
});
return found == state.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.practice.types.empty()) {
return nullptr;
}
auto index = state.selected_practice_type_index - 1U;
if (index >= state.practice.types.size()) {
return nullptr;
}
return &state.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()) {
return nullptr;
}
auto index = state.selected_practice_exercise_index - 1U;
if (index >= lesson->exercises.size()) {
return nullptr;
}
return &lesson->exercises[index];
}
auto vamp_zero::active_practice(vamp_zero::app_state const& state) -> vamp_zero::practice_exercise const* {
if (state.active_practice_id.empty()) {
return nullptr;
}
auto found = std::find_if(state.practice.exercises.begin(), state.practice.exercises.end(), [&](auto const& exercise) {
return exercise.id == state.active_practice_id;
});
return found == state.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;
}
auto index = state.selected_practice_key_index - 1U;
if (index >= exercise.variants.size()) {
return nullptr;
}
return &exercise.variants[index];
}
auto vamp_zero::selected_practice_key(vamp_zero::app_state const& state, vamp_zero::practice_exercise const& exercise) -> std::optional<std::string_view> {
auto const* variant = vamp_zero::selected_practice_variant(state, exercise);
if (variant == nullptr) {
return std::nullopt;
}
return variant->key_signature;
}
auto vamp_zero::practice_key_row_count(vamp_zero::app_state const& state) -> std::size_t {
auto const* exercise = vamp_zero::selected_practice_exercise(state);
if (exercise == nullptr || !vamp_zero::supports_key_selection(*exercise)) {
return 1U;
}
return menu_row_count(exercise->variants.size());
}
auto vamp_zero::effective_practice_key(vamp_zero::app_state const& state, vamp_zero::practice_exercise const& exercise) -> std::string {
if (auto const* variant = vamp_zero::selected_practice_variant(state, exercise); variant != nullptr && !variant->key_signature.empty()) {
return variant->key_signature;
}
return exercise.key_signature.empty() ? std::string{"C"} : exercise.key_signature;
}
auto vamp_zero::active_pages(vamp_zero::app_state const& state) -> std::size_t {
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
auto const* part = vamp_zero::active_part(state);
return part == nullptr ? 0U : part->total_pages;
}
if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
auto const* exercise = vamp_zero::active_practice(state);
auto const* variant = exercise == nullptr ? nullptr : vamp_zero::selected_practice_variant(state, *exercise);
return variant == nullptr ? 0U : variant->total_pages;
}
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;
}
+1 -145
View File
@@ -14,6 +14,7 @@
#include <boost/json/value.hpp>
#include <vamp_zero/errors.hpp>
#include <vamp_zero/navigation.hpp>
namespace {
@@ -37,151 +38,6 @@ auto menu_row_count(std::size_t item_count) -> std::size_t {
return item_count + 1U;
}
auto selected_group(vamp_zero::app_state const& state) -> vamp_zero::song_group const* {
if (state.selected_song_index == 0U || state.performance.groups.empty()) {
return nullptr;
}
auto index = state.selected_song_index - 1U;
if (index >= state.performance.groups.size()) {
return nullptr;
}
return &state.performance.groups[index];
}
auto selected_part(vamp_zero::app_state const& state) -> vamp_zero::library_part const* {
auto const* group = selected_group(state);
if (state.selected_part_index == 0U || group == nullptr || group->parts.empty()) {
return nullptr;
}
auto index = state.selected_part_index - 1U;
if (index >= group->parts.size()) {
return nullptr;
}
return &group->parts[index];
}
auto active_part(vamp_zero::app_state const& state) -> vamp_zero::library_part const* {
if (state.active_part_id.empty()) {
return nullptr;
}
auto found = std::find_if(state.performance.parts.begin(), state.performance.parts.end(), [&](auto const& part) {
return part.id == state.active_part_id;
});
return found == state.performance.parts.end() ? nullptr : &*found;
}
auto selected_practice_type(vamp_zero::app_state const& state) -> vamp_zero::practice_type_group const* {
if (state.selected_practice_type_index == 0U || state.practice.types.empty()) {
return nullptr;
}
auto index = state.selected_practice_type_index - 1U;
if (index >= state.practice.types.size()) {
return nullptr;
}
return &state.practice.types[index];
}
auto selected_practice_lesson(vamp_zero::app_state const& state) -> vamp_zero::practice_lesson_group const* {
auto const* type = 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 selected_practice_exercise(vamp_zero::app_state const& state) -> vamp_zero::practice_exercise const* {
auto const* lesson = selected_practice_lesson(state);
if (state.selected_practice_exercise_index == 0U || lesson == nullptr || lesson->exercises.empty()) {
return nullptr;
}
auto index = state.selected_practice_exercise_index - 1U;
if (index >= lesson->exercises.size()) {
return nullptr;
}
return &lesson->exercises[index];
}
auto active_practice(vamp_zero::app_state const& state) -> vamp_zero::practice_exercise const* {
if (state.active_practice_id.empty()) {
return nullptr;
}
auto found = std::find_if(state.practice.exercises.begin(), state.practice.exercises.end(), [&](auto const& exercise) {
return exercise.id == state.active_practice_id;
});
return found == state.practice.exercises.end() ? nullptr : &*found;
}
auto 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 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 (!supports_key_selection(exercise)) {
return &exercise.variants.front();
}
if (state.selected_practice_key_index == 0U) {
return nullptr;
}
auto index = state.selected_practice_key_index - 1U;
if (index >= exercise.variants.size()) {
return nullptr;
}
return &exercise.variants[index];
}
auto selected_practice_key(vamp_zero::app_state const& state, vamp_zero::practice_exercise const& exercise) -> std::optional<std::string_view> {
auto const* variant = selected_practice_variant(state, exercise);
if (variant == nullptr) {
return std::nullopt;
}
return variant->key_signature;
}
auto practice_key_row_count(vamp_zero::app_state const& state) -> std::size_t {
auto const* exercise = selected_practice_exercise(state);
if (exercise == nullptr || !supports_key_selection(*exercise)) {
return 1U;
}
return menu_row_count(exercise->variants.size());
}
auto effective_practice_key(vamp_zero::app_state const& state, vamp_zero::practice_exercise const& exercise) -> std::string {
if (auto const* variant = selected_practice_variant(state, exercise); variant != nullptr && !variant->key_signature.empty()) {
return variant->key_signature;
}
return exercise.key_signature.empty() ? std::string{"C"} : exercise.key_signature;
}
auto active_pages(vamp_zero::app_state const& state) -> std::size_t {
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
auto const* part = active_part(state);
return part == nullptr ? 0U : part->total_pages;
}
if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
auto const* exercise = active_practice(state);
auto const* variant = exercise == nullptr ? nullptr : selected_practice_variant(state, *exercise);
return variant == nullptr ? 0U : variant->total_pages;
}
return 0U;
}
auto practice_fallback_menu(vamp_zero::app_state const& state) -> vamp_zero::app_mode {
if (selected_practice_lesson(state) != nullptr) {
return vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
}
if (selected_practice_type(state) != nullptr) {
return vamp_zero::app_mode::PRACTICE_LESSON_MENU;
}
return vamp_zero::app_mode::PRACTICE_TYPE_MENU;
}
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);
+64 -8
View File
@@ -777,6 +777,45 @@ auto effective_time_signature(vamp_zero::score_document const& score, vamp_zero:
return "4/4";
}
auto notation_state_changes_before(vamp_zero::score_document const& score, std::size_t measure_index) -> std::bool_t {
if (measure_index == 0U || measure_index >= score.measures.size()) {
return false;
}
auto const& previous = score.measures[measure_index - 1U];
auto const& current = score.measures[measure_index];
return effective_key_signature(score, previous) != effective_key_signature(score, current)
|| effective_time_signature(score, &previous) != effective_time_signature(score, &current);
}
auto make_score_system_ranges(vamp_zero::score_document const& score) -> std::vector<vamp_zero::score_system_range> {
auto ranges = std::vector<vamp_zero::score_system_range>{};
if (score.measures.empty()) {
return ranges;
}
auto current = vamp_zero::score_system_range{.first_measure = 0U, .measure_count = 0U};
for (auto measure_index = std::size_t{}; measure_index < score.measures.size(); ++measure_index) {
if (current.measure_count != 0U
&& (current.measure_count >= vamp_zero::score_measures_per_system || notation_state_changes_before(score, measure_index))) {
ranges.push_back(current);
current = vamp_zero::score_system_range{.first_measure = measure_index, .measure_count = 0U};
}
++current.measure_count;
}
if (current.measure_count != 0U) {
ranges.push_back(current);
}
return ranges;
}
auto show_time_signature_for_system(vamp_zero::score_document const& score, vamp_zero::score_system_range const& range) -> std::bool_t {
if (range.first_measure == 0U) {
return true;
}
return notation_state_changes_before(score, range.first_measure)
&& effective_time_signature(score, &score.measures[range.first_measure - 1U]) != effective_time_signature(score, &score.measures[range.first_measure]);
}
auto draw_curve(luma::draw_context& context, luma::point start, luma::point control, luma::point end, std::float32_t thickness, luma::color color) -> void {
constexpr auto segments = std::int32_t{10};
auto previous = start;
@@ -2221,6 +2260,18 @@ auto vamp_zero::notated_accidentals_for_measure(vamp_zero::score_measure const&
return accidentals;
}
auto vamp_zero::score_system_ranges(vamp_zero::score_document const& score) -> std::vector<vamp_zero::score_system_range> {
return make_score_system_ranges(score);
}
auto vamp_zero::score_page_count(vamp_zero::score_document const& score) -> std::size_t {
auto ranges = make_score_system_ranges(score);
if (ranges.empty()) {
return 1U;
}
return (ranges.size() + vamp_zero::score_systems_per_page - 1U) / vamp_zero::score_systems_per_page;
}
auto vamp_zero::layout_score_page(luma::rect bounds, vamp_zero::score_document const& score, std::size_t page_index) -> vamp_zero::score_page_layout {
auto layout = vamp_zero::score_page_layout{
.bounds = bounds,
@@ -2240,23 +2291,28 @@ auto vamp_zero::layout_score_page(luma::rect bounds, vamp_zero::score_document c
auto const content_y = bounds.y + 84.0F;
auto const content_width = std::max<std::float32_t>(spacing * 18.0F, bounds.width - margin * 2.0F);
auto const system_height = (bounds.height - 108.0F) / static_cast<std::float32_t>(vamp_zero::score_systems_per_page);
auto const first_measure = page_index * vamp_zero::score_measures_per_page;
auto const system_ranges = make_score_system_ranges(score);
auto const first_system = page_index * vamp_zero::score_systems_per_page;
auto note_refs = std::vector<laid_out_note_ref>{};
auto measure_refs = std::vector<laid_out_measure_ref>{};
for (auto system = std::size_t{}; system < vamp_zero::score_systems_per_page; ++system) {
auto const system_y = content_y + static_cast<std::float32_t>(system) * system_height;
auto const staff_top = system_y + spacing * 4.25F;
auto const system_first_measure = first_measure + system * vamp_zero::score_measures_per_system;
auto const* first_system_measure = system_first_measure < score.measures.size() ? &score.measures[system_first_measure] : nullptr;
auto const range_index = first_system + system;
auto const* range = range_index < system_ranges.size() ? &system_ranges[range_index] : nullptr;
auto const* first_system_measure = range != nullptr && range->first_measure < score.measures.size() ? &score.measures[range->first_measure] : nullptr;
layout_staff(layout, content_x, staff_top, content_width, spacing, system);
auto const show_time = page_index == 0U && system == 0U;
auto const show_time = range == nullptr ? page_index == 0U && system == 0U : show_time_signature_for_system(score, *range);
auto const measure_start_x = layout_system_preamble(layout, score, first_system_measure, content_x, staff_top, spacing, show_time, system);
auto const measure_area_width = std::max<std::float32_t>(spacing * 12.0F, content_x + content_width - measure_start_x);
auto const measure_width = measure_area_width / static_cast<std::float32_t>(vamp_zero::score_measures_per_system);
if (range == nullptr || range->measure_count == 0U) {
continue;
}
auto const measure_width = measure_area_width / static_cast<std::float32_t>(range->measure_count);
for (auto measure = std::size_t{}; measure < vamp_zero::score_measures_per_system; ++measure) {
auto const measure_index = system_first_measure + measure;
for (auto measure = std::size_t{}; measure < range->measure_count; ++measure) {
auto const measure_index = range->first_measure + measure;
auto const x = measure_start_x + static_cast<std::float32_t>(measure) * measure_width;
if (measure_index < score.measures.size()) {
layout_measure(
@@ -2274,7 +2330,7 @@ auto vamp_zero::layout_score_page(luma::rect bounds, vamp_zero::score_document c
}
auto const bar_x = x + measure_width;
auto const final_bar = measure + 1U == vamp_zero::score_measures_per_system || measure_index + 1U >= score.measures.size();
auto const final_bar = measure + 1U == range->measure_count || measure_index + 1U >= score.measures.size();
add_stroke_line(
layout,
final_bar ? vamp_zero::score_layout_item_role::FINAL_BARLINE : vamp_zero::score_layout_item_role::BARLINE,
+59 -110
View File
@@ -3,11 +3,13 @@
#include <algorithm>
#include <cstdint>
#include <expected>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <boost/asio/any_io_executor.hpp>
@@ -19,8 +21,47 @@
#include <vamp_style/font.hpp>
#include <vamp_zero/color.hpp>
#include <vamp_zero/library.hpp>
#include <vamp_zero/navigation.hpp>
#include <vamp_zero/score.hpp>
struct vamp_zero::score_draw_cache {
struct entry {
std::string source_json{};
std::shared_ptr<vamp_zero::score_document const> document{};
};
auto get(std::string_view source_json, std::string source_path, std::string part_override) -> std::expected<std::shared_ptr<vamp_zero::score_document const>, modulus::error> {
auto key = source_path;
key.push_back('\n');
key += part_override;
key.push_back('\n');
key += std::to_string(source_json.size());
key.push_back('\n');
key += std::to_string(std::hash<std::string_view>{}(source_json));
auto found = entries.find(key);
if (found != entries.end() && found->second.source_json == source_json && found->second.document) {
return found->second.document;
}
auto parsed = vamp_zero::parse_score_document(source_json, source_path);
if (!parsed) {
return std::unexpected(parsed.error());
}
if (!part_override.empty()) {
parsed->part = std::move(part_override);
}
auto document = std::make_shared<vamp_zero::score_document const>(std::move(*parsed));
entries[key] = {
.source_json = std::string{source_json},
.document = document,
};
return document;
}
std::unordered_map<std::string, entry> entries{};
};
namespace {
constexpr auto touch_hold_duration = std::chrono::milliseconds{700};
@@ -41,102 +82,6 @@ auto bind_port(vamp_zero::config const& config) -> std::uint16_t {
}
}
auto active_part(vamp_zero::app_state const& state) -> vamp_zero::library_part const* {
auto found = std::find_if(state.performance.parts.begin(), state.performance.parts.end(), [&](auto const& part) {
return part.id == state.active_part_id;
});
return found == state.performance.parts.end() ? nullptr : &*found;
}
auto selected_group(vamp_zero::app_state const& state) -> vamp_zero::song_group const* {
if (state.selected_song_index == 0U || state.performance.groups.empty()) {
return nullptr;
}
auto index = state.selected_song_index - 1U;
if (index >= state.performance.groups.size()) {
return nullptr;
}
return &state.performance.groups[index];
}
auto active_practice(vamp_zero::app_state const& state) -> vamp_zero::practice_exercise const* {
auto found = std::find_if(state.practice.exercises.begin(), state.practice.exercises.end(), [&](auto const& exercise) {
return exercise.id == state.active_practice_id;
});
return found == state.practice.exercises.end() ? nullptr : &*found;
}
auto selected_practice_type(vamp_zero::app_state const& state) -> vamp_zero::practice_type_group const* {
if (state.selected_practice_type_index == 0U || state.practice.types.empty()) {
return nullptr;
}
auto index = state.selected_practice_type_index - 1U;
if (index >= state.practice.types.size()) {
return nullptr;
}
return &state.practice.types[index];
}
auto selected_practice_lesson(vamp_zero::app_state const& state) -> vamp_zero::practice_lesson_group const* {
auto const* type = 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 selected_practice_exercise(vamp_zero::app_state const& state) -> vamp_zero::practice_exercise const* {
auto const* lesson = selected_practice_lesson(state);
if (state.selected_practice_exercise_index == 0U || lesson == nullptr || lesson->exercises.empty()) {
return nullptr;
}
auto index = state.selected_practice_exercise_index - 1U;
if (index >= lesson->exercises.size()) {
return nullptr;
}
return &lesson->exercises[index];
}
auto 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 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 (!supports_key_selection(exercise)) {
return &exercise.variants.front();
}
if (state.selected_practice_key_index == 0U) {
return nullptr;
}
auto index = state.selected_practice_key_index - 1U;
if (index >= exercise.variants.size()) {
return nullptr;
}
return &exercise.variants[index];
}
auto selected_practice_key(vamp_zero::app_state const& state, vamp_zero::practice_exercise const& exercise) -> std::optional<std::string_view> {
auto const* variant = selected_practice_variant(state, exercise);
if (variant == nullptr) {
return std::nullopt;
}
return variant->key_signature;
}
auto effective_practice_key(vamp_zero::app_state const& state, vamp_zero::practice_exercise const& exercise) -> std::string {
if (auto const* variant = selected_practice_variant(state, exercise); variant != nullptr && !variant->key_signature.empty()) {
return variant->key_signature;
}
return exercise.key_signature.empty() ? std::string{"C"} : exercise.key_signature;
}
auto status_color(vamp_zero::app_state const& state) -> vamp_style::color {
return state.pico_connected ? vamp_style::connected : vamp_style::retrying;
}
@@ -361,9 +306,10 @@ auto draw_practice_key_menu(luma::draw_context& context, luma::rect bounds, vamp
}
}
auto draw_score(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
auto source_json = std::string{};
auto draw_score(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state, vamp_zero::score_draw_cache& score_cache) -> void {
auto source_json = std::string_view{};
auto source_path = std::string{};
auto part_override = std::string{};
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
auto const* part = active_part(state);
if (part == nullptr) {
@@ -385,32 +331,28 @@ auto draw_score(luma::draw_context& context, luma::rect bounds, vamp_zero::app_s
}
source_json = variant->source_json;
source_path = variant->source_path;
part_override = "Key: " + effective_practice_key(state, *exercise);
}
auto parsed = vamp_zero::parse_score_document(source_json, source_path);
auto parsed = score_cache.get(source_json, source_path, std::move(part_override));
if (!parsed) {
draw_text(context, bounds.x + 36.0F, bounds.y + 38.0F, "SCORE ERROR", 4, vamp_style::danger, 360.0F);
draw_text(context, bounds.x + 38.0F, bounds.y + 86.0F, parsed.error().describe(), 1, vamp_style::muted, bounds.width - 72.0F);
return;
}
if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
auto const* exercise = active_practice(state);
if (exercise != nullptr) {
parsed->part = "Key: " + effective_practice_key(state, *exercise);
}
}
auto paper_bounds = luma::rect{
.x = bounds.x + 24.0F,
.y = bounds.y + 18.0F,
.width = bounds.width - 48.0F,
.height = bounds.height - 36.0F,
};
vamp_zero::draw_score_page(context, paper_bounds, *parsed, state.page_index);
vamp_zero::draw_score_page(context, paper_bounds, **parsed, state.page_index);
}
class vamp_root_control final : public luma::control {
public:
explicit vamp_root_control(vamp_zero::ui_state state, touch_handler on_touch) : state_{std::move(state)}, on_touch_{std::move(on_touch)} {}
explicit vamp_root_control(vamp_zero::ui_state state, std::shared_ptr<vamp_zero::score_draw_cache> score_cache, touch_handler on_touch)
: state_{std::move(state)}, score_cache_{std::move(score_cache)}, on_touch_{std::move(on_touch)} {}
auto measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size override {
return luma::clamp({.width = 1024.0F, .height = 600.0F}, limits);
}
@@ -439,7 +381,9 @@ public:
} else if (state_.app.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
draw_practice_key_menu(context, content, state_.app);
} else if (state_.app.mode == vamp_zero::app_mode::PERFORMANCE_SCORE || state_.app.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
draw_score(context, content, state_.app);
if (score_cache_) {
draw_score(context, content, state_.app, *score_cache_);
}
} else {
draw_main_menu(context, content, state_.app);
}
@@ -449,13 +393,18 @@ public:
}
private:
vamp_zero::ui_state state_{};
std::shared_ptr<vamp_zero::score_draw_cache> score_cache_{};
touch_handler on_touch_{};
};
}
vamp_zero::zero_ui::zero_ui(boost::asio::any_io_executor executor, luma::application_config config, vamp_zero::zero_ui::command_handler command_handler)
: executor_{std::move(executor)}, tap_timer_{executor_}, application_{std::move(config)}, command_handler_{std::move(command_handler)} {}
: executor_{std::move(executor)},
tap_timer_{executor_},
application_{std::move(config)},
command_handler_{std::move(command_handler)},
score_cache_{std::make_shared<vamp_zero::score_draw_cache>()} {}
auto vamp_zero::zero_ui::send_touch_command(vamp_protocol::page_command command) -> void {
if (!command_handler_) {
@@ -593,7 +542,7 @@ auto vamp_zero::zero_ui::snapshot() const -> vamp_zero::ui_state {
}
auto vamp_zero::zero_ui::make_root(vamp_zero::ui_state const& state) -> std::expected<std::unique_ptr<luma::control>, modulus::error> {
return std::make_unique<vamp_root_control>(state, [this](luma::touch_event const& event) {
return std::make_unique<vamp_root_control>(state, score_cache_, [this](luma::touch_event const& event) {
return handle_touch(event);
});
}