Files
product-vamp/tests/test_performance.cpp
T
khodowany 82349cf897
ci / checks (push) Failing after 23m37s
Playback
2026-10-05 23:41:11 -05:00

194 lines
8.6 KiB
C++

#include <boost/test/unit_test.hpp>
#include <boost/json.hpp>
#include <array>
#include <fstream>
#include <iterator>
#include <thread>
#include <vamp_zero/audio.hpp>
#include <vamp_zero/control.hpp>
#include <vamp_zero/notation.hpp>
#include <vamp_zero/page_control.hpp>
#include <vamp_zero/performance.hpp>
namespace {
auto read(std::string const &name) -> std::string {
auto input = std::ifstream{std::string{VAMP_TEST_PERFORMANCE_ASSETS_DIR} + "/" + name};
return {std::istreambuf_iterator<char>{input}, {}};
}
auto score() -> vamp_zero::score_document {
auto parsed = vamp_zero::parse_notation_document(read("score.json"), "score.json");
BOOST_REQUIRE_MESSAGE(parsed, (parsed ? "" : parsed.error().describe()));
return *parsed;
}
auto state(bool metronome = false, bool backing = false) -> vamp_zero::app_state {
auto catalog = std::make_shared<vamp_zero::catalog>();
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->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;
result.selected_part_index = 1;
result.active_part_id = "score";
result.score_session = 1;
result.audio.metronome_enabled = metronome;
result.audio.backing_enabled = backing;
return result;
}
}
BOOST_AUTO_TEST_CASE(recordings_and_clicks_share_clock_and_tail_is_unclicked) {
for (auto metronome : {false, true}) {
for (auto backing : {false, true}) {
auto app = state(metronome, backing);
auto document = score();
auto prepared = vamp_zero::prepare_performance(document, app, VAMP_TEST_PERFORMANCE_ASSETS_DIR, 1.F);
if (!metronome && !backing) {
BOOST_CHECK(!prepared);
continue;
}
BOOST_REQUIRE_MESSAGE(prepared, (prepared ? "" : prepared.error().describe()));
auto engine = sonus::engine::create({.sample_rate = 48000, .channels = 2, .master_volume = 1.F, .no_device = true});
BOOST_REQUIRE(engine);
auto session = engine->play_transport(std::move(*prepared));
BOOST_REQUIRE(session);
auto frames = backing ? 576000U : 288000U;
auto buffer = std::array<float, 960>{};
for (unsigned cursor = 0; cursor < frames; cursor += 480) {
BOOST_REQUIRE(engine->render(buffer.data(), 480));
BOOST_TEST(session->frames() == cursor + 480);
if (cursor % 24000 == 0) {
auto expected = backing ? 0.03125F : 0.F;
if (metronome && cursor < 288000 && (!backing || cursor >= 96000)) {
expected += cursor % 96000 == 0 ? .25F : .125F;
}
BOOST_TEST(buffer.front() == expected, boost::test_tools::tolerance(0.0001F));
}
// Let the background stream worker refill between accelerated renders.
if (backing && cursor % 48000 == 0) {
std::this_thread::sleep_for(std::chrono::milliseconds{2});
}
}
BOOST_CHECK(session->status() == sonus::playback_status::COMPLETED);
BOOST_TEST(vamp_zero::performance_at(document, 95999, 120).phase == "Count-in");
BOOST_TEST(vamp_zero::performance_at(document, 96000, 120).page == 0U);
BOOST_TEST(vamp_zero::performance_at(document, 191999, 120).page == 0U);
BOOST_TEST(vamp_zero::performance_at(document, 192000, 120).page == 1U);
BOOST_TEST(vamp_zero::performance_at(document, 288000, 120).phase == "Sustain");
}
}
}
BOOST_AUTO_TEST_CASE(manual_navigation_requires_both_toggles_off) {
for (auto metro : {false, true}) {
for (auto backing : {false, true}) {
auto app = state(metro, backing);
auto next = vamp_zero::apply_app_command(app, vamp_protocol::page_command::NEXT);
BOOST_TEST(next.page_index == (metro || backing ? 0U : 1U));
next = vamp_zero::apply_app_command(next, vamp_protocol::page_command::SELECT);
BOOST_CHECK(next.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU);
}
}
}
BOOST_AUTO_TEST_CASE(tempo_steps_use_global_recording_intersection_and_nearest_slower_tie) {
auto app = state();
BOOST_TEST(vamp_zero::available_tempos(app).size() == 7U);
app.mode = vamp_zero::app_mode::SETTINGS_MENU;
app.selected_settings_index = 1;
app.audio.tempo_bpm = 80;
app = vamp_zero::apply_app_command(app, vamp_protocol::page_command::SELECT);
BOOST_TEST(app.audio.backing_enabled);
BOOST_TEST(app.audio.tempo_bpm == 120U);
auto catalog = std::make_shared<vamp_zero::catalog>(*app.catalog);
auto entry = catalog->audio_assets.back();
entry.bpm = 100;
entry.path = "backing/fixture_100.wav";
catalog->audio_assets.push_back(entry);
app.catalog = catalog;
app.audio.backing_enabled = false;
app.audio.tempo_bpm = 110;
app = vamp_zero::apply_app_command(app, vamp_protocol::page_command::SELECT);
BOOST_TEST(app.audio.tempo_bpm == 100U);
catalog->parts.push_back({.song_id = "other"});
catalog->groups.push_back({.parts = {1}});
BOOST_TEST(vamp_zero::available_tempos(app).empty());
}
BOOST_AUTO_TEST_CASE(stale_performance_updates_do_not_navigate_or_restart) {
auto app = state(true, false);
auto control = vamp_zero::control_state{{}, app};
control.update_performance(99, 1, "Playing", app.catalog);
BOOST_TEST(control.snapshot().page_index == 0U);
control.update_performance(1, 1, "Playing", app.catalog);
BOOST_TEST(control.snapshot().page_index == 1U);
control.finish_playback(99, {}, app.catalog);
BOOST_CHECK(control.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_SCORE);
control.finish_playback(1, "Recording failed", app.catalog);
control.update_performance(1, 0, "Playing", app.catalog);
BOOST_TEST(control.snapshot().page_index == 1U);
BOOST_TEST(control.snapshot().audio.phase == "Error");
control.apply(vamp_protocol::page_command::SELECT);
control.finish_playback(1, {}, app.catalog);
BOOST_CHECK(control.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU);
}
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);
auto engine = sonus::engine::create({.sample_rate = 48000, .channels = 2, .no_device = true});
BOOST_REQUIRE(engine);
auto session = engine->play_transport(std::move(*prepared));
BOOST_REQUIRE(session);
session->stop();
std::array<float, 960> buffer{};
BOOST_REQUIRE(engine->render(buffer.data(), 480));
BOOST_TEST(session->frames() == 0U);
BOOST_TEST(buffer.front() == 0.F);
}
BOOST_AUTO_TEST_CASE(page_timing_and_recording_manifests_reject_corruption) {
auto document = boost::json::parse(read("score.json"));
document.at("presentation").at("pages").as_array()[1].at("first_measure") = 1;
BOOST_CHECK(!vamp_zero::parse_notation_document(boost::json::serialize(document), "score.json"));
auto app = state(true);
auto catalog = std::make_shared<vamp_zero::catalog>(*app.catalog);
catalog->audio_assets.front().sha256 = std::string(64, '0');
app.catalog = catalog;
BOOST_CHECK(!vamp_zero::prepare_performance(score(), app, VAMP_TEST_PERFORMANCE_ASSETS_DIR, 1.F));
}
BOOST_AUTO_TEST_CASE(preparation_waits_for_pages_and_cancellation_discards_stale_work) {
auto engine = sonus::engine::create({.sample_rate = 48000, .channels = 2, .no_device = true});
BOOST_REQUIRE(engine);
auto controller = vamp_zero::audio_controller{{}, std::move(*engine)};
auto app = state(true);
auto document = std::make_shared<vamp_zero::score_document const>(score());
auto loaded = vamp_zero::score_load_state{.document = document};
BOOST_CHECK(!controller.tick(app, loaded, VAMP_TEST_PERFORMANCE_ASSETS_DIR));
BOOST_CHECK(!controller.position(*document, 120));
loaded.pages_ready = true;
BOOST_CHECK(!controller.tick(app, loaded, VAMP_TEST_PERFORMANCE_ASSETS_DIR));
auto exited = vamp_zero::apply_app_command(app, vamp_protocol::page_command::SELECT);
controller.sync(exited);
for (unsigned i = 0; i < 20; ++i) {
BOOST_CHECK(!controller.tick(exited, {}, VAMP_TEST_PERFORMANCE_ASSETS_DIR));
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
BOOST_CHECK(!controller.position(*document, 120));
++app.score_session;
for (unsigned i = 0; i < 200 && !controller.position(*document, 120); ++i) {
BOOST_CHECK(!controller.tick(app, loaded, VAMP_TEST_PERFORMANCE_ASSETS_DIR));
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
BOOST_REQUIRE(controller.position(*document, 120));
controller.sync(exited);
BOOST_CHECK(!controller.position(*document, 120));
}