~aleteoryx/muditaos

18e9fc0e1ac6061392684cab4aa923749dd0841c — Maciej Gibowicz 1 year, 8 months ago 6059ac7
[BH-2033] Add fade in and fade out for relaxation

Relaxation sounds will fade in and out
so that the songs play smoothly.
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 9,6 9,7 @@
* Added What's New section shown after update
* Added versioning for private assets
* Added greetings in all languages
* Added fade in and fade out to relaxation songs

### Changed / Improved


M module-audio/Audio/AudioCommon.hpp => module-audio/Audio/AudioCommon.hpp +9 -2
@@ 77,10 77,17 @@ namespace audio
        Other
    };

    enum class FadeIn
    enum class Fade
    {
        Disable,
        Enable
        In,
        InOut
    };

    struct FadeParams
    {
        Fade mode;
        std::optional<std::chrono::seconds> playbackDuration = std::nullopt;
    };

    enum class VolumeUpdateType

M products/BellHybrid/alarms/src/actions/PlayAudioActions.cpp => products/BellHybrid/alarms/src/actions/PlayAudioActions.cpp +6 -5
@@ 23,13 23,14 @@ namespace alarms
        if (duration.has_value()) {
            spawnTimer(duration.value());
        }
        const auto fadeInSettings = utils::getNumericValue<bool>(
        const auto fadeEnabledInSettings = utils::getNumericValue<bool>(
            settings.getValue(bell::settings::Alarm::fadeActive, settings::SettingsScope::Global));
        const auto fadeInEnabled = (fadeInSettings && (playbackType == audio::PlaybackType::Alarm))
                                       ? audio::FadeIn::Enable
                                       : audio::FadeIn::Disable;
        const auto fadeInEnabled = (fadeEnabledInSettings && (playbackType == audio::PlaybackType::Alarm))
                                       ? audio::Fade::In
                                       : audio::Fade::Disable;

        auto msg = std::make_shared<service::AudioStartPlaybackRequest>(path, playbackType, fadeInEnabled);
        auto msg =
            std::make_shared<service::AudioStartPlaybackRequest>(path, playbackType, audio::FadeParams{fadeInEnabled});
        return service.bus.sendUnicast(std::move(msg), service::audioServiceName);
    }


M products/BellHybrid/apps/application-bell-powernap/presenter/PowerNapProgressPresenter.cpp => products/BellHybrid/apps/application-bell-powernap/presenter/PowerNapProgressPresenter.cpp +4 -3
@@ 89,9 89,10 @@ namespace app::powernap
        const auto &filePath    = settings->getValue(bell::settings::Alarm::tonePath, settings::SettingsScope::Global);
        const auto fadeInActive = utils::getNumericValue<bool>(settings->getValue(bell::settings::Alarm::fadeActive,
                                                                                  settings::SettingsScope::Global))
                                      ? audio::FadeIn::Enable
                                      : audio::FadeIn::Disable;
        audioModel.play(filePath, AbstractAudioModel::PlaybackType::Alarm, {}, fadeInActive);
                                      ? audio::Fade::In
                                      : audio::Fade::Disable;

        audioModel.play(filePath, AbstractAudioModel::PlaybackType::Alarm, {}, audio::FadeParams{fadeInActive});
        napAlarmTimer.start();
        napFinished = true;
    }

