/**************************************************************************** ** ScStw Libraries ** Copyright (C) 2020 Itsblue development ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . ****************************************************************************/ #include "../headers/scstwsoundplayer.h" ScStwSoundPlayer::ScStwSoundPlayer(QObject *parent) : QObject(parent) { this->waitLoop = new QEventLoop(this); this->waitTimer = new QTimer(this); this->soundFiles.insert(AtYourMarks, {{"path","qrc:/sound/AtYourMarksSound.wav"}, {"duration", 1000}}); this->soundFiles.insert(Ready, {{"path","qrc:/sound/ReadySound.wav"}, {"duration", 570}}); this->soundFiles.insert(Start, {{"path","qrc:/sound/StartsignalSoundExtended.wav"}, {"duration", 3200}}); this->soundFiles.insert(FalseStart, {{"path","qrc:/sound/FalseStartSound.wav"}, {"duration", 2000}}); this->_initializeSondEffect(); connect(this, &ScStwSoundPlayer::playbackStarted, this->waitLoop, &QEventLoop::quit); connect(this->soundEffect, &QSoundEffect::playingChanged, this->waitLoop, &QEventLoop::quit); } ScStwSoundPlayer::PlayResult ScStwSoundPlayer::play(ScStwSoundPlayer::StartSound sound, double volume, double *timeOfStart) { if(!this->soundFiles.contains(sound)) { qDebug() << "[ERROR][SoundPlayer] Sound file was not found for sound" << sound; return ScStwSoundPlayer::Error; } // stop playback if(this->soundEffect->isPlaying()) this->soundEffect->stop(); // update currently playing action this->currentlyPlayingSound = sound; if(!this->_setSoundVolume(volume)) return Error; // load this->soundEffect->setSource(this->soundFiles[sound]["path"].toString()); // wait for the effect to load QEventLoop loop; while(this->soundEffect->status() != QSoundEffect::Ready) { qDebug() << "[DEBUG][Sound] Sound is not ready yet, status is: " << this->soundEffect->status(); QObject::connect(this->soundEffect, &QSoundEffect::statusChanged, &loop, &QEventLoop::quit); loop.exec(); } qDebug() << "[DEBUG][Sound] Playing sound now: " << sound; // start this->soundEffect->play(); // emit a playback start emit this->playbackStarted(); // save started at this->playingStartedAt = QDateTime::currentMSecsSinceEpoch(); // pass the time of start if requested if(timeOfStart != nullptr) { *timeOfStart = this->playingStartedAt; } if(sound < Start) return this->waitForSoundFinish(); return ScStwSoundPlayer::Success; } bool ScStwSoundPlayer::cancel() { if(!this->soundEffect->isPlaying() ) return false; // stop playback this->soundEffect->stop(); this->waitLoop->exit(-1); return true; } ScStwSoundPlayer::PlayResult ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) { if(!this->soundEffect->isPlaying()) return ScStwSoundPlayer::Error; // wait until the audio output reports the sound is over if(waitLoop->exec() == -1) return ScStwSoundPlayer::Cancelled; // wait until the sound is actually over // the timeOffset is the buffer time before the audio started! int timeOffset = this->soundFiles[this->currentlyPlayingSound]["duration"].toDouble() - (QDateTime::currentMSecsSinceEpoch() - playingStartedAt); if(timeOffset > 0) { QTimer timer; timer.singleShot(timeOffset, this->waitLoop, &QEventLoop::quit); this->waitLoop->exec(); } // calculate the point in time where the sound playback actually ended if(timeOfStop != nullptr) { int latency = this->playingStartedAt + this->soundFiles[this->currentlyPlayingSound]["duration"].toDouble(); *timeOfStop = QDateTime::currentMSecsSinceEpoch() - latency; } return ScStwSoundPlayer::Success; } bool ScStwSoundPlayer::isPlaying() { return this->soundEffect->isPlaying(); } #ifdef ScStwLibraries_Raspi void ScStwSoundPlayer::_initializeSondEffect() { this->_audioOutputDevice = nullptr; for(QAudioDeviceInfo info : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { qDebug() << info.deviceName(); if(info.deviceName().contains("Headphones")) this->_audioOutputDevice = new QAudioDeviceInfo(info); } if(this->_audioOutputDevice == nullptr) this->_audioOutputDevice = new QAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice()); this->soundEffect = new QSoundEffect(*this->_audioOutputDevice, this); this->soundEffect->setLoopCount(1); } #else void ScStwSoundPlayer::_initializeSondEffect() { this->_audioOutputDevice = new QAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice()); this->soundEffect = new QSoundEffect(*this->_audioOutputDevice, this); this->soundEffect->setLoopCount(1); } #endif #ifdef ScStwLibraries_Raspi bool ScStwSoundPlayer::_setSoundVolume(double volume) { QString cardName = this->_audioOutputDevice->deviceName(); QStringRef shortCardName = cardName.midRef(cardName.indexOf(QLatin1String("="), 0) + 1); int cardIndex = snd_card_get_index(shortCardName.toLocal8Bit().constData()); QString soundDevice = QString(QLatin1String("hw:%1")).arg(cardIndex); qDebug() << "[INFO][SoundPlayer] Using audio device: " << soundDevice; long min, max; snd_mixer_t *handle; snd_mixer_selem_id_t *sid; snd_mixer_elem_t *elem; if(snd_mixer_open(&handle, 0) < 0) { qDebug() << "[ERROR][SoundPlayer] Could not open mixer"; return false; } if(snd_mixer_selem_register(handle, NULL, NULL) < 0) { qDebug() << "[ERROR][SoundPlayer] Could not register selem"; return false; } if(snd_mixer_attach(handle, soundDevice.toStdString().c_str()) < 0) { qDebug() << "[ERROR][SoundPlayer] Could not attach mixer"; return false; } if(snd_mixer_load(handle) < 0) { qDebug() << "[ERROR][SoundPlayer] Could not load mixer"; return false; } // set volume for all channels snd_mixer_selem_id_alloca(&sid); bool success = false; for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) { snd_mixer_selem_get_id(elem, sid); if (!snd_mixer_selem_is_active(elem)) continue; elem = snd_mixer_find_selem(handle, sid); if(!elem) continue; snd_mixer_selem_get_playback_volume_range(elem, &min, &max); snd_mixer_selem_set_playback_volume_all(elem, min + (volume) * (max - min)); success = true; } snd_mixer_close(handle); this->soundEffect->setVolume(1); return success; } #else bool ScStwSoundPlayer::_setSoundVolume(double volume) { this->soundEffect->setVolume(volume); return true; } #endif