![]() |
Mixxx
|
00001 00007 /*************************************************************************** 00008 * * 00009 * This program is free software; you can redistribute it and/or modify * 00010 * it under the terms of the GNU General Public License as published by * 00011 * the Free Software Foundation; either version 2 of the License, or * 00012 * (at your option) any later version. * 00013 * * 00014 ***************************************************************************/ 00015 00016 #include <QDebug> 00017 #include <QMessageBox> 00018 #include "dlgprefsound.h" 00019 #include "dlgprefsounditem.h" 00020 #include "engine/enginemaster.h" 00021 #include "playermanager.h" 00022 #include "soundmanager.h" 00023 #include "sounddevice.h" 00024 00029 DlgPrefSound::DlgPrefSound(QWidget *pParent, SoundManager *pSoundManager, 00030 PlayerManager* pPlayerManager, ConfigObject<ConfigValue> *pConfig) 00031 : QWidget(pParent) 00032 , m_pSoundManager(pSoundManager) 00033 , m_pPlayerManager(pPlayerManager) 00034 , m_pConfig(pConfig) 00035 , m_settingsModified(false) 00036 , m_loading(false) 00037 , m_forceApply(false) 00038 { 00039 setupUi(this); 00040 00041 connect(m_pSoundManager, SIGNAL(devicesUpdated()), 00042 this, SLOT(refreshDevices())); 00043 00044 applyButton->setEnabled(false); 00045 connect(applyButton, SIGNAL(clicked()), 00046 this, SLOT(slotApply())); 00047 00048 apiComboBox->clear(); 00049 apiComboBox->addItem(tr("None"), "None"); 00050 updateAPIs(); 00051 connect(apiComboBox, SIGNAL(currentIndexChanged(int)), 00052 this, SLOT(apiChanged(int))); 00053 00054 sampleRateComboBox->clear(); 00055 foreach (unsigned int srate, m_pSoundManager->getSampleRates()) { 00056 if (srate > 0) { 00057 // no ridiculous sample rate values. prohibiting zero means 00058 // avoiding a potential div-by-0 error in ::updateLatencies 00059 sampleRateComboBox->addItem(QString(tr("%1 Hz")).arg(srate), srate); 00060 } 00061 } 00062 connect(sampleRateComboBox, SIGNAL(currentIndexChanged(int)), 00063 this, SLOT(sampleRateChanged(int))); 00064 connect(sampleRateComboBox, SIGNAL(currentIndexChanged(int)), 00065 this, SLOT(updateLatencies(int))); 00066 connect(latencyComboBox, SIGNAL(currentIndexChanged(int)), 00067 this, SLOT(latencyChanged(int))); 00068 00069 initializePaths(); 00070 loadSettings(); 00071 00072 connect(apiComboBox, SIGNAL(currentIndexChanged(int)), 00073 this, SLOT(settingChanged())); 00074 connect(sampleRateComboBox, SIGNAL(currentIndexChanged(int)), 00075 this, SLOT(settingChanged())); 00076 connect(latencyComboBox, SIGNAL(currentIndexChanged(int)), 00077 this, SLOT(settingChanged())); 00078 00079 connect(queryButton, SIGNAL(clicked()), 00080 this, SLOT(queryClicked())); 00081 connect(resetButton, SIGNAL(clicked()), 00082 this, SLOT(resetClicked())); 00083 00084 connect(m_pSoundManager, SIGNAL(outputRegistered(AudioOutput, const AudioSource*)), 00085 this, SLOT(addPath(AudioOutput))); 00086 connect(m_pSoundManager, SIGNAL(outputRegistered(AudioOutput, const AudioSource*)), 00087 this, SLOT(loadSettings())); 00088 00089 connect(m_pSoundManager, SIGNAL(inputRegistered(AudioInput, AudioDestination*)), 00090 this, SLOT(addPath(AudioInput))); 00091 connect(m_pSoundManager, SIGNAL(inputRegistered(AudioInput, AudioDestination*)), 00092 this, SLOT(loadSettings())); 00093 } 00094 00095 DlgPrefSound::~DlgPrefSound() { 00096 00097 } 00098 00103 void DlgPrefSound::slotUpdate() { 00104 // this is unfortunate, because slotUpdate is called every time 00105 // we change to this pane, we lose changed and unapplied settings 00106 // every time. There's no real way around this, just anothe argument 00107 // for a prefs rewrite -- bkgood 00108 loadSettings(); 00109 m_settingsModified = false; 00110 applyButton->setEnabled(false); 00111 } 00112 00116 void DlgPrefSound::slotApply() { 00117 if (!m_settingsModified && !m_forceApply) { 00118 return; 00119 } 00120 m_forceApply = false; 00121 m_config.clearInputs(); 00122 m_config.clearOutputs(); 00123 emit(writePaths(&m_config)); 00124 int err = m_pSoundManager->setConfig(m_config); 00125 if (err != OK) { 00126 QString error; 00127 QString deviceName(tr("a device")); 00128 QString detailedError(tr("An unknown error occurred")); 00129 SoundDevice *device = m_pSoundManager->getErrorDevice(); 00130 if (device != NULL) { 00131 deviceName = QString(tr("sound device \"%1\"")).arg(device->getDisplayName()); 00132 detailedError = device->getError(); 00133 } 00134 switch (err) { 00135 case SOUNDDEVICE_ERROR_DUPLICATE_OUTPUT_CHANNEL: 00136 error = QString(tr("Two outputs cannot share channels on %1")).arg(deviceName); 00137 break; 00138 default: 00139 error = QString(tr("Error opening %1\n%2")).arg(deviceName).arg(detailedError); 00140 break; 00141 } 00142 QMessageBox::warning(NULL, tr("Configuration error"), error); 00143 } 00144 m_settingsModified = false; 00145 applyButton->setEnabled(false); 00146 loadSettings(); // in case SM decided to change anything it didn't like 00147 } 00148 00155 void DlgPrefSound::forceApply() { 00156 m_forceApply = true; 00157 } 00158 00166 void DlgPrefSound::initializePaths() { 00167 foreach (AudioOutput out, m_pSoundManager->registeredOutputs()) { 00168 addPath(out); 00169 } 00170 foreach (AudioInput in, m_pSoundManager->registeredInputs()) { 00171 addPath(in); 00172 } 00173 } 00174 00175 void DlgPrefSound::addPath(AudioOutput output) { 00176 DlgPrefSoundItem *toInsert; 00177 // if we already know about this output, don't make a new entry 00178 foreach (QObject *obj, outputTab->children()) { 00179 DlgPrefSoundItem *item = qobject_cast<DlgPrefSoundItem*>(obj); 00180 if (item) { 00181 if (item->type() == output.getType()) { 00182 if (AudioPath::isIndexed(item->type())) { 00183 if (item->index() == output.getIndex()) { 00184 return; 00185 } 00186 } else { 00187 return; 00188 } 00189 } 00190 } 00191 } 00192 AudioPathType type = output.getType(); 00193 if (AudioPath::isIndexed(type)) { 00194 toInsert = new DlgPrefSoundItem(outputTab, type, 00195 m_outputDevices, false, output.getIndex()); 00196 } else { 00197 toInsert = new DlgPrefSoundItem(outputTab, type, 00198 m_outputDevices, false); 00199 } 00200 connect(this, SIGNAL(refreshOutputDevices(const QList<SoundDevice*>&)), 00201 toInsert, SLOT(refreshDevices(const QList<SoundDevice*>&))); 00202 insertItem(toInsert, outputVLayout); 00203 connectSoundItem(toInsert); 00204 } 00205 00206 void DlgPrefSound::addPath(AudioInput input) { 00207 DlgPrefSoundItem *toInsert; 00208 // if we already know about this input, don't make a new entry 00209 foreach (QObject *obj, inputTab->children()) { 00210 DlgPrefSoundItem *item = qobject_cast<DlgPrefSoundItem*>(obj); 00211 if (item) { 00212 if (item->type() == input.getType()) { 00213 if (AudioPath::isIndexed(item->type())) { 00214 if (item->index() == input.getIndex()) { 00215 return; 00216 } 00217 } else { 00218 return; 00219 } 00220 } 00221 } 00222 } 00223 AudioPathType type = input.getType(); 00224 if (AudioPath::isIndexed(type)) { 00225 toInsert = new DlgPrefSoundItem(inputTab, type, 00226 m_inputDevices, true, input.getIndex()); 00227 } else { 00228 toInsert = new DlgPrefSoundItem(inputTab, type, 00229 m_inputDevices, true); 00230 } 00231 connect(this, SIGNAL(refreshInputDevices(const QList<SoundDevice*>&)), 00232 toInsert, SLOT(refreshDevices(const QList<SoundDevice*>&))); 00233 insertItem(toInsert, inputVLayout); 00234 connectSoundItem(toInsert); 00235 } 00236 00237 void DlgPrefSound::connectSoundItem(DlgPrefSoundItem *item) { 00238 connect(item, SIGNAL(settingChanged()), 00239 this, SLOT(settingChanged())); 00240 connect(this, SIGNAL(loadPaths(const SoundManagerConfig&)), 00241 item, SLOT(loadPath(const SoundManagerConfig&))); 00242 connect(this, SIGNAL(writePaths(SoundManagerConfig*)), 00243 item, SLOT(writePath(SoundManagerConfig*))); 00244 connect(this, SIGNAL(updatingAPI()), 00245 item, SLOT(save())); 00246 connect(this, SIGNAL(updatedAPI()), 00247 item, SLOT(reload())); 00248 } 00249 00250 void DlgPrefSound::insertItem(DlgPrefSoundItem *pItem, QVBoxLayout *pLayout) { 00251 int pos; 00252 for (pos = 0; pos < pLayout->count() - 1; ++pos) { 00253 DlgPrefSoundItem *pOther(qobject_cast<DlgPrefSoundItem*>( 00254 pLayout->itemAt(pos)->widget())); 00255 if (!pOther) continue; 00256 if (pItem->type() < pOther->type()) { 00257 break; 00258 } else if (pItem->type() == pOther->type() 00259 && AudioPath::isIndexed(pItem->type()) 00260 && pItem->index() < pOther->index()) { 00261 break; 00262 } 00263 } 00264 pLayout->insertWidget(pos, pItem); 00265 } 00266 00271 void DlgPrefSound::loadSettings() { 00272 loadSettings(m_pSoundManager->getConfig()); 00273 } 00274 00278 void DlgPrefSound::loadSettings(const SoundManagerConfig &config) { 00279 m_loading = true; // so settingsChanged ignores all our modifications here 00280 m_config = config; 00281 int apiIndex = apiComboBox->findData(m_config.getAPI()); 00282 if (apiIndex != -1) { 00283 apiComboBox->setCurrentIndex(apiIndex); 00284 } 00285 int sampleRateIndex = sampleRateComboBox->findData(m_config.getSampleRate()); 00286 if (sampleRateIndex != -1) { 00287 sampleRateComboBox->setCurrentIndex(sampleRateIndex); 00288 if (latencyComboBox->count() <= 0) { 00289 updateLatencies(sampleRateIndex); // so the latency combo box is 00290 // sure to be populated, if setCurrentIndex is called with the 00291 // currentIndex, the currentIndexChanged signal won't fire and 00292 // the updateLatencies slot won't run -- bkgood lp bug 689373 00293 } 00294 } 00295 int latencyIndex = latencyComboBox->findData(m_config.getLatency()); 00296 if (latencyIndex != -1) { 00297 latencyComboBox->setCurrentIndex(latencyIndex); 00298 } 00299 emit(loadPaths(m_config)); 00300 m_loading = false; 00301 } 00302 00309 void DlgPrefSound::apiChanged(int index) { 00310 m_config.setAPI(apiComboBox->itemData(index).toString()); 00311 refreshDevices(); 00312 // JACK sets its own latency 00313 if (m_config.getAPI() == MIXXX_PORTAUDIO_JACK_STRING) { 00314 latencyLabel->setEnabled(false); 00315 latencyComboBox->setEnabled(false); 00316 } else { 00317 latencyLabel->setEnabled(true); 00318 latencyComboBox->setEnabled(true); 00319 } 00320 } 00321 00326 void DlgPrefSound::updateAPIs() { 00327 QString currentAPI(apiComboBox->itemData(apiComboBox->currentIndex()).toString()); 00328 emit(updatingAPI()); 00329 while (apiComboBox->count() > 1) { 00330 apiComboBox->removeItem(apiComboBox->count() - 1); 00331 } 00332 foreach (QString api, m_pSoundManager->getHostAPIList()) { 00333 apiComboBox->addItem(api, api); 00334 } 00335 int newIndex = apiComboBox->findData(currentAPI); 00336 if (newIndex > -1) { 00337 apiComboBox->setCurrentIndex(newIndex); 00338 } 00339 emit(updatedAPI()); 00340 } 00341 00346 void DlgPrefSound::sampleRateChanged(int index) { 00347 m_config.setSampleRate( 00348 sampleRateComboBox->itemData(index).toUInt()); 00349 } 00350 00355 void DlgPrefSound::latencyChanged(int index) { 00356 m_config.setLatency( 00357 latencyComboBox->itemData(index).toUInt()); 00358 } 00359 00367 void DlgPrefSound::updateLatencies(int sampleRateIndex) { 00368 double sampleRate = sampleRateComboBox->itemData(sampleRateIndex).toDouble(); 00369 int oldLatency = latencyComboBox->currentIndex(); 00370 unsigned int framesPerBuffer = 1; // start this at 0 and inf loop happens 00371 // we don't want to display any sub-1ms latencies (well maybe we do but I 00372 // don't right now!), so we iterate over all the buffer sizes until we 00373 // find the first that gives us a latency >= 1 ms -- bkgood 00374 // no div-by-0 in the next line because we don't allow srates of 0 in our 00375 // srate list when we construct it in the ctor -- bkgood 00376 for (; framesPerBuffer / sampleRate * 1000 < 1.0; framesPerBuffer *= 2); 00377 latencyComboBox->clear(); 00378 for (unsigned int i = 0; i < SoundManagerConfig::kMaxLatency; ++i) { 00379 float latency = framesPerBuffer / sampleRate * 1000; 00380 // i + 1 in the next line is a latency index as described in SSConfig 00381 latencyComboBox->addItem(QString(tr("%1 ms")).arg(latency,0,'g',3), i + 1); 00382 framesPerBuffer <<= 1; // *= 2 00383 } 00384 if (oldLatency < latencyComboBox->count() && oldLatency >= 0) { 00385 latencyComboBox->setCurrentIndex(oldLatency); 00386 } else { 00387 // set it to the max, let the user dig if they need better latency. better 00388 // than having a user get the pops on first use and thinking poorly of mixxx 00389 // because of it -- bkgood 00390 latencyComboBox->setCurrentIndex(latencyComboBox->count() - 1); 00391 } 00392 } 00393 00398 void DlgPrefSound::refreshDevices() { 00399 if (m_config.getAPI() == "None") { 00400 m_outputDevices.clear(); 00401 m_inputDevices.clear(); 00402 } else { 00403 m_outputDevices = 00404 m_pSoundManager->getDeviceList(m_config.getAPI(), true, false); 00405 m_inputDevices = 00406 m_pSoundManager->getDeviceList(m_config.getAPI(), false, true); 00407 } 00408 emit(refreshOutputDevices(m_outputDevices)); 00409 emit(refreshInputDevices(m_inputDevices)); 00410 } 00411 00417 void DlgPrefSound::settingChanged() { 00418 if (m_loading) return; // doesn't count if we're just loading prefs 00419 m_settingsModified = true; 00420 if (!applyButton->isEnabled()) { 00421 applyButton->setEnabled(true); 00422 } 00423 } 00424 00428 void DlgPrefSound::queryClicked() { 00429 m_pSoundManager->queryDevices(); 00430 updateAPIs(); 00431 } 00432 00436 void DlgPrefSound::resetClicked() { 00437 SoundManagerConfig newConfig; 00438 newConfig.loadDefaults(m_pSoundManager, SoundManagerConfig::ALL); 00439 loadSettings(newConfig); 00440 settingChanged(); // force the apply button to enable 00441 }