CoriEngine
Loading...
Searching...
No Matches
Mixer.cpp
Go to the documentation of this file.
1#include "Mixer.hpp"
2#include <SDL3_mixer/SDL_mixer.h>
3#include "Track.hpp"
4
5
6#include "entt/core/type_traits.hpp"
7
8namespace Cori {
9 namespace Audio {
10 Mixer::Data* Mixer::s_Data{ nullptr };
11
12 struct Mixer::Data {
13 MIX_Mixer* m_Mixer{ nullptr };
14 std::unordered_map<SoundID, MIX_Audio*> m_SDLAudios;
15 std::unordered_map<TrackID, std::pair<MIX_Track*, Track*>> m_TrackPool;
16 };
17
18 std::expected<void, Core::CoriError<>> Mixer::PauseAllTracks() {
19 const bool success = MIX_PauseAllTracks(s_Data->m_Mixer);
20 if (!success) {
21 return std::unexpected(Core::CoriError(std::format("Failed to pause all tracks. SDL_Error: {}", SDL_GetError())));
22 }
24 return {};
25 }
26
27 std::expected<void, Core::CoriError<>> Mixer::ResumeAllTracks() {
28 const bool success = MIX_ResumeAllTracks(s_Data->m_Mixer);
29 if (!success) {
30 return std::unexpected(Core::CoriError(std::format("Failed to resume all tracks. SDL_Error: {}", SDL_GetError())));
31 }
33 return {};
34 }
35
36 std::expected<void, Core::CoriError<>> Mixer::SetMasterGain(const float gain) {
37 const bool success = MIX_SetMasterGain(s_Data->m_Mixer, gain);
38 if (!success) {
39 return std::unexpected(Core::CoriError<>(std::format("Failed to set master gain. SDL_Error: {}", SDL_GetError())));
40 }
42 return {};
43 }
44
46 return MIX_GetMasterGain(s_Data->m_Mixer);
47 }
48
49 std::expected<void, Core::CoriError<>> Mixer::StartTag(const char* tag, const PlayParams& params) {
50 const SDL_PropertiesID props = SDL_CreateProperties();
51 SDL_SetNumberProperty(props, MIX_PROP_PLAY_LOOPS_NUMBER, params.Loops);
52 if (params.MaxMilliseconds != -1.0f) {
53 SDL_SetNumberProperty(props, MIX_PROP_PLAY_MAX_MILLISECONDS_NUMBER, params.MaxMilliseconds);
54 }
55
56 if (params.StartMillisecond != 0.0f) {
57 SDL_SetNumberProperty(props, MIX_PROP_PLAY_START_MILLISECOND_NUMBER, params.StartMillisecond);
58 }
59
60 if (params.LoopStartMillisecond != 0.0f) {
61 SDL_SetNumberProperty(props, MIX_PROP_PLAY_LOOP_START_MILLISECOND_NUMBER, params.LoopStartMillisecond);
62 }
63
64 if (params.FadeInMilliseconds != 0.0f) {
65 SDL_SetNumberProperty(props, MIX_PROP_PLAY_FADE_IN_MILLISECONDS_NUMBER, params.FadeInMilliseconds);
66 }
67
68 if (params.AppendSilenceMilliseconds != 0.0f) {
69 SDL_SetNumberProperty(props, MIX_PROP_PLAY_APPEND_SILENCE_MILLISECONDS_NUMBER, params.AppendSilenceMilliseconds);
70 }
71
72 const bool success = MIX_PlayTag(s_Data->m_Mixer, tag, props);
73 if (!success) {
74 return std::unexpected(Core::CoriError(std::format("Failed to play mixer Tag '{}'. SDL_Error: {}", tag, SDL_GetError())));
75 }
77 return {};
78 }
79
80 std::expected<void, Core::CoriError<>> Mixer::StopTag(const char* tag, const int64_t fadeOutMS) {
81 const bool success = MIX_StopTag(s_Data->m_Mixer, tag, fadeOutMS);
82 if (!success) {
83 return std::unexpected(Core::CoriError(std::format("Failed to stop mixer Tag '{}'. SDL_Error: {}", tag, SDL_GetError())));
84 }
86 return {};
87 }
88
89 std::expected<void, Core::CoriError<>> Mixer::PauseTag(const char* tag) {
90 const bool success = MIX_PauseTag(s_Data->m_Mixer, tag);
91 if (!success) {
92 return std::unexpected(Core::CoriError(std::format("Failed to pause mixer Tag '{}'. SDL_Error: {}", tag, SDL_GetError())));
93 }
95 return {};
96 }
97
98 std::expected<void, Core::CoriError<>> Mixer::ResumeTag(const char* tag) {
99 const bool success = MIX_ResumeTag(s_Data->m_Mixer, tag);
100 if (!success) {
101 return std::unexpected(Core::CoriError(std::format("Failed to resume mixer Tag '{}'. SDL_Error: {}", tag, SDL_GetError())));
102 }
104 return {};
105 }
106
107 std::expected<void, Core::CoriError<>> Mixer::SetTagGain(const char* tag, const float gain) {
108 const bool success = MIX_SetTagGain(s_Data->m_Mixer, tag, gain);
109 if (!success) {
110 return std::unexpected(Core::CoriError(std::format("Failed to set Tag '{}' gain. SDL_Error: {}", tag, SDL_GetError())));
111 }
112 CORI_CORE_TRACE_TAGGED({ Logger::Tags::Audio::Self, Logger::Tags::Audio::Mixer }, "Tag '{}' gain set to: {}", tag, gain);
113 return {};
114 }
115
116 void Mixer::Init() {
117 if (CORI_CORE_VERIFY(MIX_Init(), "Failed to initialize SDL_Mixer! SDL_Error: {}", SDL_GetError())) {}
118
119 CORI_CORE_INFO_TAGGED({ Logger::Tags::Audio::Self, Logger::Tags::Audio::Mixer }, "SDL3_Mixer initialized successfully.");
120
121 s_Data = new Data();
122
123 s_Data->m_Mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
124 }
125
126 void Mixer::Shutdown() {
127 delete s_Data;
128 MIX_Quit();
129 }
130
131 std::expected<void, Core::CoriError<std::filesystem::path>> Mixer::LoadSound(const std::filesystem::path& path, const bool preDecode, const SoundID soundID) {
132 const auto result = MIX_LoadAudio(s_Data->m_Mixer, path.string().c_str(), preDecode);
133 if (!result) {
134 return std::unexpected(Core::CoriError<std::filesystem::path>(std::format("Failed to load Sound. SDL_Error: {}", SDL_GetError()), "Path", path));
135 }
136
137 s_Data->m_SDLAudios.insert({ soundID, result });
138 return {};
139 }
140
141 void Mixer::UnloadSound(const SoundID soundID) {
142 MIX_DestroyAudio(s_Data->m_SDLAudios.at(soundID));
143 s_Data->m_SDLAudios.erase(soundID);
144 }
145
146 std::expected<void, Core::CoriError<>> Mixer::CreateTrack(Track* track) {
147 const auto result = MIX_CreateTrack(s_Data->m_Mixer);
148 if (!result) {
149 return std::unexpected(Core::CoriError(std::format("Failed to create Track. SDL_Error: {}", SDL_GetError())));
150 }
151
152 MIX_SetTrackStoppedCallback(result, Track::TrackStopCallback, track);
153 s_Data->m_TrackPool.insert({ track->GetID(), std::make_pair(result, track) });
154 return {};
155 }
156
157 void Mixer::DestroyTrack(const TrackID trackID) {
158 MIX_DestroyTrack(s_Data->m_TrackPool.at(trackID).first);
159 s_Data->m_TrackPool.erase(trackID);
160 }
161
162 std::expected<void, Core::CoriError<>> Mixer::SetTrackSound(const TrackID trackID, const Sound* sound) {
163 const bool success = MIX_SetTrackAudio(s_Data->m_TrackPool.at(trackID).first, s_Data->m_SDLAudios.at(sound->GetID()));
164 if (!success) {
165 return std::unexpected(Core::CoriError(std::format("Failed to assign Sound '{} (SoundID: {})' to Track '{} (TrackID: {})'. SDL_Error: {}", sound->m_Name, sound->GetID(), s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
166 }
167
168 return {};
169 }
170
171 std::expected<void, Core::CoriError<>> Mixer::PlayTrack(const TrackID trackID, const PlayParams& params) {
172 const SDL_PropertiesID props = SDL_CreateProperties();
173 SDL_SetNumberProperty(props, MIX_PROP_PLAY_LOOPS_NUMBER, params.Loops);
174 if (params.MaxMilliseconds != -1.0f) {
175 auto samples = MillisecondsToFrames(trackID, params.MaxMilliseconds);
176 if (samples) {
177 SDL_SetNumberProperty(props, MIX_PROP_PLAY_MAX_FRAME_NUMBER, samples.value());
178 } else {
179 return std::unexpected(samples.error());
180 }
181 }
182
183 if (params.StartMillisecond != 0.0f) {
184 auto samples = MillisecondsToFrames(trackID, params.StartMillisecond);
185 if (samples) {
186 SDL_SetNumberProperty(props, MIX_PROP_PLAY_START_FRAME_NUMBER, samples.value());
187 } else {
188 return std::unexpected(samples.error());
189 }
190 }
191
192 if (params.LoopStartMillisecond != 0.0f) {
193 auto samples = MillisecondsToFrames(trackID, params.LoopStartMillisecond);
194 if (samples) {
195 SDL_SetNumberProperty(props, MIX_PROP_PLAY_LOOP_START_FRAME_NUMBER, samples.value());
196 } else {
197 return std::unexpected(samples.error());
198 }
199 }
200
201 if (params.FadeInMilliseconds != 0.0f) {
202 auto samples = MillisecondsToFrames(trackID, params.FadeInMilliseconds);
203 if (samples) {
204 SDL_SetNumberProperty(props, MIX_PROP_PLAY_FADE_IN_FRAMES_NUMBER, samples.value());
205 } else {
206 return std::unexpected(samples.error());
207 }
208 }
209
210 if (params.AppendSilenceMilliseconds != 0.0f) {
211 auto samples = MillisecondsToFrames(trackID, params.AppendSilenceMilliseconds);
212 if (samples) {
213 SDL_SetNumberProperty(props, MIX_PROP_PLAY_APPEND_SILENCE_FRAMES_NUMBER, samples.value());
214 } else {
215 return std::unexpected(samples.error());
216 }
217 }
218
219 const bool success = MIX_PlayTrack(s_Data->m_TrackPool.at(trackID).first, props);
220 if (!success) {
221 return std::unexpected(Core::CoriError(std::format("Failed to play Track '{} (TrackID: {})'. SDL_Error: {}", s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
222 }
223 return {};
224 }
225
226 std::expected<void, Core::CoriError<>> Mixer::StopTrack(const TrackID trackID, const int64_t fadeOutMS) {
227 const bool success = MIX_StopTrack(s_Data->m_TrackPool.at(trackID).first, MIX_TrackMSToFrames(s_Data->m_TrackPool.at(trackID).first, fadeOutMS));
228 if (!success) {
229 return std::unexpected(Core::CoriError(std::format("Failed to stop Track '{} (TrackID: {})'. SDL_Error: '{}'", s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
230 }
231 return {};
232 }
233
234 std::expected<void, Core::CoriError<>> Mixer::PauseTrack(const TrackID trackID) {
235 const bool success = MIX_PauseTrack(s_Data->m_TrackPool.at(trackID).first);
236 if (!success) {
237 return std::unexpected(Core::CoriError(std::format("Failed to pause Track '{} (TrackID: {})'. SDL_Error: {}", s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
238 }
239 return {};
240 }
241
242 std::expected<void, Core::CoriError<>> Mixer::ResumeTrack(const TrackID trackID) {
243 const bool success = MIX_ResumeTrack(s_Data->m_TrackPool.at(trackID).first);
244 if (!success) {
245 return std::unexpected(Core::CoriError(std::format("Failed to pause Track '{} (TrackID: {})'. SDL_Error: {}", s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
246 }
247 return {};
248 }
249
250 bool Mixer::IsTrackPaused(const TrackID trackID) {
251 return MIX_TrackPaused(s_Data->m_TrackPool.at(trackID).first);
252 }
253
254 bool Mixer::IsTrackPlaying(const TrackID trackID) {
255 return MIX_TrackPlaying(s_Data->m_TrackPool.at(trackID).first);
256 }
257
258 std::expected<void, Core::CoriError<>> Mixer::SetTrackGain(const TrackID trackID, const float gain) {
259 const bool success = MIX_SetTrackGain(s_Data->m_TrackPool.at(trackID).first, gain);
260 if (!success) {
261 return std::unexpected(Core::CoriError(std::format("Failed to set Track '{} (TrackID: {})' gain. SDL_Error: {}", s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
262 }
263
264 return {};
265 }
266
267 float Mixer::GetTrackGain(const TrackID trackID) {
268 return MIX_GetTrackGain(s_Data->m_TrackPool.at(trackID).first);
269 }
270
271 std::expected<void, Core::CoriError<>> Mixer::TagTrack(const TrackID trackID, const char* tag) {
272 const bool success = MIX_TagTrack(s_Data->m_TrackPool.at(trackID).first, tag);
273 if (!success) {
274 return std::unexpected(Core::CoriError(std::format("Failed to assign the tag the Track '{} (TrackID: {}'. SDL_Error: {}", s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
275 }
276
277 return {};
278 }
279
280 void Mixer::UntagTrack(const TrackID trackID, const char* tag) {
281 MIX_UntagTrack(s_Data->m_TrackPool.at(trackID).first, tag);
282 }
283
284 std::expected<int64_t, Core::CoriError<>> Mixer::MillisecondsToFrames(const TrackID trackID, const float milliseconds) {
285 const int64_t sampleRate = MIX_TrackMSToFrames(s_Data->m_TrackPool.at(trackID).first, 1000);
286
287 if (sampleRate == -1) {
288 return std::unexpected(Core::CoriError(std::format("Failed to convert milliseconds to samples for Track '{} (TrackID: {}'. SDL_Error: {}", s_Data->m_TrackPool.at(trackID).second->m_Name, trackID, SDL_GetError())));
289 }
290
291 return milliseconds / 1000.0f * static_cast<float>(sampleRate);
292
293
294
295 }
296 }
297}
#define CORI_CORE_TRACE_TAGGED(...)
Definition Logger.hpp:1025
#define CORI_CORE_VERIFY(x,...)
Definition Logger.hpp:1030
#define CORI_CORE_INFO_TAGGED(...)
Definition Logger.hpp:1027
static std::expected< void, Core::CoriError<> > SetTagGain(const char *tag, const float gain)
Sets the gain of all tracks with a specific tag.
Definition Mixer.cpp:107
static std::expected< void, Core::CoriError<> > SetMasterGain(const float gain)
Sets the master gain of a Mixer.
Definition Mixer.cpp:36
static std::expected< void, Core::CoriError<> > StartTag(const char *tag, const PlayParams &params=PlayParams{})
Start (ot restart) mixing all Tracks with a specific tag.
Definition Mixer.cpp:49
static float GetMasterGain()
Returns the current master gain specified for the Mixer. The default gain for a Mixer is 1....
Definition Mixer.cpp:45
static std::expected< void, Core::CoriError<> > ResumeTag(const char *tag)
Resumes all Tracks with a specific tag.
Definition Mixer.cpp:98
static std::expected< void, Core::CoriError<> > PauseAllTracks()
Pauses all Track that are currently playing on the Mixer.
Definition Mixer.cpp:18
static std::expected< void, Core::CoriError<> > PauseTag(const char *tag)
Pauses all tracks with a specific tag.
Definition Mixer.cpp:89
static std::expected< void, Core::CoriError<> > ResumeAllTracks()
Resumes all Track that are currently paused on the Mixer.
Definition Mixer.cpp:27
static std::expected< void, Core::CoriError<> > StopTag(const char *tag, const int64_t fadeOutMS=0)
Stops all Track with a specific tab, possibly fading out over time.
Definition Mixer.cpp:80
Sound asset to be played on a Track.
Definition Sound.hpp:12
You use Track to play and mix Sound objects.
Definition Track.hpp:19
static void TrackStopCallback(void *userdata, MIX_Track *track)
For internal use only!
Definition Track.cpp:162
Custom error class mainly used in std::expected.
Definition Error.hpp:27
Everything connected to audio is in this namespace.
Definition IDDefs.hpp:4
uint16_t TrackID
Definition IDDefs.hpp:5
uint32_t SoundID
Definition IDDefs.hpp:6
Global engine namespace.
std::unordered_map< TrackID, std::pair< MIX_Track *, Track * > > m_TrackPool
Definition Mixer.cpp:15
std::unordered_map< SoundID, MIX_Audio * > m_SDLAudios
Definition Mixer.cpp:14
MIX_Mixer * m_Mixer
Definition Mixer.cpp:13
Parameters to be used when playing sound, you can mix your audio playback however you want with these...
Definition Mixer.hpp:20
float MaxMilliseconds
Mix at most n milliseconds. Default -1.0f, mixes all available audio.
Definition Mixer.hpp:29
int32_t Loops
Number of times to loop the track when it reaches the end. A value of -1 will result in an infinite l...
Definition Mixer.hpp:24
float LoopStartMillisecond
Start looping at n'th millisecond. Values <=0 will result in looping from the very beginning of the t...
Definition Mixer.hpp:39
float FadeInMilliseconds
Fade in the audio over n milliseconds. Will start from silence and reach full volume smoothly....
Definition Mixer.hpp:44
float StartMillisecond
Start mixing at n'th millisecond. Values <=0 will result in mixing from the very beginning of the tra...
Definition Mixer.hpp:34
float AppendSilenceMilliseconds
Append n milliseconds to the end of a track after all loops (specified in PlayParams::Loops) are comp...
Definition Mixer.hpp:49
static constexpr char Mixer[]
Definition Logger.hpp:79
static constexpr char Self[]
Definition Logger.hpp:75