From 02f95f60afd99a0ae3b1cc67ea34a647dc09d357 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 11 Aug 2019 10:50:55 +0200 Subject: [PATCH] Make the delay analyzer understand that two sources can have different starting times. If we didn't truncate the two sources so that they have different starting times, the perceived delay would be skewed (unless the are different channels of the same source, in which case they'd always be in sync anyway). --- nageru/audio_clip.cpp | 31 ++++++++++++++++++++++++++++--- nageru/audio_clip.h | 9 +++++++-- nageru/delay_analyzer.cpp | 22 +++++++++++++++++++++- nageru/peak_display.cpp | 8 +++++++- nageru/peak_display.h | 14 ++++++++++++++ 5 files changed, 77 insertions(+), 7 deletions(-) diff --git a/nageru/audio_clip.cpp b/nageru/audio_clip.cpp index 4dcb8db..ec97e6b 100644 --- a/nageru/audio_clip.cpp +++ b/nageru/audio_clip.cpp @@ -1,5 +1,6 @@ #include "audio_clip.h" +#include #include using namespace std; @@ -34,8 +35,31 @@ double AudioClip::get_length_seconds() const return double(vals.size()) / sample_rate; } +double AudioClip::get_length_seconds_after_base(std::chrono::steady_clock::time_point base) const +{ + lock_guard lock(mu); + if (vals.empty() || base < first_sample) { + // NOTE: base < first_sample can happen only during race conditions. + return 0.0; + } + + return double(vals.size()) / sample_rate - duration(base - first_sample).count(); +} + +bool AudioClip::empty() const +{ + lock_guard lock(mu); + return vals.empty(); +} + +steady_clock::time_point AudioClip::get_first_sample() const +{ + lock_guard lock(mu); + assert(!vals.empty()); + return first_sample; +} -unique_ptr[]> AudioClip::get_min_max_peaks(unsigned width) const +unique_ptr[]> AudioClip::get_min_max_peaks(unsigned width, steady_clock::time_point base) const { unique_ptr[]> min_max(new pair[width]); for (unsigned x = 0; x < width; ++x) { @@ -43,9 +67,10 @@ unique_ptr[]> AudioClip::get_min_max_peaks(unsigned width) co } lock_guard lock(mu); - for (size_t i = 0; i < vals.size(); ++i) { + double skip_samples = duration(base - first_sample).count() * sample_rate; + for (size_t i = int(floor(skip_samples)); i < vals.size(); ++i) { // We display one second. - int x = lrint(i * (double(width) / sample_rate)); + int x = lrint((i - skip_samples) * (double(width) / sample_rate)); if (x < 0 || x >= int(width)) continue; if (isnan(min_max[x].first)) { min_max[x].first = min_max[x].second = 0.0; diff --git a/nageru/audio_clip.h b/nageru/audio_clip.h index 71248af..693dbbd 100644 --- a/nageru/audio_clip.h +++ b/nageru/audio_clip.h @@ -15,9 +15,14 @@ class AudioClip public: void clear(); void add_audio(const float *samples, size_t num_samples, double sample_rate, std::chrono::steady_clock::time_point frame_time); - double get_length_seconds() const; + double get_length_seconds() const; // 0.0 if empty(). + double get_length_seconds_after_base(std::chrono::steady_clock::time_point base) const; + bool empty() const; - std::unique_ptr[]> get_min_max_peaks(unsigned width) const; + // Only valid if not empty. + std::chrono::steady_clock::time_point get_first_sample() const; + + std::unique_ptr[]> get_min_max_peaks(unsigned width, std::chrono::steady_clock::time_point base) const; private: mutable std::mutex mu; diff --git a/nageru/delay_analyzer.cpp b/nageru/delay_analyzer.cpp index 7e25738..e720c30 100644 --- a/nageru/delay_analyzer.cpp +++ b/nageru/delay_analyzer.cpp @@ -47,6 +47,8 @@ void DelayAnalyzer::grab_clicked() grabbing = true; clip1.clear(); clip2.clear(); + ui->peak_display_1->reset_base(); + ui->peak_display_2->reset_base(); ui->peak_display_1->audio_clip_updated(); ui->peak_display_2->audio_clip_updated(); } @@ -121,7 +123,25 @@ void DelayAnalyzer::add_audio(DeviceSpec device_spec, const uint8_t *data, unsig ui->peak_display_2->audio_clip_updated(); } - if (clip1.get_length_seconds() >= 1.0 && clip2.get_length_seconds() >= 1.0) { + if (clip1.empty() && clip2.empty()) { + // No interesting data yet. + return; + } + + steady_clock::time_point base; + if (!clip1.empty() && !clip2.empty()) { + base = max(clip1.get_first_sample(), clip2.get_first_sample()); + } else if (!clip1.empty()) { + base = clip1.get_first_sample(); + } else { + assert(!clip2.empty()); + base = clip2.get_first_sample(); + } + ui->peak_display_1->set_base(base); + ui->peak_display_2->set_base(base); + + if (clip1.get_length_seconds_after_base(base) >= 1.0 && + clip2.get_length_seconds_after_base(base) >= 1.0) { grabbing = false; } } diff --git a/nageru/peak_display.cpp b/nageru/peak_display.cpp index eda6b54..c8e4391 100644 --- a/nageru/peak_display.cpp +++ b/nageru/peak_display.cpp @@ -11,6 +11,7 @@ #include "audio_clip.h" using namespace std; +using namespace std::chrono; PeakDisplay::PeakDisplay(QWidget *parent) : QWidget(parent) @@ -24,8 +25,13 @@ void PeakDisplay::audio_clip_updated() void PeakDisplay::paintEvent(QPaintEvent *event) { + steady_clock::time_point paint_base = base; + if (paint_base == steady_clock::time_point() && !audio_clip->empty()) { + paint_base = audio_clip->get_first_sample(); + } + int w = width(); - unique_ptr[]> min_max = audio_clip->get_min_max_peaks(w); + unique_ptr[]> min_max = audio_clip->get_min_max_peaks(w, paint_base); QPainter painter(this); painter.fillRect(event->rect(), Qt::white); diff --git a/nageru/peak_display.h b/nageru/peak_display.h index a7a3b93..76687a6 100644 --- a/nageru/peak_display.h +++ b/nageru/peak_display.h @@ -2,6 +2,7 @@ #define PEAK_DISPLAY_H #include +#include #include class AudioClip; @@ -19,10 +20,23 @@ public: } void audio_clip_updated(); + void set_base(std::chrono::steady_clock::time_point base) + { + this->base = base; + audio_clip_updated(); + } + + void reset_base() + { + base = std::chrono::steady_clock::time_point(); + audio_clip_updated(); + } + private: void paintEvent(QPaintEvent *event) override; AudioClip *audio_clip; + std::chrono::steady_clock::time_point base; // Epoch if not set. }; #endif -- 2.39.2