Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/dlgprefsound.cpp

Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines