Playback Mode Pages
ci / checks (push) Successful in 50m29s

This commit is contained in:
khodowany
2026-10-06 23:11:25 -05:00
parent 125aaa1637
commit d2f493c17b
33 changed files with 620 additions and 130 deletions
+90 -1
View File
@@ -9,6 +9,7 @@
#include <vamp_zero/notation.hpp>
#include <vamp_zero/page_control.hpp>
#include <vamp_zero/performance.hpp>
#include <vamp_zero/score.hpp>
namespace {
@@ -29,7 +30,7 @@ auto state(bool metronome = false, bool backing = false) -> vamp_zero::app_state
auto audio = vamp_zero::parse_audio_assets(read("manifest.audio"));
BOOST_REQUIRE(audio);
catalog->audio_assets = *audio;
catalog->parts.push_back({.id = "score", .title = "Fixture", .part = "Vocal", .total_pages = 2, .song_id = "fixture"});
catalog->parts.push_back({.id = "score", .title = "Fixture", .part = "Vocal", .total_pages = 2, .song_id = "fixture", .playback_pages = 3});
catalog->groups.push_back({.title = "Fixture", .parts = {0}});
auto result = vamp_zero::app_state{.catalog = catalog, .mode = vamp_zero::app_mode::PERFORMANCE_SCORE};
result.selected_song_index = 1;
@@ -139,6 +140,94 @@ BOOST_AUTO_TEST_CASE(stale_performance_updates_do_not_navigate_or_restart) {
BOOST_CHECK(control.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU);
}
BOOST_AUTO_TEST_CASE(playback_advances_one_system_and_highlights_each_measure) {
auto document = score();
BOOST_TEST(document.pages.size() == 2U);
BOOST_TEST(document.playback_pages.size() == 3U);
for (auto bpm : {60U, 90U, 120U}) {
auto count_in = vamp_zero::frame_at({4, 1}, bpm);
BOOST_CHECK(!vamp_zero::performance_at(document, count_in - 1, bpm).measure);
for (std::size_t m = 0; m < document.measure_count; ++m) {
auto frame = count_in + vamp_zero::frame_at(document.measure_boundaries[m], bpm);
auto now = vamp_zero::performance_at(document, frame, bpm);
BOOST_REQUIRE(now.measure);
BOOST_TEST(*now.measure == m + 1);
BOOST_TEST(now.page == std::min(m, std::size_t{2}));
if (m) {
BOOST_TEST(*vamp_zero::performance_at(document, frame - 1, bpm).measure == m);
}
}
}
BOOST_TEST(document.playback_pages.back().systems.size() == 1U);
auto app = state(true);
auto control = vamp_zero::control_state{{}, app};
control.update_performance(1, 2, "Sustain", app.catalog, 3);
auto revision = control.snapshot().input_revision;
control.update_performance(1, 2, "Sustain", app.catalog, 4);
BOOST_TEST(*control.snapshot().active_measure == 4U);
BOOST_TEST(control.snapshot().input_revision == revision + 1);
auto remote = vamp_zero::remote_display_text(control.snapshot());
BOOST_TEST(remote.find("page=3\npages=3") != std::string::npos);
control.finish_playback(1, "failed", app.catalog);
BOOST_CHECK(!control.snapshot().active_measure);
}
BOOST_AUTO_TEST_CASE(fractional_pickups_and_unequal_measures_use_exact_boundaries) {
auto document = score();
document.measure_boundaries = {{0, 1}, {1, 2}, {7, 2}, {41, 6}, {65, 6}, {89, 6}};
document.playback_pages[1].start_beat = {1, 2};
document.playback_pages[2].start_beat = {7, 2};
document.duration = {89, 6};
for (auto bpm : {60U, 110U, 120U}) {
for (std::size_t i = 1; i < document.measure_count; ++i) {
auto frame = vamp_zero::frame_at({4, 1}, bpm) + vamp_zero::frame_at(document.measure_boundaries[i], bpm);
BOOST_TEST(*vamp_zero::performance_at(document, frame - 1, bpm).measure == i);
BOOST_TEST(*vamp_zero::performance_at(document, frame, bpm).measure == i + 1);
}
}
}
BOOST_AUTO_TEST_CASE(highlight_geometry_follows_letterboxed_page_and_active_view) {
auto document = score();
for (auto bounds : {luma::rect{0, 0, 976, 484}, luma::rect{10, 20, 488, 484}, luma::rect{10, 20, 1952, 968}}) {
auto image = vamp_zero::page_rectangle(bounds);
auto a = vamp_zero::measure_highlight(document.playback_pages[0], 2, image);
auto b = vamp_zero::measure_highlight(document.playback_pages[1], 2, image);
BOOST_REQUIRE(a);
BOOST_REQUIRE(b);
auto scale = image.width / 976.F;
BOOST_TEST(b->x == image.x + 27.F * scale);
BOOST_TEST(b->y == image.y + 67.F * scale);
BOOST_TEST(a->y - b->y == 220.F * scale);
BOOST_TEST(b->width == 18.F * scale);
BOOST_CHECK(!vamp_zero::measure_highlight(document.playback_pages[0], {}, image));
BOOST_CHECK(!vamp_zero::measure_highlight(document.playback_pages[0], 5, image));
}
}
BOOST_AUTO_TEST_CASE(playback_parser_requires_complete_views_and_safe_label_geometry) {
auto original = boost::json::parse(read("score.json"));
for (auto fault : {"missing", "pair", "label", "bounds", "time", "path"}) {
auto value = original;
auto &presentation = value.at("presentation").as_object();
auto &views = presentation.at("playback_pages").as_array();
if (std::string_view{fault} == "missing") {
presentation.erase("playback_pages");
} else if (std::string_view{fault} == "pair") {
views[1] = views[0];
} else if (std::string_view{fault} == "label") {
views[0].at("systems").as_array()[0].at("labels").as_array()[0].at("measure") = 99;
} else if (std::string_view{fault} == "bounds") {
views[0].at("systems").as_array()[0].at("labels").as_array()[0].at("bounds").as_array()[2] = 1000;
} else if (std::string_view{fault} == "time") {
views[1].at("start_beat").at("numerator") = 3;
} else {
views[1].at("path") = "../outside.png";
}
BOOST_CHECK_MESSAGE(!vamp_zero::parse_notation_document(boost::json::serialize(value), "score.json"), fault);
}
}
BOOST_AUTO_TEST_CASE(recording_transport_cancellation_does_not_advance_clock) {
auto prepared = vamp_zero::prepare_performance(score(), state(true), VAMP_TEST_PERFORMANCE_ASSETS_DIR, 1.F);
BOOST_REQUIRE(prepared);