M products/BellHybrid/apps/application-bell-relaxation/presenter/RelaxationRunningProgressPresenter.cpp => products/BellHybrid/apps/application-bell-relaxation/presenter/RelaxationRunningProgressPresenter.cpp +11 -7
@@ 41,12 41,15 @@ namespace app::relaxation
        Expects(timer != nullptr);

        AbstractRelaxationPlayer::PlaybackMode mode;
        const auto value      = settings->getValue(timerValueDBRecordName, settings::SettingsScope::AppLocal);
        const auto songLength = std::chrono::seconds{song.audioProperties.songLength};
        if (utils::is_number(value) && (utils::getNumericValue<int>(value) != 0) &&
            !isSongLengthEqualToPeriod(songLength, std::chrono::minutes{utils::getNumericValue<int>(value)})) {
            const auto playbackTimeInMinutes = std::chrono::minutes{utils::getNumericValue<int>(value)};
            timer->reset(playbackTimeInMinutes);
        const auto settingsValue  = settings->getValue(timerValueDBRecordName, settings::SettingsScope::AppLocal);
        const auto presetDuration = utils::getNumericValue<int>(settingsValue);
        const auto songLength     = std::chrono::seconds{song.audioProperties.songLength};
        auto playbackDuration     = songLength;

        if (utils::is_number(settingsValue) && (presetDuration != 0) &&
            !isSongLengthEqualToPeriod(songLength, std::chrono::minutes{presetDuration})) {
            playbackDuration = std::chrono::minutes{presetDuration};
            timer->reset(playbackDuration);
            mode = AbstractRelaxationPlayer::PlaybackMode::Looped;
        }
        else {


@@ 76,7 79,8 @@ namespace app::relaxation
            }
        };

        player.start(song.fileInfo.path, mode, std::move(onStartCallback), std::move(onFinishedCallback));
        player.start(
            song.fileInfo.path, mode, std::move(onStartCallback), std::move(onFinishedCallback), playbackDuration);
    }

    void RelaxationRunningProgressPresenter::stop()

M products/BellHybrid/apps/application-bell-relaxation/widgets/RelaxationPlayer.cpp => products/BellHybrid/apps/application-bell-relaxation/widgets/RelaxationPlayer.cpp +4 -2
@@ 17,7 17,8 @@ namespace app::relaxation
    void RelaxationPlayer::start(const std::string &filePath,
                                 AbstractRelaxationPlayer::PlaybackMode mode,
                                 AbstractAudioModel::OnStateChangeCallback &&stateChangeCallback,
                                 AbstractAudioModel::OnPlaybackFinishedCallback &&finishedCallback)
                                 AbstractAudioModel::OnPlaybackFinishedCallback &&finishedCallback,
                                 std::optional<std::chrono::seconds> playbackDuration)
    {
        using Status = AbstractAudioModel::PlaybackFinishStatus;
        using Type   = AbstractAudioModel::PlaybackType;


@@ 41,8 42,9 @@ namespace app::relaxation
            }
        };

        auto fadeParams = audio::FadeParams{.mode = audio::Fade::InOut, .playbackDuration = playbackDuration};
        audioModel.setPlaybackFinishedCb(std::move(onPlayerFinished));
        audioModel.play(filePath, Type::Multimedia, std::move(stateChangeCallback));
        audioModel.play(filePath, Type::Multimedia, std::move(stateChangeCallback), std::move(fadeParams));
    }

    void RelaxationPlayer::stop(AbstractAudioModel::OnStateChangeCallback &&callback)

M products/BellHybrid/apps/application-bell-relaxation/widgets/RelaxationPlayer.hpp => products/BellHybrid/apps/application-bell-relaxation/widgets/RelaxationPlayer.hpp +10 -8
@@ 26,16 26,17 @@ namespace app::relaxation
            SingleShot
        };

        virtual ~AbstractRelaxationPlayer()                                                   = default;
        virtual ~AbstractRelaxationPlayer()                                                     = default;
        virtual void start(const std::string &filePath,
                           PlaybackMode mode,
                           AbstractAudioModel::OnStateChangeCallback &&callback,
                           AbstractAudioModel::OnPlaybackFinishedCallback &&finishedCallback) = 0;
        virtual void stop(AbstractAudioModel::OnStateChangeCallback &&callback)               = 0;
        virtual void pause(AbstractAudioModel::OnStateChangeCallback &&callback)              = 0;
        virtual void resume(AbstractAudioModel::OnStateChangeCallback &&callback)             = 0;
        virtual PlaybackMode getCurrentMode() const noexcept                                  = 0;
        virtual bool isPaused()                                                               = 0;
                           AbstractAudioModel::OnPlaybackFinishedCallback &&finishedCallback,
                           std::optional<std::chrono::seconds> playbackDuration = std::nullopt) = 0;
        virtual void stop(AbstractAudioModel::OnStateChangeCallback &&callback)                 = 0;
        virtual void pause(AbstractAudioModel::OnStateChangeCallback &&callback)                = 0;
        virtual void resume(AbstractAudioModel::OnStateChangeCallback &&callback)               = 0;
        virtual PlaybackMode getCurrentMode() const noexcept                                    = 0;
        virtual bool isPaused()                                                                 = 0;
    };

    class RelaxationPlayer : public AbstractRelaxationPlayer


