MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_sound.cpp
Go to the documentation of this file.
1/**
2 * @file mxvk_sound.cpp
3 * @brief Implementation of mxvk::VK_Mixer SDL3_mixer audio manager.
4 */
5
6#include "mxvk/mxvk_sound.hpp"
7
8#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
9
11
12#include <limits>
13#include <string>
14
15namespace mxvk {
16
17 std::size_t VK_Mixer::toIndex(const int value) { return static_cast<std::size_t>(value); }
18
20
21 VK_Mixer::~VK_Mixer() noexcept { cleanup(); }
22
24 if (initialized) {
25 return;
26 }
27
28 if (!MIX_Init()) {
29 throw mxvk::Exception("Could not initialize SDL3_mixer: " + std::string(SDL_GetError()));
30 }
31
32 SDL_AudioSpec spec{};
33 spec.freq = 44100;
34 spec.format = SDL_AUDIO_S16;
35 spec.channels = 2;
36
37 MIX_Mixer *raw_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec);
38 if (raw_mixer == nullptr) {
39 MIX_Quit();
40 throw mxvk::Exception("Could not create mixer device: " + std::string(SDL_GetError()));
41 }
42
43 mixer.reset(raw_mixer);
44 initialized = true;
45 }
46
47 int VK_Mixer::loadMusic(const std::string &filename) {
48 if (filename.empty()) {
49 throw mxvk::Exception("Music filename cannot be empty");
50 }
51 if (!initialized || mixer == nullptr) {
52 throw mxvk::Exception("Audio mixer is not initialized");
53 }
54
55 MIX_Audio *raw_audio = MIX_LoadAudio(mixer.get(), filename.c_str(), false);
56 if (raw_audio == nullptr) {
57 throw mxvk::Exception("Error loading music file '" + filename + "': " + std::string(SDL_GetError()));
58 }
59
60 MIX_Track *raw_track = MIX_CreateTrack(mixer.get());
61 if (raw_track == nullptr) {
62 MIX_DestroyAudio(raw_audio);
63 throw mxvk::Exception("Error creating music track: " + std::string(SDL_GetError()));
64 }
65 if (!MIX_SetTrackAudio(raw_track, raw_audio)) {
66 MIX_DestroyTrack(raw_track);
67 MIX_DestroyAudio(raw_audio);
68 throw mxvk::Exception("Error assigning music track audio: " + std::string(SDL_GetError()));
69 }
70
71 music_files.emplace_back(raw_audio, MIX_DestroyAudio);
72 music_tracks.emplace_back(raw_track, MIX_DestroyTrack);
73 return static_cast<int>(music_files.size() - 1);
74 }
75
76 int VK_Mixer::loadWav(const std::string &filename) {
77 if (filename.empty()) {
78 throw mxvk::Exception("WAV filename cannot be empty");
79 }
80 if (!initialized || mixer == nullptr) {
81 throw mxvk::Exception("Audio mixer is not initialized");
82 }
83
84 MIX_Audio *raw_audio = MIX_LoadAudio(mixer.get(), filename.c_str(), true);
85 if (raw_audio == nullptr) {
86 throw mxvk::Exception("Error loading WAV file '" + filename + "': " + std::string(SDL_GetError()));
87 }
88
89 MIX_Track *raw_track = MIX_CreateTrack(mixer.get());
90 if (raw_track == nullptr) {
91 MIX_DestroyAudio(raw_audio);
92 throw mxvk::Exception("Error creating WAV track: " + std::string(SDL_GetError()));
93 }
94 if (!MIX_SetTrackAudio(raw_track, raw_audio)) {
95 MIX_DestroyTrack(raw_track);
96 MIX_DestroyAudio(raw_audio);
97 throw mxvk::Exception("Error assigning WAV track audio: " + std::string(SDL_GetError()));
98 }
99
100 wav_files.emplace_back(raw_audio, MIX_DestroyAudio);
101 wav_tracks.emplace_back(raw_track, MIX_DestroyTrack);
102 return static_cast<int>(wav_files.size() - 1);
103 }
104
105 int VK_Mixer::playMusic(int id, int value) {
106 if (!initialized) {
107 return -1;
108 }
109 if (id < 0 || id >= static_cast<int>(music_tracks.size())) {
110 return -1;
111 }
112
113 MIX_Track *track = music_tracks[toIndex(id)].get();
114 if (track == nullptr) {
115 return -1;
116 }
117 if (!MIX_SetTrackLoops(track, value)) {
118 return -1;
119 }
120 return MIX_PlayTrack(track, 0) ? 0 : -1;
121 }
122
123 int VK_Mixer::playWav(int id, int value, int channel) {
124 if (!initialized) {
125 return -1;
126 }
127 if (id < 0 || id >= static_cast<int>(wav_files.size())) {
128 return -1;
129 }
130
131 int target = id;
132 if (channel >= 0) {
133 if (channel >= static_cast<int>(wav_tracks.size())) {
134 return -1;
135 }
136 target = channel;
137 }
138
139 MIX_Track *track = wav_tracks[toIndex(target)].get();
140 MIX_Audio *audio = wav_files[toIndex(id)].get();
141 if (track == nullptr || audio == nullptr) {
142 return -1;
143 }
144 if (!MIX_SetTrackAudio(track, audio)) {
145 return -1;
146 }
147 if (!MIX_SetTrackLoops(track, value)) {
148 return -1;
149 }
150 return MIX_PlayTrack(track, 0) ? target : -1;
151 }
152
153 [[nodiscard]] bool VK_Mixer::isPlaying(int channel) const {
154 if (!initialized || channel < 0 || channel >= static_cast<int>(wav_tracks.size())) {
155 return false;
156 }
157
158 MIX_Track *track = wav_tracks[toIndex(channel)].get();
159 if (track == nullptr) {
160 return false;
161 }
162 return MIX_TrackPlaying(track);
163 }
164
165 [[nodiscard]] bool VK_Mixer::isMusicPlaying(int id) const {
166 if (!initialized || id < 0 || id >= static_cast<int>(music_tracks.size())) {
167 return false;
168 }
169
170 MIX_Track *track = music_tracks[toIndex(id)].get();
171 if (track == nullptr) {
172 return false;
173 }
174 return MIX_TrackPlaying(track);
175 }
176
178 if (!initialized || mixer == nullptr) {
179 return;
180 }
181
182 for (const auto &track : music_tracks) {
183 if (track != nullptr) {
184 MIX_StopTrack(track.get(), 0);
185 }
186 }
187 MIX_StopAllTracks(mixer.get(), 0);
188 }
189
191 if (!initialized) {
192 return;
193 }
194
195 stopMusic();
196 music_tracks.clear();
197 wav_tracks.clear();
198 music_files.clear();
199 wav_files.clear();
200 mixer.reset();
201 MIX_Quit();
202 initialized = false;
203 }
204
205} // namespace mxvk
206
207#endif
int loadWav(const std::string &filename)
Load a WAV file as a sound effect chunk.
int playWav(int id, int value=0, int channel=-1)
Play a previously loaded WAV chunk.
void stopMusic()
Stop background music playback immediately.
void cleanup()
Free all loaded audio chunks and music tracks.
bool isPlaying(int channel) const
Query whether a sound track is currently playing.
void init()
Open the audio device (called automatically by constructor).
int loadMusic(const std::string &filename)
Load an audio file as streaming background music.
bool isMusicPlaying(int id) const
Query whether a loaded music track is currently playing.
VK_Mixer()
Initialise SDL3_mixer and open the audio device.
int playMusic(int id, int value=0)
Start playing a previously loaded music track.
~VK_Mixer() noexcept
Halt all playback and free all loaded audio resources.
SDL3_mixer audio subsystem wrapper.
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31