+4
-25
@@ -153,22 +153,14 @@ auto make_server_endpoint(vamp_zero::config const &config) -> std::expected<ligo
|
||||
|
||||
auto load_initial_state(vamp_zero::config const &config) -> std::expected<vamp_zero::app_state, modulus::error> {
|
||||
auto io = boost::asio::io_context{};
|
||||
auto performance_future = boost::asio::co_spawn(io, vamp_zero::async_load_performance_library(io.get_executor(), config.database, config.runtime.assets_path), boost::asio::use_future);
|
||||
auto practice_future = boost::asio::co_spawn(io, vamp_zero::async_load_practice_library(io.get_executor(), config.database, config.runtime.assets_path), boost::asio::use_future);
|
||||
auto performance_future = boost::asio::co_spawn(io, vamp_zero::async_load_catalog(io.get_executor(), config.database, config.runtime.assets_path), boost::asio::use_future);
|
||||
io.run();
|
||||
auto performance = performance_future.get();
|
||||
if (!performance) {
|
||||
return std::unexpected(performance.error());
|
||||
}
|
||||
auto practice = practice_future.get();
|
||||
if (!practice) {
|
||||
return std::unexpected(practice.error());
|
||||
}
|
||||
auto state = vamp_zero::app_state{
|
||||
.catalog = std::make_shared<vamp_zero::catalog const>(vamp_zero::catalog{
|
||||
.performance = std::move(*performance),
|
||||
.practice = std::move(*practice),
|
||||
}),
|
||||
.catalog = std::make_shared<vamp_zero::catalog const>(std::move(*performance)),
|
||||
.audio =
|
||||
{
|
||||
.playback_enabled = config.audio.playback_enabled,
|
||||
@@ -204,30 +196,17 @@ auto validate_score_pages(boost::asio::any_io_executor executor, vamp_zero::conf
|
||||
}
|
||||
|
||||
auto validate_catalog(boost::asio::any_io_executor executor, vamp_zero::config const &config, vamp_zero::catalog const &catalog) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
for (auto const &part : catalog.performance.parts) {
|
||||
for (auto const &part : catalog.parts) {
|
||||
auto title = vamp_zero::validated_title_path(config.runtime.assets_path, part.title_image);
|
||||
if (!title) {
|
||||
co_return std::unexpected(title.error());
|
||||
}
|
||||
auto reference = vamp_zero::score_reference{.category = vamp_zero::score_category::PERFORMANCE, .id = part.id};
|
||||
auto reference = vamp_zero::score_reference{.id = part.id};
|
||||
auto valid = co_await validate_score_pages(executor, config, std::move(reference));
|
||||
if (!valid) {
|
||||
co_return valid;
|
||||
}
|
||||
}
|
||||
for (auto const &exercise : catalog.practice.exercises) {
|
||||
for (auto const &practice_part : exercise.parts) {
|
||||
auto title = vamp_zero::validated_title_path(config.runtime.assets_path, practice_part.title_image);
|
||||
if (!title) {
|
||||
co_return std::unexpected(title.error());
|
||||
}
|
||||
auto reference = vamp_zero::score_reference{.category = vamp_zero::score_category::PRACTICE, .id = practice_part.id};
|
||||
auto valid = co_await validate_score_pages(executor, config, std::move(reference));
|
||||
if (!valid) {
|
||||
co_return valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
co_return std::expected<void, modulus::error>{};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,6 @@ auto score_active(vamp_zero::app_state const &state) -> std::bool_t {
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
return vamp_zero::active_part(state) != nullptr;
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
auto const *exercise = vamp_zero::active_practice(state);
|
||||
return exercise != nullptr && vamp_zero::selected_practice_part(state, *exercise) != nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+12
-81
@@ -93,13 +93,9 @@ auto valid_title_image(noto::sqlite_value const &value) -> std::bool_t {
|
||||
return text.size() == 64U && std::all_of(text.begin(), text.end(), [](std::char_t c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); });
|
||||
}
|
||||
|
||||
auto compatible_table(noto::connection &connection, std::string const &table) -> boost::asio::awaitable<std::bool_t> {
|
||||
auto columns = std::vector<std::string_view>{"id", "title", "composer", "key_signature", "source_path", "source_json", "measure_count", "total_pages", "title_image"};
|
||||
if (table == "practice_exercises") {
|
||||
columns.push_back("exercise_id");
|
||||
}
|
||||
columns.push_back("part");
|
||||
auto parameters = std::vector<noto::sqlite_value>{noto::sqlite_value{table}};
|
||||
auto compatible_catalog(noto::connection &connection) -> boost::asio::awaitable<std::bool_t> {
|
||||
auto columns = std::vector<std::string_view>{"id", "title", "composer", "key_signature", "source_path", "source_json", "measure_count", "total_pages", "title_image", "part"};
|
||||
auto parameters = std::vector<noto::sqlite_value>{noto::sqlite_value{"songs"}};
|
||||
auto selected = co_await connection.async_execute("SELECT name, type, \"notnull\", pk FROM pragma_table_info(?1) "
|
||||
"WHERE EXISTS (SELECT 1 FROM pragma_table_list WHERE schema = 'main' AND name = ?1 AND type = 'table' AND strict = 1) ORDER BY cid",
|
||||
std::move(parameters));
|
||||
@@ -138,14 +134,12 @@ auto vamp_zero::async_validate_database(boost::asio::any_io_executor executor, v
|
||||
if (!application || *application != application_id) {
|
||||
status = std::unexpected(vamp_zero::make_error(vamp_zero::error_code::PERSISTENCE, "invalid or incompatible catalog: " + config.path + "; rebuild or reinstall Product Vamp"));
|
||||
}
|
||||
for (auto table : {"songs", "practice_exercises"}) {
|
||||
if (status && !(co_await compatible_table(connection, table))) {
|
||||
status = std::unexpected(vamp_zero::make_error(vamp_zero::error_code::PERSISTENCE, "incompatible catalog table " + std::string{table} + ": " + config.path + "; rebuild or reinstall Product Vamp"));
|
||||
}
|
||||
if (status && !(co_await compatible_catalog(connection))) {
|
||||
status = std::unexpected(vamp_zero::make_error(vamp_zero::error_code::PERSISTENCE, "incompatible songs catalog: " + config.path + "; rebuild or reinstall Product Vamp"));
|
||||
}
|
||||
if (status && integrity) {
|
||||
auto checked = co_await connection.async_execute("PRAGMA quick_check(1)");
|
||||
if (!checked || checked->rows.size() != 1U || checked->rows[0].size() != 1U || text_value(checked->rows[0][0]) != "ok") {
|
||||
auto checked = co_await connection.async_quick_check();
|
||||
if (!checked || *checked != std::vector<std::string>{"ok"}) {
|
||||
status = std::unexpected(vamp_zero::make_error(vamp_zero::error_code::PERSISTENCE, "corrupt catalog: " + config.path + "; rebuild or reinstall Product Vamp"));
|
||||
}
|
||||
}
|
||||
@@ -156,8 +150,8 @@ auto vamp_zero::async_validate_database(boost::asio::any_io_executor executor, v
|
||||
co_return closed;
|
||||
}
|
||||
|
||||
auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor executor, vamp_zero::database_config config, std::string assets_path)
|
||||
-> boost::asio::awaitable<std::expected<vamp_zero::library_state, modulus::error>> {
|
||||
auto vamp_zero::async_load_catalog(boost::asio::any_io_executor executor, vamp_zero::database_config config, std::string assets_path)
|
||||
-> boost::asio::awaitable<std::expected<vamp_zero::catalog, modulus::error>> {
|
||||
auto connection = noto::connection{executor, connection_config(config)};
|
||||
auto connected = co_await connection.async_connect();
|
||||
if (!connected) {
|
||||
@@ -165,7 +159,7 @@ auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor exec
|
||||
}
|
||||
auto selected =
|
||||
co_await connection.async_prepare("SELECT id, title, composer, part, source_path, measure_count, key_signature, total_pages, title_image FROM "
|
||||
"songs ORDER BY title COLLATE NOCASE, part COLLATE NOCASE");
|
||||
"songs ORDER BY title, composer, part, id");
|
||||
if (!selected) {
|
||||
auto close = co_await connection.async_close();
|
||||
if (!close) {
|
||||
@@ -210,69 +204,7 @@ auto vamp_zero::async_load_performance_library(boost::asio::any_io_executor exec
|
||||
if (!closed) {
|
||||
co_return std::unexpected(closed.error());
|
||||
}
|
||||
co_return vamp_zero::group_library_parts(std::move(parts));
|
||||
}
|
||||
|
||||
auto vamp_zero::async_load_practice_library(boost::asio::any_io_executor executor, vamp_zero::database_config config, std::string assets_path)
|
||||
-> boost::asio::awaitable<std::expected<vamp_zero::practice_library, modulus::error>> {
|
||||
auto connection = noto::connection{executor, connection_config(config)};
|
||||
auto connected = co_await connection.async_connect();
|
||||
if (!connected) {
|
||||
co_return std::unexpected(connected.error());
|
||||
}
|
||||
auto selected = co_await connection.async_prepare("SELECT id, title, composer, key_signature, source_path, measure_count, total_pages, part, exercise_id, title_image "
|
||||
"FROM practice_exercises ORDER BY exercise_id, id");
|
||||
if (!selected) {
|
||||
auto close = co_await connection.async_close();
|
||||
if (!close) {
|
||||
modulus::log_error("database close failed after practice load error: " + close.error().describe());
|
||||
}
|
||||
co_return std::unexpected(selected.error());
|
||||
}
|
||||
auto exercises = std::vector<vamp_zero::practice_exercise>{};
|
||||
auto status = std::expected<void, modulus::error>{};
|
||||
for (;;) {
|
||||
auto next = co_await selected->async_step();
|
||||
if (!next) {
|
||||
status = std::unexpected(next.error());
|
||||
break;
|
||||
}
|
||||
if (!*next) {
|
||||
break;
|
||||
}
|
||||
auto const &row = **next;
|
||||
if (row.size() != 10U || count_value(row[5]) == 0U || count_value(row[6]) == 0U || text_value(row[8]).empty() || text_value(row[3]).empty() || !valid_title_image(row[9])) {
|
||||
status = std::unexpected(vamp_zero::make_error(vamp_zero::error_code::PERSISTENCE, "invalid practice metadata"));
|
||||
break;
|
||||
}
|
||||
auto source = resolve_source_path(assets_path, text_value(row[4]));
|
||||
if (!source) {
|
||||
status = std::unexpected(source.error());
|
||||
break;
|
||||
}
|
||||
auto practice_part = vamp_zero::library_part{
|
||||
.id = text_value(row[0]),
|
||||
.title = text_value(row[1]),
|
||||
.composer = text_value(row[2]),
|
||||
.key_signature = text_value(row[3]),
|
||||
.part = text_value(row[7]),
|
||||
.source_path = std::move(*source),
|
||||
.measure_count = count_value(row[5]),
|
||||
.total_pages = count_value(row[6]),
|
||||
.title_image = text_value(row[9]),
|
||||
};
|
||||
exercises.push_back({
|
||||
.id = text_value(row[8]),
|
||||
.title = text_value(row[1]),
|
||||
.composer = text_value(row[2]),
|
||||
.parts = {std::move(practice_part)},
|
||||
});
|
||||
}
|
||||
auto closed = co_await close_catalog_query(connection, *selected, std::move(status));
|
||||
if (!closed) {
|
||||
co_return std::unexpected(closed.error());
|
||||
}
|
||||
co_return vamp_zero::group_practice_exercises(std::move(exercises));
|
||||
co_return vamp_zero::group_ordered_parts(std::move(parts));
|
||||
}
|
||||
|
||||
auto vamp_zero::async_load_score(boost::asio::any_io_executor executor, vamp_zero::database_config config, vamp_zero::score_reference reference, std::string assets_path) -> boost::asio::awaitable<std::expected<vamp_zero::score_document, modulus::error>> {
|
||||
@@ -283,8 +215,7 @@ auto vamp_zero::async_load_score(boost::asio::any_io_executor executor, vamp_zer
|
||||
if (!connected) {
|
||||
co_return std::unexpected(connected.error());
|
||||
}
|
||||
auto table = reference.category == vamp_zero::score_category::PERFORMANCE ? "songs" : "practice_exercises";
|
||||
auto selected = co_await connection.async_prepare("SELECT source_json, source_path FROM " + std::string{table} + " WHERE id = ?1");
|
||||
auto selected = co_await connection.async_prepare("SELECT source_json, source_path FROM songs WHERE id = ?1");
|
||||
if (!selected) {
|
||||
static_cast<void>(co_await connection.async_close());
|
||||
co_return std::unexpected(selected.error());
|
||||
|
||||
@@ -1,39 +1,18 @@
|
||||
#include <algorithm>
|
||||
#include <expected>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <vamp_zero/errors.hpp>
|
||||
#include <vamp_zero/score.hpp>
|
||||
#include <vamp_zero/library.hpp>
|
||||
#include <vamp_zero/notation.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
auto invalid(std::string message) -> modulus::error {
|
||||
return vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, std::move(message));
|
||||
}
|
||||
|
||||
auto part_rank(std::string const &part) -> std::size_t {
|
||||
auto const &parts = vamp_zero::practice_part_names();
|
||||
return static_cast<std::size_t>(std::distance(parts.begin(), std::find(parts.begin(), parts.end(), part)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto vamp_zero::parse_score_document(std::string_view json, std::string source_path) -> std::expected<vamp_zero::score_document, modulus::error> {
|
||||
return vamp_zero::parse_notation_document(json, std::move(source_path));
|
||||
}
|
||||
|
||||
auto vamp_zero::group_library_parts(std::vector<vamp_zero::library_part> parts) -> vamp_zero::library_state {
|
||||
std::sort(parts.begin(), parts.end(), [](auto const &left, auto const &right) {
|
||||
return std::tie(left.title, left.composer, left.part, left.id) < std::tie(right.title, right.composer, right.part, right.id);
|
||||
});
|
||||
auto vamp_zero::group_ordered_parts(std::vector<vamp_zero::library_part> parts) -> vamp_zero::catalog {
|
||||
auto groups = std::vector<vamp_zero::song_group>{};
|
||||
for (auto index = std::size_t{}; index < parts.size(); ++index) {
|
||||
auto const &part = parts[index];
|
||||
@@ -44,37 +23,3 @@ auto vamp_zero::group_library_parts(std::vector<vamp_zero::library_part> parts)
|
||||
}
|
||||
return {.parts = std::move(parts), .groups = std::move(groups)};
|
||||
}
|
||||
|
||||
auto vamp_zero::group_practice_exercises(std::vector<vamp_zero::practice_exercise> exercises) -> std::expected<vamp_zero::practice_library, modulus::error> {
|
||||
std::sort(exercises.begin(), exercises.end(), [](auto const &left, auto const &right) {
|
||||
return left.id < right.id;
|
||||
});
|
||||
auto merged = std::vector<vamp_zero::practice_exercise>{};
|
||||
for (auto &exercise : exercises) {
|
||||
if (!merged.empty() && merged.back().id == exercise.id) {
|
||||
if (merged.back().title != exercise.title || merged.back().composer != exercise.composer) {
|
||||
return std::unexpected(invalid("practice parts disagree on exercise identity: " + exercise.id));
|
||||
}
|
||||
merged.back().parts.insert(merged.back().parts.end(), std::make_move_iterator(exercise.parts.begin()), std::make_move_iterator(exercise.parts.end()));
|
||||
} else {
|
||||
merged.push_back(std::move(exercise));
|
||||
}
|
||||
}
|
||||
for (auto &exercise : merged) {
|
||||
std::sort(exercise.parts.begin(), exercise.parts.end(), [](auto const &left, auto const &right) {
|
||||
return std::tuple{part_rank(left.part), left.id} < std::tuple{part_rank(right.part), right.id};
|
||||
});
|
||||
if (exercise.parts.size() != vamp_zero::practice_part_names().size()) {
|
||||
return std::unexpected(invalid("practice exercise requires acoustic, electric, and vocal parts: " + exercise.id));
|
||||
}
|
||||
for (auto index = std::size_t{}; index < exercise.parts.size(); ++index) {
|
||||
if (exercise.parts[index].key_signature != exercise.parts.front().key_signature) {
|
||||
return std::unexpected(invalid("practice parts disagree on key: " + exercise.id));
|
||||
}
|
||||
if (part_rank(exercise.parts[index].part) != index) {
|
||||
return std::unexpected(invalid("practice exercise has missing or duplicate parts: " + exercise.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
return vamp_zero::practice_library{.exercises = std::move(merged)};
|
||||
}
|
||||
|
||||
@@ -3,23 +3,15 @@
|
||||
#include <vamp_zero/navigation.hpp>
|
||||
|
||||
|
||||
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.catalog->performance.groups.empty()) {
|
||||
if (state.selected_song_index == 0U || state.catalog->groups.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = state.selected_song_index - 1U;
|
||||
if (index >= state.catalog->performance.groups.size()) {
|
||||
if (index >= state.catalog->groups.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &state.catalog->performance.groups[index];
|
||||
return &state.catalog->groups[index];
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_part(vamp_zero::app_state const &state) -> vamp_zero::library_part const * {
|
||||
@@ -32,60 +24,17 @@ auto vamp_zero::selected_part(vamp_zero::app_state const &state) -> vamp_zero::l
|
||||
return nullptr;
|
||||
}
|
||||
auto part_index = group->parts[index];
|
||||
return part_index < state.catalog->performance.parts.size() ? &state.catalog->performance.parts[part_index] : nullptr;
|
||||
return part_index < state.catalog->parts.size() ? &state.catalog->parts[part_index] : nullptr;
|
||||
}
|
||||
|
||||
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.catalog->performance.parts.begin(), state.catalog->performance.parts.end(), [&](auto const &part) {
|
||||
auto found = std::find_if(state.catalog->parts.begin(), state.catalog->parts.end(), [&](auto const &part) {
|
||||
return part.id == state.active_part_id;
|
||||
});
|
||||
return found == state.catalog->performance.parts.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_practice_exercise(vamp_zero::app_state const &state) -> vamp_zero::practice_exercise const * {
|
||||
if (state.selected_practice_exercise_index == 0U) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = state.selected_practice_exercise_index - 1U;
|
||||
if (index >= state.catalog->practice.exercises.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &state.catalog->practice.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.catalog->practice.exercises.begin(),
|
||||
state.catalog->practice.exercises.end(),
|
||||
[&](auto const &exercise) { return exercise.id == state.active_practice_id; });
|
||||
return found == state.catalog->practice.exercises.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
auto vamp_zero::selected_practice_part(vamp_zero::app_state const &state, vamp_zero::practice_exercise const &exercise) -> vamp_zero::library_part const * {
|
||||
if (exercise.parts.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (state.selected_practice_part_index == 0U) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = state.selected_practice_part_index - 1U;
|
||||
if (index >= exercise.parts.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &exercise.parts[index];
|
||||
}
|
||||
|
||||
auto vamp_zero::practice_part_row_count(vamp_zero::app_state const &state) -> std::size_t {
|
||||
auto const *exercise = vamp_zero::selected_practice_exercise(state);
|
||||
if (exercise == nullptr) {
|
||||
return 1U;
|
||||
}
|
||||
return menu_row_count(exercise->parts.size());
|
||||
return found == state.catalog->parts.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
auto vamp_zero::active_pages(vamp_zero::app_state const &state) -> std::size_t {
|
||||
@@ -93,10 +42,5 @@ auto vamp_zero::active_pages(vamp_zero::app_state const &state) -> std::size_t {
|
||||
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 *practice_part = exercise == nullptr ? nullptr : vamp_zero::selected_practice_part(state, *exercise);
|
||||
return practice_part == nullptr ? 0U : practice_part->total_pages;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
+13
-184
@@ -20,11 +20,10 @@
|
||||
namespace {
|
||||
|
||||
constexpr auto main_menu_performance = std::size_t{0};
|
||||
constexpr auto main_menu_practice = std::size_t{1};
|
||||
constexpr auto main_menu_playback = std::size_t{2};
|
||||
constexpr auto main_menu_metronome = std::size_t{3};
|
||||
constexpr auto main_menu_tempo = std::size_t{4};
|
||||
constexpr auto main_menu_count = std::size_t{5};
|
||||
constexpr auto main_menu_playback = std::size_t{1};
|
||||
constexpr auto main_menu_metronome = std::size_t{2};
|
||||
constexpr auto main_menu_tempo = std::size_t{3};
|
||||
constexpr auto main_menu_count = std::size_t{4};
|
||||
|
||||
auto wrap_next(std::size_t value, std::size_t count) -> std::size_t {
|
||||
if (count == 0U) {
|
||||
@@ -71,48 +70,27 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
}
|
||||
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.catalog->performance.groups.size()) - 1U);
|
||||
state.selected_song_index = std::min(state.selected_song_index, menu_row_count(state.catalog->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_exercise_index = std::min(state.selected_practice_exercise_index, state.catalog->practice.exercises.size());
|
||||
state.selected_practice_part_index = std::min(state.selected_practice_part_index, practice_part_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_PART_MENU) {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
if (exercise == nullptr || exercise->parts.empty()) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.selected_practice_part_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 = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (selected_practice_part(state, *exercise) == nullptr) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_PART_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
}
|
||||
}
|
||||
if (state.mode != vamp_zero::app_mode::MAIN_MENU) {
|
||||
state.audio.tempo_editing = false;
|
||||
}
|
||||
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) {
|
||||
} else if (state.mode != vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
state.page_index = 0;
|
||||
}
|
||||
return state;
|
||||
@@ -154,26 +132,12 @@ auto part_json(vamp_zero::library_part const &part) -> boost::json::object {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto exercise_json(vamp_zero::practice_exercise const &exercise) -> boost::json::object {
|
||||
auto parts = boost::json::array{};
|
||||
for (auto const &part : exercise.parts) {
|
||||
parts.push_back(part_json(part));
|
||||
}
|
||||
auto result = boost::json::object{};
|
||||
result["id"] = exercise.id;
|
||||
result["title"] = exercise.title;
|
||||
result["composer"] = exercise.composer;
|
||||
result["part_count"] = static_cast<std::uint64_t>(exercise.parts.size());
|
||||
result["parts"] = std::move(parts);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto performance_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
auto groups = boost::json::array{};
|
||||
for (auto const &group : state.catalog->performance.groups) {
|
||||
for (auto const &group : state.catalog->groups) {
|
||||
auto parts = boost::json::array{};
|
||||
for (auto const &part : group.parts) {
|
||||
parts.push_back(part_json(state.catalog->performance.parts[part]));
|
||||
parts.push_back(part_json(state.catalog->parts[part]));
|
||||
}
|
||||
auto item = boost::json::object{};
|
||||
item["title"] = group.title;
|
||||
@@ -182,34 +146,12 @@ 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.catalog->performance.groups.size());
|
||||
library["part_count"] = static_cast<std::uint64_t>(state.catalog->performance.parts.size());
|
||||
library["song_count"] = static_cast<std::uint64_t>(state.catalog->groups.size());
|
||||
library["part_count"] = static_cast<std::uint64_t>(state.catalog->parts.size());
|
||||
library["groups"] = std::move(groups);
|
||||
return library;
|
||||
}
|
||||
|
||||
auto practice_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
auto exercises = boost::json::array{};
|
||||
for (auto const &exercise : state.catalog->practice.exercises) {
|
||||
exercises.push_back(exercise_json(exercise));
|
||||
}
|
||||
auto selection = boost::json::object{};
|
||||
selection["exercise_row_index"] = static_cast<std::uint64_t>(state.selected_practice_exercise_index);
|
||||
selection["part_row_index"] = static_cast<std::uint64_t>(state.selected_practice_part_index);
|
||||
if (auto const *exercise = selected_practice_exercise(state); exercise != nullptr) {
|
||||
selection["exercise"] = exercise_json(*exercise);
|
||||
auto practice_part = selected_practice_part(state, *exercise);
|
||||
if (practice_part != nullptr) {
|
||||
selection["part"] = part_json(*practice_part);
|
||||
}
|
||||
}
|
||||
auto practice = boost::json::object{};
|
||||
practice["exercise_count"] = static_cast<std::uint64_t>(state.catalog->practice.exercises.size());
|
||||
practice["exercises"] = std::move(exercises);
|
||||
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') {
|
||||
@@ -251,12 +193,6 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
state.selected_part_index = 0;
|
||||
state.active_part_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (state.selected_main_menu_index == main_menu_practice) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.selected_practice_exercise_index = 0;
|
||||
state.selected_practice_part_index = 0;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (state.selected_main_menu_index == main_menu_playback) {
|
||||
state.audio.playback_enabled = !state.audio.playback_enabled;
|
||||
} else if (state.selected_main_menu_index == main_menu_metronome) {
|
||||
@@ -267,11 +203,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.catalog->performance.groups.size()));
|
||||
state.selected_song_index = wrap_next(state.selected_song_index, menu_row_count(state.catalog->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.catalog->performance.groups.size()));
|
||||
wrap_previous(state.selected_song_index, menu_row_count(state.catalog->groups.size()));
|
||||
state.selected_part_index = 0;
|
||||
} else if (state.selected_song_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::MAIN_MENU;
|
||||
@@ -292,7 +228,6 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
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;
|
||||
++state.score_session;
|
||||
@@ -313,63 +248,6 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
}
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_EXERCISE_MENU:
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_practice_exercise_index = wrap_next(state.selected_practice_exercise_index, menu_row_count(state.catalog->practice.exercises.size()));
|
||||
state.selected_practice_part_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(state.catalog->practice.exercises.size()));
|
||||
state.selected_practice_part_index = 0;
|
||||
} else if (state.selected_practice_exercise_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::MAIN_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (selected_practice_exercise(state) != nullptr) {
|
||||
state.active_part_id.clear();
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_PART_MENU;
|
||||
state.selected_practice_part_index = 0;
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_PART_MENU:
|
||||
if (auto const *exercise = selected_practice_exercise(state);
|
||||
exercise != nullptr) {
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_practice_part_index =
|
||||
wrap_next(state.selected_practice_part_index, menu_row_count(exercise->parts.size()));
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.selected_practice_part_index =
|
||||
wrap_previous(state.selected_practice_part_index, menu_row_count(exercise->parts.size()));
|
||||
} else if (state.selected_practice_part_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::PRACTICE_EXERCISE_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (selected_practice_part(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;
|
||||
++state.score_session;
|
||||
state.audio.playback_error.clear();
|
||||
}
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::PRACTICE_SCORE:
|
||||
if (auto const *exercise = active_practice(state); exercise != nullptr) {
|
||||
auto const *practice_part = selected_practice_part(state, *exercise);
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
auto pages = practice_part == nullptr ? 0U : practice_part->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 = vamp_zero::app_mode::PRACTICE_PART_MENU;
|
||||
state.active_practice_id.clear();
|
||||
state.page_index = 0;
|
||||
}
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
}
|
||||
@@ -396,19 +274,6 @@ auto vamp_zero::app_status_json(vamp_zero::app_state const &state) -> boost::jso
|
||||
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 *practice_part = selected_practice_part(state, *exercise);
|
||||
active["selected"] = true;
|
||||
active["kind"] = "practice";
|
||||
active["exercise"] = exercise_json(*exercise);
|
||||
if (practice_part != nullptr) {
|
||||
active["part"] = part_json(*practice_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>(practice_part == nullptr ? 0U : practice_part->total_pages);
|
||||
}
|
||||
}
|
||||
if (active.empty()) {
|
||||
active["selected"] = false;
|
||||
@@ -424,7 +289,6 @@ auto vamp_zero::app_status_json(vamp_zero::app_state const &state) -> boost::jso
|
||||
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["audio"] = audio_json(state);
|
||||
@@ -449,16 +313,6 @@ auto vamp_zero::remote_display_text(vamp_zero::app_state const &state) -> std::s
|
||||
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 *practice_part = selected_practice_part(state, *selected);
|
||||
active = true;
|
||||
title = selected->title;
|
||||
part = practice_part == nullptr ? std::string{} : practice_part->part;
|
||||
title_image = practice_part == nullptr ? std::string{} : practice_part->title_image;
|
||||
page = state.page_index + 1U;
|
||||
pages = practice_part == nullptr ? 0U : practice_part->total_pages;
|
||||
}
|
||||
}
|
||||
auto output = std::ostringstream{};
|
||||
output << "mode=" << vamp_zero::to_string(state.mode) << "\n";
|
||||
@@ -494,25 +348,13 @@ 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.catalog->performance.groups.size())) {
|
||||
if (state.selected_song_index >= menu_row_count(state.catalog->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_exercise_index >= menu_row_count(state.catalog->practice.exercises.size())) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected practice exercise row is out of range"));
|
||||
}
|
||||
if (state.selected_practice_part_index >= practice_part_row_count(state)) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected practice part row is out of range"));
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PRACTICE_PART_MENU) {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
if (exercise == nullptr || exercise->parts.empty()) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice part mode requires an exercise with parts"));
|
||||
}
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
auto const *part = active_part(state);
|
||||
if (part == nullptr) {
|
||||
@@ -522,18 +364,5 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
|
||||
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 *practice_part = selected_practice_part(state, *exercise);
|
||||
if (practice_part == nullptr) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice score mode requires a selected generated part"));
|
||||
}
|
||||
if (practice_part->total_pages == 0U || state.page_index >= practice_part->total_pages) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "practice score page index is out of range"));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -87,14 +87,9 @@ class title_assets {
|
||||
public:
|
||||
title_assets(std::filesystem::path root, vamp_zero::catalog const &catalog)
|
||||
: root_{std::move(root)} {
|
||||
for (auto const &part : catalog.performance.parts) {
|
||||
for (auto const &part : catalog.parts) {
|
||||
identifiers_.insert(part.title_image);
|
||||
}
|
||||
for (auto const &exercise : catalog.practice.exercises) {
|
||||
for (auto const &part : exercise.parts) {
|
||||
identifiers_.insert(part.title_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto image(std::string const &identifier) const -> std::optional<title_file> {
|
||||
return identifiers_.contains(identifier) ? read_title(root_, identifier) : std::nullopt;
|
||||
@@ -224,7 +219,7 @@ auto vamp_zero::control_state::finish_playback(std::uint64_t session, std::strin
|
||||
auto updated = vamp_zero::app_state{};
|
||||
{
|
||||
auto lock = std::scoped_lock{mutex_};
|
||||
if (!state_.audio.playback_enabled || state_.score_session != session || (catalog && catalog != state_.catalog) || (state_.mode != vamp_zero::app_mode::PERFORMANCE_SCORE && state_.mode != vamp_zero::app_mode::PRACTICE_SCORE)) {
|
||||
if (!state_.audio.playback_enabled || state_.score_session != session || (catalog && catalog != state_.catalog) || state_.mode != vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
return;
|
||||
}
|
||||
if (error.empty()) {
|
||||
|
||||
@@ -131,13 +131,7 @@ auto vamp_zero::selected_score_reference(vamp_zero::app_state const &state) -> s
|
||||
}
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
if (auto part = vamp_zero::active_part(state)) {
|
||||
return vamp_zero::score_reference{.category = vamp_zero::score_category::PERFORMANCE, .id = part->id};
|
||||
}
|
||||
} else if (state.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
if (auto exercise = vamp_zero::active_practice(state)) {
|
||||
if (auto practice_part = vamp_zero::selected_practice_part(state, *exercise)) {
|
||||
return vamp_zero::score_reference{.category = vamp_zero::score_category::PRACTICE, .id = practice_part->id};
|
||||
}
|
||||
return vamp_zero::score_reference{.id = part->id};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#include <vamp_zero/types.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
|
||||
auto vamp_zero::empty_catalog() -> std::shared_ptr<vamp_zero::catalog const> const & {
|
||||
static auto const empty = std::make_shared<vamp_zero::catalog const>();
|
||||
@@ -19,17 +16,6 @@ auto vamp_zero::to_string(vamp_zero::app_mode value) -> std::string {
|
||||
return "performance_part_menu";
|
||||
case vamp_zero::app_mode::PERFORMANCE_SCORE:
|
||||
return "performance_score";
|
||||
case vamp_zero::app_mode::PRACTICE_EXERCISE_MENU:
|
||||
return "practice_exercise_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_PART_MENU:
|
||||
return "practice_part_menu";
|
||||
case vamp_zero::app_mode::PRACTICE_SCORE:
|
||||
return "practice_score";
|
||||
}
|
||||
return "main_menu";
|
||||
}
|
||||
|
||||
auto vamp_zero::practice_part_names() -> std::vector<std::string_view> const & {
|
||||
static auto const parts = std::vector<std::string_view>{"Acoustic", "Electric", "Vocal"};
|
||||
return parts;
|
||||
}
|
||||
|
||||
+19
-73
@@ -170,50 +170,45 @@ auto draw_menu_rows(luma::draw_context &context, luma::rect bounds, std::size_t
|
||||
}
|
||||
|
||||
auto draw_main_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "MODE", 4, vamp_style::ink, 260.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT WORKFLOW", 1, vamp_style::muted, 320.0F);
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "MENU", 4, vamp_style::ink, 260.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "PRODUCT VAMP", 1, vamp_style::muted, 320.0F);
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y, .width = row_width, .height = row_height},
|
||||
"Performance Mode",
|
||||
std::to_string(state.catalog->performance.groups.size()) + " SONGS",
|
||||
"Songs",
|
||||
std::to_string(state.catalog->groups.size()) + " SONGS",
|
||||
state.selected_main_menu_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height, .width = row_width, .height = row_height},
|
||||
"Practice Mode",
|
||||
std::to_string(state.catalog->practice.exercises.size()) + " EXERCISES",
|
||||
"Playback",
|
||||
state.audio.playback_enabled ? "ON" : "OFF",
|
||||
state.selected_main_menu_index == 1U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height * 2.0F, .width = row_width, .height = row_height},
|
||||
"Playback",
|
||||
state.audio.playback_enabled ? "ON" : "OFF",
|
||||
state.selected_main_menu_index == 2U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height * 3.0F, .width = row_width, .height = row_height},
|
||||
"Metronome",
|
||||
state.audio.metronome_enabled ? "ON" : "OFF",
|
||||
state.selected_main_menu_index == 3U);
|
||||
state.selected_main_menu_index == 2U);
|
||||
auto tempo = std::to_string(state.audio.metronome_bpm) + " BPM";
|
||||
if (state.audio.tempo_editing) {
|
||||
tempo = "EDIT " + tempo;
|
||||
}
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height * 4.0F, .width = row_width, .height = row_height},
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height * 3.0F, .width = row_width, .height = row_height},
|
||||
"Tempo",
|
||||
tempo,
|
||||
state.selected_main_menu_index == 4U);
|
||||
state.selected_main_menu_index == 3U);
|
||||
}
|
||||
|
||||
auto draw_performance_song_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "PERFORMANCE", 4, vamp_style::ink, 360.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(state.catalog->performance.groups.size()) + " SONGS " + std::to_string(state.catalog->performance.parts.size()) + " PARTS", 1, vamp_style::muted, 320.0F);
|
||||
draw_menu_rows(context, bounds, state.catalog->performance.groups.size(), state.selected_song_index, [&](std::size_t index, luma::rect row, std::bool_t selected) {
|
||||
auto const &group = state.catalog->performance.groups[index];
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(state.catalog->groups.size()) + " SONGS " + std::to_string(state.catalog->parts.size()) + " PARTS", 1, vamp_style::muted, 320.0F);
|
||||
draw_menu_rows(context, bounds, state.catalog->groups.size(), state.selected_song_index, [&](std::size_t index, luma::rect row, std::bool_t selected) {
|
||||
auto const &group = state.catalog->groups[index];
|
||||
draw_row(context, row, group.title, std::to_string(group.parts.size()) + " PARTS", selected);
|
||||
});
|
||||
if (state.catalog->performance.groups.empty()) {
|
||||
if (state.catalog->groups.empty()) {
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 194.0F, "NO SONGS FOUND", 3, vamp_style::retrying, 420.0F);
|
||||
}
|
||||
}
|
||||
@@ -234,62 +229,18 @@ auto draw_performance_part_menu(luma::draw_context &context, luma::rect bounds,
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, group->composer, 1, vamp_style::muted, bounds.width - 72.0F);
|
||||
draw_menu_rows(context, bounds, group->parts.size(), state.selected_part_index, [&](std::size_t index, luma::rect row, std::bool_t selected) {
|
||||
auto part_index = group->parts[index];
|
||||
if (part_index >= state.catalog->performance.parts.size()) {
|
||||
if (part_index >= state.catalog->parts.size()) {
|
||||
return;
|
||||
}
|
||||
auto const &part = state.catalog->performance.parts[part_index];
|
||||
auto const &part = state.catalog->parts[part_index];
|
||||
draw_row(context, row, part.part, std::to_string(part.total_pages) + " PAGES", selected);
|
||||
});
|
||||
}
|
||||
|
||||
auto draw_practice_exercise_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const &exercises = state.catalog->practice.exercises;
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "PRACTICE", 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(exercises.size()) + " EXERCISES", 1, vamp_style::muted, 320.0F);
|
||||
draw_menu_rows(context, bounds, exercises.size(), state.selected_practice_exercise_index, [&](std::size_t index, luma::rect row, std::bool_t selected) {
|
||||
auto const &exercise = exercises[index];
|
||||
auto title = exercise.title == exercise.id ? exercise.id : exercise.id + " - " + exercise.title;
|
||||
draw_row(context, row, title, "", selected);
|
||||
});
|
||||
if (exercises.empty()) {
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 194.0F, "NO EXERCISES FOUND", 3, vamp_style::retrying, 440.0F);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_part_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
exercise == nullptr ? std::string{"PART"} : exercise->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT PRACTICE PART", 1, vamp_style::muted, 360.0F);
|
||||
draw_menu_rows(context, bounds, exercise == nullptr ? 0U : exercise->parts.size(), state.selected_practice_part_index, [&](std::size_t index, luma::rect row, std::bool_t selected) {
|
||||
auto const &part = exercise->parts[index];
|
||||
draw_row(context, row, part.part, part.key_signature, selected);
|
||||
});
|
||||
}
|
||||
|
||||
auto draw_score(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state, vamp_zero::score_load_state const &loaded, score_draw_cache &score_cache) -> void {
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
auto const *part = active_part(state);
|
||||
if (part == nullptr) {
|
||||
draw_performance_song_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
auto const *exercise = active_practice(state);
|
||||
if (exercise == nullptr) {
|
||||
draw_practice_exercise_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
auto const *practice_part = selected_practice_part(state, *exercise);
|
||||
if (practice_part == nullptr) {
|
||||
draw_practice_part_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
if (active_part(state) == nullptr) {
|
||||
draw_performance_song_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
if (loaded.loading) {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 38.0F, "LOADING SCORE", 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
@@ -348,12 +299,7 @@ public:
|
||||
draw_performance_song_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU) {
|
||||
draw_performance_part_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_EXERCISE_MENU) {
|
||||
draw_practice_exercise_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_PART_MENU) {
|
||||
draw_practice_part_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) {
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
draw_score(context, content, state.app, loaded, score_cache_);
|
||||
} else {
|
||||
draw_main_menu(context, content, state.app);
|
||||
|
||||
Reference in New Issue
Block a user