@@ 47,7 48,8 @@ namespace app::relaxation
        void start(const std::string &filePath,
                   PlaybackMode mode,
                   AbstractAudioModel::OnStateChangeCallback &&callback,
                   AbstractAudioModel::OnPlaybackFinishedCallback &&finishedCallback) override;
                   AbstractAudioModel::OnPlaybackFinishedCallback &&finishedCallback,
                   std::optional<std::chrono::seconds> playbackDuration = std::nullopt) override;
        void stop(AbstractAudioModel::OnStateChangeCallback &&callback) override;
        void pause(AbstractAudioModel::OnStateChangeCallback &&callback) override;
        void resume(AbstractAudioModel::OnStateChangeCallback &&callback) override;

M products/BellHybrid/apps/common/include/common/models/AbstractAudioModel.hpp => products/BellHybrid/apps/common/include/common/models/AbstractAudioModel.hpp +11 -11
@@ 39,22 39,22 @@ namespace app
        using OnGetValueCallback         = std::function<void(const audio::RetCode, Volume)>;
        using OnPlaybackFinishedCallback = std::function<void(PlaybackFinishStatus)>;

        virtual ~AbstractAudioModel() noexcept                                                              = default;
        virtual ~AbstractAudioModel() noexcept                                           = default;
        virtual void setVolume(Volume volume,
                               PlaybackType playbackType,
                               audio::VolumeUpdateType updateType = audio::VolumeUpdateType::UpdateDB,
                               OnStateChangeCallback &&callback   = {})                                       = 0;
        virtual std::optional<Volume> getVolume(PlaybackType playbackType)                                  = 0;
        virtual void getVolume(PlaybackType playbackType, OnGetValueCallback &&callback)                    = 0;
                               OnStateChangeCallback &&callback   = {})                    = 0;
        virtual std::optional<Volume> getVolume(PlaybackType playbackType)               = 0;
        virtual void getVolume(PlaybackType playbackType, OnGetValueCallback &&callback) = 0;
        virtual void play(const std::string &filePath,
                          PlaybackType type,
                          OnStateChangeCallback &&callback,
                          audio::FadeIn fadeIn = audio::FadeIn::Disable)                                    = 0;
        virtual void stopAny(OnStateChangeCallback &&callback)                                              = 0;
        virtual void stopPlayedByThis(OnStateChangeCallback &&callback)                                     = 0;
        virtual void pause(OnStateChangeCallback &&callback)                                                = 0;
        virtual void resume(OnStateChangeCallback &&callback)                                               = 0;
        virtual void setPlaybackFinishedCb(OnPlaybackFinishedCallback &&callback)                           = 0;
        virtual bool hasPlaybackFinished()                                                                  = 0;
                          std::optional<audio::FadeParams> fadeParams = std::nullopt)      = 0;
        virtual void stopAny(OnStateChangeCallback &&callback)                           = 0;
        virtual void stopPlayedByThis(OnStateChangeCallback &&callback)                  = 0;
        virtual void pause(OnStateChangeCallback &&callback)                             = 0;
        virtual void resume(OnStateChangeCallback &&callback)                            = 0;
        virtual void setPlaybackFinishedCb(OnPlaybackFinishedCallback &&callback)        = 0;
        virtual bool hasPlaybackFinished()                                               = 0;
    };
} // namespace app

M products/BellHybrid/apps/common/include/common/models/AudioModel.hpp => products/BellHybrid/apps/common/include/common/models/AudioModel.hpp +1 -1
@@ 23,7 23,7 @@ namespace app
        void play(const std::string &filePath,
                  PlaybackType type,
                  OnStateChangeCallback &&callback,
                  audio::FadeIn fadeIn = audio::FadeIn::Disable) override;
                  std::optional<audio::FadeParams> fadeParams = std::nullopt) override;
        void stopAny(OnStateChangeCallback &&callback) override;
        void stopPlayedByThis(OnStateChangeCallback &&callback) override;
        void pause(OnStateChangeCallback &&callback) override;

