almost finished implementing timer, race and sounds
This commit is contained in:
parent
1570bd86c9
commit
2cfc40444e
3 changed files with 374 additions and 0 deletions
174
ScStwLibraries/headers/ScStw.hpp
Normal file
174
ScStwLibraries/headers/ScStw.hpp
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
#ifndef SCSTW_HPP
|
||||||
|
#define SCSTW_HPP
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mainpage ScStw Libraries documentation
|
||||||
|
*
|
||||||
|
* @section intro_sec Introduction
|
||||||
|
*
|
||||||
|
* This library is meant for usage with the Speed climbing stopwatch project.
|
||||||
|
* It contains some helper classes to build a client application for the ScStw basestation with Qt.
|
||||||
|
*
|
||||||
|
* @section section Installation
|
||||||
|
* @code{.sh}
|
||||||
|
* cd yourRepo
|
||||||
|
* git submodule add https://git.itsblue.de/ScStw/shared-libraries/
|
||||||
|
* git submodule update --init --recursive
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* Add to the list of libraries for the Qt-Secret assembly. For an example you can create Main.Pro in which connect Qt-Secret and your project.pro files as subprojects.
|
||||||
|
*
|
||||||
|
* Main.pro:
|
||||||
|
* @code{.pro}
|
||||||
|
* TEMPLATE = subdirs
|
||||||
|
* CONFIG += ordered
|
||||||
|
*
|
||||||
|
* SUBDIRS += \
|
||||||
|
* ScStwLibraries \
|
||||||
|
* MyProject
|
||||||
|
*
|
||||||
|
* ScStwLibraries.file = shared-libraries/ScStwLibraries/ScStwLibraries.pro
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* And in your MyProject.pro include the .pri file:
|
||||||
|
* @code{.pro}
|
||||||
|
* include($$PWD/../shared-libraries/ScStwLibraries/ScStwLibraries.pri)
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ScStw class provides some shared functions and enums for use in the ScStw project.
|
||||||
|
*/
|
||||||
|
class ScStw : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The RaceState enum contains all states a race can have
|
||||||
|
*/
|
||||||
|
<<<<<<< HEAD:ScStwLibraries/ScStw.hpp
|
||||||
|
enum RaceState { IDLE, STARTING, WAITING, RUNNING, STOPPED };
|
||||||
|
Q_ENUM(RaceState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The SignalKey enum contains all signal keys a client can subscribe to
|
||||||
|
*
|
||||||
|
* @see ScStw::signalKeyFromInt()
|
||||||
|
*/
|
||||||
|
=======
|
||||||
|
|
||||||
|
>>>>>>> - some changes to file structure:ScStwLibraries/headers/ScStw.hpp
|
||||||
|
enum SignalKey {
|
||||||
|
InvalidSignal = -1,
|
||||||
|
RaceStateChanged = 9000,
|
||||||
|
TimersChanged = 9001,
|
||||||
|
ExtensionsChanged = 9002,
|
||||||
|
NextStartActionChanged = 9003 /*, ProfilesChanged*/
|
||||||
|
};
|
||||||
|
Q_ENUM(SignalKey)
|
||||||
|
|
||||||
|
<<<<<<< HEAD:ScStwLibraries/ScStw.hpp
|
||||||
|
/**
|
||||||
|
* @brief The NextStartAction enum contains all action that occur before a race is started
|
||||||
|
*/
|
||||||
|
enum NextStartAction { AtYourMarks, Ready, Start, None };
|
||||||
|
Q_ENUM(NextStartAction)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The BaseStationSetting enum contains all settings of the base station that can be changed by a client
|
||||||
|
*
|
||||||
|
* @see ScStw::baseStationSettingFromInt()
|
||||||
|
* @see ScStw::baseStationSettingToString()
|
||||||
|
* @see ScStw::baseStationSettingFromString()
|
||||||
|
* @see ScStw::baseStationSettings
|
||||||
|
*/
|
||||||
|
=======
|
||||||
|
>>>>>>> - some changes to file structure:ScStwLibraries/headers/ScStw.hpp
|
||||||
|
enum BaseStationSetting {
|
||||||
|
InvalidSetting = -1,
|
||||||
|
ReadySoundEnableSetting,
|
||||||
|
ReadySoundDelaySetting,
|
||||||
|
AtYourMarksSoundEnableSetting,
|
||||||
|
AtYourMarksSoundDelaySetting,
|
||||||
|
SoundVolumeSetting
|
||||||
|
};
|
||||||
|
Q_ENUM(BaseStationSetting)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ErrorCode enum contains all error codes that can occur when sending a command to the basestation
|
||||||
|
*/
|
||||||
|
enum ErrorCode {
|
||||||
|
Success = 200,
|
||||||
|
|
||||||
|
Error = 900,
|
||||||
|
NotConnectedError = 910,
|
||||||
|
TimeoutError = 911,
|
||||||
|
SettingNotAccessibleError = 901
|
||||||
|
};
|
||||||
|
Q_ENUM(ErrorCode)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOCKET_MESSAGE_START_KEY contains the key, a message is supposed to start with
|
||||||
|
*/
|
||||||
|
static const char* SOCKET_MESSAGE_START_KEY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOCKET_MESSAGE_END_KEY contains the key, a message is supposed to end with
|
||||||
|
*/
|
||||||
|
static const char* SOCKET_MESSAGE_END_KEY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief baseStationSettings contains a string with reference to all BaseStationSetting values
|
||||||
|
*
|
||||||
|
* @see ScStw::BaseStationSetting
|
||||||
|
* @see ScStw::baseStationSettingToString()
|
||||||
|
* @see ScStw::baseStationSettingFromString()
|
||||||
|
*/
|
||||||
|
static const QMap<QString, ScStw::BaseStationSetting> baseStationSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to convert an int to a BaseStationSetting
|
||||||
|
* @param i the int to convert
|
||||||
|
* @return a BaseStationSetting
|
||||||
|
*
|
||||||
|
* @see ScStw::BaseStationSetting
|
||||||
|
*/
|
||||||
|
static BaseStationSetting baseStationSettingfromInt(int i);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to convert a QString to a BaseStationSetting
|
||||||
|
* @param s the string to convert
|
||||||
|
* @return a BaseStationSetting
|
||||||
|
*
|
||||||
|
* @see ScStw::BaseStationSetting
|
||||||
|
* @see ScStw::baseStationSettingToString()
|
||||||
|
*/
|
||||||
|
static BaseStationSetting baseStationSettingFromString(QString s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to convert BaseStationSetting to a QString
|
||||||
|
* @param s the BaseStationSetting to convert
|
||||||
|
* @return a QString
|
||||||
|
*
|
||||||
|
* @see ScStw::BaseStationSetting
|
||||||
|
* @see ScStw::baseStationSettingFromString()
|
||||||
|
*/
|
||||||
|
static QString baseStationSettingToString(BaseStationSetting s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to convert an int to a SignalKey
|
||||||
|
* @param i the int to convert
|
||||||
|
* @return a SignalKey
|
||||||
|
*
|
||||||
|
* @see ScStw::SignalKey
|
||||||
|
*/
|
||||||
|
static SignalKey signalKeyFromInt(int i);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ScStw() : QObject(nullptr) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SCSTW_HPP
|
41
ScStwLibraries/headers/scstwsoundplayer.h
Normal file
41
ScStwLibraries/headers/scstwsoundplayer.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#ifndef SCSTWSTARTSOUNDPLAYER_H
|
||||||
|
#define SCSTWSTARTSOUNDPLAYER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QAudioOutput>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
class ScStwSoundPlayer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ScStwSoundPlayer(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMap<int, QFile*> soundFiles;
|
||||||
|
QFile *falseStartSoundFile;
|
||||||
|
|
||||||
|
QAudioOutput *audioOutput;
|
||||||
|
QEventLoop *waitLoop;
|
||||||
|
QTimer *waitTimer;
|
||||||
|
int currentlyPlayingAction;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool play(int action, double volume, double *timeOfStop = nullptr);
|
||||||
|
bool cancel(double volume);
|
||||||
|
bool waitForSoundFinish(double *timeOfStop = nullptr);
|
||||||
|
//int interrupt();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleStateChanged(QAudio::State newState);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void playbackStarted();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SCSTWSTARTSOUNDPLAYER_H
|
159
ScStwLibraries/sources/scstwsoundplayer.cpp
Normal file
159
ScStwLibraries/sources/scstwsoundplayer.cpp
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
#include "../headers/scstwsoundplayer.h"
|
||||||
|
|
||||||
|
ScStwSoundPlayer::ScStwSoundPlayer(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
this->waitLoop = new QEventLoop(this);
|
||||||
|
this->waitTimer = new QTimer(this);
|
||||||
|
|
||||||
|
this->soundFiles.insert(0, new QFile(":/sound/AtYourMarksSound.wav", this));
|
||||||
|
this->soundFiles.insert(1, new QFile(":/sound/ReadySound.wav", this));
|
||||||
|
this->soundFiles.insert(2, new QFile(":/sound/StartsignalSound.wav", this));
|
||||||
|
this->falseStartSoundFile = new QFile(":/sound/FalseStartSound.wav", this);
|
||||||
|
|
||||||
|
// init sound format
|
||||||
|
QAudioFormat format;
|
||||||
|
format.setSampleRate(44100);
|
||||||
|
format.setChannelCount(2);
|
||||||
|
format.setSampleSize(16);
|
||||||
|
format.setCodec("audio/pcm");
|
||||||
|
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||||
|
format.setSampleType(QAudioFormat::SignedInt);
|
||||||
|
|
||||||
|
// check if format is valid
|
||||||
|
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
|
||||||
|
if (!info.isFormatSupported(format)) {
|
||||||
|
qWarning() << "Raw audio format not supported by backend, cannot play audio.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->audioOutput = new QAudioOutput(format, this);
|
||||||
|
this->audioOutput->setCategory("ScStw start sound");
|
||||||
|
|
||||||
|
connect(this->audioOutput, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));
|
||||||
|
connect(this, &ScStwSoundPlayer::playbackStarted, this->waitLoop, &QEventLoop::quit);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScStwSoundPlayer::play(int action, double volume, double *timeOfStart) {
|
||||||
|
if(!this->soundFiles.contains(action))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// stop playback
|
||||||
|
this->audioOutput->stop();
|
||||||
|
|
||||||
|
// update currently playing action
|
||||||
|
this->currentlyPlayingAction = action;
|
||||||
|
|
||||||
|
QFile *playbackFile = this->soundFiles[action];
|
||||||
|
|
||||||
|
// close soundfile
|
||||||
|
if(playbackFile->isOpen())
|
||||||
|
playbackFile->close();
|
||||||
|
|
||||||
|
// open soundfile
|
||||||
|
if(!playbackFile->open(QFile::ReadOnly)) {
|
||||||
|
qWarning() << "[ERROR][SOUNDS] Could not open sound file " << action << " !!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update volume
|
||||||
|
this->audioOutput->setVolume(volume);
|
||||||
|
|
||||||
|
// start
|
||||||
|
this->audioOutput->start(playbackFile);
|
||||||
|
|
||||||
|
qDebug() << "playback stared, took " << this->audioOutput->elapsedUSecs();
|
||||||
|
|
||||||
|
// pass the time of start if requested
|
||||||
|
if(timeOfStart != nullptr) {
|
||||||
|
*timeOfStart = QDateTime::currentMSecsSinceEpoch() - (this->audioOutput->elapsedUSecs() / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(action < 2)
|
||||||
|
return this->waitForSoundFinish();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScStwSoundPlayer::cancel(double volume) {
|
||||||
|
if(!QList<QAudio::State>{QAudio::ActiveState}.contains(this->audioOutput->state()) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// stop playback
|
||||||
|
this->audioOutput->stop();
|
||||||
|
|
||||||
|
if(this->currentlyPlayingAction != 2)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// close soundfile
|
||||||
|
if(this->falseStartSoundFile->isOpen())
|
||||||
|
this->falseStartSoundFile->close();
|
||||||
|
|
||||||
|
// open sound file
|
||||||
|
if(!this->falseStartSoundFile->open(QFile::ReadOnly)) {
|
||||||
|
qWarning() << "[ERROR][SOUNDS] Could not open false start sound file!!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update volume
|
||||||
|
this->audioOutput->setVolume(volume);
|
||||||
|
|
||||||
|
// start
|
||||||
|
this->audioOutput->start(this->falseStartSoundFile);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) {
|
||||||
|
if(this->audioOutput->state() != QAudio::ActiveState)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// wait until the audio output reports the sound is over
|
||||||
|
waitLoop->exec();
|
||||||
|
|
||||||
|
qDebug() << "waitLoop exited!! elapsed: " << this->audioOutput->elapsedUSecs() << " processed: " << this->audioOutput->processedUSecs();
|
||||||
|
// wait until the sound is actually over
|
||||||
|
// the timeOffset is the buffer time before the audio started!
|
||||||
|
int timeOffset = ((this->audioOutput->processedUSecs() - this->audioOutput->elapsedUSecs()) / 1000);
|
||||||
|
|
||||||
|
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->audioOutput->processedUSecs() - this->audioOutput->elapsedUSecs()) / 1000;
|
||||||
|
*timeOfStop = QDateTime::currentMSecsSinceEpoch() - latency;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "finished now";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScStwSoundPlayer::handleStateChanged(QAudio::State newState)
|
||||||
|
{
|
||||||
|
switch (newState) {
|
||||||
|
case QAudio::IdleState:
|
||||||
|
// Finished playing (no more data)
|
||||||
|
qDebug() << "sound reported over!! elapsed: " << this->audioOutput->elapsedUSecs() << " processed: " << this->audioOutput->processedUSecs();
|
||||||
|
waitLoop->exit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QAudio::StoppedState:
|
||||||
|
// Stopped for other reasons
|
||||||
|
if (this->audioOutput->error() != QAudio::NoError) {
|
||||||
|
// Error handling
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QAudio::ActiveState:
|
||||||
|
emit this->playbackStarted();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// ... other cases as appropriate
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue