]> git.sesse.net Git - nageru/blob - nageru/delay_analyzer.cpp
Time out grabbing if we don't get any data.
[nageru] / nageru / delay_analyzer.cpp
1 #include "delay_analyzer.h"
2
3 #include "audio_mixer.h"
4 #include "ui_delay_analyzer.h"
5
6 #include <bmusb/bmusb.h>
7
8 #include <memory>
9
10 #include <QTimer>
11
12 using namespace bmusb;
13 using namespace std;
14 using namespace std::chrono;
15 using namespace std::placeholders;
16
17 DelayAnalyzer::DelayAnalyzer()
18         : ui(new Ui::DelayAnalyzer),
19           devices(global_audio_mixer->get_devices(AudioMixer::HOLD_NO_DEVICES)),
20           grab_timeout(new QTimer(this))
21 {
22         ui->setupUi(this);
23         connect(ui->grab_btn, &QPushButton::clicked, this, &DelayAnalyzer::grab_clicked);
24
25         connect(ui->card_combo_1, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
26                 bind(&DelayAnalyzer::card_selected, this, ui->card_combo_1, _1));
27         connect(ui->card_combo_2, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
28                 bind(&DelayAnalyzer::card_selected, this, ui->card_combo_2, _1));
29         connect(ui->channel_combo_1, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
30                 bind(&DelayAnalyzer::channel_selected, this, ui->channel_combo_1));
31         connect(ui->channel_combo_2, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
32                 bind(&DelayAnalyzer::channel_selected, this, ui->channel_combo_2));
33         connect(grab_timeout, &QTimer::timeout, bind(&DelayAnalyzer::grab_timed_out, this));
34
35         for (const auto &spec_and_info : devices) {
36                 QString label(QString::fromStdString(spec_and_info.second.display_name));
37                 ui->card_combo_1->addItem(label + "   ", qulonglong(DeviceSpec_to_key(spec_and_info.first)));
38                 ui->card_combo_2->addItem(label + "   ", qulonglong(DeviceSpec_to_key(spec_and_info.first)));
39         }
40
41         ui->peak_display_1->set_audio_clip(&clip1);
42         ui->peak_display_2->set_audio_clip(&clip2);
43 }
44
45 DelayAnalyzer::~DelayAnalyzer()
46 {
47 }
48
49 void DelayAnalyzer::grab_clicked()
50 {
51         grabbing = true;
52         grab_timeout->setSingleShot(true);
53         grab_timeout->start(milliseconds(2000));
54         clip1.clear();
55         clip2.clear();
56         ui->peak_display_1->reset_base();
57         ui->peak_display_2->reset_base();
58         ui->peak_display_1->audio_clip_updated();
59         ui->peak_display_2->audio_clip_updated();
60 }
61
62 void DelayAnalyzer::card_selected(QComboBox *card_combo, int selected_index)
63 {
64         assert(card_combo == ui->card_combo_1 || card_combo == ui->card_combo_2);
65         QComboBox *channel_combo = (card_combo == ui->card_combo_1 ? ui->channel_combo_1 : ui->channel_combo_2);
66         int current_channel = channel_combo->currentIndex();
67
68         channel_combo->clear();
69
70         uint64_t key = card_combo->itemData(selected_index).toULongLong();
71         auto device_it = devices.find(key_to_DeviceSpec(key));
72         assert(device_it != devices.end());
73         unsigned num_device_channels = device_it->second.num_channels;
74         for (unsigned source = 0; source < num_device_channels; ++source) {
75                 char buf[256];
76                 snprintf(buf, sizeof(buf), "Channel %u   ", source + 1);
77                 channel_combo->addItem(QString(buf));
78         }
79
80         if (current_channel != -1 && current_channel < channel_combo->count()) {
81                 channel_combo->setCurrentIndex(current_channel);
82         } else {
83                 channel_combo->setCurrentIndex(0);
84         }
85
86         if (card_combo == ui->card_combo_1) {
87                 clip1.clear();
88                 ui->peak_display_1->audio_clip_updated();
89         } else {
90                 clip2.clear();
91                 ui->peak_display_2->audio_clip_updated();
92         }
93 }
94
95 void DelayAnalyzer::channel_selected(QComboBox *channel_combo)
96 {
97         if (channel_combo == ui->channel_combo_1) {
98                 clip1.clear();
99                 ui->peak_display_1->audio_clip_updated();
100         } else {
101                 clip2.clear();
102                 ui->peak_display_2->audio_clip_updated();
103         }
104 }
105
106 DeviceSpec DelayAnalyzer::get_selected_device(QComboBox *card_combo)
107 {
108         return key_to_DeviceSpec(card_combo->currentData().toULongLong());
109 }
110
111 void DelayAnalyzer::add_audio(DeviceSpec device_spec, const uint8_t *data, unsigned num_samples, AudioFormat audio_format, steady_clock::time_point frame_time)
112 {
113         unique_ptr<float[]> tmp;
114
115         if (device_spec == get_selected_device(ui->card_combo_1)) {
116                 tmp.reset(new float[num_samples]);
117
118                 convert_audio_to_fp32(tmp.get(), /*out_channel=*/0, /*out_num_channels=*/1, data, ui->channel_combo_1->currentIndex(), audio_format, num_samples);
119                 clip1.add_audio(tmp.get(), num_samples, audio_format.sample_rate, frame_time);
120                 ui->peak_display_1->audio_clip_updated();
121         }
122         if (device_spec == get_selected_device(ui->card_combo_2)) {
123                 if (tmp == nullptr) {
124                         tmp.reset(new float[num_samples]);
125                 }
126
127                 convert_audio_to_fp32(tmp.get(), /*out_channel=*/0, /*out_num_channels=*/1, data, ui->channel_combo_2->currentIndex(), audio_format, num_samples);
128                 clip2.add_audio(tmp.get(), num_samples, audio_format.sample_rate, frame_time);
129                 ui->peak_display_2->audio_clip_updated();
130         }
131
132         if (clip1.empty() && clip2.empty()) {
133                 // No interesting data yet.
134                 return;
135         }
136
137         steady_clock::time_point base;
138         if (!clip1.empty() && !clip2.empty()) {
139                 base = max(clip1.get_first_sample(), clip2.get_first_sample());
140         } else if (!clip1.empty()) {
141                 base = clip1.get_first_sample();
142         } else {
143                 assert(!clip2.empty());
144                 base = clip2.get_first_sample();
145         }
146         ui->peak_display_1->set_base(base);
147         ui->peak_display_2->set_base(base);
148
149         if (clip1.get_length_seconds_after_base(base) >= 1.0 &&
150             clip2.get_length_seconds_after_base(base) >= 1.0) {
151                 post_to_main_thread([this] {
152                         grab_timeout->stop();
153                 });
154                 grabbing = false;
155         }
156 }
157
158 void DelayAnalyzer::grab_timed_out()
159 {
160         grabbing = false;
161         ui->delay_estimate_label->setText("Could not capture audio (timed out).");
162 }