M products/BellHybrid/apps/common/src/AudioModel.cpp => products/BellHybrid/apps/common/src/AudioModel.cpp +3 -2
@@ 87,10 87,11 @@ namespace app
    void AudioModel::play(const std::string &filePath,
                          PlaybackType type,
                          OnStateChangeCallback &&callback,
                          audio::FadeIn fadeIn)
                          std::optional<audio::FadeParams> fadeParams)
    {
        playbackFinishedFlag = false;
        auto msg  = std::make_unique<service::AudioStartPlaybackRequest>(filePath, convertPlaybackType(type), fadeIn);
        auto msg =
            std::make_unique<service::AudioStartPlaybackRequest>(filePath, convertPlaybackType(type), fadeParams);
        auto task = app::AsyncRequest::createFromMessage(std::move(msg), service::audioServiceName);

        auto cb = [_callback = callback, this](auto response) {

M products/BellHybrid/services/audio/CMakeLists.txt => products/BellHybrid/services/audio/CMakeLists.txt +2 -2
@@ 13,12 13,12 @@ target_include_directories(bell-audio
target_sources(bell-audio
        PRIVATE
        ServiceAudio.cpp
        VolumeFadeIn.cpp
        VolumeFade.cpp

        PUBLIC
        include/audio/AudioMessage.hpp
        include/audio/ServiceAudio.hpp
        include/audio/VolumeFadeIn.hpp
        include/audio/VolumeFade.hpp

        )


M products/BellHybrid/services/audio/ServiceAudio.cpp => products/BellHybrid/services/audio/ServiceAudio.cpp +8 -8
@@ 92,11 92,11 @@ namespace service
                }
            }
        };
        volumeFadeIn = std::make_unique<audio::VolumeFadeIn>(this, std::move(callback));
        volumeFade = std::make_unique<audio::VolumeFade>(this, std::move(callback));

        connect(typeid(AudioStartPlaybackRequest), [this](sys::Message *msg) -> sys::MessagePointer {
            auto *msgl = static_cast<AudioStartPlaybackRequest *>(msg);
            return handleStart(audio::Operation::Type::Playback, msgl->fadeIn, msgl->fileName, msgl->playbackType);
            return handleStart(audio::Operation::Type::Playback, msgl->fadeParams, msgl->fileName, msgl->playbackType);
        });

        connect(typeid(internal::AudioEOFNotificationMessage), [this](sys::Message *msg) -> sys::MessagePointer {


@@ 113,7 113,7 @@ namespace service
                });

        connect(typeid(AudioStopRequest), [this](sys::Message *msg) -> sys::MessagePointer {
            volumeFadeIn->Stop();
            volumeFade->Stop();
            auto *msgl = static_cast<AudioStopRequest *>(msg);
            return handleStop(msgl->stopVec, msgl->token);
        });


