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