diff --git a/CMakeLists.txt b/CMakeLists.txt index b063fa8..c666b36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ endif() # Options option(LIBROSA_BUILD_TESTS "Build tests" ON) option(LIBROSA_BUILD_EXAMPLES "Build examples" OFF) +option(LIBROSA_BUILD_BENCHMARKS "Build Google Benchmark performance benchmarks" OFF) option(LIBROSA_BUILD_CROSSVAL_TESTS "Build cross-validation tests against Python librosa" OFF) option(LIBROSA_BUILD_CLI "Build the librosa CLI tool" OFF) option(LIBROSA_BUILD_WASM "Build the Emscripten WASM/npm binding" OFF) @@ -349,6 +350,36 @@ if(LIBROSA_BUILD_TESTS) endif() endif() +# Benchmarks +if(LIBROSA_BUILD_BENCHMARKS) + include(FetchContent) + set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Disable benchmark tests" FORCE) + set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "Disable benchmark gtest tests" FORCE) + FetchContent_Declare( + googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.8.5 + ) + FetchContent_MakeAvailable(googlebenchmark) + + add_executable(librosa_benchmarks + benchmarks/algorithm_benchmark.cpp + benchmarks/beat_benchmark.cpp + benchmarks/core_benchmark.cpp + benchmarks/feature_benchmark.cpp + benchmarks/onset_beat_benchmark.cpp + benchmarks/utility_benchmark.cpp + ) + target_link_libraries(librosa_benchmarks + PRIVATE + librosa::librosa + benchmark::benchmark_main + ) + if(LIBROSA_USE_AUDIOTOOLBOX) + target_compile_definitions(librosa_benchmarks PRIVATE LIBROSA_HAS_AUDIOTOOLBOX) + endif() +endif() + # Examples if(LIBROSA_BUILD_EXAMPLES) if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples/load_audio.cpp") diff --git a/README.md b/README.md index 390061b..e3302b5 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ Bundled (no action needed): - Eigen (git submodule at `modules/eigen`) - `fnnls`, `incbeta` (vendored in `vendor/`) -- GoogleTest and CLI11 fetched on demand via CMake `FetchContent` +- GoogleTest, Google Benchmark, and CLI11 fetched on demand via CMake + `FetchContent` On Ubuntu: @@ -125,6 +126,28 @@ ctest --test-dir build --output-on-failure The default unit tests are self-contained — they synthesise their own signals and do not require any external data. +## Run performance benchmarks + +Google Benchmark targets are optional and off by default: + +```bash +cmake -S . -B build-perf \ + -DCMAKE_BUILD_TYPE=Release \ + -DLIBROSA_BUILD_TESTS=OFF \ + -DLIBROSA_BUILD_BENCHMARKS=ON +cmake --build build-perf --target librosa_benchmarks +./build-perf/librosa_benchmarks +``` + +Real-file beat/onset benchmarks are disabled unless `LIBROSA_BENCH_AUDIO` is +set. This keeps local paths out of the build while still allowing benchmarks on +long production tracks: + +```bash +LIBROSA_BENCH_AUDIO="/path/to/file.wav" \ + ./build-perf/librosa_benchmarks --benchmark_filter=RealAudio +``` + ## Swift Package This repository is also a Swift 5.9+ package for Apple platforms. The package diff --git a/benchmarks/algorithm_benchmark.cpp b/benchmarks/algorithm_benchmark.cpp new file mode 100644 index 0000000..76fa4b4 --- /dev/null +++ b/benchmarks/algorithm_benchmark.cpp @@ -0,0 +1,330 @@ +#include "benchmark_helpers.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +using namespace librosa_bench; + +void BM_FiltersBanksAndWavelets(benchmark::State& state) { + const int n_fft = static_cast(state.range(0)); + const auto freqs = librosa::cqt_frequencies(48, librosa::note_to_hz("C2")); + const auto alpha = librosa::filters::relative_bandwidth(freqs); + + for (auto _ : state) { + auto mel = librosa::filters::mel(kSampleRate, n_fft, 64); + auto chroma = librosa::filters::chroma(kSampleRate, n_fft, 12); + auto bandwidth_enum = librosa::filters::window_bandwidth(librosa::WindowType::Hann); + auto bandwidth_name = librosa::filters::window_bandwidth("hann"); + auto cq_chroma = librosa::filters::cq_to_chroma(48, 12, 12); + auto rel = librosa::filters::relative_bandwidth(freqs); + auto lengths = librosa::filters::wavelet_lengths(freqs, kSampleRate); + auto lengths_alpha = librosa::filters::wavelet_lengths( + freqs, kSampleRate, librosa::WindowType::Hann, 1.0, std::optional(0.0), alpha); + auto wavelet = librosa::filters::wavelet(freqs, kSampleRate); + auto wavelet_alpha = librosa::filters::wavelet( + freqs, kSampleRate, librosa::WindowType::Hann, 1.0, true, 1.0, + std::optional(0.0), alpha); + auto diagonal = librosa::filters::diagonal_filter(librosa::WindowType::Hann, 21); + auto mr = librosa::filters::mr_frequencies(); + + benchmark::DoNotOptimize(bandwidth_enum); + benchmark::DoNotOptimize(bandwidth_name); + consume_eigen(mel); + consume_eigen(chroma); + consume_eigen(cq_chroma); + consume_eigen(rel); + consume_eigen(lengths.first); + benchmark::DoNotOptimize(lengths.second); + consume_eigen(lengths_alpha.first); + benchmark::DoNotOptimize(lengths_alpha.second); + consume_eigen(wavelet.first); + consume_eigen(wavelet.second); + consume_eigen(wavelet_alpha.first); + consume_eigen(wavelet_alpha.second); + consume_eigen(diagonal); + consume_eigen(mr.first); + consume_eigen(mr.second); + } +} + +void BM_FiltersSemitoneAndSOS(benchmark::State& state) { + const auto x = make_tone(440.0, state.range(0)); + librosa::ArrayXr center_freqs(3); + center_freqs << 220.0, 440.0, 880.0; + librosa::ArrayXr sample_rates = librosa::ArrayXr::Constant(3, kSampleRate); + const auto filterbank = librosa::filters::semitone_filterbank(center_freqs, 0.0, sample_rates); + const auto& sos = filterbank.first.front(); + const auto zi = librosa::filters::sosfilt_zi(sos); + + for (auto _ : state) { + auto filtered = librosa::filters::sosfilt(sos, x); + auto filtered_zi = librosa::filters::sosfilt(sos, x, zi); + auto zi_out = librosa::filters::sosfilt_zi(sos); + auto filtfilt = librosa::filters::sosfiltfilt(sos, x); + auto fb = librosa::filters::semitone_filterbank(center_freqs, 0.0, sample_rates); + + consume_eigen(filtered); + consume_eigen(filtered_zi.first); + consume_eigen(filtered_zi.second); + consume_eigen(zi_out); + consume_eigen(filtfilt); + benchmark::DoNotOptimize(fb.first.size()); + consume_eigen(fb.second); + } + + state.SetItemsProcessed(state.iterations() * x.size()); +} + +void BM_DecomposeHPSSAndMedian(benchmark::State& state) { + const auto S = make_positive_matrix(64, state.range(0)); + const auto C = make_complex_matrix(64, state.range(0)); + + for (auto _ : state) { + auto median = librosa::decompose::median_filter_2d(S, {5, 5}); + auto hpss = librosa::decompose::hpss(S, 7); + auto hpss_pair = librosa::decompose::hpss(S, std::pair{7, 9}); + auto hpss_margin = librosa::decompose::hpss(S, 7, 2.0, false, + std::pair{1.0, 2.0}); + auto complex = librosa::decompose::hpss_complex(C, 7); + auto complex_pair = librosa::decompose::hpss_complex(C, std::pair{7, 9}); + auto complex_margin = librosa::decompose::hpss_complex( + C, 7, 2.0, false, std::pair{1.0, 2.0}); + + consume_eigen(median); + consume_eigen(hpss.first); + consume_eigen(hpss.second); + consume_eigen(hpss_pair.first); + consume_eigen(hpss_pair.second); + consume_eigen(hpss_margin.first); + consume_eigen(hpss_margin.second); + consume_eigen(complex.first); + consume_eigen(complex.second); + consume_eigen(complex_pair.first); + consume_eigen(complex_pair.second); + consume_eigen(complex_margin.first); + consume_eigen(complex_margin.second); + } + + state.SetItemsProcessed(state.iterations() * S.size()); +} + +void BM_DecomposeNMFAndNNFilter(benchmark::State& state) { + const auto S = make_positive_matrix(32, state.range(0)); + const auto rec = make_neighbor_graph(state.range(0)); + + for (auto _ : state) { + auto nmf = librosa::decompose::decompose_nmf(S, 8, false, 40, 1e-4); + auto nn_mean = librosa::decompose::nn_filter(S, rec, librosa::AggregateFunc::Mean); + auto nn_median = librosa::decompose::nn_filter(S, rec, librosa::AggregateFunc::Median); + + consume_eigen(nmf.first); + consume_eigen(nmf.second); + consume_eigen(nn_mean); + consume_eigen(nn_median); + } + + state.SetItemsProcessed(state.iterations() * S.size()); +} + +void BM_EffectsPhaseAndPitch(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + + for (auto _ : state) { + auto stretched = librosa::effects::time_stretch(y, 1.25, 1024, 256); + auto shifted = librosa::effects::pitch_shift(y, kSampleRate, 2.0, 12, + "linear", 1024, 256); + consume_eigen(stretched); + consume_eigen(shifted); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_EffectsEditingAndEmphasis(benchmark::State& state) { + auto y = make_tone(440.0, state.range(0)); + y.head(state.range(0) / 4).setZero(); + y.tail(state.range(0) / 4).setZero(); + const std::vector> intervals = { + {state.range(0) / 4, state.range(0) / 2}, + {state.range(0) / 2, 3 * state.range(0) / 4}, + }; + + for (auto _ : state) { + auto trimmed = librosa::effects::trim(y, 60.0, -1.0, 1024, 256); + auto split = librosa::effects::split(y, 60.0, -1.0, 1024, 256); + auto pre = librosa::effects::preemphasis(y); + auto de = librosa::effects::deemphasis(pre); + auto remixed = librosa::effects::remix(y, intervals, true); + + consume_eigen(trimmed.first); + benchmark::DoNotOptimize(trimmed.second.first); + benchmark::DoNotOptimize(trimmed.second.second); + benchmark::DoNotOptimize(split.data()); + benchmark::DoNotOptimize(split.size()); + consume_eigen(pre); + consume_eigen(de); + consume_eigen(remixed); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_EffectsHPSSAudio(benchmark::State& state) { + const librosa::ArrayXr y = + (make_tone(440.0, state.range(0)) + make_click_track(state.range(0))).eval(); + + for (auto _ : state) { + auto harmonic = librosa::effects::harmonic(y, 15, 2.0, false, 1.0, 1024, 256); + auto percussive = librosa::effects::percussive(y, 15, 2.0, false, 1.0, 1024, 256); + consume_eigen(harmonic); + consume_eigen(percussive); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_SegmentSimilarity(benchmark::State& state) { + const auto data = make_feature_matrix(12, state.range(0)); + const auto data_ref = make_feature_matrix(12, state.range(0)); + + for (auto _ : state) { + auto cross = librosa::segment::cross_similarity(data, data_ref, 8); + auto cross_sparse = librosa::segment::cross_similarity_sparse(data, data_ref, 8); + auto rec = librosa::segment::recurrence_matrix(data, 8, 2); + auto rec_sparse = librosa::segment::recurrence_matrix_sparse(data, 8, 2); + auto lag = librosa::segment::recurrence_to_lag(rec); + auto rec2 = librosa::segment::lag_to_recurrence(lag); + auto filtered = librosa::segment::timelag_filter(rec, [](const librosa::ArrayXXr& lag_matrix) { + return lag_matrix; + }); + + consume_eigen(cross); + consume_sparse(cross_sparse); + consume_eigen(rec); + consume_sparse(rec_sparse); + consume_eigen(lag); + consume_eigen(rec2); + consume_eigen(filtered); + } + + state.SetItemsProcessed(state.iterations() * data.size()); +} + +void BM_SegmentPathAndClustering(benchmark::State& state) { + const auto data = make_feature_matrix(8, state.range(0)); + const auto rec = make_recurrence(state.range(0)); + const std::vector frames = {0, state.range(0) / 2}; + + for (auto _ : state) { + auto enhanced = librosa::segment::path_enhance(rec, 11); + auto bounds = librosa::segment::agglomerative(data, 4); + auto subsegments = librosa::segment::subsegment(data, frames, 2); + + consume_eigen(enhanced); + consume_vector(bounds); + consume_vector(subsegments); + } + + state.SetItemsProcessed(state.iterations() * data.size()); +} + +void BM_SequenceTransitionsAndViterbi(benchmark::State& state) { + const int n_states = 8; + const Eigen::Index n_frames = state.range(0); + librosa::ArrayXXr prob = librosa::ArrayXXr::Constant(n_states, n_frames, 0.1); + for (Eigen::Index t = 0; t < n_frames; ++t) { + prob(static_cast(t % n_states), t) = 0.9; + } + const auto trans = librosa::sequence::transition_loop(n_states, 0.85); + librosa::ArrayXXr trans_binary(2, 2); + trans_binary << 0.9, 0.1, + 0.1, 0.9; + librosa::ArrayXr p_state = librosa::ArrayXr::Constant(n_states, 1.0 / n_states); + librosa::ArrayXr p_init = p_state; + + for (auto _ : state) { + auto uniform = librosa::sequence::transition_uniform(n_states); + auto loop_scalar = librosa::sequence::transition_loop(n_states, 0.85); + auto loop_vector = librosa::sequence::transition_loop(n_states, p_state); + auto cycle_scalar = librosa::sequence::transition_cycle(n_states, 0.85); + auto cycle_vector = librosa::sequence::transition_cycle(n_states, p_state); + auto local = librosa::sequence::transition_local(n_states, 5); + auto states = librosa::sequence::viterbi(prob, trans, p_init); + auto states_logp = librosa::sequence::viterbi_with_logp(prob, trans, p_init); + auto discr = librosa::sequence::viterbi_discriminative(prob, trans, p_state, p_init); + auto binary = librosa::sequence::viterbi_binary(prob, trans_binary, p_state, p_init); + auto binary_logp = librosa::sequence::viterbi_binary_with_logp( + prob, trans_binary, p_state, p_init); + + consume_eigen(uniform); + consume_eigen(loop_scalar); + consume_eigen(loop_vector); + consume_eigen(cycle_scalar); + consume_eigen(cycle_vector); + consume_eigen(local); + consume_vector(states); + consume_vector(states_logp.first); + benchmark::DoNotOptimize(states_logp.second); + consume_vector(discr); + consume_eigen(binary); + consume_eigen(binary_logp.first); + consume_eigen(binary_logp.second); + } + + state.SetItemsProcessed(state.iterations() * prob.size()); +} + +void BM_SequenceAlignment(benchmark::State& state) { + const auto X = make_feature_matrix(6, state.range(0)); + const auto Y = make_feature_matrix(6, state.range(0) + 16); + const auto sim = make_recurrence(state.range(0)); + const auto steps = + Eigen::Array::Zero(state.range(0), state.range(0)); + + for (auto _ : state) { + auto euclidean = librosa::sequence::cdist_euclidean(X, Y); + auto cosine = librosa::sequence::cdist_cosine(X, Y); + auto dtw = librosa::sequence::dtw(X, Y); + auto dtw_backtrack = librosa::sequence::dtw_backtrack(X, Y); + auto path = librosa::sequence::dtw_backtracking(steps); + auto rqa = librosa::sequence::rqa(sim); + auto rqa_backtrack = librosa::sequence::rqa_backtrack(sim); + + consume_eigen(euclidean); + consume_eigen(cosine); + consume_eigen(dtw); + consume_eigen(dtw_backtrack.first); + consume_vector(dtw_backtrack.second); + consume_vector(path); + consume_eigen(rqa); + consume_eigen(rqa_backtrack.first); + consume_vector(rqa_backtrack.second); + } + + state.SetItemsProcessed(state.iterations() * X.size()); +} + +} // namespace + +BENCHMARK(BM_FiltersBanksAndWavelets)->Arg(1024)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_FiltersSemitoneAndSOS)->Arg(2048)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_DecomposeHPSSAndMedian)->Arg(96)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_DecomposeNMFAndNNFilter)->Arg(64)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_EffectsPhaseAndPitch)->Arg(4096)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_EffectsEditingAndEmphasis)->Arg(8192)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_EffectsHPSSAudio)->Arg(4096)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_SegmentSimilarity)->Arg(96)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_SegmentPathAndClustering)->Arg(96)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_SequenceTransitionsAndViterbi)->Arg(256)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_SequenceAlignment)->Arg(96)->Unit(benchmark::kMillisecond); diff --git a/benchmarks/beat_benchmark.cpp b/benchmarks/beat_benchmark.cpp new file mode 100644 index 0000000..e656435 --- /dev/null +++ b/benchmarks/beat_benchmark.cpp @@ -0,0 +1,298 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr librosa::Real kSampleRate = 22050.0; +constexpr int kHopLength = 512; +constexpr int kNFFT = 2048; + +librosa::ArrayXr make_onset_envelope(Eigen::Index n_frames, int period = 22) { + librosa::ArrayXr envelope(n_frames); + + for (Eigen::Index i = 0; i < n_frames; ++i) { + const bool beat = (i % period) == 0; + const librosa::Real slow_mod = + 0.1 * std::sin(2.0 * librosa::constants::PI * static_cast(i) / 97.0); + envelope(i) = beat ? 1.0 + slow_mod : 0.03; + } + + return envelope; +} + +librosa::ArrayXr make_click_track(int seconds, int period_frames = 22) { + const Eigen::Index n_samples = static_cast(seconds * kSampleRate); + librosa::ArrayXr y = librosa::ArrayXr::Zero(n_samples); + const Eigen::Index hop = kHopLength; + const Eigen::Index n_frames = n_samples / hop; + + for (Eigen::Index frame = 0; frame < n_frames; frame += period_frames) { + const Eigen::Index sample = frame * hop; + if (sample < n_samples) { + y(sample) = 1.0; + } + } + + return y; +} + +const librosa::ArrayXr& real_audio() { + static const librosa::ArrayXr audio = [] { + const char* path = std::getenv("LIBROSA_BENCH_AUDIO"); + if (path == nullptr || std::string(path).empty()) { + throw std::runtime_error("set LIBROSA_BENCH_AUDIO to enable real-audio benchmarks"); + } + + auto data = librosa::load(path, kSampleRate, true); + return data.mono(); + }(); + + return audio; +} + +bool has_real_audio_path() { + const char* path = std::getenv("LIBROSA_BENCH_AUDIO"); + return path != nullptr && !std::string(path).empty(); +} + +const char* real_audio_path() { + const char* path = std::getenv("LIBROSA_BENCH_AUDIO"); + if (path == nullptr || std::string(path).empty()) { + throw std::runtime_error("set LIBROSA_BENCH_AUDIO to enable real-audio benchmarks"); + } + return path; +} + +void BM_OnsetStrengthSyntheticAudio(benchmark::State& state) { + const auto y = make_click_track(static_cast(state.range(0))); + + for (auto _ : state) { + auto envelope = librosa::onset::onset_strength(y, kSampleRate, kNFFT, kHopLength); + benchmark::DoNotOptimize(envelope.data()); + benchmark::DoNotOptimize(envelope.size()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_TempoEnvelope(benchmark::State& state) { + const auto envelope = make_onset_envelope(state.range(0)); + + for (auto _ : state) { + auto bpm = librosa::beat::tempo(envelope, kSampleRate, kHopLength); + benchmark::DoNotOptimize(bpm); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +void BM_BeatTrackFixedTempoEnvelope(benchmark::State& state) { + const auto envelope = make_onset_envelope(state.range(0)); + + for (auto _ : state) { + auto result = librosa::beat::beat_track( + envelope, kSampleRate, kHopLength, 120.0, 100.0, true, 120.0); + benchmark::DoNotOptimize(result.first); + benchmark::DoNotOptimize(result.second.data()); + benchmark::DoNotOptimize(result.second.size()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +void BM_BeatTrackEstimatedTempoEnvelope(benchmark::State& state) { + const auto envelope = make_onset_envelope(state.range(0)); + + for (auto _ : state) { + auto result = librosa::beat::beat_track(envelope, kSampleRate, kHopLength); + benchmark::DoNotOptimize(result.first); + benchmark::DoNotOptimize(result.second.data()); + benchmark::DoNotOptimize(result.second.size()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +void BM_OnsetStrengthRealAudio(benchmark::State& state) { + const librosa::ArrayXr* y = nullptr; + + state.PauseTiming(); + try { + y = &real_audio(); + } catch (const std::exception& e) { + state.SkipWithError(e.what()); + return; + } + state.ResumeTiming(); + + for (auto _ : state) { + const auto start = std::chrono::steady_clock::now(); + auto envelope = librosa::onset::onset_strength(*y, kSampleRate, kNFFT, kHopLength); + const auto end = std::chrono::steady_clock::now(); + state.SetIterationTime(std::chrono::duration(end - start).count()); + benchmark::DoNotOptimize(envelope.data()); + benchmark::DoNotOptimize(envelope.size()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * y->size()); +} + +void BM_LoadRealAudioDefaultSampleRate(benchmark::State& state) { + const char* path = nullptr; + + try { + path = real_audio_path(); + } catch (const std::exception& e) { + state.SkipWithError(e.what()); + return; + } + + for (auto _ : state) { + auto audio = librosa::load(path, kSampleRate, true); + benchmark::DoNotOptimize(audio.samples.data()); + benchmark::DoNotOptimize(audio.samples.size()); + benchmark::DoNotOptimize(audio.sample_rate); + benchmark::ClobberMemory(); + } +} + +void BM_LoadRealAudioNativeSampleRate(benchmark::State& state) { + const char* path = nullptr; + + try { + path = real_audio_path(); + } catch (const std::exception& e) { + state.SkipWithError(e.what()); + return; + } + + for (auto _ : state) { + auto audio = librosa::load(path, std::nullopt, true); + benchmark::DoNotOptimize(audio.samples.data()); + benchmark::DoNotOptimize(audio.samples.size()); + benchmark::DoNotOptimize(audio.sample_rate); + benchmark::ClobberMemory(); + } +} + +void BM_TempoRealAudioEnvelope(benchmark::State& state) { + librosa::ArrayXr envelope; + + state.PauseTiming(); + try { + envelope = librosa::onset::onset_strength(real_audio(), kSampleRate, kNFFT, kHopLength); + } catch (const std::exception& e) { + state.SkipWithError(e.what()); + return; + } + state.ResumeTiming(); + + for (auto _ : state) { + const auto start = std::chrono::steady_clock::now(); + auto bpm = librosa::beat::tempo(envelope, kSampleRate, kHopLength); + const auto end = std::chrono::steady_clock::now(); + state.SetIterationTime(std::chrono::duration(end - start).count()); + benchmark::DoNotOptimize(bpm); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +void BM_BeatTrackRealAudio(benchmark::State& state) { + const librosa::ArrayXr* y = nullptr; + + state.PauseTiming(); + try { + y = &real_audio(); + } catch (const std::exception& e) { + state.SkipWithError(e.what()); + return; + } + state.ResumeTiming(); + + for (auto _ : state) { + const auto start = std::chrono::steady_clock::now(); + auto result = librosa::beat::beat_track_audio(*y, kSampleRate, kHopLength); + const auto end = std::chrono::steady_clock::now(); + state.SetIterationTime(std::chrono::duration(end - start).count()); + benchmark::DoNotOptimize(result.first); + benchmark::DoNotOptimize(result.second.data()); + benchmark::DoNotOptimize(result.second.size()); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * y->size()); +} + +bool register_real_audio_benchmarks() { + if (!has_real_audio_path()) { + return false; + } + + benchmark::RegisterBenchmark("BM_OnsetStrengthRealAudio", &BM_OnsetStrengthRealAudio) + ->Iterations(1) + ->UseManualTime() + ->Unit(benchmark::kSecond); + + benchmark::RegisterBenchmark("BM_LoadRealAudioDefaultSampleRate", + &BM_LoadRealAudioDefaultSampleRate) + ->Iterations(1) + ->Unit(benchmark::kSecond); + + benchmark::RegisterBenchmark("BM_LoadRealAudioNativeSampleRate", + &BM_LoadRealAudioNativeSampleRate) + ->Iterations(1) + ->Unit(benchmark::kSecond); + + benchmark::RegisterBenchmark("BM_TempoRealAudioEnvelope", &BM_TempoRealAudioEnvelope) + ->Iterations(1) + ->UseManualTime() + ->Unit(benchmark::kSecond); + + benchmark::RegisterBenchmark("BM_BeatTrackRealAudio", &BM_BeatTrackRealAudio) + ->Iterations(1) + ->UseManualTime() + ->Unit(benchmark::kSecond); + + return true; +} + +const bool kRealAudioBenchmarksRegistered = register_real_audio_benchmarks(); + +} // namespace + +BENCHMARK(BM_OnsetStrengthSyntheticAudio) + ->Arg(10) + ->Arg(30) + ->Unit(benchmark::kMillisecond); + +BENCHMARK(BM_TempoEnvelope) + ->Arg(1024) + ->Arg(4096) + ->Arg(16384) + ->Unit(benchmark::kMillisecond); + +BENCHMARK(BM_BeatTrackFixedTempoEnvelope) + ->Arg(1024) + ->Arg(4096) + ->Arg(16384) + ->Unit(benchmark::kMillisecond); + +BENCHMARK(BM_BeatTrackEstimatedTempoEnvelope) + ->Arg(1024) + ->Arg(4096) + ->Arg(16384) + ->Unit(benchmark::kMillisecond); diff --git a/benchmarks/benchmark_helpers.hpp b/benchmarks/benchmark_helpers.hpp new file mode 100644 index 0000000..d37e2ce --- /dev/null +++ b/benchmarks/benchmark_helpers.hpp @@ -0,0 +1,177 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace librosa_bench { + +constexpr librosa::Real kSampleRate = 22050.0; +constexpr int kHopLength = 512; +constexpr int kNFFT = 2048; + +inline librosa::ArrayXr make_tone(librosa::Real frequency, + Eigen::Index n_samples, + librosa::Real sr = kSampleRate) { + librosa::ArrayXr y(n_samples); + for (Eigen::Index i = 0; i < n_samples; ++i) { + y(i) = std::sin(2.0 * librosa::constants::PI * frequency * + static_cast(i) / sr); + } + return y; +} + +inline librosa::ArrayXr make_chirp(Eigen::Index n_samples, + librosa::Real sr = kSampleRate) { + return librosa::chirp(220.0, 1760.0, sr, static_cast(n_samples), + std::nullopt, true); +} + +inline librosa::ArrayXr make_click_track(Eigen::Index n_samples, + int period_frames = 22, + int hop_length = kHopLength) { + librosa::ArrayXr y = librosa::ArrayXr::Zero(n_samples); + const Eigen::Index n_frames = n_samples / hop_length; + + for (Eigen::Index frame = 0; frame < n_frames; frame += period_frames) { + const Eigen::Index sample = frame * hop_length; + if (sample < n_samples) { + y(sample) = 1.0; + for (Eigen::Index i = 1; i < 64 && sample + i < n_samples; ++i) { + y(sample + i) = std::exp(-static_cast(i) / 10.0); + } + } + } + + return y; +} + +inline librosa::ArrayXr make_onset_envelope(Eigen::Index n_frames, + int period = 22) { + librosa::ArrayXr envelope(n_frames); + + for (Eigen::Index i = 0; i < n_frames; ++i) { + const bool beat = (i % period) == 0; + const librosa::Real slow_mod = + 0.1 * std::sin(2.0 * librosa::constants::PI * + static_cast(i) / 97.0); + envelope(i) = beat ? 1.0 + slow_mod : 0.03; + } + + return envelope; +} + +inline librosa::ArrayXr make_random_vector(Eigen::Index n) { + librosa::ArrayXr x(n); + for (Eigen::Index i = 0; i < n; ++i) { + const librosa::Real a = std::sin(static_cast(i) * 0.17); + const librosa::Real b = std::cos(static_cast(i) * 0.031); + x(i) = 0.6 * a + 0.4 * b; + } + return x; +} + +inline librosa::ArrayXXr make_feature_matrix(Eigen::Index rows, + Eigen::Index cols) { + librosa::ArrayXXr x(rows, cols); + for (Eigen::Index r = 0; r < rows; ++r) { + for (Eigen::Index c = 0; c < cols; ++c) { + x(r, c) = std::sin(0.03 * static_cast((r + 1) * (c + 1))) + + 0.5 * std::cos(0.11 * static_cast(r + c)); + } + } + return x; +} + +inline librosa::ArrayXXr make_positive_matrix(Eigen::Index rows, + Eigen::Index cols) { + return make_feature_matrix(rows, cols).abs() + 0.01; +} + +inline librosa::ArrayXXc make_complex_matrix(Eigen::Index rows, + Eigen::Index cols) { + librosa::ArrayXXc x(rows, cols); + for (Eigen::Index r = 0; r < rows; ++r) { + for (Eigen::Index c = 0; c < cols; ++c) { + const librosa::Real mag = + 0.1 + std::abs(std::sin(0.07 * static_cast((r + 1) * (c + 1)))); + const librosa::Real phase = 0.013 * static_cast(r * c); + x(r, c) = std::polar(mag, phase); + } + } + return x; +} + +inline librosa::SparseMatrixXr make_neighbor_graph(Eigen::Index n) { + std::vector> triplets; + triplets.reserve(static_cast(n) * 2); + for (Eigen::Index i = 0; i < n; ++i) { + if (i > 0) { + triplets.emplace_back(i, i - 1, 1.0); + } + if (i + 1 < n) { + triplets.emplace_back(i, i + 1, 1.0); + } + } + + librosa::SparseMatrixXr rec(n, n); + rec.setFromTriplets(triplets.begin(), triplets.end()); + return rec; +} + +inline librosa::ArrayXXr make_recurrence(Eigen::Index n) { + librosa::ArrayXXr rec = librosa::ArrayXXr::Zero(n, n); + for (Eigen::Index i = 0; i < n; ++i) { + rec(i, i) = 1.0; + if (i >= 4) { + rec(i, i - 4) = 1.0; + rec(i - 4, i) = 1.0; + } + } + return rec; +} + +template +inline void consume_eigen(const Eigen::DenseBase& value) { + const auto& derived = value.derived(); + benchmark::DoNotOptimize(derived.data()); + benchmark::DoNotOptimize(derived.rows()); + benchmark::DoNotOptimize(derived.cols()); + benchmark::ClobberMemory(); +} + +template +inline void consume_sparse( + const Eigen::SparseMatrix& value) { + benchmark::DoNotOptimize(value.valuePtr()); + benchmark::DoNotOptimize(value.rows()); + benchmark::DoNotOptimize(value.cols()); + benchmark::DoNotOptimize(value.nonZeros()); + benchmark::ClobberMemory(); +} + +template +inline void consume_vector(const std::vector& value) { + benchmark::DoNotOptimize(value.data()); + benchmark::DoNotOptimize(value.size()); + benchmark::ClobberMemory(); +} + +template +inline void consume_pair(const std::pair& value) { + benchmark::DoNotOptimize(value.first); + benchmark::DoNotOptimize(value.second); + benchmark::ClobberMemory(); +} + +} // namespace librosa_bench diff --git a/benchmarks/core_benchmark.cpp b/benchmarks/core_benchmark.cpp new file mode 100644 index 0000000..b3ccb9f --- /dev/null +++ b/benchmarks/core_benchmark.cpp @@ -0,0 +1,629 @@ +#include "benchmark_helpers.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace librosa_bench; + +bool has_real_audio_path() { + const char* path = std::getenv("LIBROSA_BENCH_AUDIO"); + return path != nullptr && !std::string(path).empty(); +} + +const char* real_audio_path() { + const char* path = std::getenv("LIBROSA_BENCH_AUDIO"); + if (path == nullptr || std::string(path).empty()) { + throw std::runtime_error("set LIBROSA_BENCH_AUDIO to enable real-audio benchmarks"); + } + return path; +} + +void BM_AudioToMono(benchmark::State& state) { + librosa::ArrayXXr stereo(2, state.range(0)); + stereo.row(0) = make_tone(440.0, state.range(0)).transpose(); + stereo.row(1) = make_tone(660.0, state.range(0)).transpose(); + + for (auto _ : state) { + auto mono = librosa::to_mono(stereo); + consume_eigen(mono); + } + + state.SetItemsProcessed(state.iterations() * stereo.cols()); +} + +void BM_AudioResampleLinear(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + + for (auto _ : state) { + auto resampled = librosa::resample(y, kSampleRate, 11025.0, "linear"); + consume_eigen(resampled); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_AudioResampleFFT(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + + for (auto _ : state) { + auto resampled = librosa::resample(y, kSampleRate, 11025.0, "fft"); + consume_eigen(resampled); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_AudioResampleKaiserHQ(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + + for (auto _ : state) { + auto resampled = librosa::resample(y, kSampleRate, 11025.0, "kaiser_hq"); + consume_eigen(resampled); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_AudioAutocorrelate(benchmark::State& state) { + const auto y = make_random_vector(state.range(0)); + const auto matrix = make_feature_matrix(8, state.range(0) / 8); + + for (auto _ : state) { + auto ac = librosa::autocorrelate(y, 512); + auto ac2 = librosa::autocorrelate(matrix, 32, -1); + consume_eigen(ac); + consume_eigen(ac2); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_AudioLPC(benchmark::State& state) { + const auto y = make_tone(220.0, state.range(0)); + const auto matrix = make_feature_matrix(4, state.range(0) / 4); + + for (auto _ : state) { + auto coeffs = librosa::lpc(y, 16); + auto coeffs2 = librosa::lpc(matrix, 8, -1); + consume_eigen(coeffs); + consume_eigen(coeffs2); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_AudioZeroCrossings(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto matrix = make_feature_matrix(4, state.range(0) / 4); + + for (auto _ : state) { + auto crossings = librosa::zero_crossings(y); + auto crossings2 = librosa::zero_crossings(matrix, 1e-10, std::nullopt, true, true, -1); + consume_eigen(crossings); + consume_eigen(crossings2); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_AudioSynthesisAndMuLaw(benchmark::State& state) { + const Eigen::Index n = state.range(0); + librosa::ArrayXr times(8); + times.setLinSpaced(0.0, 1.0); + std::vector frames = {0, 8, 16, 24, 32, 40, 48, 56}; + + for (auto _ : state) { + auto clicks = librosa::clicks(times, kSampleRate, 1000.0, 0.05, static_cast(n)); + auto frame_clicks = librosa::clicks_frames(frames, kSampleRate, kHopLength, + 1000.0, 0.05, static_cast(n)); + auto tone = librosa::tone(440.0, kSampleRate, static_cast(n)); + auto chirp = librosa::chirp(110.0, 1760.0, kSampleRate, static_cast(n)); + auto compressed = librosa::mu_compress(tone); + auto expanded = librosa::mu_expand(compressed); + consume_eigen(clicks); + consume_eigen(frame_clicks); + consume_eigen(tone); + consume_eigen(chirp); + consume_eigen(compressed); + consume_eigen(expanded); + } + + state.SetItemsProcessed(state.iterations() * n); +} + +void BM_AudioDuration(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto D = librosa::stft(y, 1024, 256); + const auto S = librosa::magnitude(D); + + for (auto _ : state) { + auto from_audio = librosa::get_duration(y, kSampleRate); + auto from_spectrogram = librosa::get_duration(S, kSampleRate, 256, 1024, true); + benchmark::DoNotOptimize(from_audio); + benchmark::DoNotOptimize(from_spectrogram); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_AudioFileMetadataRealAudio(benchmark::State& state) { + const char* path = nullptr; + + try { + path = real_audio_path(); + } catch (const std::exception& e) { + state.SkipWithError(e.what()); + return; + } + + for (auto _ : state) { + auto duration = librosa::get_duration(path); + auto sample_rate = librosa::get_samplerate(path); + auto info = librosa::get_audio_info(path); + benchmark::DoNotOptimize(duration); + benchmark::DoNotOptimize(sample_rate); + benchmark::DoNotOptimize(info.samples); + benchmark::DoNotOptimize(info.sample_rate); + benchmark::DoNotOptimize(info.channels); + benchmark::DoNotOptimize(info.duration); + } +} + +void BM_ConvertFrameSampleTime(benchmark::State& state) { + const auto values = librosa::ArrayXr::LinSpaced(state.range(0), 0.0, + static_cast(state.range(0) - 1)); + const auto matrix = make_feature_matrix(8, state.range(0)); + + for (auto _ : state) { + auto samples = librosa::frames_to_samples(values, kHopLength, kNFFT); + auto frames = librosa::samples_to_frames(samples, kHopLength, kNFFT); + auto times = librosa::frames_to_time(frames, kSampleRate, kHopLength, kNFFT); + auto frames2 = librosa::time_to_frames(times, kSampleRate, kHopLength, kNFFT); + auto samples2 = librosa::time_to_samples(times, kSampleRate); + auto times2 = librosa::samples_to_time(samples2, kSampleRate); + auto block_frames = librosa::blocks_to_frames(values, 4); + auto block_samples = librosa::blocks_to_samples(values, 4, kHopLength); + auto block_times = librosa::blocks_to_time(values, 4, kHopLength, kSampleRate); + auto samples_like = librosa::samples_like(matrix, kHopLength, std::nullopt, -1); + auto times_like = librosa::times_like(matrix, kSampleRate, kHopLength, std::nullopt, -1); + + benchmark::DoNotOptimize(librosa::frames_to_samples(Eigen::Index{128}, kHopLength, kNFFT)); + benchmark::DoNotOptimize(librosa::samples_to_frames(Eigen::Index{65536}, kHopLength, kNFFT)); + benchmark::DoNotOptimize(librosa::frames_to_time(Eigen::Index{128}, kSampleRate, kHopLength, kNFFT)); + benchmark::DoNotOptimize(librosa::time_to_frames(1.25, kSampleRate, kHopLength, kNFFT)); + benchmark::DoNotOptimize(librosa::time_to_samples(1.25, kSampleRate)); + benchmark::DoNotOptimize(librosa::samples_to_time(Eigen::Index{65536}, kSampleRate)); + benchmark::DoNotOptimize(librosa::blocks_to_frames(Eigen::Index{16}, 4)); + benchmark::DoNotOptimize(librosa::blocks_to_samples(Eigen::Index{16}, 4, kHopLength)); + benchmark::DoNotOptimize(librosa::blocks_to_time(Eigen::Index{16}, 4, kHopLength, kSampleRate)); + + consume_eigen(samples); + consume_eigen(frames); + consume_eigen(times); + consume_eigen(frames2); + consume_eigen(samples2); + consume_eigen(times2); + consume_eigen(block_frames); + consume_eigen(block_samples); + consume_eigen(block_times); + consume_eigen(samples_like); + consume_eigen(times_like); + } + + state.SetItemsProcessed(state.iterations() * values.size()); +} + +void BM_ConvertPitchScales(benchmark::State& state) { + const auto midi = librosa::ArrayXr::LinSpaced(state.range(0), 24.0, 96.0); + const auto freqs = librosa::ArrayXr::LinSpaced(state.range(0), 55.0, 1760.0); + std::vector notes = {"C2", "E2", "G2", "B2", "C3", "E3", + "G3", "B3", "C4", "E4", "G4", "B4"}; + + for (auto _ : state) { + auto hz = librosa::midi_to_hz(midi); + auto midi_back = librosa::hz_to_midi(freqs); + auto note_midi = librosa::note_to_midi(notes); + auto note_hz = librosa::note_to_hz(notes); + auto midi_notes = librosa::midi_to_note(midi, true, false, false); + auto hz_notes = librosa::hz_to_note(freqs, true, false, false); + auto mel = librosa::hz_to_mel(freqs); + auto hz_back = librosa::mel_to_hz(mel); + auto octs = librosa::hz_to_octs(freqs); + auto hz_from_octs = librosa::octs_to_hz(octs); + auto tuning = librosa::A4_to_tuning(freqs); + auto a4 = librosa::tuning_to_A4(tuning); + + benchmark::DoNotOptimize(librosa::midi_to_hz(69.0)); + benchmark::DoNotOptimize(librosa::hz_to_midi(440.0)); + benchmark::DoNotOptimize(librosa::note_to_midi("A4")); + benchmark::DoNotOptimize(librosa::note_to_hz("A4")); + benchmark::DoNotOptimize(librosa::midi_to_note(69.0, true, false, false)); + benchmark::DoNotOptimize(librosa::hz_to_note(440.0, true, false, false)); + benchmark::DoNotOptimize(librosa::hz_to_mel(1000.0)); + benchmark::DoNotOptimize(librosa::mel_to_hz(100.0)); + benchmark::DoNotOptimize(librosa::hz_to_octs(440.0)); + benchmark::DoNotOptimize(librosa::octs_to_hz(4.0)); + benchmark::DoNotOptimize(librosa::A4_to_tuning(440.0)); + benchmark::DoNotOptimize(librosa::tuning_to_A4(0.0)); + + consume_eigen(hz); + consume_eigen(midi_back); + consume_eigen(note_midi); + consume_eigen(note_hz); + consume_vector(midi_notes); + consume_vector(hz_notes); + consume_eigen(mel); + consume_eigen(hz_back); + consume_eigen(octs); + consume_eigen(hz_from_octs); + consume_eigen(tuning); + consume_eigen(a4); + } + + state.SetItemsProcessed(state.iterations() * midi.size()); +} + +void BM_ConvertFrequencyGridsAndWeighting(benchmark::State& state) { + const Eigen::Index n = state.range(0); + const auto freqs = librosa::ArrayXr::LinSpaced(n, 20.0, 20000.0); + + for (auto _ : state) { + auto fft_freqs = librosa::fft_frequencies(kSampleRate, kNFFT); + auto cqt_freqs = librosa::cqt_frequencies(static_cast(n), librosa::note_to_hz("C1")); + auto mel_freqs = librosa::mel_frequencies(static_cast(n), 0.0, kSampleRate / 2.0); + auto tempo_freqs = librosa::tempo_frequencies(static_cast(n), kHopLength, kSampleRate); + auto fourier_tempo = librosa::fourier_tempo_frequencies(kSampleRate, 384, kHopLength); + auto a = librosa::A_weighting(freqs); + auto b = librosa::B_weighting(freqs); + auto c = librosa::C_weighting(freqs); + auto d = librosa::D_weighting(freqs); + auto z = librosa::Z_weighting(freqs); + auto combined = librosa::frequency_weighting(freqs, librosa::WeightType::A); + + benchmark::DoNotOptimize(librosa::A_weighting(1000.0)); + benchmark::DoNotOptimize(librosa::B_weighting(1000.0)); + benchmark::DoNotOptimize(librosa::C_weighting(1000.0)); + benchmark::DoNotOptimize(librosa::D_weighting(1000.0)); + benchmark::DoNotOptimize(librosa::Z_weighting(1000.0)); + + consume_eigen(fft_freqs); + consume_eigen(cqt_freqs); + consume_eigen(mel_freqs); + consume_eigen(tempo_freqs); + consume_eigen(fourier_tempo); + consume_eigen(a); + consume_eigen(b); + consume_eigen(c); + consume_eigen(d); + consume_eigen(z); + consume_eigen(combined); + } + + state.SetItemsProcessed(state.iterations() * n); +} + +void BM_ConvertNotation(benchmark::State& state) { + for (auto _ : state) { + auto thaat = librosa::thaat_to_degrees("bilaval"); + auto mela = librosa::mela_to_degrees(29); + auto mela_name = librosa::mela_to_degrees("dheerasankarabharanam"); + auto svara = librosa::mela_to_svara(29, true, false); + auto svara_name = librosa::mela_to_svara("dheerasankarabharanam", true, false); + auto mela_list = librosa::list_mela(); + auto thaat_list = librosa::list_thaat(); + auto key_degrees = librosa::key_to_degrees("C:maj"); + auto key_notes = librosa::key_to_notes("C:maj", false); + auto fifth = librosa::fifths_to_note("C", 1, false); + auto fjs = librosa::interval_to_fjs(3.0 / 2.0, "C", 65.0 / 63.0, false); + auto svara_h = librosa::midi_to_svara_h(67.0, 60.0, true, true, false); + auto svara_c = librosa::midi_to_svara_c(67.0, 60.0, 29, true, false); + auto svara_c_name = librosa::midi_to_svara_c(67.0, 60.0, "dheerasankarabharanam", true, false); + auto hz_svara_h = librosa::hz_to_svara_h(440.0, 261.63, true, true, false); + auto hz_svara_c = librosa::hz_to_svara_c(440.0, 261.63, 29, true, false); + auto hz_svara_c_name = librosa::hz_to_svara_c(440.0, 261.63, "dheerasankarabharanam", true, false); + auto note_svara_h = librosa::note_to_svara_h("G4", "C4", true, true, false); + auto note_svara_c = librosa::note_to_svara_c("G4", "C4", 29, true, false); + auto note_svara_c_name = librosa::note_to_svara_c("G4", "C4", "dheerasankarabharanam", true, false); + auto hz_fjs = librosa::hz_to_fjs(660.0, 440.0, "A", false); + + consume_vector(thaat); + consume_vector(mela); + consume_vector(mela_name); + consume_vector(svara); + consume_vector(svara_name); + benchmark::DoNotOptimize(mela_list.size()); + consume_vector(thaat_list); + consume_vector(key_degrees); + consume_vector(key_notes); + benchmark::DoNotOptimize(fifth); + benchmark::DoNotOptimize(fjs); + benchmark::DoNotOptimize(svara_h); + benchmark::DoNotOptimize(svara_c); + benchmark::DoNotOptimize(svara_c_name); + benchmark::DoNotOptimize(hz_svara_h); + benchmark::DoNotOptimize(hz_svara_c); + benchmark::DoNotOptimize(hz_svara_c_name); + benchmark::DoNotOptimize(note_svara_h); + benchmark::DoNotOptimize(note_svara_c); + benchmark::DoNotOptimize(note_svara_c_name); + benchmark::DoNotOptimize(hz_fjs); + } +} + +void BM_Intervals(benchmark::State& state) { + for (auto _ : state) { + auto pythagorean = librosa::pythagorean_intervals(12); + auto plimit = librosa::plimit_intervals({3, 5, 7}, 12); + auto equal = librosa::interval_frequencies(48, 55.0, "equal", 12); + auto pyth = librosa::interval_frequencies(48, 55.0, "pythagorean", 12); + auto ji = librosa::interval_frequencies(48, 55.0, "ji5", 12); + auto custom = librosa::interval_frequencies(48, 55.0, pythagorean); + + consume_eigen(pythagorean); + consume_eigen(plimit); + consume_eigen(equal); + consume_eigen(pyth); + consume_eigen(ji); + consume_eigen(custom); + } +} + +void BM_SpectrumSTFTISTFT(benchmark::State& state) { + const auto y = make_random_vector(state.range(0)); + const auto window = librosa::get_window(librosa::WindowType::Hann, kNFFT); + + for (auto _ : state) { + auto D = librosa::stft(y, kNFFT, kHopLength); + auto D_window = librosa::stft(y, kNFFT, kHopLength, window); + auto reconstructed = librosa::istft(D, kHopLength, std::nullopt, kNFFT, + librosa::WindowType::Hann, true, + static_cast(y.size())); + consume_eigen(D); + consume_eigen(D_window); + consume_eigen(reconstructed); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_SpectrumMagnitudePhaseAndDb(benchmark::State& state) { + const auto y = make_random_vector(state.range(0)); + const auto D = librosa::stft(y, kNFFT, kHopLength); + const librosa::ArrayXXr S = (librosa::magnitude(D).square() + 1e-6).eval(); + const auto freqs = librosa::fft_frequencies(kSampleRate, kNFFT); + + for (auto _ : state) { + auto magphase = librosa::magphase(D); + auto mag = librosa::magnitude(D); + auto ph = librosa::phase(D); + auto power_db = librosa::power_to_db(S); + auto power_db_fn = librosa::power_to_db(S, [](const librosa::ArrayXXr& x) { + return x.maxCoeff(); + }); + auto power = librosa::db_to_power(power_db); + auto amp_db = librosa::amplitude_to_db(mag); + auto amp_db_fn = librosa::amplitude_to_db(mag, [](const librosa::ArrayXXr& x) { + return x.maxCoeff(); + }); + auto amp = librosa::db_to_amplitude(amp_db); + auto weighted = librosa::perceptual_weighting(S, freqs); + + benchmark::DoNotOptimize(librosa::power_to_db(1.0)); + benchmark::DoNotOptimize(librosa::db_to_power(0.0)); + benchmark::DoNotOptimize(librosa::amplitude_to_db(1.0)); + benchmark::DoNotOptimize(librosa::db_to_amplitude(0.0)); + + consume_eigen(magphase.first); + consume_eigen(magphase.second); + consume_eigen(mag); + consume_eigen(ph); + consume_eigen(power_db); + consume_eigen(power_db_fn); + consume_eigen(power); + consume_eigen(amp_db); + consume_eigen(amp_db_fn); + consume_eigen(amp); + consume_eigen(weighted); + } + + state.SetItemsProcessed(state.iterations() * D.size()); +} + +void BM_SpectrumPhaseVocoderAndGriffinLim(benchmark::State& state) { + const auto y = make_random_vector(state.range(0)); + const auto D = librosa::stft(y, 1024, 256); + const auto S = librosa::magnitude(D); + + for (auto _ : state) { + auto stretched = librosa::phase_vocoder(D, 1.5, 256, 1024); + auto reconstructed = librosa::griffinlim(S, 4, 256, std::nullopt, 1024, + librosa::WindowType::Hann, true, + static_cast(y.size()), + librosa::PadMode::Constant, 0.99, + "random", 123); + consume_eigen(stretched); + consume_eigen(reconstructed); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_SpectrumPCENWindowIIRTReassignedFMT(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto D = librosa::stft(y, 1024, 256); + const librosa::ArrayXXr S = (librosa::magnitude(D).square() + 1e-6).eval(); + const auto window = librosa::get_window("hann", 1024); + + for (auto _ : state) { + auto pcen = librosa::pcen(S, kSampleRate, 256); + auto window_enum = librosa::get_window(librosa::WindowType::Hann, 1024); + auto window_string = librosa::get_window("hann", 1024); + auto sumsquare = librosa::window_sumsquare(window, static_cast(S.cols()), 256, 1024); + auto iirt = librosa::iirt(y, kSampleRate, 1024, 256); + auto reassigned = librosa::reassigned_spectrogram(y, kSampleRate, 1024, 256, std::nullopt); + auto fmt = librosa::fmt(y, 0.5, 128, "linear"); + + consume_eigen(pcen); + consume_eigen(window_enum); + consume_eigen(window_string); + consume_eigen(sumsquare); + consume_eigen(iirt); + consume_eigen(std::get<0>(reassigned)); + consume_eigen(std::get<1>(reassigned)); + consume_eigen(std::get<2>(reassigned)); + consume_eigen(fmt); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_ConstantQForward(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + + for (auto _ : state) { + auto C = librosa::cqt(y, kSampleRate, kHopLength, std::nullopt, 24, 12, 0.0); + auto V = librosa::vqt(y, kSampleRate, kHopLength, std::nullopt, 24, std::nullopt, 12, 0.0); + auto P = librosa::pseudo_cqt(y, kSampleRate, kHopLength, std::nullopt, 24, 12, 0.0); + auto H = librosa::hybrid_cqt(y, kSampleRate, kHopLength, std::nullopt, 24, 12, 0.0); + consume_eigen(C); + consume_eigen(V); + consume_eigen(P); + consume_eigen(H); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_ConstantQInverse(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto C = librosa::cqt(y, kSampleRate, kHopLength, std::nullopt, 24, 12, 0.0); + const auto magnitude = C.abs(); + + for (auto _ : state) { + auto inverted = librosa::icqt(C, kSampleRate, kHopLength, std::nullopt, 12, 0.0, + 1.0, 1.0, 0.01, librosa::WindowType::Hann, + true, static_cast(y.size())); + auto griffinlim = librosa::griffinlim_cqt(magnitude, 2, kSampleRate, kHopLength, + std::nullopt, 12, 0.0, 1.0, 1.0, + 0.01, librosa::WindowType::Hann, + true, librosa::PadMode::Constant, + static_cast(y.size()), 0.99, + "random", 123); + consume_eigen(inverted); + consume_eigen(griffinlim); + } + + state.SetItemsProcessed(state.iterations() * C.size()); +} + +void BM_PitchTuningAndTracking(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto D = librosa::stft(y, 1024, 256); + const auto S = librosa::magnitude(D); + const auto freqs = librosa::cqt_frequencies(36, librosa::note_to_hz("C2")); + + for (auto _ : state) { + auto tuning = librosa::pitch_tuning(freqs); + auto estimate_audio = librosa::estimate_tuning(y, kSampleRate, 1024, 0.05, 12, 100.0, 1000.0); + auto estimate_spec = librosa::estimate_tuning(S, kSampleRate, 1024, 0.05, 12, 100.0, 1000.0); + auto pip_audio = librosa::piptrack(y, kSampleRate, 1024, 256, 100.0, 1000.0); + auto pip_spec = librosa::piptrack(S, kSampleRate, 1024, 256, 100.0, 1000.0); + auto yin = librosa::yin(y, 100.0, 1000.0, kSampleRate, 1024, 256); + auto pyin = librosa::pyin(y, 100.0, 1000.0, kSampleRate, 1024, 256, + 32, 2.0, 18.0, 2.0, 0.1); + + benchmark::DoNotOptimize(tuning); + benchmark::DoNotOptimize(estimate_audio); + benchmark::DoNotOptimize(estimate_spec); + consume_eigen(pip_audio.first); + consume_eigen(pip_audio.second); + consume_eigen(pip_spec.first); + consume_eigen(pip_spec.second); + consume_eigen(yin); + consume_eigen(pyin.f0); + consume_eigen(pyin.voiced_flag); + consume_eigen(pyin.voiced_prob); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_HarmonicCore(benchmark::State& state) { + const auto S = make_positive_matrix(state.range(0), 64); + const auto freqs = librosa::ArrayXr::LinSpaced(state.range(0), 55.0, 4000.0); + const auto spectrum = S.col(0).eval(); + const auto f0 = librosa::ArrayXr::Constant(S.cols(), 220.0); + const std::vector harmonics = {1.0, 2.0, 3.0, 4.0}; + const std::vector weights = {1.0, 0.5, 0.25, 0.125}; + + for (auto _ : state) { + auto interp2d = librosa::core::interp_harmonics(S, freqs, harmonics); + auto interp1d = librosa::core::interp_harmonics(spectrum, freqs, harmonics); + auto f0_harmonics = librosa::core::f0_harmonics(S, f0, freqs, harmonics); + auto salience = librosa::core::salience(S, freqs, harmonics, weights); + consume_eigen(interp2d); + consume_eigen(interp1d); + consume_eigen(f0_harmonics); + consume_eigen(salience); + } + + state.SetItemsProcessed(state.iterations() * S.size()); +} + +bool register_real_audio_benchmarks() { + if (!has_real_audio_path()) { + return false; + } + + benchmark::RegisterBenchmark("BM_AudioFileMetadataRealAudio", + &BM_AudioFileMetadataRealAudio) + ->Iterations(1) + ->Unit(benchmark::kSecond); + + return true; +} + +const bool kRealAudioBenchmarksRegistered = register_real_audio_benchmarks(); + +} // namespace + +BENCHMARK(BM_AudioToMono)->Arg(22050)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioResampleLinear)->Arg(22050)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioResampleFFT)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioResampleKaiserHQ)->Arg(4096)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioAutocorrelate)->Arg(4096)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioLPC)->Arg(4096)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioZeroCrossings)->Arg(22050)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioSynthesisAndMuLaw)->Arg(22050)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_AudioDuration)->Arg(22050)->Unit(benchmark::kMicrosecond); + +BENCHMARK(BM_ConvertFrameSampleTime)->Arg(4096)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_ConvertPitchScales)->Arg(4096)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_ConvertFrequencyGridsAndWeighting)->Arg(256)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_ConvertNotation)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_Intervals)->Unit(benchmark::kMicrosecond); + +BENCHMARK(BM_SpectrumSTFTISTFT)->Arg(22050)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_SpectrumMagnitudePhaseAndDb)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_SpectrumPhaseVocoderAndGriffinLim)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_SpectrumPCENWindowIIRTReassignedFMT)->Arg(8192)->Unit(benchmark::kMillisecond); + +BENCHMARK(BM_ConstantQForward)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_ConstantQInverse)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_PitchTuningAndTracking)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_HarmonicCore)->Arg(128)->Unit(benchmark::kMicrosecond); diff --git a/benchmarks/feature_benchmark.cpp b/benchmarks/feature_benchmark.cpp new file mode 100644 index 0000000..4b085e0 --- /dev/null +++ b/benchmarks/feature_benchmark.cpp @@ -0,0 +1,239 @@ +#include "benchmark_helpers.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +using namespace librosa_bench; + +using MelSpecFn = librosa::ArrayXXr (*)( + const librosa::ArrayXXr&, librosa::Real, int, int, librosa::Real, + std::optional, bool, bool); +using ChromaStftAudioFn = librosa::ArrayXXr (*)( + const librosa::ArrayXr&, librosa::Real, int, int, int, + std::optional, librosa::Real, librosa::WindowType, bool); +using ChromaStftSpecFn = librosa::ArrayXXr (*)( + const librosa::ArrayXXr&, librosa::Real, int, int, + std::optional, librosa::Real); +using FlatnessSpecFn = librosa::ArrayXXr (*)(const librosa::ArrayXXr&, + librosa::Real, librosa::Real); +using RMSSpecFn = librosa::ArrayXXr (*)(const librosa::ArrayXXr&, int); + +void BM_FeatureMelAndMFCC(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto D = librosa::stft(y, 1024, 256); + const librosa::ArrayXXr S = librosa::magnitude(D).square(); + + for (auto _ : state) { + auto mel_audio = librosa::feature::melspectrogram( + y, kSampleRate, 1024, 256, std::nullopt, librosa::WindowType::Hann, + true, librosa::PadMode::Constant, 2.0, 64); + auto mel_spec = static_cast(&librosa::feature::melspectrogram)( + S, kSampleRate, 1024, 64, 0.0, std::nullopt, false, true); + auto mfcc_audio = librosa::feature::mfcc(y, kSampleRate, 20, 2, true, 0.0, + 1024, 256, 64); + auto mfcc_spec = librosa::feature::mfcc(librosa::power_to_db(mel_spec), 20); + consume_eigen(mel_audio); + consume_eigen(mel_spec); + consume_eigen(mfcc_audio); + consume_eigen(mfcc_spec); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_FeatureChroma(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto D = librosa::stft(y, 2048, kHopLength); + const librosa::ArrayXXr S = librosa::magnitude(D).square(); + const auto C = librosa::cqt(y, kSampleRate, kHopLength, std::nullopt, 36, 12, 0.0); + const auto V = librosa::vqt(y, kSampleRate, kHopLength, std::nullopt, 36, + 0.0, 12, 0.0); + + for (auto _ : state) { + auto stft_audio = static_cast(&librosa::feature::chroma_stft)( + y, kSampleRate, 2048, kHopLength, 12, 0.0, + std::numeric_limits::infinity(), + librosa::WindowType::Hann, true); + auto stft_spec = static_cast(&librosa::feature::chroma_stft)( + S, kSampleRate, 2048, 12, 0.0, + std::numeric_limits::infinity()); + auto cqt_audio = librosa::feature::chroma_cqt( + y, kSampleRate, kHopLength, std::nullopt, + std::numeric_limits::infinity(), 0.0, 0.0, 12, 3, 12); + auto cqt_spec = librosa::feature::chroma_cqt( + C.abs(), std::nullopt, std::numeric_limits::infinity(), + 0.0, 12, 12); + auto cens = librosa::feature::chroma_cens( + y, kSampleRate, kHopLength, std::nullopt, 0.0, 12, 3, 12, 2.0, 9); + auto vqt_audio = librosa::feature::chroma_vqt( + y, kSampleRate, kHopLength, std::nullopt, + std::numeric_limits::infinity(), 0.0, 3, 12, 0.0); + auto vqt_spec = librosa::feature::chroma_vqt( + V.abs(), std::nullopt, std::numeric_limits::infinity(), + 0.0, 12); + + consume_eigen(stft_audio); + consume_eigen(stft_spec); + consume_eigen(cqt_audio); + consume_eigen(cqt_spec); + consume_eigen(cens); + consume_eigen(vqt_audio); + consume_eigen(vqt_spec); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_FeatureSpectralShape(benchmark::State& state) { + const librosa::ArrayXr y = + (make_tone(440.0, state.range(0)) + 0.25 * make_tone(1760.0, state.range(0))).eval(); + const auto D = librosa::stft(y, 1024, 256); + const librosa::ArrayXXr S = (librosa::magnitude(D) + 1e-6).eval(); + const auto freqs = librosa::fft_frequencies(kSampleRate, 1024); + + for (auto _ : state) { + auto centroid_audio = librosa::feature::spectral_centroid(y, kSampleRate, 1024, 256); + auto centroid_spec = librosa::feature::spectral_centroid(S, kSampleRate, 1024, &freqs); + auto bandwidth_audio = librosa::feature::spectral_bandwidth(y, kSampleRate, 1024, 256); + auto bandwidth_spec = librosa::feature::spectral_bandwidth( + S, kSampleRate, 1024, ¢roid_spec, &freqs); + auto rolloff_audio = librosa::feature::spectral_rolloff(y, kSampleRate, 1024, 256); + auto rolloff_spec = librosa::feature::spectral_rolloff(S, kSampleRate, 1024, &freqs); + auto flatness_audio = librosa::feature::spectral_flatness(y, 1024, 256); + auto flatness_spec = static_cast(&librosa::feature::spectral_flatness)( + S, 1e-10, 2.0); + auto contrast_audio = librosa::feature::spectral_contrast(y, kSampleRate, 1024, 256); + auto contrast_spec = librosa::feature::spectral_contrast(S, kSampleRate, 1024, &freqs); + + consume_eigen(centroid_audio); + consume_eigen(centroid_spec); + consume_eigen(bandwidth_audio); + consume_eigen(bandwidth_spec); + consume_eigen(rolloff_audio); + consume_eigen(rolloff_spec); + consume_eigen(flatness_audio); + consume_eigen(flatness_spec); + consume_eigen(contrast_audio); + consume_eigen(contrast_spec); + } + + state.SetItemsProcessed(state.iterations() * S.size()); +} + +void BM_FeatureEnergyPolynomialTonnetz(benchmark::State& state) { + const librosa::ArrayXr y = + (make_tone(440.0, state.range(0)) + 0.3 * make_tone(660.0, state.range(0))).eval(); + const auto D = librosa::stft(y, 1024, 256); + const librosa::ArrayXXr S = (librosa::magnitude(D) + 1e-6).eval(); + const auto freqs = librosa::fft_frequencies(kSampleRate, 1024); + const auto chroma = static_cast(&librosa::feature::chroma_stft)( + y, kSampleRate, 2048, kHopLength, 12, 0.0, + std::numeric_limits::infinity(), + librosa::WindowType::Hann, true); + + for (auto _ : state) { + auto rms_audio = librosa::feature::rms(y, 1024, 256); + auto rms_spec = static_cast(&librosa::feature::rms)(S, 1024); + auto zcr = librosa::feature::zero_crossing_rate(y, 1024, 256); + auto poly_audio = librosa::feature::poly_features(y, kSampleRate, 1024, 256, 2); + auto poly_spec = librosa::feature::poly_features(S, kSampleRate, 1024, 2, &freqs); + auto tonnetz_audio = librosa::feature::tonnetz(y, kSampleRate, &chroma); + auto tonnetz_chroma = librosa::feature::tonnetz(chroma); + + consume_eigen(rms_audio); + consume_eigen(rms_spec); + consume_eigen(zcr); + consume_eigen(poly_audio); + consume_eigen(poly_spec); + consume_eigen(tonnetz_audio); + consume_eigen(tonnetz_chroma); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_FeatureDeltaStackMemory(benchmark::State& state) { + const auto data = make_feature_matrix(16, state.range(0)); + + for (auto _ : state) { + auto d1 = librosa::feature::delta(data, 9, 1, -1, "interp"); + auto d2 = librosa::feature::delta(data, 9, 2, -1, "interp"); + auto memory = librosa::feature::stack_memory(data, 4, 2); + consume_eigen(d1); + consume_eigen(d2); + consume_eigen(memory); + } + + state.SetItemsProcessed(state.iterations() * data.size()); +} + +void BM_FeatureInverse(benchmark::State& state) { + const auto y = make_tone(440.0, state.range(0)); + const auto mel = librosa::feature::melspectrogram( + y, kSampleRate, 1024, 256, std::nullopt, librosa::WindowType::Hann, + true, librosa::PadMode::Constant, 2.0, 64); + const auto mfcc = librosa::feature::mfcc(y, kSampleRate, 20, 2, true, 0.0, + 1024, 256, 64); + + for (auto _ : state) { + auto stft = librosa::feature::mel_to_stft(mel, kSampleRate, 1024); + auto audio = librosa::feature::mel_to_audio(mel, kSampleRate, 1024, 256, + std::nullopt, librosa::WindowType::Hann, + true, 2.0, 2, + static_cast(y.size())); + auto mel_from_mfcc = librosa::feature::mfcc_to_mel(mfcc, 64); + auto audio_from_mfcc = librosa::feature::mfcc_to_audio( + mfcc, 64, 2, true, 1.0, 0.0, kSampleRate, 1024, 256, 2); + + consume_eigen(stft); + consume_eigen(audio); + consume_eigen(mel_from_mfcc); + consume_eigen(audio_from_mfcc); + } + + state.SetItemsProcessed(state.iterations() * mel.size()); +} + +void BM_FeatureRhythm(benchmark::State& state) { + const auto envelope = make_onset_envelope(state.range(0)); + const auto y = make_click_track(state.range(0) * kHopLength); + const auto tg = librosa::beat::tempogram(envelope, kSampleRate, kHopLength, 128); + const std::vector factors = {0.5, 1.0, 2.0, 3.0}; + + for (auto _ : state) { + auto fourier = librosa::feature::fourier_tempogram( + envelope, kSampleRate, kHopLength, 128); + auto fourier_audio = librosa::feature::fourier_tempogram_audio( + y, kSampleRate, kHopLength, 128); + auto ratio = librosa::feature::tempogram_ratio( + tg, kSampleRate, kHopLength, factors, 120.0, 1.0, 320.0); + + consume_eigen(fourier); + consume_eigen(fourier_audio); + consume_eigen(ratio); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +} // namespace + +BENCHMARK(BM_FeatureMelAndMFCC)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_FeatureChroma)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_FeatureSpectralShape)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_FeatureEnergyPolynomialTonnetz)->Arg(8192)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_FeatureDeltaStackMemory)->Arg(512)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_FeatureInverse)->Arg(4096)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_FeatureRhythm)->Arg(1024)->Unit(benchmark::kMillisecond); diff --git a/benchmarks/onset_beat_benchmark.cpp b/benchmarks/onset_beat_benchmark.cpp new file mode 100644 index 0000000..467fc70 --- /dev/null +++ b/benchmarks/onset_beat_benchmark.cpp @@ -0,0 +1,139 @@ +#include "benchmark_helpers.hpp" + +#include +#include + +#include +#include + +namespace { + +using namespace librosa_bench; + +void BM_OnsetMaximumFilterAndMatching(benchmark::State& state) { + const auto S = make_positive_matrix(128, state.range(0)); + std::vector from; + std::vector to; + for (Eigen::Index i = 0; i < state.range(0); i += 7) { + from.push_back(i); + } + for (Eigen::Index i = 0; i < state.range(0); i += 5) { + to.push_back(i); + } + + for (auto _ : state) { + auto filtered_freq = librosa::onset::maximum_filter1d(S, 5, -2); + auto filtered_time = librosa::onset::maximum_filter1d(S, 5, -1); + auto matches = librosa::onset::match_events(from, to); + consume_eigen(filtered_freq); + consume_eigen(filtered_time); + consume_vector(matches); + } + + state.SetItemsProcessed(state.iterations() * S.size()); +} + +void BM_OnsetStrengthMulti(benchmark::State& state) { + const auto y = make_click_track(state.range(0)); + const auto S = make_positive_matrix(128, state.range(0) / kHopLength + 1); + const std::vector channels = {0, 32, 64, 96, 128}; + + for (auto _ : state) { + auto single_audio = librosa::onset::onset_strength(y, kSampleRate, kNFFT, kHopLength); + auto single_spec = librosa::onset::onset_strength(S, kSampleRate, kNFFT, kHopLength); + auto multi_audio = librosa::onset::onset_strength_multi( + y, kSampleRate, kNFFT, kHopLength, 1, 1, false, true, channels); + auto multi_spec = librosa::onset::onset_strength_multi( + S, kSampleRate, kNFFT, kHopLength, 1, 1, false, true, channels); + consume_eigen(single_audio); + consume_eigen(single_spec); + consume_eigen(multi_audio); + consume_eigen(multi_spec); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_OnsetDetection(benchmark::State& state) { + const auto y = make_click_track(state.range(0)); + const auto envelope = make_onset_envelope(state.range(0) / kHopLength + 1); + + for (auto _ : state) { + auto from_audio = librosa::onset::onset_detect( + y, kSampleRate, kHopLength, false, librosa::onset::OnsetUnits::Frames, + true, 3, 3, 3, 3, 0.1, 3); + auto from_envelope = librosa::onset::onset_detect_envelope( + envelope, kSampleRate, kHopLength, false, librosa::onset::OnsetUnits::Frames, + true, 3, 3, 3, 3, 0.1, 3); + auto times = librosa::onset::onset_detect_times(y, kSampleRate, kHopLength); + auto backtracked = librosa::onset::onset_backtrack(from_envelope, envelope); + consume_vector(from_audio); + consume_vector(from_envelope); + consume_eigen(times); + consume_vector(backtracked); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_BeatTempogramAndTempo(benchmark::State& state) { + const auto envelope = make_onset_envelope(state.range(0)); + const auto y = make_click_track(state.range(0) * kHopLength); + + for (auto _ : state) { + auto tg = librosa::beat::tempogram(envelope, kSampleRate, kHopLength, 128); + auto tg_audio = librosa::beat::tempogram_audio(y, kSampleRate, kHopLength, 128); + auto tempo = librosa::beat::tempo(envelope, kSampleRate, kHopLength); + auto tempo_audio = librosa::beat::tempo_audio(y, kSampleRate, kHopLength); + auto tempo_frames = librosa::beat::tempo_frames(envelope, kSampleRate, kHopLength); + benchmark::DoNotOptimize(tempo); + benchmark::DoNotOptimize(tempo_audio); + consume_eigen(tg); + consume_eigen(tg_audio); + consume_eigen(tempo_frames); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +void BM_BeatTrackVariants(benchmark::State& state) { + const auto envelope = make_onset_envelope(state.range(0)); + const auto y = make_click_track(state.range(0) * kHopLength); + + for (auto _ : state) { + auto beats = librosa::beat::beat_track(envelope, kSampleRate, kHopLength); + auto beat_audio = librosa::beat::beat_track_audio(y, kSampleRate, kHopLength); + auto beat_times = librosa::beat::beat_track_times(y, kSampleRate, kHopLength); + benchmark::DoNotOptimize(beats.first); + consume_vector(beats.second); + benchmark::DoNotOptimize(beat_audio.first); + consume_vector(beat_audio.second); + benchmark::DoNotOptimize(beat_times.first); + consume_eigen(beat_times.second); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +void BM_BeatPLP(benchmark::State& state) { + const auto envelope = make_onset_envelope(state.range(0)); + const auto y = make_click_track(state.range(0) * kHopLength); + + for (auto _ : state) { + auto plp = librosa::beat::plp(envelope, kSampleRate, kHopLength, 128); + auto plp_audio = librosa::beat::plp_audio(y, kSampleRate, kHopLength, 128); + consume_eigen(plp); + consume_eigen(plp_audio); + } + + state.SetItemsProcessed(state.iterations() * envelope.size()); +} + +} // namespace + +BENCHMARK(BM_OnsetMaximumFilterAndMatching)->Arg(1024)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_OnsetStrengthMulti)->Arg(22050)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_OnsetDetection)->Arg(22050)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_BeatTempogramAndTempo)->Arg(1024)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_BeatTrackVariants)->Arg(1024)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_BeatPLP)->Arg(1024)->Unit(benchmark::kMillisecond); diff --git a/benchmarks/utility_benchmark.cpp b/benchmarks/utility_benchmark.cpp new file mode 100644 index 0000000..72d7840 --- /dev/null +++ b/benchmarks/utility_benchmark.cpp @@ -0,0 +1,174 @@ +#include "benchmark_helpers.hpp" + +#include + +#include +#include +#include + +namespace { + +using namespace librosa_bench; + +void BM_UtilValidationAndShape(benchmark::State& state) { + const auto y = make_random_vector(state.range(0)); + const auto matrix = make_feature_matrix(16, state.range(0)); + librosa::ArrayXXr intervals(3, 2); + intervals << 0.0, 1.0, + 1.0, 2.0, + 2.0, 4.0; + std::vector frames = {4, 12, 18, 32, 48}; + + for (auto _ : state) { + auto valid_y = librosa::util::valid_audio(y); + auto valid_matrix = librosa::util::valid_audio(matrix); + auto positive = librosa::util::is_positive_int(4); + auto integer = librosa::util::valid_int(12.75); + auto valid_intervals = librosa::util::valid_intervals(intervals); + auto tiny = librosa::util::tiny(); + auto padded = librosa::util::pad_center(y, y.size() + 128); + auto padded_matrix = librosa::util::pad_center(matrix, matrix.cols() + 16, -1); + auto fixed = librosa::util::fix_length(y, y.size() + 128); + auto fixed_matrix = librosa::util::fix_length(matrix, matrix.cols() + 16, -1); + auto fixed_frames = librosa::util::fix_frames(frames, 0, state.range(0), true); + auto framed = librosa::util::frame(y, 256, 64); + + benchmark::DoNotOptimize(valid_y); + benchmark::DoNotOptimize(valid_matrix); + benchmark::DoNotOptimize(positive); + benchmark::DoNotOptimize(integer); + benchmark::DoNotOptimize(valid_intervals); + benchmark::DoNotOptimize(tiny); + consume_eigen(padded); + consume_eigen(padded_matrix); + consume_eigen(fixed); + consume_eigen(fixed_matrix); + consume_vector(fixed_frames); + consume_eigen(framed); + } + + state.SetItemsProcessed(state.iterations() * y.size()); +} + +void BM_UtilNormalizePeaksAndSort(benchmark::State& state) { + const auto y = make_onset_envelope(state.range(0)); + const auto matrix = make_positive_matrix(32, state.range(0)); + + for (auto _ : state) { + auto norm_vector = librosa::util::normalize(y); + auto norm_matrix = librosa::util::normalize(matrix, 2.0, -1); + auto max_vector = librosa::util::localmax(y); + auto max_matrix = librosa::util::localmax(matrix, -1); + auto min_vector = librosa::util::localmin(y); + auto min_matrix = librosa::util::localmin(matrix, -1); + auto peaks = librosa::util::peak_pick(y, 3, 3, 3, 3, 0.1, 3); + auto sorted = librosa::util::axis_sort(matrix, -1); + auto sorted_index = librosa::util::axis_sort_with_index(matrix, -1); + auto sparse = librosa::util::sparsify_rows(matrix, 0.1); + + consume_eigen(norm_vector); + consume_eigen(norm_matrix); + consume_eigen(max_vector); + consume_eigen(max_matrix); + consume_eigen(min_vector); + consume_eigen(min_matrix); + consume_vector(peaks); + consume_eigen(sorted); + consume_eigen(sorted_index.first); + consume_vector(sorted_index.second); + consume_eigen(sparse); + } + + state.SetItemsProcessed(state.iterations() * matrix.size()); +} + +void BM_UtilMaskSyncComplexAndStack(benchmark::State& state) { + const auto x = make_positive_matrix(16, state.range(0)); + const librosa::ArrayXXr x_ref = (make_positive_matrix(16, state.range(0)) + 0.5).eval(); + const auto complex = make_complex_matrix(16, state.range(0)); + const auto angles = make_random_vector(state.range(0)); + const auto mags = librosa::ArrayXr::Ones(state.range(0)); + std::vector idx = {0, state.range(0) / 4, state.range(0) / 2, + 3 * state.range(0) / 4}; + std::vector arrays = { + make_random_vector(state.range(0)), + make_random_vector(state.range(0)), + make_random_vector(state.range(0)), + }; + + for (auto _ : state) { + auto mask = librosa::util::softmask(x, x_ref, 2.0); + auto sync = librosa::util::sync(x, idx, librosa::AggregateFunc::Mean, true, -1); + auto abs2_matrix = librosa::util::abs2(complex); + auto abs2_vector = librosa::util::abs2(complex.col(0).eval()); + auto phasor_mag = librosa::util::phasor(angles, mags); + auto phasor_scalar = librosa::util::phasor(angles, 1.0); + auto gradient = librosa::util::cyclic_gradient(angles); + auto filled = librosa::util::fill_off_diagonal(make_recurrence(64), 0.0); + auto stacked_rows = librosa::util::stack(arrays, 0); + auto stacked_cols = librosa::util::stack(arrays, 1); + + consume_eigen(mask); + consume_eigen(sync); + consume_eigen(abs2_matrix); + consume_eigen(abs2_vector); + consume_eigen(phasor_mag); + consume_eigen(phasor_scalar); + consume_eigen(gradient); + consume_eigen(filled); + consume_eigen(stacked_rows); + consume_eigen(stacked_cols); + } + + state.SetItemsProcessed(state.iterations() * x.size()); +} + +void BM_UtilBufferUniquenessShearIntervalsNNLS(benchmark::State& state) { + const auto matrix = make_positive_matrix(32, state.range(0)); + const auto A_array = make_positive_matrix(48, 16); + const librosa::MatrixXr A = A_array.matrix(); + const auto B = make_positive_matrix(48, 8); + std::vector pcm(static_cast(state.range(0))); + for (Eigen::Index i = 0; i < state.range(0); ++i) { + pcm[static_cast(i)] = static_cast((i % 1024) - 512); + } + const auto unique = librosa::ArrayXr::LinSpaced(state.range(0), 0.0, + static_cast(state.range(0) - 1)); + librosa::ArrayXXr intervals_from(3, 2); + intervals_from << 0.0, 1.0, + 1.0, 2.0, + 2.0, 3.0; + librosa::ArrayXXr intervals_to(3, 2); + intervals_to << 0.0, 1.5, + 1.5, 2.5, + 2.5, 4.0; + + for (auto _ : state) { + auto floats = librosa::util::buf_to_float(pcm.data(), pcm.size(), 2); + auto is_unique = librosa::util::is_unique(unique); + auto unique_count = librosa::util::count_unique(unique); + auto sheared_time = librosa::util::shear(matrix, 1, -1); + auto sheared_freq = librosa::util::shear(matrix, 1, 0); + auto slice = librosa::util::index_to_slice(8, 0, 64, 2, true); + auto matches = librosa::util::match_intervals(intervals_from, intervals_to, true); + auto solution = librosa::util::nnls(A, B); + + consume_eigen(floats); + benchmark::DoNotOptimize(is_unique); + benchmark::DoNotOptimize(unique_count); + consume_eigen(sheared_time); + consume_eigen(sheared_freq); + consume_vector(slice); + consume_vector(matches); + consume_eigen(solution); + } + + state.SetItemsProcessed(state.iterations() * matrix.size()); +} + +} // namespace + +BENCHMARK(BM_UtilValidationAndShape)->Arg(2048)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_UtilNormalizePeaksAndSort)->Arg(256)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_UtilMaskSyncComplexAndStack)->Arg(256)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_UtilBufferUniquenessShearIntervalsNNLS)->Arg(128)->Unit(benchmark::kMicrosecond);