#include #include #include #include #include #include namespace { struct recordings { std::filesystem::path root; recordings() { auto path = (std::filesystem::temp_directory_path() / "sonus-recordings-XXXXXX").string(); BOOST_REQUIRE(::mkdtemp(path.data())); root = path; write("backing.wav", 16, 1024, -1024); write("beat1.wav", 2, 8192, -8192); write("beat2.wav", 2, 4096, -4096); } ~recordings() { std::error_code error; std::filesystem::remove_all(root, error); } auto write(char const *name, unsigned frames, int left, int right) -> void { auto output = std::ofstream{root / name, std::ios::binary}; auto word = [&](unsigned value, unsigned bytes) { for (unsigned i = 0; i < bytes; ++i){ output.put(static_cast(value >> (8 * i))); } }; output.write("RIFF", 4); word(36 + frames * 4, 4); output.write("WAVEfmt ", 8); word(16, 4); word(1, 2); word(2, 2); word(48000, 4); word(192000, 4); word(4, 2); word(16, 2); output.write("data", 4); word(frames * 4, 4); for (unsigned i = 0; i < frames; ++i) { word(static_cast(left), 2); word(static_cast(right), 2); } } auto config() const -> sonus::transport_config { return {.backing = root / "backing.wav", .beat1 = root / "beat1.wav", .beat2 = root / "beat2.wav", .clicks = {{4, true}, {8, false}}, .duration_frames = 10, .click_volume = 1.F}; } }; } BOOST_AUTO_TEST_CASE(recording_transport_preserves_stereo_and_has_one_sample_clock) { auto files = recordings{}; auto prepared = sonus::prepared_transport::create(files.config(), 48000, 2); BOOST_REQUIRE(prepared); 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); std::array output{}; BOOST_REQUIRE(engine->render(output.data(), 20)); BOOST_TEST(session->frames() == 16U); BOOST_CHECK(session->status() == sonus::playback_status::COMPLETED); for (unsigned frame = 0; frame < 20; ++frame) { auto value = frame < 16 ? .03125F : 0.F; if (frame == 4 || frame == 5) { value += .25F; } if (frame == 8 || frame == 9) { value += .125F; } BOOST_TEST(output[frame * 2] == value); BOOST_TEST(output[frame * 2 + 1] == -value); } engine->collect_finished(); BOOST_CHECK(session->status() == sonus::playback_status::COMPLETED); } BOOST_AUTO_TEST_CASE(recording_transport_replacement_cancels_old_session_and_validates_inputs) { auto files = recordings{}; auto engine = sonus::engine::create({.sample_rate = 48000, .channels = 2, .no_device = true}); BOOST_REQUIRE(engine); auto config = files.config(); auto first = sonus::prepared_transport::create(config, 48000, 2); BOOST_REQUIRE(first); auto session = engine->play_transport(std::move(*first)); BOOST_REQUIRE(session); auto second = sonus::prepared_transport::create(config, 48000, 2); BOOST_REQUIRE(second); auto replacement = engine->play_transport(std::move(*second)); BOOST_REQUIRE(replacement); BOOST_CHECK(session->status() == sonus::playback_status::STOPPED); replacement->stop(); std::array output{}; BOOST_REQUIRE(engine->render(output.data(), 8)); BOOST_TEST(replacement->frames() == 0U); BOOST_TEST(output.front() == 0.F); config.duration_frames = 17; BOOST_CHECK(!sonus::prepared_transport::create(config, 48000, 2)); config = files.config(); config.beat1 = files.root / "missing.wav"; BOOST_CHECK(!sonus::prepared_transport::create(config, 48000, 2)); config = files.config(); config.clicks = {{9, true}, {4, false}}; BOOST_CHECK(!sonus::prepared_transport::create(config, 48000, 2)); config = files.config(); config.backing.clear(); config.clicks.push_back({10, true}); BOOST_CHECK(!sonus::prepared_transport::create(config, 48000, 2)); }