#include #include #include #include #include #include #include #include #include namespace { auto no_device_config() -> sonus::audio_config { auto config = sonus::audio_config{}; config.no_device = true; return config; } auto peak(std::vector const& samples, std::size_t begin_frame, std::size_t frame_count, std::size_t channels) -> std::float32_t { auto result = 0.0F; auto const begin = begin_frame * channels; auto const end = std::min(samples.size(), begin + frame_count * channels); for (auto index = begin; index < end; ++index) { result = std::max(result, std::abs(samples[index])); } return result; } } BOOST_AUTO_TEST_CASE(sonus_validates_configs) { auto audio = sonus::audio_config{}; BOOST_TEST(static_cast(sonus::validate(audio))); audio.sample_rate = 0; BOOST_TEST(!sonus::validate(audio)); audio = {}; audio.channels = 0; BOOST_TEST(!sonus::validate(audio)); audio = {}; audio.master_volume = 1.5F; BOOST_TEST(!sonus::validate(audio)); auto metronome = sonus::metronome_config{}; BOOST_TEST(static_cast(sonus::validate(metronome))); metronome.bpm = 0; BOOST_TEST(!sonus::validate(metronome)); metronome = {}; metronome.beats_per_bar = 0; BOOST_TEST(!sonus::validate(metronome)); metronome = {}; metronome.volume = -0.1F; BOOST_TEST(!sonus::validate(metronome)); } BOOST_AUTO_TEST_CASE(sonus_no_device_engine_renders_silence_when_idle) { auto created = sonus::engine::create(no_device_config()); BOOST_REQUIRE(created); auto samples = std::vector(2048U * created->channels(), 1.0F); auto rendered = created->render(samples.data(), 2048U); BOOST_REQUIRE(rendered); BOOST_TEST(std::all_of(samples.begin(), samples.end(), [](auto sample) { return sample == 0.0F; })); } BOOST_AUTO_TEST_CASE(sonus_metronome_renders_accented_clicks) { auto config = no_device_config(); config.sample_rate = 48000; config.channels = 2; auto created = sonus::engine::create(config); BOOST_REQUIRE(created); auto metro = sonus::metronome_config{}; metro.bpm = 120; metro.beats_per_bar = 4; metro.volume = 0.70F; metro.click_duration = std::chrono::milliseconds{25}; BOOST_REQUIRE(created->start_metronome(metro)); auto samples = std::vector(config.sample_rate * config.channels); BOOST_REQUIRE(created->render(samples.data(), config.sample_rate)); auto downbeat_peak = peak(samples, 0, 2400, config.channels); auto beat_peak = peak(samples, config.sample_rate / 2, 2400, config.channels); auto between_peak = peak(samples, config.sample_rate / 4, 2400, config.channels); BOOST_TEST(downbeat_peak > 0.2F); BOOST_TEST(beat_peak > 0.1F); BOOST_TEST(downbeat_peak > beat_peak * 1.2F); BOOST_TEST(between_peak < 0.05F); } BOOST_AUTO_TEST_CASE(sonus_metronome_can_be_updated_concurrently) { auto created = sonus::engine::create(no_device_config()); BOOST_REQUIRE(created); auto worker = [&created](std::uint32_t offset) { for (auto index = std::uint32_t{}; index < 100U; ++index) { auto metro = sonus::metronome_config{}; metro.bpm = 80U + ((index + offset) % 80U); static_cast(created->start_metronome(metro)); if (index % 7U == 0U) { created->stop_metronome(); } } }; auto first = std::thread{worker, 0U}; auto second = std::thread{worker, 11U}; auto third = std::thread{worker, 23U}; first.join(); second.join(); third.join(); auto samples = std::vector(4096U * created->channels()); BOOST_TEST(static_cast(created->render(samples.data(), 4096U))); } BOOST_AUTO_TEST_CASE(sonus_file_playback_reports_missing_files) { auto created = sonus::engine::create(no_device_config()); BOOST_REQUIRE(created); auto missing = std::filesystem::path{"/tmp/sonus-missing-file.wav"}; auto played = created->play_file(missing); BOOST_TEST(!played); }