🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1440 lines
52 KiB

  1. #include "config.h"
  2. #include "mainwindow.h"
  3. #include <iostream>
  4. #include <cmath>
  5. #include <QFileDialog>
  6. #include <QMessageBox>
  7. #include <QCloseEvent>
  8. #include <QSettings>
  9. #include <QtGlobal>
  10. #include "ui_mainwindow.h"
  11. #include "verstr.h"
  12. #ifdef _WIN32
  13. #include <windows.h>
  14. #include <shlobj.h>
  15. #endif
  16. namespace {
  17. static const struct {
  18. char backend_name[16];
  19. char full_string[32];
  20. } backendList[] = {
  21. #ifdef HAVE_JACK
  22. { "jack", "JACK" },
  23. #endif
  24. #ifdef HAVE_PIPEWIRE
  25. { "pipewire", "PipeWire" },
  26. #endif
  27. #ifdef HAVE_PULSEAUDIO
  28. { "pulse", "PulseAudio" },
  29. #endif
  30. #ifdef HAVE_ALSA
  31. { "alsa", "ALSA" },
  32. #endif
  33. #ifdef HAVE_COREAUDIO
  34. { "core", "CoreAudio" },
  35. #endif
  36. #ifdef HAVE_OSS
  37. { "oss", "OSS" },
  38. #endif
  39. #ifdef HAVE_SOLARIS
  40. { "solaris", "Solaris" },
  41. #endif
  42. #ifdef HAVE_SNDIO
  43. { "sndio", "SoundIO" },
  44. #endif
  45. #ifdef HAVE_QSA
  46. { "qsa", "QSA" },
  47. #endif
  48. #ifdef HAVE_WASAPI
  49. { "wasapi", "WASAPI" },
  50. #endif
  51. #ifdef HAVE_DSOUND
  52. { "dsound", "DirectSound" },
  53. #endif
  54. #ifdef HAVE_WINMM
  55. { "winmm", "Windows Multimedia" },
  56. #endif
  57. #ifdef HAVE_PORTAUDIO
  58. { "port", "PortAudio" },
  59. #endif
  60. #ifdef HAVE_OPENSL
  61. { "opensl", "OpenSL" },
  62. #endif
  63. { "null", "Null Output" },
  64. #ifdef HAVE_WAVE
  65. { "wave", "Wave Writer" },
  66. #endif
  67. { "", "" }
  68. };
  69. static const struct NameValuePair {
  70. const char name[64];
  71. const char value[16];
  72. } speakerModeList[] = {
  73. { "Autodetect", "" },
  74. { "Mono", "mono" },
  75. { "Stereo", "stereo" },
  76. { "Quadraphonic", "quad" },
  77. { "5.1 Surround", "surround51" },
  78. { "6.1 Surround", "surround61" },
  79. { "7.1 Surround", "surround71" },
  80. { "Ambisonic, 1st Order", "ambi1" },
  81. { "Ambisonic, 2nd Order", "ambi2" },
  82. { "Ambisonic, 3rd Order", "ambi3" },
  83. { "", "" }
  84. }, sampleTypeList[] = {
  85. { "Autodetect", "" },
  86. { "8-bit int", "int8" },
  87. { "8-bit uint", "uint8" },
  88. { "16-bit int", "int16" },
  89. { "16-bit uint", "uint16" },
  90. { "32-bit int", "int32" },
  91. { "32-bit uint", "uint32" },
  92. { "32-bit float", "float32" },
  93. { "", "" }
  94. }, resamplerList[] = {
  95. { "Point", "point" },
  96. { "Linear", "linear" },
  97. { "Default (Linear)", "" },
  98. { "Cubic Spline", "cubic" },
  99. { "11th order Sinc (fast)", "fast_bsinc12" },
  100. { "11th order Sinc", "bsinc12" },
  101. { "23rd order Sinc (fast)", "fast_bsinc24" },
  102. { "23rd order Sinc", "bsinc24" },
  103. { "", "" }
  104. }, stereoModeList[] = {
  105. { "Autodetect", "" },
  106. { "Speakers", "speakers" },
  107. { "Headphones", "headphones" },
  108. { "", "" }
  109. }, stereoEncList[] = {
  110. { "Default", "" },
  111. { "Pan Pot", "panpot" },
  112. { "UHJ", "uhj" },
  113. { "Binaural", "hrtf" },
  114. { "", "" }
  115. }, ambiFormatList[] = {
  116. { "Default", "" },
  117. { "AmbiX (ACN, SN3D)", "ambix" },
  118. { "Furse-Malham", "fuma" },
  119. { "ACN, N3D", "acn+n3d" },
  120. { "ACN, FuMa", "acn+fuma" },
  121. { "", "" }
  122. }, hrtfModeList[] = {
  123. { "1st Order Ambisonic", "ambi1" },
  124. { "2nd Order Ambisonic", "ambi2" },
  125. { "3rd Order Ambisonic", "ambi3" },
  126. { "Default (Full)", "" },
  127. { "Full", "full" },
  128. { "", "" }
  129. };
  130. static QString getDefaultConfigName()
  131. {
  132. #ifdef Q_OS_WIN32
  133. static const char fname[] = "alsoft.ini";
  134. auto get_appdata_path = []() noexcept -> QString
  135. {
  136. WCHAR buffer[MAX_PATH];
  137. if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
  138. return QString::fromWCharArray(buffer);
  139. return QString();
  140. };
  141. QString base = get_appdata_path();
  142. #else
  143. static const char fname[] = "alsoft.conf";
  144. QByteArray base = qgetenv("XDG_CONFIG_HOME");
  145. if(base.isEmpty())
  146. {
  147. base = qgetenv("HOME");
  148. if(base.isEmpty() == false)
  149. base += "/.config";
  150. }
  151. #endif
  152. if(base.isEmpty() == false)
  153. return base +'/'+ fname;
  154. return fname;
  155. }
  156. static QString getBaseDataPath()
  157. {
  158. #ifdef Q_OS_WIN32
  159. auto get_appdata_path = []() noexcept -> QString
  160. {
  161. WCHAR buffer[MAX_PATH];
  162. if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
  163. return QString::fromWCharArray(buffer);
  164. return QString();
  165. };
  166. QString base = get_appdata_path();
  167. #else
  168. QByteArray base = qgetenv("XDG_DATA_HOME");
  169. if(base.isEmpty())
  170. {
  171. base = qgetenv("HOME");
  172. if(!base.isEmpty())
  173. base += "/.local/share";
  174. }
  175. #endif
  176. return base;
  177. }
  178. static QStringList getAllDataPaths(const QString &append)
  179. {
  180. QStringList list;
  181. list.append(getBaseDataPath());
  182. #ifdef Q_OS_WIN32
  183. // TODO: Common AppData path
  184. #else
  185. QString paths = qgetenv("XDG_DATA_DIRS");
  186. if(paths.isEmpty())
  187. paths = "/usr/local/share/:/usr/share/";
  188. #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
  189. list += paths.split(QChar(':'), Qt::SkipEmptyParts);
  190. #else
  191. list += paths.split(QChar(':'), QString::SkipEmptyParts);
  192. #endif
  193. #endif
  194. QStringList::iterator iter = list.begin();
  195. while(iter != list.end())
  196. {
  197. if(iter->isEmpty())
  198. iter = list.erase(iter);
  199. else
  200. {
  201. iter->append(append);
  202. iter++;
  203. }
  204. }
  205. return list;
  206. }
  207. template<size_t N>
  208. static QString getValueFromName(const NameValuePair (&list)[N], const QString &str)
  209. {
  210. for(size_t i = 0;i < N-1;i++)
  211. {
  212. if(str == list[i].name)
  213. return list[i].value;
  214. }
  215. return QString{};
  216. }
  217. template<size_t N>
  218. static QString getNameFromValue(const NameValuePair (&list)[N], const QString &str)
  219. {
  220. for(size_t i = 0;i < N-1;i++)
  221. {
  222. if(str == list[i].value)
  223. return list[i].name;
  224. }
  225. return QString{};
  226. }
  227. Qt::CheckState getCheckState(const QVariant &var)
  228. {
  229. if(var.isNull())
  230. return Qt::PartiallyChecked;
  231. if(var.toBool())
  232. return Qt::Checked;
  233. return Qt::Unchecked;
  234. }
  235. QString getCheckValue(const QCheckBox *checkbox)
  236. {
  237. const Qt::CheckState state{checkbox->checkState()};
  238. if(state == Qt::Checked)
  239. return QString{"true"};
  240. if(state == Qt::Unchecked)
  241. return QString{"false"};
  242. return QString{};
  243. }
  244. }
  245. MainWindow::MainWindow(QWidget *parent) :
  246. QMainWindow(parent),
  247. ui(new Ui::MainWindow),
  248. mPeriodSizeValidator(nullptr),
  249. mPeriodCountValidator(nullptr),
  250. mSourceCountValidator(nullptr),
  251. mEffectSlotValidator(nullptr),
  252. mSourceSendValidator(nullptr),
  253. mSampleRateValidator(nullptr),
  254. mJackBufferValidator(nullptr),
  255. mNeedsSave(false)
  256. {
  257. ui->setupUi(this);
  258. for(int i = 0;speakerModeList[i].name[0];i++)
  259. ui->channelConfigCombo->addItem(speakerModeList[i].name);
  260. ui->channelConfigCombo->adjustSize();
  261. for(int i = 0;sampleTypeList[i].name[0];i++)
  262. ui->sampleFormatCombo->addItem(sampleTypeList[i].name);
  263. ui->sampleFormatCombo->adjustSize();
  264. for(int i = 0;stereoModeList[i].name[0];i++)
  265. ui->stereoModeCombo->addItem(stereoModeList[i].name);
  266. ui->stereoModeCombo->adjustSize();
  267. for(int i = 0;stereoEncList[i].name[0];i++)
  268. ui->stereoEncodingComboBox->addItem(stereoEncList[i].name);
  269. ui->stereoEncodingComboBox->adjustSize();
  270. for(int i = 0;ambiFormatList[i].name[0];i++)
  271. ui->ambiFormatComboBox->addItem(ambiFormatList[i].name);
  272. ui->ambiFormatComboBox->adjustSize();
  273. int count;
  274. for(count = 0;resamplerList[count].name[0];count++) {
  275. }
  276. ui->resamplerSlider->setRange(0, count-1);
  277. for(count = 0;hrtfModeList[count].name[0];count++) {
  278. }
  279. ui->hrtfmodeSlider->setRange(0, count-1);
  280. ui->hrtfStateComboBox->adjustSize();
  281. #if !defined(HAVE_NEON) && !defined(HAVE_SSE)
  282. ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60);
  283. #else
  284. ui->cpuExtDisabledLabel->setVisible(false);
  285. #endif
  286. #ifndef HAVE_NEON
  287. #ifndef HAVE_SSE4_1
  288. #ifndef HAVE_SSE3
  289. #ifndef HAVE_SSE2
  290. #ifndef HAVE_SSE
  291. ui->enableSSECheckBox->setVisible(false);
  292. #endif /* !SSE */
  293. ui->enableSSE2CheckBox->setVisible(false);
  294. #endif /* !SSE2 */
  295. ui->enableSSE3CheckBox->setVisible(false);
  296. #endif /* !SSE3 */
  297. ui->enableSSE41CheckBox->setVisible(false);
  298. #endif /* !SSE4.1 */
  299. ui->enableNeonCheckBox->setVisible(false);
  300. #else /* !Neon */
  301. #ifndef HAVE_SSE4_1
  302. #ifndef HAVE_SSE3
  303. #ifndef HAVE_SSE2
  304. #ifndef HAVE_SSE
  305. ui->enableNeonCheckBox->move(ui->enableNeonCheckBox->x(), ui->enableNeonCheckBox->y() - 30);
  306. ui->enableSSECheckBox->setVisible(false);
  307. #endif /* !SSE */
  308. ui->enableSSE2CheckBox->setVisible(false);
  309. #endif /* !SSE2 */
  310. ui->enableSSE3CheckBox->setVisible(false);
  311. #endif /* !SSE3 */
  312. ui->enableSSE41CheckBox->setVisible(false);
  313. #endif /* !SSE4.1 */
  314. #endif
  315. mPeriodSizeValidator = new QIntValidator{64, 8192, this};
  316. ui->periodSizeEdit->setValidator(mPeriodSizeValidator);
  317. mPeriodCountValidator = new QIntValidator{2, 16, this};
  318. ui->periodCountEdit->setValidator(mPeriodCountValidator);
  319. mSourceCountValidator = new QIntValidator{0, 4096, this};
  320. ui->srcCountLineEdit->setValidator(mSourceCountValidator);
  321. mEffectSlotValidator = new QIntValidator{0, 64, this};
  322. ui->effectSlotLineEdit->setValidator(mEffectSlotValidator);
  323. mSourceSendValidator = new QIntValidator{0, 16, this};
  324. ui->srcSendLineEdit->setValidator(mSourceSendValidator);
  325. mSampleRateValidator = new QIntValidator{8000, 192000, this};
  326. ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator);
  327. mJackBufferValidator = new QIntValidator{0, 8192, this};
  328. ui->jackBufferSizeLine->setValidator(mJackBufferValidator);
  329. connect(ui->actionLoad, &QAction::triggered, this, &MainWindow::loadConfigFromFile);
  330. connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::saveConfigAsFile);
  331. connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutPage);
  332. connect(ui->closeCancelButton, &QPushButton::clicked, this, &MainWindow::cancelCloseAction);
  333. connect(ui->applyButton, &QPushButton::clicked, this, &MainWindow::saveCurrentConfig);
  334. auto qcb_cicint = static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged);
  335. connect(ui->channelConfigCombo, qcb_cicint, this, &MainWindow::enableApplyButton);
  336. connect(ui->sampleFormatCombo, qcb_cicint, this, &MainWindow::enableApplyButton);
  337. connect(ui->stereoModeCombo, qcb_cicint, this, &MainWindow::enableApplyButton);
  338. connect(ui->sampleRateCombo, qcb_cicint, this, &MainWindow::enableApplyButton);
  339. connect(ui->sampleRateCombo, &QComboBox::editTextChanged, this, &MainWindow::enableApplyButton);
  340. connect(ui->resamplerSlider, &QSlider::valueChanged, this, &MainWindow::updateResamplerLabel);
  341. connect(ui->periodSizeSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodSizeEdit);
  342. connect(ui->periodSizeEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodSizeSlider);
  343. connect(ui->periodCountSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodCountEdit);
  344. connect(ui->periodCountEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodCountSlider);
  345. connect(ui->stereoEncodingComboBox, qcb_cicint, this, &MainWindow::enableApplyButton);
  346. connect(ui->ambiFormatComboBox, qcb_cicint, this, &MainWindow::enableApplyButton);
  347. connect(ui->outputLimiterCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  348. connect(ui->outputDitherCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  349. connect(ui->decoderHQModeCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  350. connect(ui->decoderDistCompCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  351. connect(ui->decoderNFEffectsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  352. auto qdsb_vcd = static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged);
  353. connect(ui->decoderNFRefDelaySpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton);
  354. connect(ui->decoderQuadLineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  355. connect(ui->decoderQuadButton, &QPushButton::clicked, this, &MainWindow::selectQuadDecoderFile);
  356. connect(ui->decoder51LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  357. connect(ui->decoder51Button, &QPushButton::clicked, this, &MainWindow::select51DecoderFile);
  358. connect(ui->decoder61LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  359. connect(ui->decoder61Button, &QPushButton::clicked, this, &MainWindow::select61DecoderFile);
  360. connect(ui->decoder71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  361. connect(ui->decoder71Button, &QPushButton::clicked, this, &MainWindow::select71DecoderFile);
  362. connect(ui->preferredHrtfComboBox, qcb_cicint, this, &MainWindow::enableApplyButton);
  363. connect(ui->hrtfStateComboBox, qcb_cicint, this, &MainWindow::enableApplyButton);
  364. connect(ui->hrtfmodeSlider, &QSlider::valueChanged, this, &MainWindow::updateHrtfModeLabel);
  365. connect(ui->hrtfAddButton, &QPushButton::clicked, this, &MainWindow::addHrtfFile);
  366. connect(ui->hrtfRemoveButton, &QPushButton::clicked, this, &MainWindow::removeHrtfFile);
  367. connect(ui->hrtfFileList, &QListWidget::itemSelectionChanged, this, &MainWindow::updateHrtfRemoveButton);
  368. connect(ui->defaultHrtfPathsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  369. connect(ui->srcCountLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton);
  370. connect(ui->srcSendLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton);
  371. connect(ui->effectSlotLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton);
  372. connect(ui->enableSSECheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  373. connect(ui->enableSSE2CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  374. connect(ui->enableSSE3CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  375. connect(ui->enableSSE41CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  376. connect(ui->enableNeonCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  377. ui->enabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu);
  378. connect(ui->enabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showEnabledBackendMenu);
  379. ui->disabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu);
  380. connect(ui->disabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showDisabledBackendMenu);
  381. connect(ui->backendCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  382. connect(ui->defaultReverbComboBox, qcb_cicint, this, &MainWindow::enableApplyButton);
  383. connect(ui->enableEaxReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  384. connect(ui->enableStdReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  385. connect(ui->enableAutowahCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  386. connect(ui->enableChorusCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  387. connect(ui->enableCompressorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  388. connect(ui->enableDistortionCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  389. connect(ui->enableEchoCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  390. connect(ui->enableEqualizerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  391. connect(ui->enableFlangerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  392. connect(ui->enableFrequencyShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  393. connect(ui->enableModulatorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  394. connect(ui->enableDedicatedCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  395. connect(ui->enablePitchShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  396. connect(ui->enableVocalMorpherCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  397. connect(ui->pulseAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  398. connect(ui->pulseAllowMovesCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  399. connect(ui->pulseFixRateCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  400. connect(ui->pulseAdjLatencyCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  401. connect(ui->pwireAssumeAudioCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  402. connect(ui->jackAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  403. connect(ui->jackConnectPortsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  404. connect(ui->jackRtMixCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  405. connect(ui->jackBufferSizeSlider, &QSlider::valueChanged, this, &MainWindow::updateJackBufferSizeEdit);
  406. connect(ui->jackBufferSizeLine, &QLineEdit::editingFinished, this, &MainWindow::updateJackBufferSizeSlider);
  407. connect(ui->alsaDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  408. connect(ui->alsaDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  409. connect(ui->alsaResamplerCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  410. connect(ui->alsaMmapCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  411. connect(ui->ossDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  412. connect(ui->ossPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectOSSPlayback);
  413. connect(ui->ossDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  414. connect(ui->ossCapturePushButton, &QPushButton::clicked, this, &MainWindow::selectOSSCapture);
  415. connect(ui->solarisDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  416. connect(ui->solarisPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectSolarisPlayback);
  417. connect(ui->waveOutputLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
  418. connect(ui->waveOutputButton, &QPushButton::clicked, this, &MainWindow::selectWaveOutput);
  419. connect(ui->waveBFormatCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
  420. ui->backendListWidget->setCurrentRow(0);
  421. ui->tabWidget->setCurrentIndex(0);
  422. for(int i = 1;i < ui->backendListWidget->count();i++)
  423. ui->backendListWidget->setRowHidden(i, true);
  424. for(int i = 0;backendList[i].backend_name[0];i++)
  425. {
  426. QList<QListWidgetItem*> items = ui->backendListWidget->findItems(
  427. backendList[i].full_string, Qt::MatchFixedString);
  428. foreach(QListWidgetItem *item, items)
  429. item->setHidden(false);
  430. }
  431. loadConfig(getDefaultConfigName());
  432. }
  433. MainWindow::~MainWindow()
  434. {
  435. delete ui;
  436. delete mPeriodSizeValidator;
  437. delete mPeriodCountValidator;
  438. delete mSourceCountValidator;
  439. delete mEffectSlotValidator;
  440. delete mSourceSendValidator;
  441. delete mSampleRateValidator;
  442. delete mJackBufferValidator;
  443. }
  444. void MainWindow::closeEvent(QCloseEvent *event)
  445. {
  446. if(!mNeedsSave)
  447. event->accept();
  448. else
  449. {
  450. QMessageBox::StandardButton btn = QMessageBox::warning(this,
  451. tr("Apply changes?"), tr("Save changes before quitting?"),
  452. QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel);
  453. if(btn == QMessageBox::Save)
  454. saveCurrentConfig();
  455. if(btn == QMessageBox::Cancel)
  456. event->ignore();
  457. else
  458. event->accept();
  459. }
  460. }
  461. void MainWindow::cancelCloseAction()
  462. {
  463. mNeedsSave = false;
  464. close();
  465. }
  466. void MainWindow::showAboutPage()
  467. {
  468. QMessageBox::information(this, tr("About"),
  469. tr("OpenAL Soft Configuration Utility.\nBuilt for OpenAL Soft library version ") +
  470. GetVersionString());
  471. }
  472. QStringList MainWindow::collectHrtfs()
  473. {
  474. QStringList ret;
  475. QStringList processed;
  476. for(int i = 0;i < ui->hrtfFileList->count();i++)
  477. {
  478. QDir dir(ui->hrtfFileList->item(i)->text());
  479. QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name);
  480. foreach(const QString &fname, fnames)
  481. {
  482. if(!fname.endsWith(".mhr", Qt::CaseInsensitive))
  483. continue;
  484. QString fullname{dir.absoluteFilePath(fname)};
  485. if(processed.contains(fullname))
  486. continue;
  487. processed.push_back(fullname);
  488. QString name{fname.left(fname.length()-4)};
  489. if(!ret.contains(name))
  490. ret.push_back(name);
  491. else
  492. {
  493. size_t i{2};
  494. do {
  495. QString s = name+" #"+QString::number(i);
  496. if(!ret.contains(s))
  497. {
  498. ret.push_back(s);
  499. break;
  500. }
  501. ++i;
  502. } while(1);
  503. }
  504. }
  505. }
  506. if(ui->defaultHrtfPathsCheckBox->isChecked())
  507. {
  508. QStringList paths = getAllDataPaths("/openal/hrtf");
  509. foreach(const QString &name, paths)
  510. {
  511. QDir dir{name};
  512. QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)};
  513. foreach(const QString &fname, fnames)
  514. {
  515. if(!fname.endsWith(".mhr", Qt::CaseInsensitive))
  516. continue;
  517. QString fullname{dir.absoluteFilePath(fname)};
  518. if(processed.contains(fullname))
  519. continue;
  520. processed.push_back(fullname);
  521. QString name{fname.left(fname.length()-4)};
  522. if(!ret.contains(name))
  523. ret.push_back(name);
  524. else
  525. {
  526. size_t i{2};
  527. do {
  528. QString s{name+" #"+QString::number(i)};
  529. if(!ret.contains(s))
  530. {
  531. ret.push_back(s);
  532. break;
  533. }
  534. ++i;
  535. } while(1);
  536. }
  537. }
  538. }
  539. #ifdef ALSOFT_EMBED_HRTF_DATA
  540. ret.push_back("Built-In HRTF");
  541. #endif
  542. }
  543. return ret;
  544. }
  545. void MainWindow::loadConfigFromFile()
  546. {
  547. QString fname = QFileDialog::getOpenFileName(this, tr("Select Files"));
  548. if(fname.isEmpty() == false)
  549. loadConfig(fname);
  550. }
  551. void MainWindow::loadConfig(const QString &fname)
  552. {
  553. QSettings settings{fname, QSettings::IniFormat};
  554. QString sampletype = settings.value("sample-type").toString();
  555. ui->sampleFormatCombo->setCurrentIndex(0);
  556. if(sampletype.isEmpty() == false)
  557. {
  558. QString str{getNameFromValue(sampleTypeList, sampletype)};
  559. if(!str.isEmpty())
  560. {
  561. const int j{ui->sampleFormatCombo->findText(str)};
  562. if(j > 0) ui->sampleFormatCombo->setCurrentIndex(j);
  563. }
  564. }
  565. QString channelconfig{settings.value("channels").toString()};
  566. ui->channelConfigCombo->setCurrentIndex(0);
  567. if(channelconfig.isEmpty() == false)
  568. {
  569. if(channelconfig == "surround51rear")
  570. channelconfig = "surround51";
  571. QString str{getNameFromValue(speakerModeList, channelconfig)};
  572. if(!str.isEmpty())
  573. {
  574. const int j{ui->channelConfigCombo->findText(str)};
  575. if(j > 0) ui->channelConfigCombo->setCurrentIndex(j);
  576. }
  577. }
  578. QString srate{settings.value("frequency").toString()};
  579. if(srate.isEmpty())
  580. ui->sampleRateCombo->setCurrentIndex(0);
  581. else
  582. {
  583. ui->sampleRateCombo->lineEdit()->clear();
  584. ui->sampleRateCombo->lineEdit()->insert(srate);
  585. }
  586. ui->srcCountLineEdit->clear();
  587. ui->srcCountLineEdit->insert(settings.value("sources").toString());
  588. ui->effectSlotLineEdit->clear();
  589. ui->effectSlotLineEdit->insert(settings.value("slots").toString());
  590. ui->srcSendLineEdit->clear();
  591. ui->srcSendLineEdit->insert(settings.value("sends").toString());
  592. QString resampler = settings.value("resampler").toString().trimmed();
  593. ui->resamplerSlider->setValue(2);
  594. ui->resamplerLabel->setText(resamplerList[2].name);
  595. /* The "sinc4" and "sinc8" resamplers are no longer supported. Use "cubic"
  596. * as a fallback.
  597. */
  598. if(resampler == "sinc4" || resampler == "sinc8")
  599. resampler = "cubic";
  600. /* The "bsinc" resampler name is an alias for "bsinc12". */
  601. else if(resampler == "bsinc")
  602. resampler = "bsinc12";
  603. for(int i = 0;resamplerList[i].name[0];i++)
  604. {
  605. if(resampler == resamplerList[i].value)
  606. {
  607. ui->resamplerSlider->setValue(i);
  608. ui->resamplerLabel->setText(resamplerList[i].name);
  609. break;
  610. }
  611. }
  612. QString stereomode = settings.value("stereo-mode").toString().trimmed();
  613. ui->stereoModeCombo->setCurrentIndex(0);
  614. if(stereomode.isEmpty() == false)
  615. {
  616. QString str{getNameFromValue(stereoModeList, stereomode)};
  617. if(!str.isEmpty())
  618. {
  619. const int j{ui->stereoModeCombo->findText(str)};
  620. if(j > 0) ui->stereoModeCombo->setCurrentIndex(j);
  621. }
  622. }
  623. int periodsize{settings.value("period_size").toInt()};
  624. ui->periodSizeEdit->clear();
  625. if(periodsize >= 64)
  626. {
  627. ui->periodSizeEdit->insert(QString::number(periodsize));
  628. updatePeriodSizeSlider();
  629. }
  630. int periodcount{settings.value("periods").toInt()};
  631. ui->periodCountEdit->clear();
  632. if(periodcount >= 2)
  633. {
  634. ui->periodCountEdit->insert(QString::number(periodcount));
  635. updatePeriodCountSlider();
  636. }
  637. ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value("output-limiter")));
  638. ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value("dither")));
  639. QString stereopan{settings.value("stereo-encoding").toString()};
  640. ui->stereoEncodingComboBox->setCurrentIndex(0);
  641. if(stereopan.isEmpty() == false)
  642. {
  643. QString str{getNameFromValue(stereoEncList, stereopan)};
  644. if(!str.isEmpty())
  645. {
  646. const int j{ui->stereoEncodingComboBox->findText(str)};
  647. if(j > 0) ui->stereoEncodingComboBox->setCurrentIndex(j);
  648. }
  649. }
  650. QString ambiformat{settings.value("ambi-format").toString()};
  651. ui->ambiFormatComboBox->setCurrentIndex(0);
  652. if(ambiformat.isEmpty() == false)
  653. {
  654. QString str{getNameFromValue(ambiFormatList, ambiformat)};
  655. if(!str.isEmpty())
  656. {
  657. const int j{ui->ambiFormatComboBox->findText(str)};
  658. if(j > 0) ui->ambiFormatComboBox->setCurrentIndex(j);
  659. }
  660. }
  661. ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value("decoder/hq-mode")));
  662. ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp")));
  663. ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc")));
  664. double refdelay{settings.value("decoder/nfc-ref-delay", 0.0).toDouble()};
  665. ui->decoderNFRefDelaySpinBox->setValue(refdelay);
  666. ui->decoderQuadLineEdit->setText(settings.value("decoder/quad").toString());
  667. ui->decoder51LineEdit->setText(settings.value("decoder/surround51").toString());
  668. ui->decoder61LineEdit->setText(settings.value("decoder/surround61").toString());
  669. ui->decoder71LineEdit->setText(settings.value("decoder/surround71").toString());
  670. QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()};
  671. if(disabledCpuExts.size() == 1)
  672. disabledCpuExts = disabledCpuExts[0].split(QChar(','));
  673. for(QString &name : disabledCpuExts)
  674. name = name.trimmed();
  675. ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains("sse", Qt::CaseInsensitive));
  676. ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains("sse2", Qt::CaseInsensitive));
  677. ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains("sse3", Qt::CaseInsensitive));
  678. ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains("sse4.1", Qt::CaseInsensitive));
  679. ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains("neon", Qt::CaseInsensitive));
  680. QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()};
  681. ui->hrtfmodeSlider->setValue(2);
  682. ui->hrtfmodeLabel->setText(hrtfModeList[3].name);
  683. /* The "basic" mode name is no longer supported. Use "ambi2" instead. */
  684. if(hrtfmode == "basic")
  685. hrtfmode = "ambi2";
  686. for(int i = 0;hrtfModeList[i].name[0];i++)
  687. {
  688. if(hrtfmode == hrtfModeList[i].value)
  689. {
  690. ui->hrtfmodeSlider->setValue(i);
  691. ui->hrtfmodeLabel->setText(hrtfModeList[i].name);
  692. break;
  693. }
  694. }
  695. QStringList hrtf_paths{settings.value("hrtf-paths").toStringList()};
  696. if(hrtf_paths.size() == 1)
  697. hrtf_paths = hrtf_paths[0].split(QChar(','));
  698. for(QString &name : hrtf_paths)
  699. name = name.trimmed();
  700. if(!hrtf_paths.empty() && !hrtf_paths.back().isEmpty())
  701. ui->defaultHrtfPathsCheckBox->setCheckState(Qt::Unchecked);
  702. else
  703. {
  704. hrtf_paths.removeAll(QString());
  705. ui->defaultHrtfPathsCheckBox->setCheckState(Qt::Checked);
  706. }
  707. hrtf_paths.removeDuplicates();
  708. ui->hrtfFileList->clear();
  709. ui->hrtfFileList->addItems(hrtf_paths);
  710. updateHrtfRemoveButton();
  711. QString hrtfstate{settings.value("hrtf").toString().toLower()};
  712. if(hrtfstate == "true")
  713. ui->hrtfStateComboBox->setCurrentIndex(1);
  714. else if(hrtfstate == "false")
  715. ui->hrtfStateComboBox->setCurrentIndex(2);
  716. else
  717. ui->hrtfStateComboBox->setCurrentIndex(0);
  718. ui->preferredHrtfComboBox->clear();
  719. ui->preferredHrtfComboBox->addItem("- Any -");
  720. if(ui->defaultHrtfPathsCheckBox->isChecked())
  721. {
  722. QStringList hrtfs{collectHrtfs()};
  723. foreach(const QString &name, hrtfs)
  724. ui->preferredHrtfComboBox->addItem(name);
  725. }
  726. QString defaulthrtf{settings.value("default-hrtf").toString()};
  727. ui->preferredHrtfComboBox->setCurrentIndex(0);
  728. if(defaulthrtf.isEmpty() == false)
  729. {
  730. int i{ui->preferredHrtfComboBox->findText(defaulthrtf)};
  731. if(i > 0)
  732. ui->preferredHrtfComboBox->setCurrentIndex(i);
  733. else
  734. {
  735. i = ui->preferredHrtfComboBox->count();
  736. ui->preferredHrtfComboBox->addItem(defaulthrtf);
  737. ui->preferredHrtfComboBox->setCurrentIndex(i);
  738. }
  739. }
  740. ui->preferredHrtfComboBox->adjustSize();
  741. ui->enabledBackendList->clear();
  742. ui->disabledBackendList->clear();
  743. QStringList drivers{settings.value("drivers").toStringList()};
  744. if(drivers.size() == 0)
  745. ui->backendCheckBox->setChecked(true);
  746. else
  747. {
  748. if(drivers.size() == 1)
  749. drivers = drivers[0].split(QChar(','));
  750. for(QString &name : drivers)
  751. {
  752. name = name.trimmed();
  753. /* Convert "mmdevapi" references to "wasapi" for backwards
  754. * compatibility.
  755. */
  756. if(name == "-mmdevapi")
  757. name = "-wasapi";
  758. else if(name == "mmdevapi")
  759. name = "wasapi";
  760. }
  761. bool lastWasEmpty = false;
  762. foreach(const QString &backend, drivers)
  763. {
  764. lastWasEmpty = backend.isEmpty();
  765. if(lastWasEmpty) continue;
  766. if(!backend.startsWith(QChar('-')))
  767. for(int j = 0;backendList[j].backend_name[0];j++)
  768. {
  769. if(backend == backendList[j].backend_name)
  770. {
  771. ui->enabledBackendList->addItem(backendList[j].full_string);
  772. break;
  773. }
  774. }
  775. else if(backend.size() > 1)
  776. {
  777. QStringRef backendref{backend.rightRef(backend.size()-1)};
  778. for(int j = 0;backendList[j].backend_name[0];j++)
  779. {
  780. if(backendref == backendList[j].backend_name)
  781. {
  782. ui->disabledBackendList->addItem(backendList[j].full_string);
  783. break;
  784. }
  785. }
  786. }
  787. }
  788. ui->backendCheckBox->setChecked(lastWasEmpty);
  789. }
  790. QString defaultreverb{settings.value("default-reverb").toString().toLower()};
  791. ui->defaultReverbComboBox->setCurrentIndex(0);
  792. if(defaultreverb.isEmpty() == false)
  793. {
  794. for(int i = 0;i < ui->defaultReverbComboBox->count();i++)
  795. {
  796. if(defaultreverb.compare(ui->defaultReverbComboBox->itemText(i).toLower()) == 0)
  797. {
  798. ui->defaultReverbComboBox->setCurrentIndex(i);
  799. break;
  800. }
  801. }
  802. }
  803. QStringList excludefx{settings.value("excludefx").toStringList()};
  804. if(excludefx.size() == 1)
  805. excludefx = excludefx[0].split(QChar(','));
  806. for(QString &name : excludefx)
  807. name = name.trimmed();
  808. ui->enableEaxReverbCheck->setChecked(!excludefx.contains("eaxreverb", Qt::CaseInsensitive));
  809. ui->enableStdReverbCheck->setChecked(!excludefx.contains("reverb", Qt::CaseInsensitive));
  810. ui->enableAutowahCheck->setChecked(!excludefx.contains("autowah", Qt::CaseInsensitive));
  811. ui->enableChorusCheck->setChecked(!excludefx.contains("chorus", Qt::CaseInsensitive));
  812. ui->enableCompressorCheck->setChecked(!excludefx.contains("compressor", Qt::CaseInsensitive));
  813. ui->enableDistortionCheck->setChecked(!excludefx.contains("distortion", Qt::CaseInsensitive));
  814. ui->enableEchoCheck->setChecked(!excludefx.contains("echo", Qt::CaseInsensitive));
  815. ui->enableEqualizerCheck->setChecked(!excludefx.contains("equalizer", Qt::CaseInsensitive));
  816. ui->enableFlangerCheck->setChecked(!excludefx.contains("flanger", Qt::CaseInsensitive));
  817. ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains("fshifter", Qt::CaseInsensitive));
  818. ui->enableModulatorCheck->setChecked(!excludefx.contains("modulator", Qt::CaseInsensitive));
  819. ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive));
  820. ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive));
  821. ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive));
  822. ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value("pulse/spawn-server")));
  823. ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value("pulse/allow-moves")));
  824. ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value("pulse/fix-rate")));
  825. ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value("pulse/adjust-latency")));
  826. ui->pwireAssumeAudioCheckBox->setCheckState(settings.value("pipewire/assume-audio").toBool()
  827. ? Qt::Checked : Qt::Unchecked);
  828. ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server")));
  829. ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports")));
  830. ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value("jack/rt-mix")));
  831. ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString());
  832. updateJackBufferSizeSlider();
  833. ui->alsaDefaultDeviceLine->setText(settings.value("alsa/device", QString()).toString());
  834. ui->alsaDefaultCaptureLine->setText(settings.value("alsa/capture", QString()).toString());
  835. ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value("alsa/allow-resampler")));
  836. ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value("alsa/mmap")));
  837. ui->ossDefaultDeviceLine->setText(settings.value("oss/device", QString()).toString());
  838. ui->ossDefaultCaptureLine->setText(settings.value("oss/capture", QString()).toString());
  839. ui->solarisDefaultDeviceLine->setText(settings.value("solaris/device", QString()).toString());
  840. ui->waveOutputLine->setText(settings.value("wave/file", QString()).toString());
  841. ui->waveBFormatCheckBox->setChecked(settings.value("wave/bformat", false).toBool());
  842. ui->applyButton->setEnabled(false);
  843. ui->closeCancelButton->setText(tr("Close"));
  844. mNeedsSave = false;
  845. }
  846. void MainWindow::saveCurrentConfig()
  847. {
  848. saveConfig(getDefaultConfigName());
  849. ui->applyButton->setEnabled(false);
  850. ui->closeCancelButton->setText(tr("Close"));
  851. mNeedsSave = false;
  852. QMessageBox::information(this, tr("Information"),
  853. tr("Applications using OpenAL need to be restarted for changes to take effect."));
  854. }
  855. void MainWindow::saveConfigAsFile()
  856. {
  857. QString fname{QFileDialog::getOpenFileName(this, tr("Select Files"))};
  858. if(fname.isEmpty() == false)
  859. {
  860. saveConfig(fname);
  861. ui->applyButton->setEnabled(false);
  862. mNeedsSave = false;
  863. }
  864. }
  865. void MainWindow::saveConfig(const QString &fname) const
  866. {
  867. QSettings settings{fname, QSettings::IniFormat};
  868. /* HACK: Compound any stringlist values into a comma-separated string. */
  869. QStringList allkeys{settings.allKeys()};
  870. foreach(const QString &key, allkeys)
  871. {
  872. QStringList vals{settings.value(key).toStringList()};
  873. if(vals.size() > 1)
  874. settings.setValue(key, vals.join(QChar(',')));
  875. }
  876. settings.setValue("sample-type", getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText()));
  877. settings.setValue("channels", getValueFromName(speakerModeList, ui->channelConfigCombo->currentText()));
  878. uint rate{ui->sampleRateCombo->currentText().toUInt()};
  879. if(rate <= 0)
  880. settings.setValue("frequency", QString{});
  881. else
  882. settings.setValue("frequency", rate);
  883. settings.setValue("period_size", ui->periodSizeEdit->text());
  884. settings.setValue("periods", ui->periodCountEdit->text());
  885. settings.setValue("sources", ui->srcCountLineEdit->text());
  886. settings.setValue("slots", ui->effectSlotLineEdit->text());
  887. settings.setValue("resampler", resamplerList[ui->resamplerSlider->value()].value);
  888. settings.setValue("stereo-mode", getValueFromName(stereoModeList, ui->stereoModeCombo->currentText()));
  889. settings.setValue("stereo-encoding", getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText()));
  890. settings.setValue("ambi-format", getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText()));
  891. settings.setValue("output-limiter", getCheckValue(ui->outputLimiterCheckBox));
  892. settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox));
  893. settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox));
  894. settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox));
  895. settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox));
  896. double refdelay = ui->decoderNFRefDelaySpinBox->value();
  897. settings.setValue("decoder/nfc-ref-delay",
  898. (refdelay > 0.0) ? QString::number(refdelay) : QString{}
  899. );
  900. settings.setValue("decoder/quad", ui->decoderQuadLineEdit->text());
  901. settings.setValue("decoder/surround51", ui->decoder51LineEdit->text());
  902. settings.setValue("decoder/surround61", ui->decoder61LineEdit->text());
  903. settings.setValue("decoder/surround71", ui->decoder71LineEdit->text());
  904. QStringList strlist;
  905. if(!ui->enableSSECheckBox->isChecked())
  906. strlist.append("sse");
  907. if(!ui->enableSSE2CheckBox->isChecked())
  908. strlist.append("sse2");
  909. if(!ui->enableSSE3CheckBox->isChecked())
  910. strlist.append("sse3");
  911. if(!ui->enableSSE41CheckBox->isChecked())
  912. strlist.append("sse4.1");
  913. if(!ui->enableNeonCheckBox->isChecked())
  914. strlist.append("neon");
  915. settings.setValue("disable-cpu-exts", strlist.join(QChar(',')));
  916. settings.setValue("hrtf-mode", hrtfModeList[ui->hrtfmodeSlider->value()].value);
  917. if(ui->hrtfStateComboBox->currentIndex() == 1)
  918. settings.setValue("hrtf", "true");
  919. else if(ui->hrtfStateComboBox->currentIndex() == 2)
  920. settings.setValue("hrtf", "false");
  921. else
  922. settings.setValue("hrtf", QString{});
  923. if(ui->preferredHrtfComboBox->currentIndex() == 0)
  924. settings.setValue("default-hrtf", QString{});
  925. else
  926. {
  927. QString str{ui->preferredHrtfComboBox->currentText()};
  928. settings.setValue("default-hrtf", str);
  929. }
  930. strlist.clear();
  931. strlist.reserve(ui->hrtfFileList->count());
  932. for(int i = 0;i < ui->hrtfFileList->count();i++)
  933. strlist.append(ui->hrtfFileList->item(i)->text());
  934. if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked())
  935. strlist.append(QString{});
  936. settings.setValue("hrtf-paths", strlist.join(QChar{','}));
  937. strlist.clear();
  938. for(int i = 0;i < ui->enabledBackendList->count();i++)
  939. {
  940. QString label{ui->enabledBackendList->item(i)->text()};
  941. for(int j = 0;backendList[j].backend_name[0];j++)
  942. {
  943. if(label == backendList[j].full_string)
  944. {
  945. strlist.append(backendList[j].backend_name);
  946. break;
  947. }
  948. }
  949. }
  950. for(int i = 0;i < ui->disabledBackendList->count();i++)
  951. {
  952. QString label{ui->disabledBackendList->item(i)->text()};
  953. for(int j = 0;backendList[j].backend_name[0];j++)
  954. {
  955. if(label == backendList[j].full_string)
  956. {
  957. strlist.append(QChar{'-'}+QString{backendList[j].backend_name});
  958. break;
  959. }
  960. }
  961. }
  962. if(strlist.size() == 0 && !ui->backendCheckBox->isChecked())
  963. strlist.append("-all");
  964. else if(ui->backendCheckBox->isChecked())
  965. strlist.append(QString{});
  966. settings.setValue("drivers", strlist.join(QChar(',')));
  967. // TODO: Remove check when we can properly match global values.
  968. if(ui->defaultReverbComboBox->currentIndex() == 0)
  969. settings.setValue("default-reverb", QString{});
  970. else
  971. {
  972. QString str{ui->defaultReverbComboBox->currentText().toLower()};
  973. settings.setValue("default-reverb", str);
  974. }
  975. strlist.clear();
  976. if(!ui->enableEaxReverbCheck->isChecked())
  977. strlist.append("eaxreverb");
  978. if(!ui->enableStdReverbCheck->isChecked())
  979. strlist.append("reverb");
  980. if(!ui->enableAutowahCheck->isChecked())
  981. strlist.append("autowah");
  982. if(!ui->enableChorusCheck->isChecked())
  983. strlist.append("chorus");
  984. if(!ui->enableDistortionCheck->isChecked())
  985. strlist.append("distortion");
  986. if(!ui->enableCompressorCheck->isChecked())
  987. strlist.append("compressor");
  988. if(!ui->enableEchoCheck->isChecked())
  989. strlist.append("echo");
  990. if(!ui->enableEqualizerCheck->isChecked())
  991. strlist.append("equalizer");
  992. if(!ui->enableFlangerCheck->isChecked())
  993. strlist.append("flanger");
  994. if(!ui->enableFrequencyShifterCheck->isChecked())
  995. strlist.append("fshifter");
  996. if(!ui->enableModulatorCheck->isChecked())
  997. strlist.append("modulator");
  998. if(!ui->enableDedicatedCheck->isChecked())
  999. strlist.append("dedicated");
  1000. if(!ui->enablePitchShifterCheck->isChecked())
  1001. strlist.append("pshifter");
  1002. if(!ui->enableVocalMorpherCheck->isChecked())
  1003. strlist.append("vmorpher");
  1004. settings.setValue("excludefx", strlist.join(QChar{','}));
  1005. settings.setValue("pulse/spawn-server", getCheckValue(ui->pulseAutospawnCheckBox));
  1006. settings.setValue("pulse/allow-moves", getCheckValue(ui->pulseAllowMovesCheckBox));
  1007. settings.setValue("pulse/fix-rate", getCheckValue(ui->pulseFixRateCheckBox));
  1008. settings.setValue("pulse/adjust-latency", getCheckValue(ui->pulseAdjLatencyCheckBox));
  1009. settings.setValue("pipewire/assume-audio", ui->pwireAssumeAudioCheckBox->isChecked()
  1010. ? QString{"true"} : QString{/*"false"*/});
  1011. settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox));
  1012. settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox));
  1013. settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox));
  1014. settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text());
  1015. settings.setValue("alsa/device", ui->alsaDefaultDeviceLine->text());
  1016. settings.setValue("alsa/capture", ui->alsaDefaultCaptureLine->text());
  1017. settings.setValue("alsa/allow-resampler", getCheckValue(ui->alsaResamplerCheckBox));
  1018. settings.setValue("alsa/mmap", getCheckValue(ui->alsaMmapCheckBox));
  1019. settings.setValue("oss/device", ui->ossDefaultDeviceLine->text());
  1020. settings.setValue("oss/capture", ui->ossDefaultCaptureLine->text());
  1021. settings.setValue("solaris/device", ui->solarisDefaultDeviceLine->text());
  1022. settings.setValue("wave/file", ui->waveOutputLine->text());
  1023. settings.setValue("wave/bformat",
  1024. ui->waveBFormatCheckBox->isChecked() ? QString{"true"} : QString{/*"false"*/}
  1025. );
  1026. /* Remove empty keys
  1027. * FIXME: Should only remove keys whose value matches the globally-specified value.
  1028. */
  1029. allkeys = settings.allKeys();
  1030. foreach(const QString &key, allkeys)
  1031. {
  1032. QString str{settings.value(key).toString()};
  1033. if(str == QString{})
  1034. settings.remove(key);
  1035. }
  1036. }
  1037. void MainWindow::enableApplyButton()
  1038. {
  1039. if(!mNeedsSave)
  1040. ui->applyButton->setEnabled(true);
  1041. mNeedsSave = true;
  1042. ui->closeCancelButton->setText(tr("Cancel"));
  1043. }
  1044. void MainWindow::updateResamplerLabel(int num)
  1045. {
  1046. ui->resamplerLabel->setText(resamplerList[num].name);
  1047. enableApplyButton();
  1048. }
  1049. void MainWindow::updatePeriodSizeEdit(int size)
  1050. {
  1051. ui->periodSizeEdit->clear();
  1052. if(size >= 64)
  1053. ui->periodSizeEdit->insert(QString::number(size));
  1054. enableApplyButton();
  1055. }
  1056. void MainWindow::updatePeriodSizeSlider()
  1057. {
  1058. int pos = ui->periodSizeEdit->text().toInt();
  1059. if(pos >= 64)
  1060. {
  1061. if(pos > 8192)
  1062. pos = 8192;
  1063. ui->periodSizeSlider->setSliderPosition(pos);
  1064. }
  1065. enableApplyButton();
  1066. }
  1067. void MainWindow::updatePeriodCountEdit(int count)
  1068. {
  1069. ui->periodCountEdit->clear();
  1070. if(count >= 2)
  1071. ui->periodCountEdit->insert(QString::number(count));
  1072. enableApplyButton();
  1073. }
  1074. void MainWindow::updatePeriodCountSlider()
  1075. {
  1076. int pos = ui->periodCountEdit->text().toInt();
  1077. if(pos < 2)
  1078. pos = 0;
  1079. else if(pos > 16)
  1080. pos = 16;
  1081. ui->periodCountSlider->setSliderPosition(pos);
  1082. enableApplyButton();
  1083. }
  1084. void MainWindow::selectQuadDecoderFile()
  1085. { selectDecoderFile(ui->decoderQuadLineEdit, "Select Quadraphonic Decoder");}
  1086. void MainWindow::select51DecoderFile()
  1087. { selectDecoderFile(ui->decoder51LineEdit, "Select 5.1 Surround Decoder");}
  1088. void MainWindow::select61DecoderFile()
  1089. { selectDecoderFile(ui->decoder61LineEdit, "Select 6.1 Surround Decoder");}
  1090. void MainWindow::select71DecoderFile()
  1091. { selectDecoderFile(ui->decoder71LineEdit, "Select 7.1 Surround Decoder");}
  1092. void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption)
  1093. {
  1094. QString dir{line->text()};
  1095. if(dir.isEmpty() || QDir::isRelativePath(dir))
  1096. {
  1097. QStringList paths{getAllDataPaths("/openal/presets")};
  1098. while(!paths.isEmpty())
  1099. {
  1100. if(QDir{paths.last()}.exists())
  1101. {
  1102. dir = paths.last();
  1103. break;
  1104. }
  1105. paths.removeLast();
  1106. }
  1107. }
  1108. QString fname{QFileDialog::getOpenFileName(this, tr(caption),
  1109. dir, tr("AmbDec Files (*.ambdec);;All Files (*.*)"))};
  1110. if(!fname.isEmpty())
  1111. {
  1112. line->setText(fname);
  1113. enableApplyButton();
  1114. }
  1115. }
  1116. void MainWindow::updateJackBufferSizeEdit(int size)
  1117. {
  1118. ui->jackBufferSizeLine->clear();
  1119. if(size > 0)
  1120. ui->jackBufferSizeLine->insert(QString::number(1<<size));
  1121. enableApplyButton();
  1122. }
  1123. void MainWindow::updateJackBufferSizeSlider()
  1124. {
  1125. int value{ui->jackBufferSizeLine->text().toInt()};
  1126. auto pos = static_cast<int>(floor(log2(value) + 0.5));
  1127. ui->jackBufferSizeSlider->setSliderPosition(pos);
  1128. enableApplyButton();
  1129. }
  1130. void MainWindow::updateHrtfModeLabel(int num)
  1131. {
  1132. ui->hrtfmodeLabel->setText(hrtfModeList[num].name);
  1133. enableApplyButton();
  1134. }
  1135. void MainWindow::addHrtfFile()
  1136. {
  1137. QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))};
  1138. if(path.isEmpty() == false && !getAllDataPaths("/openal/hrtf").contains(path))
  1139. {
  1140. ui->hrtfFileList->addItem(path);
  1141. enableApplyButton();
  1142. }
  1143. }
  1144. void MainWindow::removeHrtfFile()
  1145. {
  1146. QList<QListWidgetItem*> selected{ui->hrtfFileList->selectedItems()};
  1147. if(!selected.isEmpty())
  1148. {
  1149. foreach(QListWidgetItem *item, selected)
  1150. delete item;
  1151. enableApplyButton();
  1152. }
  1153. }
  1154. void MainWindow::updateHrtfRemoveButton()
  1155. {
  1156. ui->hrtfRemoveButton->setEnabled(ui->hrtfFileList->selectedItems().size() != 0);
  1157. }
  1158. void MainWindow::showEnabledBackendMenu(QPoint pt)
  1159. {
  1160. QHash<QAction*,QString> actionMap;
  1161. pt = ui->enabledBackendList->mapToGlobal(pt);
  1162. QMenu ctxmenu;
  1163. QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")};
  1164. if(ui->enabledBackendList->selectedItems().size() == 0)
  1165. removeAction->setEnabled(false);
  1166. ctxmenu.addSeparator();
  1167. for(size_t i = 0;backendList[i].backend_name[0];i++)
  1168. {
  1169. QString backend{backendList[i].full_string};
  1170. QAction *action{ctxmenu.addAction(QString("Add ")+backend)};
  1171. actionMap[action] = backend;
  1172. if(ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 ||
  1173. ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0)
  1174. action->setEnabled(false);
  1175. }
  1176. QAction *gotAction{ctxmenu.exec(pt)};
  1177. if(gotAction == removeAction)
  1178. {
  1179. QList<QListWidgetItem*> selected{ui->enabledBackendList->selectedItems()};
  1180. foreach(QListWidgetItem *item, selected)
  1181. delete item;
  1182. enableApplyButton();
  1183. }
  1184. else if(gotAction != nullptr)
  1185. {
  1186. auto iter = actionMap.constFind(gotAction);
  1187. if(iter != actionMap.cend())
  1188. ui->enabledBackendList->addItem(iter.value());
  1189. enableApplyButton();
  1190. }
  1191. }
  1192. void MainWindow::showDisabledBackendMenu(QPoint pt)
  1193. {
  1194. QHash<QAction*,QString> actionMap;
  1195. pt = ui->disabledBackendList->mapToGlobal(pt);
  1196. QMenu ctxmenu;
  1197. QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")};
  1198. if(ui->disabledBackendList->selectedItems().size() == 0)
  1199. removeAction->setEnabled(false);
  1200. ctxmenu.addSeparator();
  1201. for(size_t i = 0;backendList[i].backend_name[0];i++)
  1202. {
  1203. QString backend{backendList[i].full_string};
  1204. QAction *action{ctxmenu.addAction(QString("Add ")+backend)};
  1205. actionMap[action] = backend;
  1206. if(ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 ||
  1207. ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0)
  1208. action->setEnabled(false);
  1209. }
  1210. QAction *gotAction{ctxmenu.exec(pt)};
  1211. if(gotAction == removeAction)
  1212. {
  1213. QList<QListWidgetItem*> selected{ui->disabledBackendList->selectedItems()};
  1214. foreach(QListWidgetItem *item, selected)
  1215. delete item;
  1216. enableApplyButton();
  1217. }
  1218. else if(gotAction != nullptr)
  1219. {
  1220. auto iter = actionMap.constFind(gotAction);
  1221. if(iter != actionMap.cend())
  1222. ui->disabledBackendList->addItem(iter.value());
  1223. enableApplyButton();
  1224. }
  1225. }
  1226. void MainWindow::selectOSSPlayback()
  1227. {
  1228. QString current{ui->ossDefaultDeviceLine->text()};
  1229. if(current.isEmpty()) current = ui->ossDefaultDeviceLine->placeholderText();
  1230. QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)};
  1231. if(!fname.isEmpty())
  1232. {
  1233. ui->ossDefaultDeviceLine->setText(fname);
  1234. enableApplyButton();
  1235. }
  1236. }
  1237. void MainWindow::selectOSSCapture()
  1238. {
  1239. QString current{ui->ossDefaultCaptureLine->text()};
  1240. if(current.isEmpty()) current = ui->ossDefaultCaptureLine->placeholderText();
  1241. QString fname{QFileDialog::getOpenFileName(this, tr("Select Capture Device"), current)};
  1242. if(!fname.isEmpty())
  1243. {
  1244. ui->ossDefaultCaptureLine->setText(fname);
  1245. enableApplyButton();
  1246. }
  1247. }
  1248. void MainWindow::selectSolarisPlayback()
  1249. {
  1250. QString current{ui->solarisDefaultDeviceLine->text()};
  1251. if(current.isEmpty()) current = ui->solarisDefaultDeviceLine->placeholderText();
  1252. QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)};
  1253. if(!fname.isEmpty())
  1254. {
  1255. ui->solarisDefaultDeviceLine->setText(fname);
  1256. enableApplyButton();
  1257. }
  1258. }
  1259. void MainWindow::selectWaveOutput()
  1260. {
  1261. QString fname{QFileDialog::getSaveFileName(this, tr("Select Wave File Output"),
  1262. ui->waveOutputLine->text(), tr("Wave Files (*.wav *.amb);;All Files (*.*)"))};
  1263. if(!fname.isEmpty())
  1264. {
  1265. ui->waveOutputLine->setText(fname);
  1266. enableApplyButton();
  1267. }
  1268. }