// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md
#pragma once
#include "AudioDevice.hpp"
#include "Profiles/Profile.hpp"
#include <Service/Message.hpp>
#include <Utils.hpp>
#include <bitset>
#include <map>
#include <memory>
#include <utility>
namespace audio
{
class AudioMux;
}
namespace audio
{
inline constexpr Volume defaultVolumeStep{1};
inline constexpr Volume maxVolume{10};
inline constexpr Volume minVolume{0};
inline constexpr Gain maxGain{100};
inline constexpr Gain minGain{0};
inline constexpr auto audioDbPrefix{"audio"};
inline constexpr auto dbPathSeparator{'/'};
inline constexpr std::chrono::seconds defaultMaxFadeDuration{5};
enum class Setting
{
Volume,
Gain,
EnableVibration,
VibrationLevel,
EnableSound,
Sound,
IsSystemSound
};
enum class SettingState : bool
{
Enabled,
Disabled
};
enum class PlaybackMode
{
Single,
Loop
};
enum class PlaybackType
{
None,
Multimedia,
Notifications,
System = Notifications,
SingleVibration = Notifications,
KeypadSound,
CallRingtone,
TextMessageRingtone,
Meditation,
Alarm,
PreWakeUp,
Snooze,
FocusTimer,
Bedtime,
Last = Bedtime
};
enum class VolumeChangeRequestSource
{
A2DP,
HFP,
HSP,
Other
};
enum class Fade
{
Disable,
In,
InOut
};
struct FadeParams
{
Fade mode;
std::chrono::seconds maxFadeDuration{defaultMaxFadeDuration};
std::optional<std::chrono::seconds> playbackDuration{std::nullopt};
};
enum class VolumeUpdateType
{
UpdateDB,
SkipUpdateDB
};
/// Used to describe audio operations
using Context = std::pair<Profile::Type, PlaybackType>;
struct DbPathElement
{
Setting setting;
PlaybackType playbackType;
Profile::Type profileType;
};
[[nodiscard]] const std::string str(const PlaybackType &playbackType) noexcept;
[[nodiscard]] const std::string str(const Setting &setting) noexcept;
[[nodiscard]] const std::string dbPath(const DbPathElement &element);
[[nodiscard]] const std::string dbPath(const Setting &setting,
const PlaybackType &playbackType,
const Profile::Type &profileType);
enum class EventType
{
// HW state change notifications
JackState, //!< Jack input plugged / unplugged event
MicrophoneState, //!< Microphone presence in headset (3-pole w/o microphone or 4-pole with microphone)
BluetoothHSPDeviceState, //!< BT device connected / disconnected event (Headset Profile)
BluetoothHFPDeviceState, //!< BT device connected / disconnected event (Headset Profile)
BluetoothA2DPDeviceState, //!< BT device connected / disconnected event (Advanced Audio Distribution Profile)
// call control
CallMute,
CallUnmute,
CallLoudspeakerOn,
CallLoudspeakerOff,
};
inline constexpr auto hwStateUpdateMaxEvent = magic_enum::enum_index(EventType::BluetoothA2DPDeviceState);
class Event
{
public:
enum class DeviceState
{
Connected,
Disconnected
};
explicit Event(EventType eType, DeviceState deviceState = DeviceState::Connected)
: eventType(eType), deviceState(deviceState)
{}
virtual ~Event() = default;
[[nodiscard]] EventType getType() const noexcept
{
return eventType;
}
[[nodiscard]] DeviceState getDeviceState() const noexcept
{
return deviceState;
}
private:
const EventType eventType;
const DeviceState deviceState;
};
class AudioSinkState
{
public:
void UpdateState(std::shared_ptr<Event> stateChangeEvent)
{
auto hwUpdateEventIdx = magic_enum::enum_integer(stateChangeEvent->getType());
if (hwUpdateEventIdx <= hwStateUpdateMaxEvent) {
audioSinkState.set(hwUpdateEventIdx,
stateChangeEvent->getDeviceState() == Event::DeviceState::Connected);
}
}
[[nodiscard]] std::vector<std::shared_ptr<Event>> getUpdateEvents() const
{
std::vector<std::shared_ptr<Event>> updateEvents;
for (auto i = 0; i <= hwStateUpdateMaxEvent; i++) {
auto isConnected =
audioSinkState.test(i) ? Event::DeviceState::Connected : Event::DeviceState::Disconnected;
auto updateEvt = magic_enum::enum_cast<EventType>(i);
updateEvents.emplace_back(std::make_unique<Event>(updateEvt.value(), isConnected));
}
return updateEvents;
}
[[nodiscard]] bool isConnected(EventType deviceUpdateEvent) const
{
return audioSinkState.test(magic_enum::enum_integer(deviceUpdateEvent));
}
void setConnected(EventType deviceUpdateEvent, bool isConnected)
{
audioSinkState.set(magic_enum::enum_integer(deviceUpdateEvent), isConnected);
}
private:
std::bitset<magic_enum::enum_count<EventType>()> audioSinkState;
};
enum class RetCode : std::uint8_t
{
Success = 0,
InvokedInIncorrectState,
UnsupportedProfile,
UnsupportedEvent,
InvalidFormat,
OperationCreateFailed,
FileDoesntExist,
FailedToAllocateMemory,
OperationNotSet,
ProfileNotSet,
DeviceFailure,
TokenNotFound,
Ignored,
Failed
};
struct AudioInitException : public std::runtime_error
{
public:
AudioInitException(const char *message, audio::RetCode errorCode) : runtime_error(message), errorCode{errorCode}
{}
[[nodiscard]] audio::RetCode getErrorCode() const noexcept
{
return errorCode;
}
protected:
audio::RetCode errorCode = audio::RetCode::Failed;
};
class Token
{
using TokenType = std::int16_t;
public:
explicit Token(TokenType initValue = tokenUninitialized) : t(initValue)
{}
bool operator==(const Token &other) const noexcept
{
return other.t == t;
}
bool operator!=(const Token &other) const noexcept
{
return !(other.t == t);
}
/**
* Valid token is one connected with existing sequence of operations
* @return True if valid, false otherwise
*/
[[nodiscard]] bool IsValid() const
{
return t > tokenUninitialized;
}
/**
* Bad token cannot be used anymore
* @return True if token is flagged bad
*/
[[nodiscard]] bool IsBad() const
{
return t == tokenBad;
}
/**
* Uninitialized token can be used but it is not connected to any sequence of operations
* @return True if token is flagged uninitialized
*/
[[nodiscard]] bool IsUninitialized() const
{
return t == tokenUninitialized;
}
/**
* Helper - returns bad Token
* @return Unusable bad Token
*/
static inline Token MakeBadToken()
{
return Token(tokenBad);
}
private:
static constexpr auto maxToken = std::numeric_limits<TokenType>::max();
Token IncrementToken()
{
t = (t == maxToken) ? 0 : t + 1;
return *this;
}
static constexpr TokenType tokenUninitialized{-1};
static constexpr TokenType tokenBad{-2};
TokenType t;
friend class ::audio::AudioMux;
};
RetCode GetDeviceError(AudioDevice::RetCode retCode);
const std::string str(RetCode retcode);
[[nodiscard]] auto GetVolumeText(const audio::Volume &volume) -> std::string;
} // namespace audio
namespace AudioServiceMessage
{
class EndOfFile : public sys::DataMessage
{
public:
explicit EndOfFile(audio::Token &token) : token(token)
{}
[[nodiscard]] const audio::Token &GetToken() const
{
return token;
}
private:
audio::Token token = audio::Token::MakeBadToken();
};
class FileDeleted : public sys::DataMessage
{
public:
explicit FileDeleted(audio::Token &token) : token(token)
{}
[[nodiscard]] const audio::Token &GetToken() const
{
return token;
}
private:
audio::Token token = audio::Token::MakeBadToken();
};
class FileSystemNoSpace : public sys::DataMessage
{
public:
explicit FileSystemNoSpace(audio::Token &token) : token(token)
{}
[[nodiscard]] const audio::Token &GetToken() const
{
return token;
}
private:
audio::Token token = audio::Token::MakeBadToken();
};
class DbRequest : public sys::DataMessage
{
public:
explicit DbRequest(const audio::Setting &setting,
const audio::PlaybackType &playback,
const audio::Profile::Type &profile)
: setting(setting), profile(profile), playback(playback)
{}
const audio::Setting setting;
const audio::Profile::Type profile;
const audio::PlaybackType playback;
};
class AudioDeviceCreated : public sys::DataMessage
{
public:
explicit AudioDeviceCreated(std::shared_ptr<audio::AudioDevice> device, audio::AudioDevice::Type type)
: device(std::move(device)), type(type)
{}
[[nodiscard]] auto getDevice() const noexcept -> std::shared_ptr<audio::AudioDevice>
{
return device;
}
[[nodiscard]] auto getDeviceType() const noexcept -> audio::AudioDevice::Type
{
return type;
}
private:
std::shared_ptr<audio::AudioDevice> device;
audio::AudioDevice::Type type;
};
using Callback = std::function<std::optional<std::string>(const sys::Message *msg)>;
} // namespace AudioServiceMessage