diff --git a/Qt-Secret/src/build/release/libQt-Secret.so.1.2.0 b/Qt-Secret/src/build/release/libQt-Secret.so.1.2.0 index 9a6695c..534641a 100755 Binary files a/Qt-Secret/src/build/release/libQt-Secret.so.1.2.0 and b/Qt-Secret/src/build/release/libQt-Secret.so.1.2.0 differ diff --git a/ScStwLibraries/ScStw.hpp b/ScStwLibraries/ScStw.hpp deleted file mode 100644 index 3dab7fd..0000000 --- a/ScStwLibraries/ScStw.hpp +++ /dev/null @@ -1,167 +0,0 @@ -#ifndef SCSTW_HPP -#define SCSTW_HPP - -#include -#include - -/** - * @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 - */ - 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() - */ - enum SignalKey { - InvalidSignal = -1, - RaceStateChanged = 9000, - TimersChanged = 9001, - ExtensionsChanged = 9002, - NextStartActionChanged = 9003 /*, ProfilesChanged*/ - }; - Q_ENUM(SignalKey) - - /** - * @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 - */ - 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 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 diff --git a/ScStwLibraries/ScStwLibraries.pri b/ScStwLibraries/ScStwLibraries.pri index 9f20d3e..f6a507d 100644 --- a/ScStwLibraries/ScStwLibraries.pri +++ b/ScStwLibraries/ScStwLibraries.pri @@ -13,3 +13,4 @@ unix:LIBS += -L$$SCSTWLIBRARIES_LIB_OUTPUT_DIR -lScStwLibraries win32:LIBS += -L$$SCSTWLIBRARIES_LIB_OUTPUT_DIR -lScStwLibraries1 INCLUDEPATH += "$$PWD" +INCLUDEPATH += "$$PWD"/headers diff --git a/ScStwLibraries/ScStwLibraries.pro b/ScStwLibraries/ScStwLibraries.pro index d2fcbc9..42fe8f2 100644 --- a/ScStwLibraries/ScStwLibraries.pro +++ b/ScStwLibraries/ScStwLibraries.pro @@ -1,5 +1,5 @@ QT -= gui -QT += network +QT += network multimedia TEMPLATE = lib DEFINES += SCSTWLIBRARIES_LIBRARY @@ -18,16 +18,22 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ - ScStw.cpp \ - scstwclient.cpp + sources/ScStw.cpp \ + sources/scstwclient.cpp \ + sources/scstwrace.cpp \ + sources/scstwsoundplayer.cpp \ + sources/scstwtimer.cpp HEADERS += \ - ScStw.hpp \ - ScStwLibraries_global.h \ - scstwclient.h + headers/ScStw.hpp \ + headers/ScStwLibraries_global.h \ + headers/scstwrace.h \ + headers/scstwclient.h \ + headers/scstwsoundplayer.h \ + headers/scstwtimer.h RESOURCES += \ - ScStwLibrariesShared.qrc + resources/ScStwLibrariesShared.qrc DISTFILES += @@ -39,7 +45,5 @@ CONFIG(release, debug|release): { } # Default rules for deployment. -unix { - target.path = /usr/lib -} +target.path = $$GLOBAL_TARGET_PATH/lib !isEmpty(target.path): INSTALLS += target diff --git a/ScStwLibraries/ScStwLibrariesShared.qrc b/ScStwLibraries/ScStwLibrariesShared.qrc deleted file mode 100644 index 2e7d415..0000000 --- a/ScStwLibraries/ScStwLibrariesShared.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - ScStwBasestation.sb64 - - diff --git a/ScStwLibraries/ScStwLibraries_global.h b/ScStwLibraries/headers/ScStwLibraries_global.h similarity index 100% rename from ScStwLibraries/ScStwLibraries_global.h rename to ScStwLibraries/headers/ScStwLibraries_global.h diff --git a/ScStwLibraries/scstwclient.h b/ScStwLibraries/headers/scstwclient.h similarity index 99% rename from ScStwLibraries/scstwclient.h rename to ScStwLibraries/headers/scstwclient.h index 830ba98..7200c66 100644 --- a/ScStwLibraries/scstwclient.h +++ b/ScStwLibraries/headers/scstwclient.h @@ -14,7 +14,7 @@ #include #include -#include +#include "ScStw.hpp" /** * This class is used to connect and talk to the ScStw basestation. diff --git a/ScStwLibraries/headers/scstwrace.h b/ScStwLibraries/headers/scstwrace.h new file mode 100644 index 0000000..e0b2788 --- /dev/null +++ b/ScStwLibraries/headers/scstwrace.h @@ -0,0 +1,78 @@ +#ifndef SCSTWRACE_H +#define SCSTWRACE_H + +#include +#include +#include +#include +#include "scstwtimer.h" +#include "scstwsoundplayer.h" + +class ScStwRace : public QObject +{ + Q_OBJECT +public: + explicit ScStwRace(QObject *parent = nullptr); + + enum RaceState { IDLE, STARTING, WAITING, RUNNING, STOPPED }; + Q_ENUM(RaceState) + + enum StartAction { None = -1, AtYourMarks, Ready, Start }; + Q_ENUM(StartAction) + +private: + RaceState state; + + QList timers; + QList timerEnableQueque; + + QTimer *nextActionTimer; + QEventLoop *nextActionLoop; + + // sounds + ScStwSoundPlayer * soundPlayer; + + StartAction nextStartAction; + + // some settings + double soundVolume; + + /** + * @brief stores the start action settings + * + * @details Stores the settings for the action ScStwRace::AtYourMarks and ScStwRace::Ready. The settings keys are: "Enabled" and "Delay" + */ + QMap startActionSettings; + + void setState(RaceState newState); + +public slots: + int start(); + int stop(); + void handleTimerStop(); + int reset(); + void cancelStart(bool falseStart); + QVariantMap getNextStartActionDetails(); + bool writeStartActionSetting(StartAction action, bool enabled, int delay); + bool setSoundVolume(double volume); + bool addTimer(ScStwTimer *timer); + RaceState getState(); + StartAction getNextStartAction(); + QVariantList getTimerDetailList(); + +private slots: + void refreshTimerStates(); + void handleTimerEnable(ScStwTimer* timer); + bool playSoundsAndStartTimers(StartAction thisAction); + +signals: + void startTimers(); + void stopTimers(int type); + void resetTimers(); + void stateChanged(RaceState state); + void nextStartActionChanged(); + + +}; + +#endif // SCSTWRACE_H diff --git a/ScStwLibraries/headers/scstwstartsoundplayer.h b/ScStwLibraries/headers/scstwstartsoundplayer.h new file mode 100644 index 0000000..961a01c --- /dev/null +++ b/ScStwLibraries/headers/scstwstartsoundplayer.h @@ -0,0 +1,34 @@ +#ifndef SCSTWSTARTSOUNDPLAYER_H +#define SCSTWSTARTSOUNDPLAYER_H + +#include +#include +#include +#include +#include +#include +#include + +class ScStwStartSoundPlayer : public QObject +{ + Q_OBJECT +public: + explicit ScStwStartSoundPlayer(QObject *parent = nullptr); + +private: + QFile *startSoundFile; + QAudioOutput *audioOutput; + QEventLoop *waitLoop; + +public slots: + bool play(double volume, double *timeOfStop = nullptr); + //int interrupt(); + +private slots: + void handleStateChanged(QAudio::State newState); + +signals: + +}; + +#endif // SCSTWSTARTSOUNDPLAYER_H diff --git a/ScStwLibraries/headers/scstwtimer.h b/ScStwLibraries/headers/scstwtimer.h new file mode 100644 index 0000000..5e3d890 --- /dev/null +++ b/ScStwLibraries/headers/scstwtimer.h @@ -0,0 +1,63 @@ +#ifndef SCSTWTIMER_H +#define SCSTWTIMER_H + +#include +#include +#include +#include "ScStw.hpp" + +class ScStwTimer : public QObject +{ + Q_OBJECT +public: + explicit ScStwTimer(QObject *parent = nullptr); + + enum TimerState { IDLE, STARTING, WAITING, RUNNING, WON, LOST, FAILED, CANCELLED, DISABLED }; + enum StopReason { ManualStop, CancelStop, FailStop, TopPadStop }; + +protected: + TimerState state; + + // variables for capturing the time + double startTime; + double stopTime; + double stoppedTime; + double reactionTime; + + // values for the startpad + double startPadTriggerTime; + + QDateTime *date; + +public slots: + + // --- main functionality --- + bool start(); + bool cancel(); + bool stop(); + bool stop(TimerState); + bool reset(); + + // --- helper functions --- + TimerState getState(); + double getCurrentTime(); + double getReactionTime(); + void refreshDisableStatus(); + void setDisabled(bool disabled); + +protected slots: + + bool stop(StopReason reason); + + // --- helper functions --- + void setState(TimerState newState); + +signals: + void stateChanged(); + void startCanceled(bool falseStart); + void reactionTimeChanged(); + void stopRequested(); + +}; + +#endif // SCSTWTIMER_H diff --git a/ScStwLibraries/ScStwBasestation.sb64 b/ScStwLibraries/resources/ScStwBasestation.sb64 similarity index 100% rename from ScStwLibraries/ScStwBasestation.sb64 rename to ScStwLibraries/resources/ScStwBasestation.sb64 diff --git a/ScStwLibraries/resources/ScStwLibrariesShared.qrc b/ScStwLibraries/resources/ScStwLibrariesShared.qrc new file mode 100644 index 0000000..e90008a --- /dev/null +++ b/ScStwLibraries/resources/ScStwLibrariesShared.qrc @@ -0,0 +1,10 @@ + + + ScStwBasestation.sb64 + sound/AtYourMarksSound.wav + sound/IFSC frequenzy and duration conform false start sound.wav + sound/ReadySound.wav + sound/FalseStartSound.wav + sound/StartsignalSound.wav + + diff --git a/ScStwLibraries/resources/sound/AtYourMarksSound.wav b/ScStwLibraries/resources/sound/AtYourMarksSound.wav new file mode 100755 index 0000000..f73c703 Binary files /dev/null and b/ScStwLibraries/resources/sound/AtYourMarksSound.wav differ diff --git a/ScStwLibraries/resources/sound/FalseStartSound.wav b/ScStwLibraries/resources/sound/FalseStartSound.wav new file mode 100644 index 0000000..d65ac46 Binary files /dev/null and b/ScStwLibraries/resources/sound/FalseStartSound.wav differ diff --git a/ScStwLibraries/resources/sound/IFSC frequenzy and duration conform false start sound.wav b/ScStwLibraries/resources/sound/IFSC frequenzy and duration conform false start sound.wav new file mode 100644 index 0000000..c40105f Binary files /dev/null and b/ScStwLibraries/resources/sound/IFSC frequenzy and duration conform false start sound.wav differ diff --git a/ScStwLibraries/resources/sound/ReadySound.wav b/ScStwLibraries/resources/sound/ReadySound.wav new file mode 100755 index 0000000..42f0867 Binary files /dev/null and b/ScStwLibraries/resources/sound/ReadySound.wav differ diff --git a/ScStwLibraries/resources/sound/StartsignalSound.wav b/ScStwLibraries/resources/sound/StartsignalSound.wav new file mode 100644 index 0000000..3b5b9bf Binary files /dev/null and b/ScStwLibraries/resources/sound/StartsignalSound.wav differ diff --git a/ScStwLibraries/ScStw.cpp b/ScStwLibraries/sources/ScStw.cpp similarity index 97% rename from ScStwLibraries/ScStw.cpp rename to ScStwLibraries/sources/ScStw.cpp index bcfa78c..3c54958 100644 --- a/ScStwLibraries/ScStw.cpp +++ b/ScStwLibraries/sources/ScStw.cpp @@ -1,4 +1,4 @@ -#include +#include "../headers/ScStw.hpp" const char *ScStw::SOCKET_MESSAGE_START_KEY = ""; const char *ScStw::SOCKET_MESSAGE_END_KEY = ""; diff --git a/ScStwLibraries/scstwclient.cpp b/ScStwLibraries/sources/scstwclient.cpp similarity index 99% rename from ScStwLibraries/scstwclient.cpp rename to ScStwLibraries/sources/scstwclient.cpp index 3c2a865..d53dcda 100644 --- a/ScStwLibraries/scstwclient.cpp +++ b/ScStwLibraries/sources/scstwclient.cpp @@ -1,4 +1,4 @@ -#include "scstwclient.h" +#include "../headers/scstwclient.h" ScStwClient * pGlobalScStwClient = nullptr; diff --git a/ScStwLibraries/sources/scstwrace.cpp b/ScStwLibraries/sources/scstwrace.cpp new file mode 100644 index 0000000..7c611d1 --- /dev/null +++ b/ScStwLibraries/sources/scstwrace.cpp @@ -0,0 +1,389 @@ +#include "../headers/scstwrace.h" + +ScStwRace::ScStwRace(QObject *parent) : QObject(parent) +{ + this->state = IDLE; + + // configure the loop that waits for the sound effect to finish + this->soundPlayer = new ScStwSoundPlayer(); + + // configure timer that handles the delay between the start commands + this->nextActionTimer = new QTimer(this); + nextActionTimer->setSingleShot(true); + this->nextActionLoop = new QEventLoop(this); + + connect(this->nextActionTimer, &QTimer::timeout, this->nextActionLoop, &QEventLoop::quit); + + // write default settings + this->startActionSettings.insert(Start, {{"Enabled", true}, {"Delay", 1}}); + this->writeStartActionSetting(AtYourMarks, false, 0); + this->writeStartActionSetting(Ready, false, 0); + this->setSoundVolume(1.0); +} + +// -------------------------- +// --- Main Functionality --- +// -------------------------- + +/** + * Function to start the race + * + * @brief ScStwRace::startRace + * @return {int} 200: OK; 904: MAIN state not matching + */ +int ScStwRace::start() { + if(this->state != IDLE) { + return 904; + } + + qDebug() << "+ [INFO] starting race"; + + this->setState(STARTING); + + this->playSoundsAndStartTimers(None); + + return 200; +} + +/** + * Function to stop the currently running race + * + * @brief ScStwRace::stopRace + * @param {int} type 0: stop; 1: cancel; 2: false start + * @return {int} 200: OK; 904: MAIN state not matching + */ +int ScStwRace::stop() { + if(this->state != RUNNING && this->state != STARTING) { + return 904; + } + + qDebug() << "+ [INFO] stopping race"; + + int returnCode = 900; + + bool stopOk = true; + + foreach(ScStwTimer *speedTimer, this->timers){ + if(!speedTimer->stop() && speedTimer->getState() != ScStwTimer::DISABLED){ + stopOk = false; + } + } + + returnCode = stopOk ? 200:904; + + if(returnCode == 200) { + this->setState(STOPPED); + } + + return returnCode; +} + +/** + * @brief ScStwRace::handleTimerStop Function to declare the winner and looser timers after a timer has been stopped + */ +void ScStwRace::handleTimerStop() { + if(this->state == RUNNING) { + // find out which timer has won + double lowestStoppedTime = -1; + QList timersWhichHaveWonIds; + + // iterate through all timers and find the lowest time taht was stopped + for(int i = 0; i < this->timers.length(); i++) { + ScStwTimer * timer = this->timers[i]; + if(timer->getCurrentTime() > 0 && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)) { + // this is the timer with the lowest stopped time + lowestStoppedTime = timer->getCurrentTime(); + } + } + + // append the timer(s) with the lowest stopped time to the winner list + for(int i = 0; i < this->timers.length(); i++) { + ScStwTimer * timer = this->timers[i]; + if(timer->getCurrentTime() > 0 && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)) { + // this is the timer with the lowest stopped time + timersWhichHaveWonIds.append(i); + } + } + + // update the states of all timers + for(int i = 0; i < this->timers.length(); i++) { + ScStwTimer * timer = this->timers[i]; + + if(!timersWhichHaveWonIds.contains(i) && timer->getCurrentTime() > 0) { + timer->stop(ScStwTimer::LOST); + } + else if (timersWhichHaveWonIds.contains(i)) { + qDebug() << "timer " << i << " has won"; + timer->stop(ScStwTimer::WON); + } + } + + } +} + +/** + * @brief ScStwRace::resetRace + * @return {int} 200: OK; 904: MAIN state not matching + */ +int ScStwRace::reset() { + if(this->state != STOPPED) { + return 904; + } + + qDebug() << "+ [INFO] resetting race"; + + int returnCode = 200; + + foreach(ScStwTimer *speedTimer, this->timers){ + if(!speedTimer->reset() && speedTimer->getState() != ScStwTimer::DISABLED) { + returnCode = 904; + } + } + + if(returnCode == 200){ + this->setState(IDLE); + } + + return returnCode; +} + +void ScStwRace::cancelStart(bool falseStart){ + // TODO: FIXME + qDebug() << "+ [INFO] cancelling race: " << falseStart; + + if(falseStart){ + // cancel all running timers + foreach(ScStwTimer *timer, this->timers) { + timer->cancel(); + } + + this->setState(STOPPED); + + this->soundPlayer->cancel(this->soundVolume); + } + else { + this->soundPlayer->cancel(this->soundVolume); + this->nextActionTimer->stop(); + this->nextActionLoop->quit(); + this->nextStartAction = None; + + foreach(ScStwTimer *timer, this->timers){ + timer->cancel(); + } + } +} + +bool ScStwRace::playSoundsAndStartTimers(StartAction thisAction) { + if(this->state != STARTING) + return true; + + double timeOfSoundPlaybackStart; + if(this->startActionSettings.contains(thisAction) && this->startActionSettings[thisAction]["Enabled"].toBool()) { + if(!this->soundPlayer->play(thisAction, this->soundVolume, &timeOfSoundPlaybackStart)) + return false; + } + + this->nextStartAction = StartAction(thisAction + 1); + qDebug() << "next action is: " << nextStartAction; + + int nextActionDelay = -1; + if(this->startActionSettings.contains(this->nextStartAction) && this->startActionSettings[this->nextStartAction]["Enabled"].toBool()) { + nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt(); + } + + if(this->nextStartAction <= Start) { + // perform next action + this->nextActionTimer->start(nextActionDelay > 0 ? nextActionDelay:1); + if(nextActionDelay > 0) + emit this->nextStartActionChanged(); + this->nextActionLoop->exec(); + return this->playSoundsAndStartTimers(this->nextStartAction); + } + + // perform start + + // start all timers + bool startOk = true; + foreach(ScStwTimer *timer, this->timers){ + if(!timer->start(timeOfSoundPlaybackStart + 3100) && timer->getState() != ScStwTimer::DISABLED){ + startOk = false; + } + } + if(!startOk) { + qDebug() << "[ERROR][START] error staring all timers"; + return false; + } + + if(!this->soundPlayer->waitForSoundFinish()) { + qDebug() << "[ERROR][START] start sound wait error"; + return false; + } + + // check if a false start occured + if(this->state != STARTING) + return true; + + nextStartAction = None; + emit this->nextStartActionChanged(); + + this->setState(RUNNING); + + return true; + + +} + +/** + * @brief ScStwRace::setState + * @param {QString} newState + */ +void ScStwRace::setState(RaceState newState) { + if(newState != this->state) { + qDebug() << "+ [INFO][MAIN] state changed: " << newState; + this->state = newState; + emit this->stateChanged(newState); + + if(this->state == IDLE) { + // if we changed to IDLE -> enable timers + foreach(ScStwTimer* timer, this->timerEnableQueque) { + this->handleTimerEnable(timer); + } + + this->timerEnableQueque.clear(); + } + } +} + +/** + * @brief ScStwRace::refreshTimerStates + */ +void ScStwRace::refreshTimerStates() { + + qDebug() << "[INFO][MAIN] refreshing timer states"; + + // check if the race is over + int stoppedState = 1; + + foreach(ScStwTimer * timer, this->timers){ + if(timer->getState() < ScStwTimer::WON){ + // if the timer is not in stoped state + stoppedState = 0; + break; + } + else if(this->state != RUNNING) { + // TODO athleteProfiles->addResult(timer->getCurrTime(), timer->getReactionTime(), this->speedTimers.indexOf(timer)); + } + } + + if(stoppedState == 1){ + this->setState(STOPPED); + return; + } +} + +// ------------------------ +// --- helper functions --- +// ------------------------ + +/** + * @brief ScStwRace::handleTimerEnable function to enable timers at the right moment to prevent them from bricking the state machine + * @param {ScStwExtensionControlledTimer*} timer timer to be enabled + */ +void ScStwRace::handleTimerEnable(ScStwTimer* timer) { + if(this->state == IDLE) { + if(timer->getState() == ScStwTimer::DISABLED) { + timer->setDisabled(false); + } + else { + timer->setDisabled(true); + } + } + else { + this->timerEnableQueque.append(timer); + } +} + +QVariantMap ScStwRace::getNextStartActionDetails() { + int nextActionDelay = 0; + double nextActionDelayProg = 0; + + if(this->nextStartAction == AtYourMarks || this->nextStartAction == Ready) { + // get the total delay and the delay progress of the next action timer + double remaining = this->nextActionTimer->remainingTime(); + nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt(); + if(remaining < 0) { + remaining = nextActionDelay; + } + nextActionDelayProg = 1 - (remaining / nextActionDelay); + } + + return {{"nextAction", this->nextStartAction}, {"nextActionDelay", nextActionDelay}, {"nextActionDelayProg", nextActionDelayProg}}; +} + +bool ScStwRace::writeStartActionSetting(StartAction action, bool enabled, int delay) { + if(action != AtYourMarks && action != Ready) + return false; + + QVariantMap setting = {{"Enabled", enabled}, {"Delay", delay}}; + + if(!this->startActionSettings.contains(action)) + this->startActionSettings.insert(action, setting); + else + this->startActionSettings[action] = setting; + + return true; +} + +bool ScStwRace::setSoundVolume(double volume) { + if(volume >= 0 && volume <= 1) { + this->soundVolume = volume; + return true; + } + else { + return false; + } +} + +bool ScStwRace::addTimer(ScStwTimer *timer) { + if(this->state != IDLE) + return false; + + foreach(ScStwTimer *existingTimer, this->timers) { + if(existingTimer == timer) + return true; + } + + this->timers.append(timer); + + connect(timer, &ScStwTimer::startCanceled, this, &ScStwRace::cancelStart); + connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::refreshTimerStates); + connect(timer, &ScStwTimer::stopRequested, this, &ScStwRace::handleTimerStop); + connect(timer, &ScStwTimer::requestEnableChange, this, &ScStwRace::handleTimerEnable); + + return true; + +} + +ScStwRace::RaceState ScStwRace::getState() { + return this->state; +} + +ScStwRace::StartAction ScStwRace::getNextStartAction() +{ + return this->nextStartAction; +} + +QVariantList ScStwRace::getTimerDetailList() { + QVariantList tmpTimers; + + foreach(ScStwTimer * timer, this->timers){ + QVariantMap tmpTimer; + tmpTimer.insert("id", this->timers.indexOf(timer)); + tmpTimer.insert("state", timer->getState()); + tmpTimer.insert("currentTime", timer->getCurrentTime()); + tmpTimer.insert("reactionTime", timer->getReactionTime()); + tmpTimers.append(tmpTimer); + } + + return tmpTimers; +} diff --git a/ScStwLibraries/sources/scstwstartsoundplayer.cpp b/ScStwLibraries/sources/scstwstartsoundplayer.cpp new file mode 100644 index 0000000..a04946e --- /dev/null +++ b/ScStwLibraries/sources/scstwstartsoundplayer.cpp @@ -0,0 +1,94 @@ +#include "../headers/scstwstartsoundplayer.h" + +ScStwStartSoundPlayer::ScStwStartSoundPlayer(QObject *parent) : QObject(parent) +{ + this->waitLoop = new QEventLoop(); + + this->startSoundFile = new QFile(":/sound/StartsignalSound.wav", this); + + if(!this->startSoundFile->open(QFile::ReadOnly)) { + qWarning() << "Cannot open audio File!!"; + return; + } + + // 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->audioOutput->setCategory("ScStw start sound"); + + connect(this->audioOutput, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State))); +} + +bool ScStwStartSoundPlayer::play(double volume, double *timeOfStop) { + + double st = QDateTime::currentMSecsSinceEpoch(); + + // update volume + this->audioOutput->setVolume(volume); + + // start + this->audioOutput->start(this->startSoundFile); + + double ed = QDateTime::currentMSecsSinceEpoch(); + qDebug() << "time passed: " << ed-st << " playback elapsed: " << this->audioOutput->elapsedUSecs(); + + // 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); + 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; + } + + // check for errors and return + if(this->audioOutput->state() == QAudio::IdleState) + return true; + return false; +} + +void ScStwStartSoundPlayer::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; + + default: + // ... other cases as appropriate + break; + } +} diff --git a/ScStwLibraries/sources/scstwtimer.cpp b/ScStwLibraries/sources/scstwtimer.cpp new file mode 100644 index 0000000..17a645f --- /dev/null +++ b/ScStwLibraries/sources/scstwtimer.cpp @@ -0,0 +1,152 @@ +#include "../headers/scstwtimer.h" + +ScStwTimer::ScStwTimer(QObject *parent) : QObject(parent) +{ + + this->startTime = 0; + this->stopTime = 0; + this->stoppedTime = 0; + this->reactionTime = 0; +} + +bool ScStwTimer::start() { + switch (this->state) { + case STARTING: { + // in case of STARTING, start the race! + + this->startTime = QDateTime::currentMSecsSinceEpoch(); + this->stopTime = 0; + this->stoppedTime = 0; + + this->setState(RUNNING); + return true; + } + default: { + // otherwise the timer is not supposed to be started! + return false; + } + } +} + +bool ScStwTimer::cancel() { + if(!(this->state == STARTING || this->state == RUNNING)) + return false; + + this->setState(CANCELLED); + return true; +} + +bool ScStwTimer::stop() { + this->stop(ManualStop); +} + +bool ScStwTimer::stop(TimerState result) { + switch (result) { + case WON: + this->setState(WON); + return true; + case LOST: + this->setState(LOST); + return false; + default: + return false; + } +} + +bool ScStwTimer::stop(StopReason reason) { + if(this->state != STARTING && this->state != RUNNING && this->state != WAITING){ + return false; + } + + switch (reason) { + case ManualStop: { + if(this->state == STARTING){ + emit startCanceled(false); + this->setState(CANCELLED); + } + else { + + this->stopTime = this->date->currentMSecsSinceEpoch(); + this->stoppedTime = this->stopTime - this->startTime; + + // trigger an external state refresh to set the state to either WON or LOST depending on the other timers values (see MainActivity::refreshTimerStates()) + emit this->stopRequested(); + } + break; + } + case FailStop: { + this->stoppedTime = 0; + //qDebug() << "+ [INFO][TIMER] Stopped: " << "start Time: " << startTime << " stopTime: " << stopTime << " stoppedTime: " << stoppedTime << " reactionTime: " << reactionTime; + + if(this->state == RUNNING){ + qDebug() << "+ [INFO][TIMER] False Start detected, step 2: " << "start Time: " << startTime << " startpadTriggerTime: " << startPadTriggerTime << " reactionTime: " << reactionTime; + this->setState(FAILED); + } + + emit startCanceled(true); + + return true; + } + default: { + return false; + } + } + + qDebug() << "+ [INFO][TIMER] Stopped: " << "start Time: " << startTime << " stopTime: " << stopTime << " stoppedTime: " << stoppedTime << " reactionTime: " << reactionTime; + return true; +} + +bool ScStwTimer::reset(){ + if( this->state < WON || this->state == DISABLED ){ + return false; + } + + this->startTime = 0; + this->stopTime = 0; + this->stoppedTime = 0; + this->reactionTime = 0; + + this->startPadTriggerTime = 0; + + this->setState(IDLE); + return true; +} + +// ------------------------ +// --- helper functions --- +// ------------------------ + +void ScStwTimer::setState(TimerState newState){ + if(this->state == DISABLED && newState != IDLE) + return; + + if(this->state != newState) { + this->state = newState; + qDebug() << "+ [INFO][TIMER] timer state changed: " << newState; + emit this->stateChanged(); + } +} + +ScStwTimer::TimerState ScStwTimer::getState() { + return this->state; +} + +double ScStwTimer::getCurrentTime() { + if(this->state == RUNNING){ + return this->date->currentMSecsSinceEpoch() - this->startTime; + } + else { + return this->stoppedTime; + } +} + +double ScStwTimer::getReactionTime() { + return this->reactionTime; +} + +void ScStwTimer::setDisabled(bool disabled) { + if(disabled) + this->setState(DISABLED); + else + this->setState(IDLE); +}