diff --git a/src/guess_tempo.cpp b/src/guess_tempo.cpp index ae5a011..dafcbd4 100644 --- a/src/guess_tempo.cpp +++ b/src/guess_tempo.cpp @@ -24,12 +24,12 @@ #include "aubio_cpp.hpp" #include "special_numeric_types.hpp" -std::vector guess_tempo(const std::filesystem::path& audio) { +std::vector guess_tempo(const std::filesystem::path& audio) { feis::InputSoundFile music; music.open_from_path(audio); const auto onsets = detect_onsets(music); - estimate_bpm(onsets, music.getSampleRate()); - // estimate offsets + const auto bpm_candidates = estimate_bpm(onsets, music.getSampleRate()); + return estimate_offset(bpm_candidates, music); } std::set detect_onsets(feis::InputSoundFile& music) { @@ -63,10 +63,11 @@ std::set detect_onsets(feis::InputSoundFile& music) { } -void estimate_bpm(const std::set& onsets, const std::size_t sample_rate) { +std::vector estimate_bpm(const std::set& onsets, const std::size_t sample_rate) { const auto broad_fitness = broad_interval_test(onsets, sample_rate); const auto corrected_fitness = correct_bias(broad_fitness); - const auto interval_candidates = narrow_interval_test(broad_fitness, corrected_fitness, onsets, sample_rate); + auto interval_candidates = narrow_interval_test(broad_fitness, corrected_fitness, onsets, sample_rate); + return select_bpm_candidates(interval_candidates, onsets, sample_rate); } const float min_tested_bpm = 89.f; @@ -130,7 +131,9 @@ std::vector fitness_of_interval_range(const std::set(max_index)}); } return fitness_values; } @@ -228,8 +231,8 @@ std::vector narrow_interval_test( std::vector select_bpm_candidates( std::vector& interval_candidates, - const std::size_t sample_rate, - const std::set& onsets + const std::set& onsets, + const std::size_t sample_rate ) { // Bram van de Wetering's original paper [BvdW] : // "BPM candidates that differ by less than 0.1 from a multiple of a @@ -281,9 +284,10 @@ std::vector select_bpm_candidates( } if (diff < 0.05f) { const auto rounded_bpm_fitness = fitness_of_bpm(onsets, sample_rate, rounded_bpm); - if (rounded_bpm_fitness > 0.99f * candidate.fitness) { + if (rounded_bpm_fitness.fitness > 0.99f * candidate.fitness) { candidate.bpm = rounded_bpm; - candidate.fitness = rounded_bpm_fitness; + candidate.fitness = rounded_bpm_fitness.fitness; + candidate.max_onset = rounded_bpm_fitness.max_onset; } } } @@ -305,7 +309,7 @@ std::vector select_bpm_candidates( return bpm_candidates; } -float fitness_of_bpm( +Fitness fitness_of_bpm( const std::set& onsets, const std::size_t sample_rate, const Fraction BPM @@ -330,5 +334,32 @@ float fitness_of_bpm( const auto second_half_interval = rounded_interval - first_half_interval; confidence.head(first_half_interval) += evidence_cache.tail(first_half_interval) / 2; confidence.tail(second_half_interval) += evidence_cache.head(second_half_interval) / 2; - return confidence.maxCoeff(); + Eigen::Index max_index; + const auto fitness = confidence.maxCoeff(&max_index); + return {fitness, static_cast(max_index)}; +} + +std::vector estimate_offset(const std::vector& bpm_candidates, feis::InputSoundFile& music) { + std::vector result; + result.reserve(bpm_candidates.size()); + music.seek(0); + const std::size_t mono_samples = music.getSampleCount() / music.getChannelCount(); + std::vector amplitude(mono_samples); + const std::size_t chunk_size = 4096 * music.getChannelCount(); + std::size_t read; + std::vector buffer(chunk_size); + do { + read = music.read(buffer.data(), chunk_size); + for (std::size_t i = 0; i < read / music.getChannelCount(); i++) { + float downmixed_sample = 0; + for (std::size_t channel = 0; channel < music.getChannelCount(); channel++) { + downmixed_sample += buffer[i * music.getChannelCount() + channel]; + } + amplitude[i] = downmixed_sample / channel_count; + } + } while ( read == chunk_size ); + + for (const auto& bpm_candidate : bpm_candidates) { + const Fraction initial_offset_estimate = Fraction{bpm_candidate.max_onset, music.getSampleRate()}; + } } \ No newline at end of file diff --git a/src/guess_tempo.hpp b/src/guess_tempo.hpp index 82beea7..b908726 100644 --- a/src/guess_tempo.hpp +++ b/src/guess_tempo.hpp @@ -14,24 +14,31 @@ struct IntervalFitness { std::size_t interval; float fitness; + std::size_t max_onset; }; struct BPMFitness { Fraction bpm; float fitness; + std::size_t max_onset; }; -struct BPMCandidate { +struct Fitness { + float fitness; + std::size_t max_onset; +}; + +struct TempoCandidate { Fraction bpm; std::size_t offset; float fitness; }; -std::vector guess_tempo(const std::filesystem::path& audio); +std::vector guess_tempo(const std::filesystem::path& audio); std::set detect_onsets(feis::InputSoundFile& music); -void estimate_bpm(const std::set& onsets, const std::size_t sample_rate); +std::vector estimate_bpm(const std::set& onsets, const std::size_t sample_rate); std::vector broad_interval_test(const std::set& onsets, const std::size_t sample_rate); std::vector fitness_of_interval_range( const std::set& onsets, @@ -49,7 +56,9 @@ std::vector narrow_interval_test( ); std::vector select_bpm_candidates( std::vector& interval_candidates, - const std::size_t sample_rate, - const std::set& onsets + const std::set& onsets, + const std::size_t sample_rate ); -float fitness_of_bpm(const std::set& onsets, const std::size_t sample_rate, const Fraction BPM); +Fitness fitness_of_bpm(const std::set& onsets, const std::size_t sample_rate, const Fraction BPM); + +std::vector estimate_offset(const std::vector& bpm_candidates, feis::InputSoundFile& music);