1 #include "delay_analyzer.h"
3 #include "audio_mixer.h"
4 #include "ui_delay_analyzer.h"
5 #include "shared/post_to_main_thread.h"
7 #include <bmusb/bmusb.h>
13 using namespace bmusb;
15 using namespace std::chrono;
16 using namespace std::placeholders;
18 DelayAnalyzer::DelayAnalyzer()
19 : ui(new Ui::DelayAnalyzer),
20 devices(global_audio_mixer->get_devices(AudioMixer::HOLD_NO_DEVICES)),
21 grab_timeout(new QTimer(this))
24 connect(ui->grab_btn, &QPushButton::clicked, this, &DelayAnalyzer::grab_clicked);
26 connect(ui->card_combo_1, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
27 bind(&DelayAnalyzer::card_selected, this, ui->card_combo_1, _1));
28 connect(ui->card_combo_2, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
29 bind(&DelayAnalyzer::card_selected, this, ui->card_combo_2, _1));
30 connect(ui->channel_combo_1, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
31 bind(&DelayAnalyzer::channel_selected, this, ui->channel_combo_1));
32 connect(ui->channel_combo_2, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
33 bind(&DelayAnalyzer::channel_selected, this, ui->channel_combo_2));
34 connect(grab_timeout, &QTimer::timeout, bind(&DelayAnalyzer::grab_timed_out, this));
36 for (const auto &spec_and_info : devices) {
37 QString label(QString::fromStdString(spec_and_info.second.display_name));
38 ui->card_combo_1->addItem(label + " ", qulonglong(DeviceSpec_to_key(spec_and_info.first)));
39 ui->card_combo_2->addItem(label + " ", qulonglong(DeviceSpec_to_key(spec_and_info.first)));
42 ui->peak_display_1->set_audio_clip(&clip1);
43 ui->peak_display_2->set_audio_clip(&clip2);
46 DelayAnalyzer::~DelayAnalyzer()
50 void DelayAnalyzer::grab_clicked()
53 grab_timeout->setSingleShot(true);
54 grab_timeout->start(milliseconds(2000));
57 ui->delay_estimate_label->clear();
58 ui->peak_display_1->reset_base();
59 ui->peak_display_2->reset_base();
60 ui->peak_display_1->audio_clip_updated();
61 ui->peak_display_2->audio_clip_updated();
64 void DelayAnalyzer::card_selected(QComboBox *card_combo, int selected_index)
66 assert(card_combo == ui->card_combo_1 || card_combo == ui->card_combo_2);
67 QComboBox *channel_combo = (card_combo == ui->card_combo_1 ? ui->channel_combo_1 : ui->channel_combo_2);
68 int current_channel = channel_combo->currentIndex();
70 channel_combo->clear();
72 uint64_t key = card_combo->itemData(selected_index).toULongLong();
73 auto device_it = devices.find(key_to_DeviceSpec(key));
74 assert(device_it != devices.end());
75 unsigned num_device_channels = device_it->second.num_channels;
76 for (unsigned source = 0; source < num_device_channels; ++source) {
78 snprintf(buf, sizeof(buf), "Channel %u ", source + 1);
79 channel_combo->addItem(QString(buf));
82 if (current_channel != -1 && current_channel < channel_combo->count()) {
83 channel_combo->setCurrentIndex(current_channel);
85 channel_combo->setCurrentIndex(0);
88 if (card_combo == ui->card_combo_1) {
90 ui->peak_display_1->audio_clip_updated();
93 ui->peak_display_2->audio_clip_updated();
95 ui->delay_estimate_label->clear();
98 void DelayAnalyzer::channel_selected(QComboBox *channel_combo)
100 if (channel_combo == ui->channel_combo_1) {
102 ui->peak_display_1->audio_clip_updated();
105 ui->peak_display_2->audio_clip_updated();
107 ui->delay_estimate_label->clear();
110 DeviceSpec DelayAnalyzer::get_selected_device(QComboBox *card_combo)
112 return key_to_DeviceSpec(card_combo->currentData().toULongLong());
115 void DelayAnalyzer::add_audio(DeviceSpec device_spec, const uint8_t *data, unsigned num_samples, AudioFormat audio_format, steady_clock::time_point frame_time)
117 unique_ptr<float[]> tmp;
119 if (device_spec == get_selected_device(ui->card_combo_1)) {
120 tmp.reset(new float[num_samples]);
122 convert_audio_to_fp32(tmp.get(), /*out_channel=*/0, /*out_num_channels=*/1, data, ui->channel_combo_1->currentIndex(), audio_format, num_samples);
123 clip1.add_audio(tmp.get(), num_samples, audio_format.sample_rate, frame_time);
124 ui->peak_display_1->audio_clip_updated();
126 if (device_spec == get_selected_device(ui->card_combo_2)) {
127 if (tmp == nullptr) {
128 tmp.reset(new float[num_samples]);
131 convert_audio_to_fp32(tmp.get(), /*out_channel=*/0, /*out_num_channels=*/1, data, ui->channel_combo_2->currentIndex(), audio_format, num_samples);
132 clip2.add_audio(tmp.get(), num_samples, audio_format.sample_rate, frame_time);
133 ui->peak_display_2->audio_clip_updated();
136 if (clip1.empty() && clip2.empty()) {
137 // No interesting data yet.
141 steady_clock::time_point base;
142 if (!clip1.empty() && !clip2.empty()) {
143 base = max(clip1.get_first_sample(), clip2.get_first_sample());
144 } else if (!clip1.empty()) {
145 base = clip1.get_first_sample();
147 assert(!clip2.empty());
148 base = clip2.get_first_sample();
150 ui->peak_display_1->set_base(base);
151 ui->peak_display_2->set_base(base);
153 if (clip1.get_length_seconds_after_base(base) >= 1.0 &&
154 clip2.get_length_seconds_after_base(base) >= 1.0) {
155 post_to_main_thread([this] {
156 grab_timeout->stop();
160 // The delay estimation is cheap, but it's not free, and we're on the main mixer thread,
161 // so fire it off onto a background thread.
162 std::thread(&DelayAnalyzer::estimate_delay, this).detach();
166 void DelayAnalyzer::estimate_delay()
168 AudioClip::BestCorrelation corr = clip1.find_best_correlation(&clip2);
170 if (isnan(corr.correlation) || fabs(corr.correlation) < 0.1) { // Very loose bound, basically to guard against digital silence.
171 snprintf(buf, sizeof(buf), "Could not estimate delay.\n");
172 } else if (corr.correlation < 0.0) {
173 snprintf(buf, sizeof(buf), "Best estimate: Channel is %.1f ms delayed compared to reference <em>and inverted</em> (correlation = %.3f)\n",
174 corr.delay_ms, corr.correlation);
176 snprintf(buf, sizeof(buf), "Best estimate: Channel is %.1f ms delayed compared to reference (correlation = %.3f)\n",
177 corr.delay_ms, corr.correlation);
179 post_to_main_thread([this, buf] {
180 ui->delay_estimate_label->setText(buf);
184 void DelayAnalyzer::grab_timed_out()
187 ui->delay_estimate_label->setText("Could not capture audio (timed out).");