#include #include #include #include #include #include #include #include #include #include namespace { auto playback_score() -> vamp_zero::score_document { auto input = std::ifstream{std::string{VAMP_TEST_PLAYBACK_ASSETS_DIR} + "/score.json"}; auto score = vamp_zero::score_document{}; score.source_json = std::string{std::istreambuf_iterator{input}, std::istreambuf_iterator{}}; return score; } auto performance_state() -> vamp_zero::app_state { auto catalog = std::make_shared(); catalog->parts.push_back({.id = "part", .title = "Song", .part = "Vocal", .total_pages = 2U}); catalog->groups.push_back({.title = "Song", .parts = {0U}}); auto state = vamp_zero::app_state{}; state.catalog = std::move(catalog); state.mode = vamp_zero::app_mode::PERFORMANCE_SCORE; state.selected_song_index = 1U; state.selected_part_index = 1U; state.active_part_id = "part"; state.score_session = 42U; state.audio.playback_enabled = true; return state; } } BOOST_AUTO_TEST_CASE(generated_audio_plays_at_menu_bpm) { auto score = playback_score(); auto normal = vamp_zero::prepare_playback(score, VAMP_TEST_PLAYBACK_ASSETS_DIR, 120U); auto slow = vamp_zero::prepare_playback(score, VAMP_TEST_PLAYBACK_ASSETS_DIR, 60U); BOOST_REQUIRE_MESSAGE(normal, (normal ? "" : normal.error().describe())); BOOST_REQUIRE(slow); BOOST_TEST(normal->duration_frames == 96000U); BOOST_TEST(slow->duration_frames == normal->duration_frames * 2U); BOOST_TEST(normal->samples[0].frames == slow->samples[0].frames, boost::test_tools::per_element()); auto engine = sonus::engine::create({.sample_rate = 48000U, .channels = 1U, .no_device = true}); BOOST_REQUIRE(engine); auto session = engine->play_sequence(std::move(*normal)); BOOST_REQUIRE(session); auto output = std::array{}; auto energy = std::float32_t{}; for (auto block = std::uint32_t{}; block < 200U; ++block) { BOOST_REQUIRE(engine->render(output.data(), output.size())); for (auto value : output) { energy += value * value; } if (block == 150U) { BOOST_CHECK(session->status() == sonus::playback_status::PLAYING); } } BOOST_TEST(energy > 0.1F); BOOST_CHECK(session->status() == sonus::playback_status::COMPLETED); BOOST_REQUIRE(vamp_zero::validate_playback_assets(score, VAMP_TEST_PLAYBACK_ASSETS_DIR)); } BOOST_AUTO_TEST_CASE(playback_completion_is_session_guarded) { auto state = vamp_zero::control_state{{}, performance_state()}; state.finish_playback(41U); BOOST_CHECK(state.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_SCORE); state.finish_playback(42U, {}, std::make_shared()); BOOST_CHECK(state.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_SCORE); state.finish_playback(42U, "sample missing"); BOOST_CHECK(state.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_SCORE); BOOST_TEST(state.snapshot().audio.playback_error == "sample missing"); state.finish_playback(42U); BOOST_CHECK(state.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU); BOOST_TEST(state.snapshot().selected_part_index == 1U); BOOST_TEST(state.snapshot().select_count == 0U); state.apply(vamp_protocol::page_command::SELECT); BOOST_TEST(state.snapshot().score_session == 43U); state.finish_playback(42U); BOOST_CHECK(state.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_SCORE); auto disabled = performance_state(); disabled.audio.playback_enabled = false; auto quiet = vamp_zero::control_state{{}, disabled}; quiet.finish_playback(42U); BOOST_CHECK(quiet.snapshot().mode == vamp_zero::app_mode::PERFORMANCE_SCORE); } BOOST_AUTO_TEST_CASE(playback_worker_discards_canceled_preparation_and_reports_errors_once) { auto engine = sonus::engine::create({.no_device = true}); BOOST_REQUIRE(engine); auto audio = vamp_zero::audio_controller{{}, std::move(*engine)}; auto state = performance_state(); auto invalid = std::make_shared(); invalid->id = "part"; invalid->source_json = "{}"; auto loaded = vamp_zero::score_load_state{.document = invalid}; BOOST_CHECK(!audio.tick(state, loaded, VAMP_TEST_PLAYBACK_ASSETS_DIR)); state.mode = vamp_zero::app_mode::PERFORMANCE_PART_MENU; for (auto attempt = std::uint32_t{}; attempt < 100U; ++attempt) { BOOST_CHECK(!audio.tick(state, {}, VAMP_TEST_PLAYBACK_ASSETS_DIR)); std::this_thread::sleep_for(std::chrono::milliseconds{1}); } state.mode = vamp_zero::app_mode::PERFORMANCE_SCORE; ++state.score_session; auto reported = std::bool_t{}; for (auto attempt = std::uint32_t{}; attempt < 1000U && !reported; ++attempt) { if (auto result = audio.tick(state, loaded, VAMP_TEST_PLAYBACK_ASSETS_DIR)) { BOOST_CHECK(!*result); reported = true; } std::this_thread::sleep_for(std::chrono::milliseconds{1}); } BOOST_TEST(reported); BOOST_CHECK(!audio.tick(state, loaded, VAMP_TEST_PLAYBACK_ASSETS_DIR)); } BOOST_AUTO_TEST_CASE(playback_rejects_legacy_config_and_invalid_assets) { auto legacy = vamp_zero::parse_config(boost::json::object{{"audio", {{"track_enabled", true}}}}); BOOST_REQUIRE(!legacy); BOOST_TEST(std::string{legacy.error().message()}.find("use audio.playback_enabled") != std::string::npos); BOOST_CHECK(!vamp_zero::parse_config(boost::json::object{{"audio", {{"track_enabled", true}, {"playback_enabled", false}}}})); auto score = playback_score(); BOOST_CHECK(!vamp_zero::prepare_playback(score, "/missing-vamp-assets", 120U)); BOOST_CHECK(!vamp_zero::prepare_playback(score, VAMP_TEST_PLAYBACK_ASSETS_DIR, 0U)); score.source_json = "{}"; BOOST_CHECK(!vamp_zero::prepare_playback(score, VAMP_TEST_PLAYBACK_ASSETS_DIR, 120U)); }