2020-06-07 14:43:47 +02:00
|
|
|
/****************************************************************************
|
|
|
|
** 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 <http://www.gnu.org/licenses/>.
|
|
|
|
****************************************************************************/
|
|
|
|
|
2020-04-13 23:48:29 +02:00
|
|
|
#include "../headers/scstwsoundplayer.h"
|
|
|
|
|
|
|
|
ScStwSoundPlayer::ScStwSoundPlayer(QObject *parent) : QObject(parent)
|
|
|
|
{
|
|
|
|
this->waitLoop = new QEventLoop(this);
|
|
|
|
this->waitTimer = new QTimer(this);
|
|
|
|
|
2020-10-02 15:20:11 +02:00
|
|
|
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}});
|
2020-04-13 23:48:29 +02:00
|
|
|
|
2021-05-02 20:16:55 +02:00
|
|
|
this->_initializeSondEffect();
|
2020-04-13 23:48:29 +02:00
|
|
|
|
|
|
|
connect(this, &ScStwSoundPlayer::playbackStarted, this->waitLoop, &QEventLoop::quit);
|
2020-06-18 11:53:25 +02:00
|
|
|
connect(this->soundEffect, &QSoundEffect::playingChanged, this->waitLoop, &QEventLoop::quit);
|
2020-04-13 23:48:29 +02:00
|
|
|
}
|
|
|
|
|
2020-10-03 18:33:17 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-04-13 23:48:29 +02:00
|
|
|
|
|
|
|
// stop playback
|
2020-06-18 11:53:25 +02:00
|
|
|
if(this->soundEffect->isPlaying())
|
|
|
|
this->soundEffect->stop();
|
2020-04-13 23:48:29 +02:00
|
|
|
|
|
|
|
// update currently playing action
|
2020-10-02 15:20:11 +02:00
|
|
|
this->currentlyPlayingSound = sound;
|
2020-04-13 23:48:29 +02:00
|
|
|
|
2021-05-02 20:16:55 +02:00
|
|
|
if(!this->_setSoundVolume(volume))
|
2021-05-02 15:29:08 +02:00
|
|
|
return Error;
|
2020-04-13 23:48:29 +02:00
|
|
|
|
2020-06-18 11:53:25 +02:00
|
|
|
// load
|
2020-10-02 15:20:11 +02:00
|
|
|
this->soundEffect->setSource(this->soundFiles[sound]["path"].toString());
|
2020-04-13 23:48:29 +02:00
|
|
|
|
2020-06-18 11:53:25 +02:00
|
|
|
// wait for the effect to load
|
|
|
|
QEventLoop loop;
|
|
|
|
while(this->soundEffect->status() != QSoundEffect::Ready) {
|
2020-10-03 18:33:17 +02:00
|
|
|
qDebug() << "[DEBUG][Sound] Sound is not ready yet, status is: " << this->soundEffect->status();
|
2020-06-18 11:53:25 +02:00
|
|
|
QObject::connect(this->soundEffect, &QSoundEffect::statusChanged, &loop, &QEventLoop::quit);
|
|
|
|
loop.exec();
|
2020-04-13 23:48:29 +02:00
|
|
|
}
|
|
|
|
|
2020-10-04 15:11:07 +02:00
|
|
|
qDebug() << "[DEBUG][Sound] Playing sound now: " << sound;
|
|
|
|
|
2020-04-13 23:48:29 +02:00
|
|
|
// start
|
2020-06-18 11:53:25 +02:00
|
|
|
this->soundEffect->play();
|
|
|
|
|
|
|
|
// emit a playback start
|
|
|
|
emit this->playbackStarted();
|
|
|
|
|
|
|
|
// save started at
|
|
|
|
this->playingStartedAt = QDateTime::currentMSecsSinceEpoch();
|
2020-04-13 23:48:29 +02:00
|
|
|
|
|
|
|
// pass the time of start if requested
|
|
|
|
if(timeOfStart != nullptr) {
|
2020-06-18 11:53:25 +02:00
|
|
|
*timeOfStart = this->playingStartedAt;
|
2020-04-13 23:48:29 +02:00
|
|
|
}
|
|
|
|
|
2020-10-02 17:05:52 +02:00
|
|
|
if(sound < Start)
|
2020-04-13 23:48:29 +02:00
|
|
|
return this->waitForSoundFinish();
|
|
|
|
|
2020-10-03 18:33:17 +02:00
|
|
|
return ScStwSoundPlayer::Success;
|
2020-04-13 23:48:29 +02:00
|
|
|
}
|
|
|
|
|
2020-10-03 11:10:15 +02:00
|
|
|
bool ScStwSoundPlayer::cancel() {
|
2020-06-18 11:53:25 +02:00
|
|
|
if(!this->soundEffect->isPlaying() )
|
2020-04-13 23:48:29 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// stop playback
|
2020-06-18 11:53:25 +02:00
|
|
|
this->soundEffect->stop();
|
2020-10-03 18:33:17 +02:00
|
|
|
this->waitLoop->exit(-1);
|
2020-10-03 11:10:15 +02:00
|
|
|
|
|
|
|
return true;
|
2020-04-13 23:48:29 +02:00
|
|
|
}
|
|
|
|
|
2020-10-03 18:33:17 +02:00
|
|
|
ScStwSoundPlayer::PlayResult ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) {
|
2020-06-18 11:53:25 +02:00
|
|
|
if(!this->soundEffect->isPlaying())
|
2020-10-03 18:33:17 +02:00
|
|
|
return ScStwSoundPlayer::Error;
|
2020-04-13 23:48:29 +02:00
|
|
|
|
|
|
|
// wait until the audio output reports the sound is over
|
2020-10-03 18:33:17 +02:00
|
|
|
if(waitLoop->exec() == -1)
|
|
|
|
return ScStwSoundPlayer::Cancelled;
|
2020-04-13 23:48:29 +02:00
|
|
|
|
|
|
|
// wait until the sound is actually over
|
|
|
|
// the timeOffset is the buffer time before the audio started!
|
2020-10-02 15:20:11 +02:00
|
|
|
int timeOffset = this->soundFiles[this->currentlyPlayingSound]["duration"].toDouble() - (QDateTime::currentMSecsSinceEpoch() - playingStartedAt);
|
2020-04-13 23:48:29 +02:00
|
|
|
|
|
|
|
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) {
|
2020-10-02 15:20:11 +02:00
|
|
|
int latency = this->playingStartedAt + this->soundFiles[this->currentlyPlayingSound]["duration"].toDouble();
|
2020-04-13 23:48:29 +02:00
|
|
|
*timeOfStop = QDateTime::currentMSecsSinceEpoch() - latency;
|
|
|
|
}
|
|
|
|
|
2020-10-03 18:33:17 +02:00
|
|
|
return ScStwSoundPlayer::Success;
|
2020-04-13 23:48:29 +02:00
|
|
|
}
|
2020-10-03 13:49:56 +02:00
|
|
|
|
|
|
|
bool ScStwSoundPlayer::isPlaying() {
|
|
|
|
return this->soundEffect->isPlaying();
|
|
|
|
}
|
2021-05-02 20:16:55 +02:00
|
|
|
|
|
|
|
#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
|