@@ 168,7 168,7 @@ namespace service
    }

    auto Audio::handleStart(audio::Operation::Type opType,
                            audio::FadeIn fadeIn,
                            std::optional<audio::FadeParams> fadeParams,
                            const std::string &fileName,
                            const audio::PlaybackType &playbackType) -> std::unique_ptr<AudioResponseMessage>
    {


@@ 193,8 193,8 @@ namespace service
        auto input = audioMux.GetPlaybackInput(playbackType);
        AudioStart(input);
        manageCpuSentinel();
        if (fadeIn == audio::FadeIn::Enable) {
            volumeFadeIn->Start(getVolume(playbackType), minVolumeToSet, maxVolumeToSet);
        if (fadeParams.has_value()) {
            volumeFade->Start(getVolume(playbackType), minVolumeToSet, maxVolumeToSet, fadeParams.value());
        }

        return std::make_unique<AudioStartPlaybackResponse>(retCode, retToken);


@@ 275,8 275,8 @@ namespace service
        if (const auto input = audioMux.GetInput(token); input) {
            if (shouldLoop((*input)->audio->GetCurrentOperationPlaybackType())) {
                (*input)->audio->Start();
                if (volumeFadeIn->IsActive()) {
                    volumeFadeIn->Restart();
                if (volumeFade->IsActive()) {
                    volumeFade->Restart();
                }

                if ((*input)->audio->IsMuted()) {

R products/BellHybrid/services/audio/VolumeFadeIn.cpp => products/BellHybrid/services/audio/VolumeFade.cpp +73 -17
@@ 1,70 1,126 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "VolumeFadeIn.hpp"
#include "VolumeFade.hpp"
#include <log/log.hpp>

namespace audio
{
    namespace
    {
        using namespace std::chrono_literals;
        constexpr auto timerName{"volumeFadeTimer"};
        constexpr std::chrono::milliseconds fadeInterval{300};
        constexpr auto fadeInterval{33ms};
        constexpr auto fadeStep{0.1f};

        std::chrono::milliseconds calculateTimerPeriod(std::chrono::seconds elapsed,
                                                       std::chrono::seconds duration,
                                                       float currentVolume)
        {
            using floatToMs         = std::chrono::duration<float, std::chrono::milliseconds::period>;
            const auto timeRequired = floatToMs(currentVolume / fadeStep) * fadeInterval.count();
            const auto calculatedTime =
                std::chrono::duration_cast<std::chrono::milliseconds>(duration - elapsed - timeRequired);
            return calculatedTime > std::chrono::milliseconds::zero() ? calculatedTime
                                                                      : std::chrono::milliseconds::zero();
        }
    } // namespace

    VolumeFadeIn::VolumeFadeIn(sys::Service *parent, SetCallback callback) : setVolumeCallback(std::move(callback))
    VolumeFade::VolumeFade(sys::Service *parent, SetCallback callback) : setVolumeCallback(std::move(callback))
    {
        timerHandle = sys::TimerFactory::createPeriodicTimer(
            parent, timerName, fadeInterval, [this]([[maybe_unused]] sys::Timer &timer) { PerformNextFadeStep(); });
    }

    VolumeFadeIn::~VolumeFadeIn()
    VolumeFade::~VolumeFade()
    {
        Stop();
    }

    void VolumeFadeIn::Start(float targetVolume, float minVolume, float maxVolume)
    void VolumeFade::Start(float targetVolume, float minVolume, float maxVolume, audio::FadeParams fadeParams)
    {
        if (fadeParams.mode == audio::Fade::Disable) {
            return;
        }
        if (targetVolume > maxVolume || minVolume > maxVolume) {
            LOG_ERROR("Incorrect parameters for audio fade in!");
            LOG_ERROR("Incorrect parameters for audio fade!");
            return;
        }
        this->fadeParams   = fadeParams;
        this->targetVolume = targetVolume;
        this->minVolume    = minVolume;
        this->maxVolume    = maxVolume;
        currentVolume      = minVolume + fadeStep;
        state              = State::FadeIn;
        timestamp          = std::chrono::system_clock::now();
        Restart();
        timerHandle.start();
        timerHandle.restart(fadeInterval);
    }

    void VolumeFadeIn::Restart()
    void VolumeFade::Restart()
    {
        if (setVolumeCallback != nullptr) {
            setVolumeCallback(currentVolume);
        }
    }

    void VolumeFadeIn::Stop()
    void VolumeFade::Stop()
    {
        timerHandle.stop();
        state = State::Disable;
    }

    bool VolumeFadeIn::IsActive()
    bool VolumeFade::IsActive()
    {
        return timerHandle.isActive();
    }

    void VolumeFadeIn::PerformNextFadeStep()
    void VolumeFade::PerformNextFadeStep()
    {
        if (currentVolume < targetVolume) {
            currentVolume = std::clamp(std::min(currentVolume + fadeStep, targetVolume), minVolume, maxVolume);
            if (setVolumeCallback != nullptr) {
                setVolumeCallback(currentVolume);
        switch (state) {
        case State::FadeIn:
            if (currentVolume < targetVolume) {
                TurnUpVolume();
            }
            else if (fadeParams.mode == audio::Fade::InOut && fadeParams.playbackDuration.has_value()) {
                state                  = State::FadeOut;
                const auto now         = std::chrono::system_clock::now();
                const auto timeElapsed = std::chrono::duration_cast<std::chrono::seconds>(now - timestamp);
                timerHandle.restart(
                    calculateTimerPeriod(timeElapsed, fadeParams.playbackDuration.value(), currentVolume));
            }
            else {
                Stop();
            }
            break;
        case State::FadeOut:
            if (currentVolume > 0.0f) {
                TurnDownVolume();
                timerHandle.restart(fadeInterval);
            }
            else {
                Stop();
            }
            break;

        default:
            break;
        }
    }

    void VolumeFade::TurnUpVolume()
    {
        currentVolume = std::clamp(std::min(currentVolume + fadeStep, targetVolume), minVolume, maxVolume);
        if (setVolumeCallback != nullptr) {
            setVolumeCallback(currentVolume);
        }
        else {
            Stop();
    }
    void VolumeFade::TurnDownVolume()
    {
        currentVolume = std::clamp(std::max(currentVolume - fadeStep, 0.0f), minVolume, maxVolume);
        if (setVolumeCallback != nullptr) {
            setVolumeCallback(currentVolume);
        }
    }

} // namespace audio

M products/BellHybrid/services/audio/include/audio/AudioMessage.hpp => products/BellHybrid/services/audio/include/audio/AudioMessage.hpp +3 -3
@@ 120,13 120,13 @@ namespace service
      public:
        AudioStartPlaybackRequest(const std::string &fileName,
                                  const audio::PlaybackType &playbackType,
                                  const audio::FadeIn fadeIn = audio::FadeIn::Disable)
            : AudioMessage(), fileName(fileName), playbackType(playbackType), fadeIn(fadeIn)
                                  std::optional<audio::FadeParams> fadeParams = std::nullopt)
            : AudioMessage(), fileName(fileName), playbackType(playbackType), fadeParams(fadeParams)
        {}

        const std::string fileName;
        const audio::PlaybackType playbackType;
        const audio::FadeIn fadeIn;
        const std::optional<audio::FadeParams> fadeParams;
    };

    class AudioStartPlaybackResponse : public AudioResponseMessage

M products/BellHybrid/services/audio/include/audio/ServiceAudio.hpp => products/BellHybrid/services/audio/include/audio/ServiceAudio.hpp +3 -3
@@ 4,7 4,7 @@
#pragma once

#include "AudioMessage.hpp"
#include "VolumeFadeIn.hpp"
#include "VolumeFade.hpp"
#include <Audio/Audio.hpp>
#include <Audio/AudioMux.hpp>
#include <MessageType.hpp>


@@ 42,7 42,7 @@ namespace service
        };

        auto handleStart(audio::Operation::Type opType,
                         audio::FadeIn fadeIn,
                         std::optional<audio::FadeParams> fadeParams,
                         const std::string &fileName             = {},
                         const audio::PlaybackType &playbackType = audio::PlaybackType::None)
            -> std::unique_ptr<AudioResponseMessage>;


@@ 75,7 75,7 @@ namespace service
        mutable audio::AudioMux audioMux;
        std::shared_ptr<sys::CpuSentinel> cpuSentinel;
        std::unique_ptr<settings::Settings> settingsProvider;
        std::unique_ptr<audio::VolumeFadeIn> volumeFadeIn;
        std::unique_ptr<audio::VolumeFade> volumeFade;
    };
} // namespace service


R products/BellHybrid/services/audio/include/audio/VolumeFadeIn.hpp => products/BellHybrid/services/audio/include/audio/VolumeFade.hpp +16 -4
@@ 8,27 8,39 @@

namespace audio
{
    class VolumeFadeIn
    class VolumeFade
    {
      public:
        using SetCallback = std::function<void(float)>;

        VolumeFadeIn(sys::Service *parent, SetCallback callback);
        ~VolumeFadeIn();
        VolumeFade(sys::Service *parent, SetCallback callback);
        ~VolumeFade();

        void Start(float targetVolume, float minVolume, float maxVolume);
        void Start(float targetVolume, float minVolume, float maxVolume, audio::FadeParams fadeParams);
        void Restart();
        void Stop();
        bool IsActive();

      private:
        enum class State
        {
            Disable,
            FadeIn,
            FadeOut
        };

        audio::FadeParams fadeParams;
        sys::TimerHandle timerHandle;
        float targetVolume;
        float minVolume;
        float maxVolume;
        float currentVolume;
        SetCallback setVolumeCallback;
        State state{State::Disable};
        std::chrono::time_point<std::chrono::system_clock> timestamp;

        void PerformNextFadeStep();
        void TurnUpVolume();
        void TurnDownVolume();
    };
} // namespace audio