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).
#include "audio_clip.h"
+#include <assert.h>
#include <math.h>
using namespace std;
return double(vals.size()) / sample_rate;
}
+double AudioClip::get_length_seconds_after_base(std::chrono::steady_clock::time_point base) const
+{
+ lock_guard<mutex> 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<double>(base - first_sample).count();
+}
+
+bool AudioClip::empty() const
+{
+ lock_guard<mutex> lock(mu);
+ return vals.empty();
+}
+
+steady_clock::time_point AudioClip::get_first_sample() const
+{
+ lock_guard<mutex> lock(mu);
+ assert(!vals.empty());
+ return first_sample;
+}
-unique_ptr<pair<float, float>[]> AudioClip::get_min_max_peaks(unsigned width) const
+unique_ptr<pair<float, float>[]> AudioClip::get_min_max_peaks(unsigned width, steady_clock::time_point base) const
{
unique_ptr<pair<float, float>[]> min_max(new pair<float, float>[width]);
for (unsigned x = 0; x < width; ++x) {
}
lock_guard<mutex> lock(mu);
- for (size_t i = 0; i < vals.size(); ++i) {
+ double skip_samples = duration<double>(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;
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<std::pair<float, float>[]> 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<std::pair<float, float>[]> get_min_max_peaks(unsigned width, std::chrono::steady_clock::time_point base) const;
private:
mutable std::mutex mu;
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();
}
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;
}
}
#include "audio_clip.h"
using namespace std;
+using namespace std::chrono;
PeakDisplay::PeakDisplay(QWidget *parent)
: QWidget(parent)
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<pair<float, float>[]> min_max = audio_clip->get_min_max_peaks(w);
+ unique_ptr<pair<float, float>[]> min_max = audio_clip->get_min_max_peaks(w, paint_base);
QPainter painter(this);
painter.fillRect(event->rect(), Qt::white);
#define PEAK_DISPLAY_H
#include <QWidget>
+#include <chrono>
#include <mutex>
class AudioClip;
}
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