ACMX 2.139.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
audio-window.cpp
Go to the documentation of this file.
1#include "audio-window.hpp"
2#include "audio-playlist.hpp"
3#include "custom_style.hpp"
4#include <QFileDialog>
5#include <QFileInfo>
6#include <QProcess>
7#include <QRegularExpression>
8#include <QSettings>
9
10AudioSettings::AudioSettings(QWidget *parent) : QDialog(parent) {
11 setWindowTitle("Audio Settings");
12
13 audioReactivityCheckBox = new QCheckBox("Enable Audio Reactivity", this);
14 audioPassThroughCheckBox = new QCheckBox("Enable Audio Pass Through / File Playback", this);
15 audioPassThroughCheckBox->setToolTip("Play live microphone input or the selected audio source through the output device.");
16 recordAudioCheckBox = new QCheckBox("Record Audio to File", this);
17
18 QLabel *recordVolumeLabel = new QLabel("Recording Volume:", this);
19 recordVolumeSlider = new QSlider(Qt::Horizontal, this);
20 recordVolumeSlider->setRange(0, 200);
21 recordVolumeSlider->setValue(100);
22 QLabel *recordVolumeValueLabel = new QLabel("100%", this);
23 connect(recordVolumeSlider, &QSlider::valueChanged, this, [recordVolumeValueLabel](int value) { recordVolumeValueLabel->setText(QString::number(value) + "%"); });
24
25 QLabel *channelLabel = new QLabel("Number of Channels:", this);
26 channelSpinBox = new QSpinBox(this);
27 channelSpinBox->setRange(1, 32);
28 channelSpinBox->setValue(2);
29
30 QLabel *sensitivityLabel = new QLabel("Sensitivity:", this);
31 sensitivitySlider = new QSlider(Qt::Horizontal, this);
32 sensitivitySlider->setRange(1, 50);
33 sensitivitySlider->setValue(10);
34
35 QLabel *sensitivityValueLabel = new QLabel("1.0", this);
36 connect(sensitivitySlider, &QSlider::valueChanged, this, [this, sensitivityValueLabel](int value) {
37 double floatValue = value / 10.0;
38 sensitivityValueLabel->setText(QString::number(floatValue, 'f', 1));
39 });
40
42
43 QLabel *inputDeviceLabel = new QLabel("Input Device:", this);
44 inputDeviceComboBox = new QComboBox(this);
45
46 QLabel *outputDeviceLabel = new QLabel("Output Device:", this);
47 outputDeviceComboBox = new QComboBox(this);
48
50 outputDeviceComboBox->setEnabled(false);
51
52 connect(audioPassThroughCheckBox, &QCheckBox::toggled, this, [this](bool checked) { outputDeviceComboBox->setEnabled(checked); });
53
54 okButton = new QPushButton("OK", this);
55 cancelButton = new QPushButton("Cancel", this);
56
57 audioFileCheckBox = new QCheckBox("Use Audio File for Reactivity (instead of mic)", this);
58 audioFileLineEdit = new QLineEdit(this);
59 audioFileLineEdit->setReadOnly(true);
60 audioFileLineEdit->setEnabled(false);
61 audioFileBrowseButton = new QPushButton("Browse", this);
62 audioFileBrowseButton->setEnabled(false);
63 audioPlaylistCheckBox = new QCheckBox("Use M3U Audio Playlist", this);
64 audioPlaylistCheckBox->setToolTip("Use the tracks in an M3U playlist instead of the selected audio file.");
65 audioPlaylistLineEdit = new QLineEdit(this);
66 audioPlaylistLineEdit->setReadOnly(true);
67 audioPlaylistLineEdit->setEnabled(false);
68 audioPlaylistBrowseButton = new QPushButton("Browse", this);
69 audioPlaylistBrowseButton->setEnabled(false);
70 audioPlaylistEditButton = new QPushButton("Create / Edit...", this);
71 audioTruncCheckBox = new QCheckBox("Stop video when audio source completes", this);
72 audioTruncCheckBox->setEnabled(false);
73 audioRepeatCheckBox = new QCheckBox("Repeat", this);
74 audioRepeatCheckBox->setToolTip("Restart the audio file, or the full playlist, when it reaches the end.");
75 audioRepeatCheckBox->setEnabled(false);
76 audioBuffersCheckBox = new QCheckBox("Enable Audio Spectrum History Buffers", this);
77 audioBuffersSpinBox = new QSpinBox(this);
78 audioBuffersSpinBox->setRange(1, 512);
79 audioBuffersSpinBox->setValue(8);
80 audioBuffersSpinBox->setEnabled(false);
81
82 audioWarmRateSpinBox = new QDoubleSpinBox(this);
83 audioWarmRateSpinBox->setRange(0.0, 10.0);
84 audioWarmRateSpinBox->setSingleStep(0.05);
85 audioWarmRateSpinBox->setDecimals(2);
86 audioWarmRateSpinBox->setValue(0.5);
87 audioWarmRateSpinBox->setToolTip("Audio startup warmup rate (1/sec). 0.5 is about a 2 second fade-in; 0 disables warmup.");
88
89 connect(audioBuffersCheckBox, &QCheckBox::toggled, this, [this](bool checked) { audioBuffersSpinBox->setEnabled(checked); });
90
91 connect(audioFileCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
92 if (checked)
93 audioPlaylistCheckBox->setChecked(false);
95 });
96
97 connect(audioTruncCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
98 if (checked)
99 audioRepeatCheckBox->setChecked(false);
100 });
101
102 connect(audioPlaylistCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
103 if (checked)
104 audioFileCheckBox->setChecked(false);
106 });
107 connect(audioRepeatCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
108 if (checked)
109 audioTruncCheckBox->setChecked(false);
110 });
111
112 connect(audioFileBrowseButton, &QPushButton::clicked, this, [this]() {
113 QSettings appSettings("LostSideDead");
114 QString lastDir = appSettings.value("lastAudioFileDir", "").toString();
115 QString fileName = QFileDialog::getOpenFileName(this, "Select Audio File", lastDir, "Audio Files (*.wav *.mp3 *.flac *.aac *.ogg *.m4a *.wma *.mp4 *.mkv *.mov *.avi)");
116 if (!fileName.isEmpty()) {
117 appSettings.setValue("lastAudioFileDir", QFileInfo(fileName).absolutePath());
118 audioFileLineEdit->setText(fileName);
119 }
120 });
121
122 connect(audioPlaylistBrowseButton, &QPushButton::clicked, this, [this]() {
123 QSettings appSettings("LostSideDead");
124 QString lastDir = appSettings.value("lastAudioPlaylistDir", "").toString();
125 QString fileName = QFileDialog::getOpenFileName(this, "Select M3U Audio Playlist", lastDir, "M3U Playlists (*.m3u *.m3u8)");
126 if (!fileName.isEmpty()) {
127 appSettings.setValue("lastAudioPlaylistDir", QFileInfo(fileName).absolutePath());
128 audioPlaylistLineEdit->setText(fileName);
129 }
130 });
131
132 connect(audioPlaylistEditButton, &QPushButton::clicked, this, [this]() {
133 AudioPlaylistDialog editor(audioPlaylistLineEdit->text(), this);
134 if (editor.exec() != QDialog::Accepted || editor.playlistPath().isEmpty())
135 return;
136 audioPlaylistLineEdit->setText(editor.playlistPath());
137 audioPlaylistCheckBox->setChecked(true);
138 });
139
140 connect(okButton, &QPushButton::clicked, this, [this]() {
141 saveUiState();
142 accept();
143 });
144 connect(cancelButton, &QPushButton::clicked, this, [this]() {
145 saveUiState();
146 reject();
147 });
148
149 QVBoxLayout *mainLayout = new QVBoxLayout(this);
150 mainLayout->addWidget(audioReactivityCheckBox);
151 mainLayout->addWidget(audioPassThroughCheckBox);
152 mainLayout->addWidget(recordAudioCheckBox);
153
154 QHBoxLayout *recordVolumeLayout = new QHBoxLayout();
155 recordVolumeLayout->addWidget(recordVolumeLabel);
156 recordVolumeLayout->addWidget(recordVolumeSlider);
157 recordVolumeLayout->addWidget(recordVolumeValueLabel);
158 mainLayout->addLayout(recordVolumeLayout);
159
160 QHBoxLayout *channelLayout = new QHBoxLayout();
161 channelLayout->addWidget(channelLabel);
162 channelLayout->addWidget(channelSpinBox);
163 mainLayout->addLayout(channelLayout);
164
165 QHBoxLayout *sensitivityLayout = new QHBoxLayout();
166 sensitivityLayout->addWidget(sensitivityLabel);
167 sensitivityLayout->addWidget(sensitivitySlider);
168 sensitivityLayout->addWidget(sensitivityValueLabel);
169 mainLayout->addLayout(sensitivityLayout);
170
171 QHBoxLayout *inputDeviceLayout = new QHBoxLayout();
172 inputDeviceLayout->addWidget(inputDeviceLabel);
173 inputDeviceLayout->addWidget(inputDeviceComboBox);
174 mainLayout->addLayout(inputDeviceLayout);
175
176 QHBoxLayout *outputDeviceLayout = new QHBoxLayout();
177 outputDeviceLayout->addWidget(outputDeviceLabel);
178 outputDeviceLayout->addWidget(outputDeviceComboBox);
179 mainLayout->addLayout(outputDeviceLayout);
180
181 QHBoxLayout *audioFileLayout = new QHBoxLayout();
182 audioFileLayout->addWidget(audioFileLineEdit);
183 audioFileLayout->addWidget(audioFileBrowseButton);
184 mainLayout->addWidget(audioFileCheckBox);
185 mainLayout->addLayout(audioFileLayout);
186 QHBoxLayout *audioPlaylistLayout = new QHBoxLayout();
187 audioPlaylistLayout->addWidget(audioPlaylistLineEdit);
188 audioPlaylistLayout->addWidget(audioPlaylistBrowseButton);
189 audioPlaylistLayout->addWidget(audioPlaylistEditButton);
190 mainLayout->addWidget(audioPlaylistCheckBox);
191 mainLayout->addLayout(audioPlaylistLayout);
192 mainLayout->addWidget(audioTruncCheckBox);
193 mainLayout->addWidget(audioRepeatCheckBox);
194
195 QHBoxLayout *audioBuffersLayout = new QHBoxLayout();
196 audioBuffersLayout->addWidget(audioBuffersCheckBox);
197 audioBuffersLayout->addWidget(new QLabel("Frames:", this));
198 audioBuffersLayout->addWidget(audioBuffersSpinBox);
199 audioBuffersLayout->addStretch();
200 mainLayout->addLayout(audioBuffersLayout);
201
202 QHBoxLayout *audioWarmLayout = new QHBoxLayout();
203 audioWarmLayout->addWidget(new QLabel("Audio Warm Rate:", this));
204 audioWarmLayout->addWidget(audioWarmRateSpinBox);
205 audioWarmLayout->addWidget(new QLabel("(1/sec)", this));
206 audioWarmLayout->addStretch();
207 mainLayout->addLayout(audioWarmLayout);
208
209 QHBoxLayout *buttonLayout = new QHBoxLayout();
210 buttonLayout->addWidget(okButton);
211 buttonLayout->addWidget(cancelButton);
212 mainLayout->addLayout(buttonLayout);
213
214 setLayout(mainLayout);
215 loadUiState();
216}
217
219 QSettings appSettings("LostSideDead", "acmx2");
220
221 audioReactivityCheckBox->setChecked(appSettings.value("audio/enabled", false).toBool());
222 audioPassThroughCheckBox->setChecked(appSettings.value("audio/passthrough", false).toBool());
223 recordAudioCheckBox->setChecked(appSettings.value("audio/record", false).toBool());
224 recordVolumeSlider->setValue(appSettings.value("audio/record_volume", 100).toInt());
225 channelSpinBox->setValue(appSettings.value("audio/channels", 2).toInt());
226 sensitivitySlider->setValue(appSettings.value("audio/sensitivity", 10).toInt());
227
228 int inputId = appSettings.value("audio/input_device", -1).toInt();
229 int outputId = appSettings.value("audio/output_device", -1).toInt();
230 int inputIdx = inputDeviceComboBox->findData(inputId);
231 int outputIdx = outputDeviceComboBox->findData(outputId);
232 if (inputIdx >= 0) {
233 inputDeviceComboBox->setCurrentIndex(inputIdx);
234 }
235 if (outputIdx >= 0) {
236 outputDeviceComboBox->setCurrentIndex(outputIdx);
237 }
238
239 audioFileCheckBox->setChecked(appSettings.value("audio/file_enabled", false).toBool());
240 audioFileLineEdit->setText(appSettings.value("audio/file_path", "").toString());
241 audioPlaylistCheckBox->setChecked(appSettings.value("audio/playlist_enabled", false).toBool());
242 audioPlaylistLineEdit->setText(appSettings.value("audio/playlist_path", "").toString());
243 audioTruncCheckBox->setChecked(appSettings.value("audio/file_trunc", false).toBool());
244 audioRepeatCheckBox->setChecked(appSettings.value("audio/file_repeat", false).toBool());
245 audioBuffersCheckBox->setChecked(appSettings.value("audio/buffers_enabled", false).toBool());
246 audioBuffersSpinBox->setValue(appSettings.value("audio/buffers_frames", 8).toInt());
247 audioBuffersSpinBox->setEnabled(audioBuffersCheckBox->isChecked());
248 audioWarmRateSpinBox->setValue(appSettings.value("audio/warm_rate", 0.5).toDouble());
249}
250
252 QSettings appSettings("LostSideDead", "acmx2");
253 appSettings.setValue("audio/enabled", audioReactivityCheckBox->isChecked());
254 appSettings.setValue("audio/passthrough", audioPassThroughCheckBox->isChecked());
255 appSettings.setValue("audio/record", recordAudioCheckBox->isChecked());
256 appSettings.setValue("audio/record_volume", recordVolumeSlider->value());
257 appSettings.setValue("audio/channels", channelSpinBox->value());
258 appSettings.setValue("audio/sensitivity", sensitivitySlider->value());
259 appSettings.setValue("audio/input_device", inputDeviceComboBox->currentData().toInt());
260 appSettings.setValue("audio/output_device", outputDeviceComboBox->currentData().toInt());
261 appSettings.setValue("audio/file_enabled", audioFileCheckBox->isChecked());
262 appSettings.setValue("audio/file_path", audioFileLineEdit->text());
263 appSettings.setValue("audio/playlist_enabled", audioPlaylistCheckBox->isChecked());
264 appSettings.setValue("audio/playlist_path", audioPlaylistLineEdit->text());
265 appSettings.setValue("audio/file_trunc", audioTruncCheckBox->isChecked());
266 appSettings.setValue("audio/file_repeat", audioRepeatCheckBox->isChecked());
267 appSettings.setValue("audio/buffers_enabled", audioBuffersCheckBox->isChecked());
268 appSettings.setValue("audio/buffers_frames", audioBuffersSpinBox->value());
269 appSettings.setValue("audio/warm_rate", audioWarmRateSpinBox->value());
270}
271
273 const bool fileEnabled = audioFileCheckBox->isChecked();
274 const bool playlistEnabled = audioPlaylistCheckBox->isChecked();
275 const bool sourceEnabled = fileEnabled || playlistEnabled;
276
277 audioFileLineEdit->setEnabled(fileEnabled);
278 audioFileBrowseButton->setEnabled(fileEnabled);
279 audioPlaylistLineEdit->setEnabled(playlistEnabled);
280 audioPlaylistBrowseButton->setEnabled(playlistEnabled);
281 audioTruncCheckBox->setEnabled(sourceEnabled);
282 audioRepeatCheckBox->setEnabled(sourceEnabled);
283 outputDeviceComboBox->setEnabled(audioPassThroughCheckBox->isChecked());
284
285 // File and playlist audio replace microphone-specific input controls.
286 // Pass-through and output selection remain available for playback.
287 channelSpinBox->setEnabled(!sourceEnabled);
288 inputDeviceComboBox->setEnabled(!sourceEnabled);
289}
290
292 inputDeviceComboBox->addItem("Default", -1);
293 outputDeviceComboBox->addItem("Default", -1);
294
295 QProcess process;
296 process.start("acmx2", QStringList() << "--list-devices");
297 process.waitForFinished(5000);
298
299 QString output = process.readAllStandardOutput();
300 if (output.isEmpty() || process.exitCode() != 0) {
301 return;
302 }
303
304 // Parse lines like:
305 // Device 132: EMEET SmartCam C950 4K [DEFAULT INPUT]
306 // Input channels: 2
307 // Output channels: 0
308 QRegularExpression deviceRegex(R"(^\s*Device\s+(\d+):\s*(.+?)\s*$)");
309 QRegularExpression inputChRegex(R"(^\s*Input channels:\s*(\d+))");
310 QRegularExpression outputChRegex(R"(^\s*Output channels:\s*(\d+))");
311
312 QStringList lines = output.split('\n');
313 int currentId = -1;
314 QString currentName;
315 bool isDefaultInput = false;
316 bool isDefaultOutput = false;
317
318 auto flushDevice = [&]() {
319 // called when we hit the next "Device" line or end of output
320 // nothing to flush on the first call
321 };
322
323 int inputCh = 0;
324 int outputCh = 0;
325
326 for (int i = 0; i < lines.size(); ++i) {
327 const QString &line = lines[i];
328 QRegularExpressionMatch devMatch = deviceRegex.match(line);
329
330 if (devMatch.hasMatch()) {
331 // Flush previous device
332 if (currentId >= 0) {
333 if (inputCh > 0) {
334 QString label = currentName + " (" + QString::number(inputCh) + " ch)";
335 if (isDefaultInput)
336 label += " [Default]";
337 inputDeviceComboBox->addItem(label, currentId);
338 }
339 if (outputCh > 0) {
340 QString label = currentName + " (" + QString::number(outputCh) + " ch)";
341 if (isDefaultOutput)
342 label += " [Default]";
343 outputDeviceComboBox->addItem(label, currentId);
344 }
345 }
346
347 currentId = devMatch.captured(1).toInt();
348 currentName = devMatch.captured(2).trimmed();
349 isDefaultInput = currentName.contains("[DEFAULT INPUT]");
350 isDefaultOutput = currentName.contains("[DEFAULT OUTPUT]");
351 // Strip the tags from the display name
352 currentName.remove("[DEFAULT INPUT]");
353 currentName.remove("[DEFAULT OUTPUT]");
354 currentName = currentName.trimmed();
355 inputCh = 0;
356 outputCh = 0;
357 continue;
358 }
359
360 QRegularExpressionMatch inMatch = inputChRegex.match(line);
361 if (inMatch.hasMatch()) {
362 inputCh = inMatch.captured(1).toInt();
363 continue;
364 }
365
366 QRegularExpressionMatch outMatch = outputChRegex.match(line);
367 if (outMatch.hasMatch()) {
368 outputCh = outMatch.captured(1).toInt();
369 continue;
370 }
371 }
372
373 // Flush last device
374 if (currentId >= 0) {
375 if (inputCh > 0) {
376 QString label = currentName + " (" + QString::number(inputCh) + " ch)";
377 if (isDefaultInput)
378 label += " [Default]";
379 inputDeviceComboBox->addItem(label, currentId);
380 }
381 if (outputCh > 0) {
382 QString label = currentName + " (" + QString::number(outputCh) + " ch)";
383 if (isDefaultOutput)
384 label += " [Default]";
385 outputDeviceComboBox->addItem(label, currentId);
386 }
387 }
388
389 inputDeviceComboBox->setCurrentIndex(0);
390 outputDeviceComboBox->setCurrentIndex(0);
391}
392
394
396
397bool AudioSettings::isRecordAudioEnabled() const { return recordAudioCheckBox->isChecked(); }
398
399double AudioSettings::getRecordVolume() const { return recordVolumeSlider->value() / 100.0; }
400
402
403double AudioSettings::getSensitivity() const { return sensitivitySlider->value() / 10.0; }
404
405int AudioSettings::getInputDeviceIndex() const { return inputDeviceComboBox->currentData().toInt(); }
406
407int AudioSettings::getOutputDeviceIndex() const { return outputDeviceComboBox->currentData().toInt(); }
408
409bool AudioSettings::isAudioFileEnabled() const { return (audioFileCheckBox->isChecked() && !audioFileLineEdit->text().isEmpty()) || (audioPlaylistCheckBox->isChecked() && !audioPlaylistLineEdit->text().isEmpty()); }
410
412
413bool AudioSettings::isAudioPlaylistEnabled() const { return audioPlaylistCheckBox->isChecked() && !audioPlaylistLineEdit->text().isEmpty(); }
414
416
417bool AudioSettings::isAudioTruncEnabled() const { return audioTruncCheckBox->isChecked(); }
418
419bool AudioSettings::isAudioRepeatEnabled() const { return audioRepeatCheckBox->isChecked(); }
420
422
424
425double AudioSettings::getAudioWarmRate() const { return audioWarmRateSpinBox->value(); }
Qt6 dialog for configuring audio reactivity settings.
Editor for creating and modifying ordered M3U audio playlists.
QString playlistPath() const
Return the playlist most recently opened or saved.
QString getAudioFilePath() const
Get the effective audio file or M3U playlist path.
QPushButton * audioPlaylistEditButton
Opens the M3U playlist editor.
int getNumberOfChannels() const
QSpinBox * audioBuffersSpinBox
Number of spectrum history frames.
QPushButton * okButton
QCheckBox * audioReactivityCheckBox
QPushButton * cancelButton
QComboBox * inputDeviceComboBox
double getSensitivity() const
QPushButton * audioPlaylistBrowseButton
Opens a file dialog to choose an M3U playlist.
QPushButton * audioFileBrowseButton
Opens a file dialog to choose an audio file.
int getAudioBufferFrames() const
Number of spectrum history frames requested.
QCheckBox * audioRepeatCheckBox
Restart playback when the audio file ends.
QCheckBox * audioTruncCheckBox
Stop playback when the audio file ends.
QCheckBox * recordAudioCheckBox
bool isAudioFileEnabled() const
Check whether a file or M3U audio source is selected and has a path.
int getInputDeviceIndex() const
bool isAudioReactivityEnabled() const
QLineEdit * audioFileLineEdit
Displays the selected audio file path.
bool isRecordAudioEnabled() const
QString getAudioPlaylistPath() const
Get the selected M3U playlist path.
QCheckBox * audioPassThroughCheckBox
bool isAudioPlaylistEnabled() const
Check whether the M3U playlist overrides the single audio file.
bool isAudioPassThroughEnabled() const
bool isAudioRepeatEnabled() const
Check whether file audio should restart when it reaches the end.
AudioSettings(QWidget *parent=nullptr)
void populateAudioDevices()
double getRecordVolume() const
QCheckBox * audioFileCheckBox
Toggle file-based audio input.
QCheckBox * audioPlaylistCheckBox
Use an M3U playlist instead of the audio file.
QCheckBox * audioBuffersCheckBox
Enable spectrum history buffer CLI option.
int getOutputDeviceIndex() const
double getAudioWarmRate() const
Audio startup warmup rate in 1/sec.
QComboBox * outputDeviceComboBox
QDoubleSpinBox * audioWarmRateSpinBox
Startup warmup rate for audio intensity ramp.
bool isAudioTruncEnabled() const
Check whether "stop when audio ends" is selected.
bool isAudioBuffersEnabled() const
Check whether spectrum history buffers are enabled.
QSpinBox * channelSpinBox
QLineEdit * audioPlaylistLineEdit
Displays the selected M3U playlist path.
QSlider * sensitivitySlider
QSlider * recordVolumeSlider
void updateAudioSourceControls()
void applyCustomStyleIfEnabled(QWidget *widget)