// Copyright 2005-2019 The Mumble Developers. All rights reserved. // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file at the root of the // Mumble source tree or at . #include "mumble_pch.hpp" #include "AudioWizard.h" #include "AudioInput.h" #include "AudioOutputSample.h" #include "Log.h" #include "MainWindow.h" // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include. #include "Global.h" CompletablePage::CompletablePage(QWizard *p) : QWizardPage(p) { bComplete = true; } void CompletablePage::setComplete(bool b) { bComplete = b; emit completeChanged(); } bool CompletablePage::isComplete() const { return bComplete; } AudioWizard::AudioWizard(QWidget *p) : QWizard(p) { bInit = true; bLastActive = false; g.bInAudioWizard = true; ticker = new QTimer(this); ticker->setObjectName(QLatin1String("Ticker")); setupUi(this); // Done qcbUsage->setChecked(g.s.bUsage); // Device if (AudioInputRegistrar::qmNew) { foreach(AudioInputRegistrar *air, *AudioInputRegistrar::qmNew) { qcbInput->addItem(air->name); if (air->name == AudioInputRegistrar::current) { qcbInput->setCurrentIndex(qcbInput->count() - 1); qcbEcho->setEnabled(air->canEcho(qcbOutput->currentText())); } QList ql= air->getDeviceChoices(); } } if (qcbInput->count() < 2) { qcbInput->setEnabled(false); } qcbEcho->setChecked(g.s.bEcho); if (AudioOutputRegistrar::qmNew) { foreach(AudioOutputRegistrar *aor, *AudioOutputRegistrar::qmNew) { qcbOutput->addItem(aor->name); if (aor->name == AudioOutputRegistrar::current) { qcbOutput->setCurrentIndex(qcbOutput->count() - 1); bDelay = aor->usesOutputDelay(); qcbAttenuateOthers->setEnabled(aor->canMuteOthers()); } QList ql= aor->getDeviceChoices(); } } if (qcbOutput->count() < 2) { qcbOutput->setEnabled(false); } qcbHighContrast->setChecked(g.s.bHighContrast); on_qcbHighContrast_clicked(g.s.bHighContrast); #ifdef Q_OS_WIN // On windows we can autodetect this qcbHighContrast->setVisible(false); #endif // Settings if (g.s.iQuality == 16000 && g.s.iFramesPerPacket == 6) qrbQualityLow->setChecked(true); else if (g.s.iQuality == 40000 && g.s.iFramesPerPacket == 2) qrbQualityBalanced->setChecked(true); else if (g.s.iQuality == 72000 && g.s.iFramesPerPacket == 1) qrbQualityUltra->setChecked(true); else qrbQualityCustom->setChecked(true); quint32 iMessage = Settings::LogNone; for (int i = Log::firstMsgType;i <= Log::lastMsgType; ++i) { iMessage |= (g.s.qmMessages[i] & (Settings::LogSoundfile | Settings::LogTTS)); } #ifdef USE_NO_TTS qrbNotificationCustom->setChecked(false); qrbNotificationCustom->setDisabled(true); qrbNotificationTTS->setChecked(false); qrbNotificationTTS->setDisabled(true); qrbNotificationSounds->setChecked(true); #else if (iMessage == Settings::LogTTS && g.s.bTTS) qrbNotificationTTS->setChecked(true); else if (iMessage == Settings::LogSoundfile) qrbNotificationSounds->setChecked(true); else // If we find mixed message types or only tts with main tts disable assume custom qrbNotificationCustom->setChecked(true); qrbNotificationCustom->setVisible(qrbNotificationCustom->isChecked()); #endif qrbQualityCustom->setVisible(qrbQualityCustom->isChecked()); qlQualityCustom->setVisible(qrbQualityCustom->isChecked()); qcbPositional->setChecked(g.s.bPositionalAudio); qcbAttenuateOthers->setChecked(g.s.bAttenuateOthers); on_qcbInput_activated(qcbInput->currentIndex()); on_qcbOutput_activated(qcbOutput->currentIndex()); abAmplify->qcBelow = Qt::blue; abAmplify->qcInside = Qt::green; abAmplify->qcAbove = Qt::red; // Trigger foreach(const Shortcut &s, g.s.qlShortcuts) { if (s.iIndex == g.mw->gsPushTalk->idx) { skwPTT->setShortcut(s.qlButtons); break; } } if (g.s.atTransmit == Settings::PushToTalk) qrPTT->setChecked(true); else if (g.s.vsVAD == Settings::Amplitude) qrAmplitude->setChecked(true); else qrSNR->setChecked(true); abVAD->qcBelow = Qt::red; abVAD->qcInside = Qt::yellow; abVAD->qcAbove = Qt::green; qsVAD->setValue(iroundf(g.s.fVADmax * 32767.f + 0.5f)); // Positional qcbHeadphone->setChecked(g.s.bPositionalHeadphone); fAngle = 0.0f; fX = fY = 0.0f; qgsScene = NULL; qgiSource = NULL; aosSource = NULL; qgvView->scale(1.0f, -1.0f); qgvView->viewport()->installEventFilter(this); // Volume qsMaxAmp->setValue(g.s.iMinLoudness); // Device Tuning qsOutputDelay->setValue(g.s.iOutputDelay); on_qsOutputDelay_valueChanged(qsOutputDelay->value()); setOption(QWizard::NoCancelButton, false); resize(700, 500); updateTriggerWidgets(qrPTT->isChecked()); sOldSettings = g.s; g.s.lmLoopMode = Settings::Local; g.s.dPacketLoss = 0.0; g.s.dMaxPacketDelay = 0.0; g.s.bMute = true; g.s.bDeaf = false; bTransmitChanged = false; iMaxPeak = 0; iTicks = 0; qpTalkingOn = QPixmap::fromImage(QImage(QLatin1String("skin:talking_on.svg")).scaled(64,64)); qpTalkingOff = QPixmap::fromImage(QImage(QLatin1String("skin:talking_off.svg")).scaled(64,64)); bInit = false; connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(showPage(int))); ticker->setSingleShot(false); ticker->start(20); } bool AudioWizard::eventFilter(QObject *obj, QEvent *evt) { if ((evt->type() == QEvent::MouseButtonPress) || (evt->type() == QEvent::MouseMove)) { QMouseEvent *qme = dynamic_cast(evt); if (qme) { if (qme->buttons() & Qt::LeftButton) { QPointF qpf = qgvView->mapToScene(qme->pos()); fX = static_cast(qpf.x()); fY = static_cast(qpf.y()); } } } return QWizard::eventFilter(obj, evt); } void AudioWizard::on_qcbInput_activated(int) { qcbInputDevice->clear(); if (! AudioInputRegistrar::qmNew) return; AudioInputRegistrar *air = AudioInputRegistrar::qmNew->value(qcbInput->currentText()); QList ql = air->getDeviceChoices(); foreach(audioDevice d, ql) { qcbInputDevice->addItem(d.first, d.second); } qcbInputDevice->setEnabled(ql.count() > 1); on_qcbInputDevice_activated(0); } void AudioWizard::on_qcbInputDevice_activated(int) { if (bInit) return; if (! AudioInputRegistrar::qmNew) return; Audio::stopInput(); AudioInputRegistrar *air = AudioInputRegistrar::qmNew->value(qcbInput->currentText()); int idx = qcbInputDevice->currentIndex(); if (idx > -1) { air->setDeviceChoice(qcbInputDevice->itemData(idx), g.s); } qcbEcho->setEnabled(air->canEcho(qcbOutput->currentText())); g.ai = AudioInputPtr(air->create()); g.ai->start(QThread::HighestPriority); } void AudioWizard::on_qcbOutput_activated(int) { qcbOutputDevice->clear(); if (! AudioOutputRegistrar::qmNew) return; AudioOutputRegistrar *aor = AudioOutputRegistrar::qmNew->value(qcbOutput->currentText()); QList ql = aor->getDeviceChoices(); foreach(audioDevice d, ql) { qcbOutputDevice->addItem(d.first, d.second); } qcbAttenuateOthers->setEnabled(aor->canMuteOthers()); qcbOutputDevice->setEnabled(ql.count() > 1); on_qcbOutputDevice_activated(0); } void AudioWizard::on_qcbOutputDevice_activated(int) { if (bInit) return; if (! AudioOutputRegistrar::qmNew) return; Audio::stopOutput(); AudioOutputRegistrar *aor = AudioOutputRegistrar::qmNew->value(qcbOutput->currentText()); int idx = qcbOutputDevice->currentIndex(); if (idx > -1) { aor->setDeviceChoice(qcbOutputDevice->itemData(idx), g.s); bDelay = aor->usesOutputDelay(); } AudioInputRegistrar *air = AudioInputRegistrar::qmNew->value(qcbInput->currentText()); qcbEcho->setEnabled(air->canEcho(qcbOutput->currentText())); g.ao = AudioOutputPtr(aor->create()); g.ao->start(QThread::HighPriority); } void AudioWizard::on_qsOutputDelay_valueChanged(int v) { qlOutputDelay->setText(tr("%1 ms").arg(v*10)); g.s.iOutputDelay = v; restartAudio(); } void AudioWizard::on_qsMaxAmp_valueChanged(int v) { g.s.iMinLoudness = qMin(v, 30000); } void AudioWizard::showPage(int pageid) { if (pageid == -1) return; CompletablePage *cp = qobject_cast(currentPage()); AudioOutputPtr ao = g.ao; if (ao) ao->wipe(); aosSource = NULL; g.bPosTest = false; if (cp == qwpIntro) { g.s.bMute = true; } else if (cp == qwpDone) { g.s.bMute = true; } else if (cp == qwpDeviceTuning) { g.s.bMute = true; playChord(); } else if (cp == qwpPositional) { fX = fY = 0.0f; g.s.bMute = true; g.bPosTest = true; if (qgsScene) { delete qgsScene; qgiSource = NULL; qgsScene = NULL; } playChord(); } else { g.s.bMute = false; } if ((cp == qwpTrigger) || (cp == qwpSettings)) { if (! bTransmitChanged) g.s.atTransmit = sOldSettings.atTransmit; else if (qrPTT->isChecked()) g.s.atTransmit = Settings::PushToTalk; else g.s.atTransmit = Settings::VAD; } else { g.s.atTransmit = Settings::Continuous; } } int AudioWizard::nextId() const { AudioOutputPtr ao = g.ao; int nextid = QWizard::nextId(); if (currentPage() == qwpSettings && ! g.s.bPositionalAudio) nextid++; else if ((currentPage() == qwpDevice) && ! bDelay) nextid++; return nextid; } void AudioWizard::playChord() { AudioOutputPtr ao = g.ao; if (! ao || aosSource || bInit) return; aosSource = ao->playSample(QLatin1String(":/wb_male.oga"), true); } void AudioWizard::restartAudio() { aosSource = NULL; Audio::stop(); g.s.qsAudioInput = qcbInput->currentText(); g.s.qsAudioOutput = qcbOutput->currentText(); Audio::start(); if (qgsScene) { delete qgsScene; qgiSource = NULL; qgsScene = NULL; } if ((currentPage() == qwpPositional) || (currentPage() == qwpDeviceTuning)) playChord(); } void AudioWizard::reject() { g.s = sOldSettings; g.s.lmLoopMode = Settings::None; restartAudio(); AudioOutputPtr ao = g.ao; if (ao) ao->wipe(); aosSource = NULL; g.bInAudioWizard = false; QWizard::reject(); } void AudioWizard::accept() { if (! bTransmitChanged) g.s.atTransmit = sOldSettings.atTransmit; else if (qrPTT->isChecked()) g.s.atTransmit = Settings::PushToTalk; else g.s.atTransmit = Settings::VAD; g.s.bMute = sOldSettings.bMute; g.s.bDeaf = sOldSettings.bDeaf; g.s.lmLoopMode = Settings::None; // Switch TTS<->Sounds according to user selection if (!qrbNotificationCustom->isChecked()) { Settings::MessageLog mlReplace = qrbNotificationTTS->isChecked() ? Settings::LogSoundfile : Settings::LogTTS; for (int i = Log::firstMsgType;i <= Log::lastMsgType; ++i) { if (g.s.qmMessages[i] & mlReplace) g.s.qmMessages[i] ^= Settings::LogSoundfile | Settings::LogTTS; } if (qrbNotificationTTS->isChecked()) { g.s.bTTS = true; g.mw->qaAudioTTS->setChecked(true); } } g.s.bUsage = qcbUsage->isChecked(); g.bPosTest = false; restartAudio(); g.bInAudioWizard = false; QWizard::accept(); } bool AudioWizard::validateCurrentPage() { if (currentId() == 1) { if ((qcbInput->currentIndex() < 0) || (qcbOutput->currentIndex() < 0)) return false; } return true; } void AudioWizard::on_Ticker_timeout() { AudioInputPtr ai = g.ai; AudioOutputPtr ao = g.ao; if (! ai || ! ao) return; int iPeak = static_cast(ai->dMaxMic); if (iTicks++ >= 50) { iMaxPeak = 0; iTicks = 0; } if (iPeak > iMaxPeak) iMaxPeak = iPeak; abAmplify->iBelow = qsMaxAmp->value(); abAmplify->iValue = iPeak; abAmplify->iPeak = iMaxPeak; abAmplify->update(); abVAD->iBelow = iroundf(g.s.fVADmin * 32767.0f + 0.5f); abVAD->iAbove = iroundf(g.s.fVADmax * 32767.0f + 0.5f); if (g.s.vsVAD == Settings::Amplitude) { abVAD->iValue = iroundf((32767.f/96.0f) * (96.0f + ai->dPeakCleanMic) + 0.5f); } else { abVAD->iValue = iroundf(ai->fSpeechProb * 32767.0f + 0.5f); } abVAD->update(); bool active = ai->isTransmitting(); if (active != bLastActive) { bLastActive = active; qlTalkIcon->setPixmap(active ? qpTalkingOn : qpTalkingOff); } if (! qgsScene) { unsigned int nspeaker = 0; const float *spos = ao->getSpeakerPos(nspeaker); if ((nspeaker > 0) && spos) { qgsScene = new QGraphicsScene(QRectF(-4.0f, -4.0f, 8.0f, 8.0f), this); qgsScene->addEllipse(QRectF(-0.12f, -0.12f, 0.24f, 0.24f), QPen(Qt::black), QBrush(Qt::darkRed)); for (unsigned int i=0;iaddEllipse(QRectF(spos[3*i] - 0.1f, spos[3*i+2] - 0.1f, 0.2f, 0.2f), QPen(Qt::black), QBrush(Qt::yellow)); } qgiSource = qgsScene->addEllipse(QRectF(-.15f, -.15f, 0.3f, 0.3f), QPen(Qt::black), QBrush(Qt::green)); qgvView->setScene(qgsScene); qgvView->fitInView(-4.0f, -4.0f, 8.0f, 8.0f, Qt::KeepAspectRatio); } } else if (currentPage() == qwpPositional) { float xp, yp; if ((fX == 0.0f) && (fY == 0.0f)) { fAngle += 0.05f; xp = sinf(fAngle) * 2.0f; yp = cosf(fAngle) * 2.0f; } else { xp = fX; yp = fY; } qgiSource->setPos(xp, yp); if (aosSource) { aosSource->fPos[0] = xp; aosSource->fPos[1] = 0; aosSource->fPos[2] = yp; } } } void AudioWizard::on_qsVAD_valueChanged(int v) { if (! bInit) { g.s.fVADmax = static_cast(v) / 32767.0f; g.s.fVADmin = g.s.fVADmax * 0.9f; } } void AudioWizard::on_qrSNR_clicked(bool on) { if (on) { g.s.vsVAD = Settings::SignalToNoise; g.s.atTransmit = Settings::VAD; updateTriggerWidgets(false); bTransmitChanged = true; } } void AudioWizard::on_qrAmplitude_clicked(bool on) { if (on) { g.s.vsVAD = Settings::Amplitude; g.s.atTransmit = Settings::VAD; updateTriggerWidgets(false); bTransmitChanged = true; } } void AudioWizard::on_qrPTT_clicked(bool on) { if (on) { g.s.atTransmit = Settings::PushToTalk; updateTriggerWidgets(true); bTransmitChanged = true; } } void AudioWizard::on_skwPTT_keySet(bool valid, bool last) { if (valid) qrPTT->setChecked(true); else if (qrPTT->isChecked()) qrAmplitude->setChecked(true); updateTriggerWidgets(valid); bTransmitChanged = true; if (last) { const QList &buttons = skwPTT->getShortcut(); QList ql; bool found = false; foreach(Shortcut s, g.s.qlShortcuts) { if (s.iIndex == g.mw->gsPushTalk->idx) { if (buttons.isEmpty()) continue; else if (! found) { s.qlButtons = buttons; found = true; } } ql << s; } if (! found && ! buttons.isEmpty()) { Shortcut s; s.iIndex = g.mw->gsPushTalk->idx; s.bSuppress = false; s.qlButtons = buttons; ql << s; } g.s.qlShortcuts = ql; GlobalShortcutEngine::engine->bNeedRemap = true; GlobalShortcutEngine::engine->needRemap(); } } void AudioWizard::on_qcbEcho_clicked(bool on) { g.s.bEcho = on; restartAudio(); } void AudioWizard::on_qcbHeadphone_clicked(bool on) { g.s.bPositionalHeadphone = on; restartAudio(); } void AudioWizard::on_qcbPositional_clicked(bool on) { g.s.bPositionalAudio = on; g.s.bTransmitPosition = on; restartAudio(); } void AudioWizard::updateTriggerWidgets(bool ptt) { qwVAD->setEnabled(!ptt); qwpTrigger->setComplete(!ptt || (skwPTT->qlButtons.count() > 0)); } void AudioWizard::on_qcbAttenuateOthers_clicked(bool checked) { g.s.bAttenuateOthers = checked; } void AudioWizard::on_qcbHighContrast_clicked(bool on) { g.s.bHighContrast = on; qliAmpTuningText->setVisible(!g.s.bHighContrast); qliAmpTuningTextHC->setVisible(g.s.bHighContrast); qliVolumeTuningText->setVisible(!g.s.bHighContrast); qliVolumeTuningTextHC->setVisible(g.s.bHighContrast); qliVadTuningText->setVisible(!g.s.bHighContrast); qliVadTuningTextHC->setVisible(g.s.bHighContrast); } void AudioWizard::on_qrbQualityLow_clicked() { g.s.iQuality = 16000; g.s.iFramesPerPacket = 6; restartAudio(); } void AudioWizard::on_qrbQualityBalanced_clicked() { g.s.iQuality = 40000; g.s.iFramesPerPacket = 2; restartAudio(); } void AudioWizard::on_qrbQualityUltra_clicked() { g.s.iQuality = 72000; g.s.iFramesPerPacket = 1; restartAudio(); } void AudioWizard::on_qrbQualityCustom_clicked() { g.s.iQuality = sOldSettings.iQuality; g.s.iFramesPerPacket = sOldSettings.iFramesPerPacket; restartAudio(); }