1 /++
2  + Authors: Avaxar <avaxar@nekkl.org>
3  + Copyright: Copyright © 2023, Avaxar
4  + License: $(LINK2 https://mit-license.org, MIT License)
5  +/
6 
7 module dsdl2.mixer;
8 @safe:
9 
10 // dfmt off
11 import bindbc.sdl;
12 static if (bindSDLMixer):
13 // dfmt on
14 
15 import dsdl2.sdl;
16 import dsdl2.audio;
17 
18 import core.memory : GC;
19 import std.conv : to;
20 import std.format : format;
21 import std.string : toStringz;
22 import std.typecons : Tuple;
23 
24 version (BindSDL_Static) {
25 }
26 else {
27     /++
28      + Loads the SDL2_mixer shared dynamic library, which wraps bindbc-sdl's `loadSDLMixer` function
29      +
30      + Unless if bindbc-sdl is on static mode (by adding a `BindSDL_Static` version), this function will exist and must
31      + be called before any calls are made to the library. Otherwise, a segfault will happen upon any function calls.
32      +
33      + Params:
34      +   libName = name or path to look the SDL2_mixer SO/DLL for, otherwise `null` for default searching path
35      + Throws: `dsdl2.SDLException` if failed to find the library
36      +/
37     void loadSO(string libName = null) @trusted {
38         SDLMixerSupport current = libName is null ? loadSDLMixer() : loadSDLMixer(libName.toStringz());
39         if (current == sdlMixerSupport) {
40             return;
41         }
42 
43         Version wanted = Version(sdlMixerSupport);
44         if (current == SDLMixerSupport.badLibrary) {
45             import std.stdio : writeln;
46 
47             writeln("WARNING: dsdl2 expects SDL_mixer ", wanted.format(), ", but got ", getVersion().format(), ".");
48         }
49         else if (current == SDLMixerSupport.noLibrary) {
50             throw new SDLException("No SDL2_mixer library found, especially of version " ~ wanted.format(),
51                 __FILE__, __LINE__);
52         }
53     }
54 }
55 
56 /++
57  + Wraps `Mix_Init` which initializes selected SDL2_mixer audio format subsystems
58  +
59  + Params:
60  +   flac = selects the `MIX_INIT_FLAC` subsystem
61  +   mod = selects the `MIX_INIT_MOD` subsystem
62  +   mp3 = selects the `MIX_INIT_MP3` subsystem
63  +   ogg = selects the `MIX_INIT_OGG` subsystem
64  +   mid = selects the `MIX_INIT_FLUIDSYNTH` (for SDL_mixer below 2.0.2) / `MIX_INIT_MID` subsystem
65  +   opus = selects the `MIX_INIT_OPUS` subsystem (from SDL_mixer 2.0.4)
66  +   everything = selects every available subsystem
67  + Throws: `dsdl2.SDLException` if any selected subsystem failed to initialize
68  + Example:
69  + ---
70  + dsdl2.mixer.init(everything : true);
71  + ---
72  +/
73 void init(bool flac = false, bool mod = false, bool mp3 = false, bool ogg = false, bool mid = false,
74     bool opus = false, bool everything = false) @trusted
75 in {
76     static if (sdlMixerSupport < SDLMixerSupport.v2_0_4) {
77         assert(opus == false);
78     }
79     else {
80         if (opus) {
81             assert(dsdl2.mixer.getVersion() >= Version(2, 0, 4));
82         }
83     }
84 }
85 do {
86     int flags = 0;
87 
88     flags |= flac ? MIX_INIT_FLAC : 0;
89     flags |= mod ? MIX_INIT_MOD : 0;
90     flags |= mp3 ? MIX_INIT_MP3 : 0;
91     flags |= ogg ? MIX_INIT_OGG : 0;
92     flags |= everything ? MIX_INIT_FLAC | MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG : 0;
93 
94     static if (sdlMixerSupport >= SDLMixerSupport.v2_0_2) {
95         flags |= mid ? MIX_INIT_MID : 0;
96         flags |= everything ? MIX_INIT_MID : 0;
97     }
98     else {
99         flags |= mid ? MIX_INIT_FLUIDSYNTH : 0;
100         flags |= everything ? MIX_INIT_FLUIDSYNTH : 0;
101     }
102 
103     static if (sdlMixerSupport >= SDLMixerSupport.v2_0_4) {
104         flags |= opus ? MIX_INIT_OPUS : 0;
105         flags |= everything ? MIX_INIT_OPUS : 0;
106     }
107 
108     if ((Mix_Init(flags) & flags) != flags) {
109         throw new SDLException;
110     }
111 }
112 
113 version (unittest) {
114     static this() {
115         version (BindSDL_Static) {
116         }
117         else {
118             dsdl2.mixer.loadSO();
119         }
120 
121         dsdl2.mixer.init(everything : true);
122     }
123 }
124 
125 /++
126  + Wraps `Mix_Quit` which entirely deinitializes SDL2_mixer
127  +/
128 void quit() @trusted {
129     Mix_Quit();
130 }
131 
132 version (unittest) {
133     static ~this() {
134         dsdl2.mixer.quit();
135     }
136 }
137 
138 /++
139  + Wraps `Mix_Linked_Version` which gets the version of the linked SDL2_mixer library
140  +
141  + Returns: `dsdl2.Version` of the linked SDL2_mixer library
142  +/
143 Version getVersion() @trusted {
144     return Version(*Mix_Linked_Version());
145 }
146 
147 enum channels = cast(ubyte) MIX_CHANNELS; /// Alias to `MIX_CHANNELS`
148 enum defaultFrequency = cast(uint) MIX_DEFAULT_FREQUENCY; /// Alias to `MIX_DEFAULT_FREQUENCY`
149 enum defaultFormat = cast(AudioFormat) MIX_DEFAULT_FORMAT; /// Alias to `MIX_DEFAULT_FORMAT`
150 enum maxVolume = cast(ubyte) MIX_MAX_VOLUME; /// Alias to `MIX_MAX_VOLUME`
151 
152 /++
153  + D enum that wraps `Mix_Fading`
154  +/
155 enum Fading {
156     /++
157      + Wraps `MIX_*` enumeration constants
158      +/
159     noFading = MIX_NO_FADING,
160     fadingIn = MIX_FADING_IN, /// ditto
161     fadingOut = MIX_FADING_OUT /// ditto
162 }
163 
164 /++
165  + D enum that wraps `Mix_MusicType`
166  +/
167 enum MusicType {
168     /++
169      + Wraps `MUS_*` enumeration constants
170      +/
171     none = MUS_NONE,
172     cmd = MUS_CMD, /// ditto
173     wav = MUS_WAV, /// ditto
174     mod = MUS_MOD, /// ditto
175     mid = MUS_MID, /// ditto
176     ogg = MUS_OGG, /// ditto
177     mp3 = MUS_MP3, /// ditto
178     flac = MUS_FLAC, /// ditto
179 
180     opus = 10 /// Wraps `MUS_OPUS` (from SDL_mixer 2.0.4)
181 }
182 
183 /++
184  + Wraps `Mix_OpenAudio` which opens the default audio device for playback by SDL_mixer
185  +
186  + Params:
187  +   frequency = audio playback frequency in Hz
188  +   format = `dsdl2.AudioFormat` enumeration indicating the scalar type of each audio sample
189  +   channels = channels for `dsdl2.mixer.Chunk` playback (1 for mono; 2 for stereo)
190  +   chunkSize = audio buffer size
191  + Throws: `dsdl2.SDLException` if failed to open default audio device
192  +/
193 void openAudio(uint frequency, AudioFormat format, uint channels, uint chunkSize) @trusted {
194     if (Mix_OpenAudio(frequency.to!int, cast(ushort) format, channels.to!int, chunkSize.to!int) != 0) {
195         throw new SDLException;
196     }
197 }
198 
199 static if (sdlMixerSupport >= SDLMixerSupport.v2_0_2) {
200     /++
201      + Wraps `Mix_OpenAudioDevice` (from SDL_mixer 2.0.2) which opens a selected audio device for playback by SDL_mixer
202      +
203      + Params:
204      +   frequency = audio playback frequency in Hz
205      +   format = `dsdl2.AudioFormat` enumeration indicating the scalar type of each audio sample
206      +   channels = channels for `dsdl2.mixer.Chunk` playback (1 for mono; 2 for stereo)
207      +   chunkSize = audio buffer size
208      +   deviceName = name of the selected device
209      +   allowFrequencyChange = adds `SDL_AUDIO_ALLOW_FREQUENCY_CHANGE` flag
210      +   allowFormatChange = adds `SDL_AUDIO_ALLOW_FORMAT_CHANGE` flag
211      +   allowChannelsChange = adds `SDL_AUDIO_ALLOW_CHANNELS_CHANGE` flag
212      +   allowSamplesChange = adds `SDL_AUDIO_ALLOW_SAMPLES_CHANGE` flag (from SDL_mixer 2.0.9)
213      +   allowAnyChange = adds `SDL_AUDIO_ALLOW_ANY_CHANGE` flag
214      + Throws: `dsdl2.SDLException` if failed to open the selected audio device
215      +/
216     void openAudioDevice(uint frequency, AudioFormat format, uint channels, uint chunkSize, string deviceName,
217         bool allowFrequencyChange = false, bool allowFormatChange = false, bool allowChannelsChange = false,
218         bool allowSamplesChange = false, bool allowAnyChange = false) @trusted
219     in {
220         assert(dsdl2.mixer.getVersion() >= Version(2, 0, 2));
221 
222         static if (sdlSupport < SDLSupport.v2_0_9) {
223             assert(allowSamplesChange == false);
224         }
225         else {
226             if (allowSamplesChange) {
227                 assert(dsdl2.mixer.getVersion() >= Version(2, 0, 9));
228             }
229         }
230     }
231     do {
232         int changeFlags = 0;
233         changeFlags |= allowFrequencyChange ? SDL_AUDIO_ALLOW_FREQUENCY_CHANGE : 0;
234         changeFlags |= allowFormatChange ? SDL_AUDIO_ALLOW_FORMAT_CHANGE : 0;
235         changeFlags |= allowChannelsChange ? SDL_AUDIO_ALLOW_CHANNELS_CHANGE : 0;
236         changeFlags |= allowAnyChange ? SDL_AUDIO_ALLOW_ANY_CHANGE : 0;
237 
238         static if (sdlSupport >= SDLSupport.v2_0_9) {
239             changeFlags |= allowSamplesChange ? SDL_AUDIO_ALLOW_SAMPLES_CHANGE : 0;
240         }
241 
242         if (Mix_OpenAudioDevice(frequency.to!int, cast(ushort) format, channels.to!int, chunkSize.to!int,
243                 deviceName.toStringz(), changeFlags) != 0) {
244             throw new SDLException;
245         }
246     }
247 }
248 
249 /++
250  + Wraps `Mix_AllocateChannels` which changes the amount of track channels managed by SDL_mixer
251  +
252  + Params:
253  +   channels = new amount of track channels (not the same as device channels)
254  +/
255 void allocateChannels(uint channels) @trusted {
256     Mix_AllocateChannels(channels.to!int);
257 }
258 
259 /++
260  + Wraps `Mix_ReserveChannels` which reserves the first track channels managed by SDL_mixer for the application
261  +
262  + Params:
263  +   channels = amount of the first track channels to reserve (not the same as device channels)
264  +/
265 void reserveChannels(uint channels) @trusted {
266     Mix_ReserveChannels(channels.to!int);
267 }
268 
269 private alias MixerSpec = Tuple!(uint, "frequency", AudioFormat, "format", uint, "channels");
270 
271 /++
272  + Wraps `Mix_QuerySpec` which queries the default audio device information
273  +
274  + Returns: a named tuple detailing the default audio device's `frequency`, audio `format`, and `channels`
275  + Throws: `dsdl2.SDLException` if failed to query the information
276  +/
277 MixerSpec querySpec() @trusted {
278     MixerSpec spec = void;
279     if (Mix_QuerySpec(cast(int*)&spec.frequency, cast(ushort*)&spec.format, cast(int*)&spec.channels) != 0) {
280         throw new SDLException;
281     }
282 
283     return spec;
284 }
285 
286 static if (sdlMixerSupport >= SDLMixerSupport.v2_6) {
287     /++
288      + Wraps `Mix_MasterVolume` (from SDL_mixer 2.6) which gets the master volume
289      +
290      + Returns: master volume ranging from `0` to `128`
291      +/
292     ubyte getMasterVolume() @trusted {
293         return cast(ubyte) Mix_MasterVolume(-1);
294     }
295 
296     /++
297      + Wraps `Mix_MasterVolume` (from SDL_mixer 2.6) which sets the master volume
298      +
299      + Params:
300      +   newVolume = new master volume ranging from `0` to `128`
301      +/
302     void setMasterVolume(ubyte newVolume) @trusted {
303         Mix_MasterVolume(newVolume);
304     }
305 }
306 
307 /++
308  + D class that acts as a proxy for a mixer audio channel from a channel ID
309  +/
310 final class Channel {
311     const uint mixChannel; /// Channel ID from SDL_mixer
312 
313     this() @disable;
314 
315     private this(uint mixChannel) {
316         this.mixChannel = mixChannel;
317     }
318 
319     /++
320      + Equality operator overload
321      +/
322     bool opEquals(const Channel rhs) const {
323         return this.mixChannel == rhs.mixChannel;
324     }
325 
326     /++
327      + Gets the hash of the `dsdl2.mixer.Channel`
328      +
329      + Returns: unique hash for the instance being the channel ID
330      +/
331     override hash_t toHash() const {
332         return cast(hash_t) this.mixChannel;
333     }
334 
335     /++
336      + Formats the `dsdl2.mixer.Channel` showing its internal information: `"dsdl2.mixer.Channel(<mixChannel>)"`
337      +
338      + Returns: the formatted `string`
339      +/
340     override string toString() const {
341         return "dsdl2.mixer.Channel(%d)".format(this.mixChannel);
342     }
343 
344     /++
345      + Wraps `Mix_SetPanning` which sets the panning volume of the left and right channels for the track channel
346      +
347      + Params:
348      +   newLR = tuple of the volumes from `0` to `255` of the left and right channels in order
349      + Throws: `dsdl2.SDLException` if failed to set the panning
350      +/
351     void panning(ubyte[2] newLR) const @property @trusted {
352         if (Mix_SetPanning(this.mixChannel, newLR[0], newLR[1]) == 0) {
353             throw new SDLException;
354         }
355     }
356 
357     /++
358      + Wraps `Mix_SetPanning` which sets the panning volume of the left and right channels as posteffect for all
359      + channels
360      +
361      + Params:
362      +   newLR = tuple of the volumes from `0` to `255` of the left and right channels in order
363      + Throws: `dsdl2.SDLException` if failed to set the panning
364      +/
365     static void allPanning(ubyte[2] newLR) @property @trusted {
366         if (Mix_SetPanning(MIX_CHANNEL_POST, newLR[0], newLR[1]) == 0) {
367             throw new SDLException;
368         }
369     }
370 
371     /++
372      + Wraps `Mix_SetPosition` which sets the simulated angle and distance of the channel playing from the listener
373      +
374      + Params:
375      +   newAngleDistance = tuple of the simulated angle (in degrees clockwise, with `0` being the front) and distance
376      + Throws: `dsdl2.SDLException` if failed to set the position
377      +/
378     void position(Tuple!(short, ubyte) newAngleDistance) const @property @trusted {
379         if (Mix_SetPosition(this.mixChannel, newAngleDistance[0], newAngleDistance[1]) == 0) {
380             throw new SDLException;
381         }
382     }
383 
384     /++
385      + Acts as `Mix_SetPosition(MIX_CHANNEL_POST, newAngleDistance[0], newAngleDistance[1])` which sets the simulated
386      + angle and distance for all channels as posteffect
387      +
388      + Params:
389      +   newAngleDistance = tuple of the simulated angle (in degrees clockwise, with `0` being the front) and distance
390      + Throws: `dsdl2.SDLException` if failed to set the position
391      +/
392     static void allPosition(Tuple!(short, ubyte) newAngleDistance) @property @trusted {
393         if (Mix_SetPosition(MIX_CHANNEL_POST, newAngleDistance[0], newAngleDistance[1]) == 0) {
394             throw new SDLException;
395         }
396     }
397 
398     /++
399      + Wraps `Mix_SetDistance` which sets the simulated distance for the channel playing from the listener
400      +
401      + Params:
402      +   newDistance = simulated distance from `0` to `255`
403      + Throws: `dsdl2.SDLException` if failed to set the distance
404      +/
405     void distance(ubyte newDistance) const @property @trusted {
406         if (Mix_SetDistance(this.mixChannel, newDistance) == 0) {
407             throw new SDLException;
408         }
409     }
410 
411     /++
412      + Acts as `Mix_SetDistance(MIX_CHANNEL_POST, newDistance)` which sets the simulated distance for all channels as
413      + posteffect
414      +
415      + Params:
416      +   newDistance = simulated distance from `0` to `255`
417      + Throws: `dsdl2.SDLException` if failed to set the distance
418      +/
419     static void allDistance(ubyte newDistance) @property @trusted {
420         if (Mix_SetDistance(MIX_CHANNEL_POST, newDistance) == 0) {
421             throw new SDLException;
422         }
423     }
424 
425     /++
426      + Wraps `Mix_Volume` which gets the volume of the channel
427      +
428      + Returns: volume ranging from `0` to `128`
429      +/
430     ubyte volume() const @property @trusted {
431         return cast(ubyte) Mix_Volume(this.mixChannel, -1);
432     }
433 
434     /++
435      + Acts as `Mix_Volume(-1, -1)` which gets the volume of all channels
436      +
437      + Returns: volume ranging from `0` to `128`
438      +/
439     static ubyte allVolume() @property @trusted {
440         return cast(ubyte) Mix_Volume(-1, -1);
441     }
442 
443     /++
444      + Wraps `Mix_Volume` which sets the volume of the channel
445      +
446      + Params:
447      +   newVolume = new volume ranging from `0` to `128`
448      +/
449     void volume(ubyte newVolume) const @property @trusted {
450         Mix_Volume(this.mixChannel, newVolume);
451     }
452 
453     /++
454      + Acts as `Mix_Volume(-1, newVolume)` which sets the volume of all channels
455      +
456      + Params:
457      +   newVolume = new volume ranging from `0` to `128`
458      +/
459     static void allVolume(ubyte newVolume) @property @trusted {
460         Mix_Volume(-1, newVolume);
461     }
462 
463     /++
464      + Wraps `Mix_SetReverseStereo` which sets whether the left and right channels are flipped in the channel
465      +
466      + Params:
467      +   newReverse = `true` to enable reverse stereo; `false` to disable
468      + Throws: `dsdl2.SDLException` if failed to set the reverse stereo mode
469      +/
470     void reverseStereo(bool newReverse) const @property @trusted {
471         if (Mix_SetReverseStereo(this.mixChannel, newReverse ? 1 : 0) == 0) {
472             throw new SDLException;
473         }
474     }
475 
476     /++
477      + Acts as `Mix_SetReverseStereo(MIX_CHANNEL_POST, newReverse)` which sets whether the left and right channels are
478      + flipped for all channels as posteffect
479      +
480      + Params:
481      +   newReverse = `true` to enable reverse stereo; `false` to disable
482      + Throws: `dsdl2.SDLException` if failed to set the reverse stereo mode
483      +/
484     static void allReverseStereo(bool newReverse) @property @trusted {
485         if (Mix_SetReverseStereo(MIX_CHANNEL_POST, newReverse ? 1 : 0) == 0) {
486             throw new SDLException;
487         }
488     }
489 
490     /++
491      + Wraps `Mix_Paused` which checks whether the channel is paused
492      +
493      + Returns: `true` if channel is paused, otherwise `false`
494      +/
495     bool paused() const @property @trusted {
496         return Mix_Paused(this.mixChannel) == 1;
497     }
498 
499     /++
500      + Wraps `Mix_Playing` which checks whether the channel is playing
501      +
502      + Returns: `true` if channel is playing, otherwise `false`
503      +/
504     bool playing() const @property @trusted {
505         return Mix_Playing(this.mixChannel) == 1;
506     }
507 
508     /++
509      + Wraps `Mix_FadingChannel` which gets the fading stage of the channel
510      +
511      + Returns: `dsdl2.mixer.Fading` enumeration indicating the channel's fading stage
512      +/
513     Fading fading() const @property @trusted {
514         return cast(Fading) Mix_FadingChannel(this.mixChannel);
515     }
516 
517     /++
518      + Wraps `Mix_GetChunk` which gets the currently-playing `dsdl2.mixer.Chunk` in the channel
519      +
520      + This function is marked as `@system` due to the potential of referencing invalid memory.
521      +
522      + Returns: `dsdl2.mixer.Chunk` proxy to the playing chunk
523      + Throws: `dsdl2.SDLException` if failed to get the playing chunk
524      +/
525     Chunk chunk() const @property @system {
526         if (Mix_Chunk* mixChunk = Mix_GetChunk(this.mixChannel)) {
527             return new Chunk(mixChunk, false);
528         }
529         else {
530             throw new SDLException;
531         }
532     }
533 
534     /++
535      + Wraps `Mix_PlayChannelTimed` which plays a `dsdl2.mixer.Chunk` in the channel
536      +
537      + Params:
538      +   chunk = `dsdl2.mixer.Chunk` to be played
539      +   loops = how many times the chunk should be played (`cast(uint) -1` for infinity)
540      +   ms = number of milliseconds the chunk should be played for
541      + Throws: `dsdl2.SDLException` if failed to play the chunk
542      +/
543     void play(const Chunk chunk, uint loops = 1, uint ms = cast(uint)-1) const @trusted {
544         if (Mix_PlayChannelTimed(this.mixChannel, cast(Mix_Chunk*) chunk.mixChunk, loops, ms) == -1) {
545             throw new SDLException;
546         }
547     }
548 
549     /++
550      + Wraps `Mix_FadeInChannelTimed` which plays a `dsdl2.mixer.Chunk` in the channel with a fade-in effect
551      +
552      + Params:
553      +   chunk = `dsdl2.mixer.Chunk` to be played
554      +   loops = how many times the chunk should be played (`cast(uint) -1` for infinity)
555      +   fadeMs = number of milliseconds the chunk fades in before fully playing at full volume
556      +   ms = number of milliseconds the chunk should be played for
557      + Throws: `dsdl2.SDLException` if failed to play the chunk
558      +/
559     void fadeIn(const Chunk chunk, uint loops = 1, uint fadeMs = 0, uint ms = cast(uint)-1) const @trusted {
560         if (Mix_FadeInChannelTimed(this.mixChannel, cast(Mix_Chunk*) chunk.mixChunk, loops, fadeMs, ms) == -1) {
561             throw new SDLException;
562         }
563     }
564 
565     /++
566      + Wraps `Mix_HaltChannel` which halts the channel
567      +
568      + Throws: `dsdl2.SDLException` if failed to halt the channel
569      +/
570     void halt() const @trusted {
571         if (Mix_HaltChannel(this.mixChannel) != 0) {
572             throw new SDLException;
573         }
574     }
575 
576     /++
577      + Acts as `Mix_HaltChannel(-1)` which halts all channels
578      +
579      + Throws: `dsdl2.SDLException` if failed to halt the channels
580      +/
581     static void haltAll() @trusted {
582         if (Mix_HaltChannel(-1) != 0) {
583             throw new SDLException;
584         }
585     }
586 
587     /++
588      + Wraps `Mix_ExpireChannel` which halts the channel after a specified delay
589      +
590      + Params:
591      +   ms = number of milliseconds before the channel halts
592      +/
593     void expire(uint ms) const @trusted {
594         Mix_ExpireChannel(this.mixChannel, ms);
595     }
596 
597     /++
598      + Acts as `Mix_ExpireChannel(-1, ms)` which halts all channels after a specified delay
599      +
600      + Params:
601      +   ms = number of milliseconds before the channels halt
602      +/
603     static void expireAll(uint ms) @trusted {
604         Mix_ExpireChannel(-1, ms);
605     }
606 
607     /++
608      + Wraps `Mix_FadeOutChannel` which performs fade-out for whatever chunk is playing in the channel
609      +
610      + Params:
611      +   fadeMs = number of milliseconds to fade-out before fully halting
612      +/
613     void fadeOut(uint fadeMs) const @trusted {
614         Mix_FadeOutChannel(this.mixChannel, fadeMs);
615     }
616 
617     /++
618      + Acts as `Mix_FadeOutChannel(-1, fadeMs)` which performs fade-out for whatever chunks are playing in all channels
619      +
620      + Params:
621      +   fadeMs = number of milliseconds to fade-out before fully halting
622      +/
623     static void fadeOutAll(uint fadeMs) @trusted {
624         Mix_FadeOutChannel(-1, fadeMs);
625     }
626 
627     /++
628      + Wraps `Mix_Pause` which pauses the channel
629      +/
630     void pause() const @trusted {
631         Mix_Pause(this.mixChannel);
632     }
633 
634     /++
635      + Acts as `Mix_Pause(-1)` which pauses all channels
636      +/
637     static void pauseAll() @trusted {
638         Mix_Pause(-1);
639     }
640 
641     /++
642      + Wraps `Mix_Resume` which resumes the channel
643      +/
644     void resume() const @trusted {
645         Mix_Resume(this.mixChannel);
646     }
647 
648     /++
649      + Acts as `Mix_Resume(-1)` which resumes all channels
650      +/
651     static void resumeAll() @trusted {
652         Mix_Resume(-1);
653     }
654 }
655 
656 /++
657  + Gets `dsdl2.mixer.Channel` proxy instances of the available audio channels provided by SDL_mixer
658  +
659  + Returns: array of proxies to the available `dsdl2.mixer.Channel`s
660  + Throws: `dsdl2.SDLException` if failed to get the available audio channels
661  +/
662 const(Channel[]) getChannels() @trusted {
663     int numChannels = Mix_AllocateChannels(-1);
664 
665     static Channel[] channels;
666     if (channels !is null) {
667         size_t originalLength = channels.length;
668         channels.length = numChannels;
669 
670         if (numChannels > originalLength) {
671             foreach (i; originalLength .. numChannels) {
672                 channels[i] = new Channel(i.to!uint);
673             }
674         }
675     }
676     else {
677         channels = new Channel[](numChannels);
678         foreach (i; 0 .. numChannels) {
679             channels[i] = new Channel(i);
680         }
681     }
682 
683     return channels;
684 }
685 
686 /++
687  + D class that wraps `Mix_Chunk` storing an audio chunk for playback
688  +/
689 final class Chunk {
690     private bool isOwner = true;
691     private void* userRef = null;
692 
693     @system Mix_Chunk* mixChunk = null; /// Internal `Mix_Chunk` pointer
694 
695     /++
696      + Constructs a `dsdl2.mixer.Chunk` from a vanilla `Mix_Chunk*` from bindbc-sdl
697      +
698      + Params:
699      +   mixChunk = the `Mix_Chunk` pointer to manage
700      +   isOwner = whether the instance owns the given `Mix_Chunk*` and should destroy it on its own
701      +   userRef = optional pointer to maintain reference link, avoiding GC cleanup
702      +/
703     this(Mix_Chunk* mixChunk, bool isOwner = true, void* userRef = null) @system
704     in {
705         assert(mixChunk !is null);
706     }
707     do {
708         this.mixChunk = mixChunk;
709         this.isOwner = isOwner;
710         this.userRef = userRef;
711     }
712 
713     ~this() @trusted {
714         if (this.isOwner) {
715             Mix_FreeChunk(this.mixChunk);
716         }
717     }
718 
719     @trusted invariant { // @suppress(dscanner.trust_too_much)
720         // Instance might be in an invalid state due to holding a non-owned externally-freed object when
721         // destructed in an unpredictable order.
722         if (!this.isOwner && GC.inFinalizer) {
723             return;
724         }
725 
726         assert(this.mixChunk !is null);
727     }
728 
729     /++
730      + Equality operator overload
731      +/
732     bool opEquals(const Chunk rhs) const @trusted {
733         return this.mixChunk is rhs.mixChunk;
734     }
735 
736     /++
737      + Gets the hash of the `dsdl2.mixer.Chunk`
738      +
739      + Returns: unique hash for the instance being the pointer of the internal `Mix_Chunk` pointer
740      +/
741     override hash_t toHash() const @trusted {
742         return cast(hash_t) this.mixChunk;
743     }
744 
745     /++
746      + Formats the `dsdl2.mixer.Chunk` into its construction representation:
747      + `"dsdl2.mixer.Chunk(<mixChunk>)"`
748      +
749      + Returns: the formatted `string`
750      +/
751     override string toString() const @trusted {
752         return "dsdl2.mixer.Chunk(0x%x)".format(this.mixChunk);
753     }
754 
755     /++
756      + Wraps `Mix_GetNumChunkDecoders` and `Mix_GetChunkDecoder` which return a list of chunk decoders
757      +
758      + Returns: names of the available chunk decoders
759      +/
760     static string[] decoders() @property @trusted {
761         int numDecoders = Mix_GetNumChunkDecoders();
762         if (numDecoders <= 0) {
763             throw new SDLException;
764         }
765 
766         static string[] decoders;
767         if (decoders !is null) {
768             size_t originalLength = decoders.length;
769             decoders.length = numDecoders;
770 
771             if (numDecoders > originalLength) {
772                 foreach (i; originalLength .. numDecoders) {
773                     decoders[i] = Mix_GetChunkDecoder(i.to!int).to!string;
774                 }
775             }
776         }
777         else {
778             decoders = new string[](numDecoders);
779             foreach (i; 0 .. numDecoders) {
780                 decoders[i] = Mix_GetChunkDecoder(i).to!string;
781             }
782         }
783 
784         return decoders;
785     }
786 
787     static if (sdlMixerSupport >= SDLMixerSupport.v2_0_2) {
788         /++
789          + Wraps `Mix_HasChunkDecoder` (from SDL_mixer 2.0.2) which checks whether a chunk decoder is available
790          +
791          + Params:
792          +   decoder = name of the chunk decoder
793          + Returns: `true` if available, otherwise `false`
794          +/
795         static bool hasDecoder(string decoder) @trusted
796         in {
797             assert(dsdl2.mixer.getVersion() >= Version(2, 0, 2));
798         }
799         do {
800             return Mix_HasChunkDecoder(decoder.toStringz()) == SDL_TRUE;
801         }
802     }
803 
804     /++
805      + Gets the raw PCM audio data buffer for the `dsdl2.mixer.Chunk`
806      +
807      + This function is marked as `@system` due to the potential of referencing invalid memory.
808      +
809      + Returns: slice of the buffer
810      +/
811     inout(void[]) buffer() inout @property @trusted {
812         return (cast(inout(void*)) this.mixChunk.abuf)[0 .. this.mixChunk.alen];
813     }
814 
815     /++
816      + Wraps `Mix_VolumeChunk` which gets the volume of the chunk
817      +
818      + Returns: volume ranging from `0` to `128`
819      +/
820     ubyte volume() const @property @trusted {
821         return cast(ubyte) Mix_VolumeChunk(cast(Mix_Chunk*) this.mixChunk, -1);
822     }
823 
824     /++
825      + Wraps `Mix_VolumeChunk` which sets the volume of the chunk
826      +
827      + Params:
828      +   newVolume = new volume ranging from `0` to `128`
829      +/
830     void volume(ubyte newVolume) @property @trusted {
831         Mix_VolumeChunk(this.mixChunk, newVolume);
832     }
833 
834     /++
835      + Wraps `Mix_PlayChannelTimed` which plays the chunk on the first available free channel
836      +
837      + Params:
838      +   loops = how many times the chunk should be played (`cast(uint) -1` for infinite)
839      +   ms = number of milliseconds the chunk should be played for
840      + Returns: `dsdl2.mixer.Channel` the chunk is played on; `null` if no free channel
841      +/
842     const(Channel) play(uint loops = 1, uint ms = cast(uint)-1) const @trusted {
843         int channel = Mix_PlayChannelTimed(-1, cast(Mix_Chunk*) this.mixChunk, loops, ms);
844         if (channel == -1) {
845             return null;
846         }
847 
848         return getChannels()[channel];
849     }
850 
851     /++
852      + Wraps `Mix_FadeInChannelTimed` which plays the chunk on the first available free channel with a fade-in effect
853      +
854      + Params:
855      +   loops = how many times the chunk should be played (`cast(uint) -1` for infinite)
856      +   fadeMs = number of milliseconds the chunk fades in before fully playing at full volume
857      +   ms = number of milliseconds the chunk should be played for
858      + Returns: `dsdl2.mixer.Channel` the chunk is played on; `null` if no free channel
859      +/
860     const(Channel) fadeIn(uint loops = 1, uint fadeMs = 0, uint ms = cast(uint)-1) const @trusted {
861         int channel = Mix_FadeInChannelTimed(-1, cast(Mix_Chunk*) this.mixChunk, loops, fadeMs, ms);
862         if (channel == -1) {
863             return null;
864         }
865 
866         return getChannels()[channel];
867     }
868 }
869 
870 /++
871  + Wraps `Mix_LoadWAV` which loads an audio file from the filesystem to a `dsdl2.mixer.Chunk`
872  +
873  + Params:
874  +   file = path to the audio file
875  + Returns: loaded `dsdl2.mixer.Chunk`
876  + Throws: `dsdl2.SDLException` if unable to load
877  +/
878 Chunk load(string file) @trusted {
879     Mix_Chunk* mixChunk = Mix_LoadWAV(file.toStringz());
880     if (mixChunk !is null) {
881         return new Chunk(mixChunk);
882     }
883     else {
884         throw new SDLException;
885     }
886 }
887 
888 /++
889  + Wraps `Mix_LoadWAV_RW` which loads an audio file from a buffer to a `dsdl2.mixer.Chunk`
890  +
891  + Params:
892  +   data = buffer of the audio file
893  + Returns: loaded `dsdl2.mixer.Chunk`
894  + Throws: `dsdl2.SDLException` if unable to load
895  +/
896 Chunk loadRaw(const void[] data) @trusted {
897     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
898     if (sdlRWops is null) {
899         throw new SDLException;
900     }
901 
902     Mix_Chunk* mixChunk = Mix_LoadWAV_RW(sdlRWops, 1);
903     if (mixChunk !is null) {
904         return new Chunk(mixChunk);
905     }
906     else {
907         throw new SDLException;
908     }
909 }
910 
911 /++
912  + Wraps `Mix_QuickLoad_RAW` which loads raw PCM audio data to a `dsdl2.mixer.Chunk`
913  +
914  + Params:
915  +   pcm = buffer of the raw PCM audio data
916  + Returns: loaded `dsdl2.mixer.Chunk`
917  +/
918 Chunk loadPCM(const void[] pcm) @trusted {
919     Mix_Chunk* mixChunk = Mix_QuickLoad_RAW(cast(ubyte*) pcm.ptr, pcm.length.to!int);
920     if (mixChunk !is null) {
921         return new Chunk(mixChunk);
922     }
923     else {
924         throw new SDLException;
925     }
926 }
927 
928 /++
929  + D class that wraps `Mix_Music` storing music for playback
930  +/
931 final class Music {
932     private bool isOwner = true;
933     private void* userRef = null;
934 
935     @system Mix_Music* mixMusic = null; /// Internal `Mix_Music` pointer
936 
937     /++
938      + Constructs a `dsdl2.mixer.Music` from a vanilla `Mix_Music*` from bindbc-sdl
939      +
940      + Params:
941      +   mixMusic = the `Mix_Music` pointer to manage
942      +   isOwner = whether the instance owns the given `Mix_Music*` and should destroy it on its own
943      +   userRef = optional pointer to maintain reference link, avoiding GC cleanup
944      +/
945     this(Mix_Music* mixMusic, bool isOwner = true, void* userRef = null) @system
946     in {
947         assert(mixMusic !is null);
948     }
949     do {
950         this.mixMusic = mixMusic;
951         this.isOwner = isOwner;
952         this.userRef = userRef;
953     }
954 
955     ~this() @trusted {
956         if (this.isOwner) {
957             Mix_FreeMusic(this.mixMusic);
958         }
959     }
960 
961     @trusted invariant { // @suppress(dscanner.trust_too_much)
962         // Instance might be in an invalid state due to holding a non-owned externally-freed object when
963         // destructed in an unpredictable order.
964         if (!this.isOwner && GC.inFinalizer) {
965             return;
966         }
967 
968         assert(this.mixMusic !is null);
969     }
970 
971     /++
972      + Equality operator overload
973      +/
974     bool opEquals(const Music rhs) const @trusted {
975         return this.mixMusic is rhs.mixMusic;
976     }
977 
978     /++
979      + Gets the hash of the `dsdl2.mixer.Music`
980      +
981      + Returns: unique hash for the instance being the pointer of the internal `Mix_Music` pointer
982      +/
983     override hash_t toHash() const @trusted {
984         return cast(hash_t) this.mixMusic;
985     }
986 
987     /++
988      + Formats the `dsdl2.mixer.Music` into its construction representation:
989      + `"dsdl2.mixer.Music(<mixMusic>)"`
990      +
991      + Returns: the formatted `string`
992      +/
993     override string toString() const @trusted {
994         return "dsdl2.mixer.Music(0x%x)".format(this.mixMusic);
995     }
996 
997     /++
998      + Wraps `Mix_GetNumMusicDecoders` and `Mix_GetMusicDecoder` which return a list of music decoders
999      +
1000      + Returns: names of the available music decoders
1001      +/
1002     static string[] decoders() @property @trusted {
1003         int numDecoders = Mix_GetNumMusicDecoders();
1004         if (numDecoders <= 0) {
1005             throw new SDLException;
1006         }
1007 
1008         static string[] decoders;
1009         if (decoders !is null) {
1010             size_t originalLength = decoders.length;
1011             decoders.length = numDecoders;
1012 
1013             if (numDecoders > originalLength) {
1014                 foreach (i; originalLength .. numDecoders) {
1015                     decoders[i] = Mix_GetMusicDecoder(i.to!int).to!string;
1016                 }
1017             }
1018         }
1019         else {
1020             decoders = new string[](numDecoders);
1021             foreach (i; 0 .. numDecoders) {
1022                 decoders[i] = Mix_GetMusicDecoder(i).to!string;
1023             }
1024         }
1025 
1026         return decoders;
1027     }
1028 
1029     static if (sdlMixerSupport >= SDLMixerSupport.v2_6) {
1030         /++
1031          + Wraps `Mix_HasMusicDecoder` (from SDL_mixer 2.6) which checks whether a music decoder is available
1032          +
1033          + Params:
1034          +   decoder = name of the music decoder
1035          + Returns: `true` if available, otherwise `false`
1036          +/
1037         static bool hasDecoder(string decoder) @trusted
1038         in {
1039             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1040         }
1041         do {
1042             return Mix_HasMusicDecoder(decoder.toStringz()) == SDL_TRUE;
1043         }
1044     }
1045 
1046     /++
1047      + Wraps `Mix_GetMusicType` which gets the format type of the `dsdl2.mixer.Music`
1048      +
1049      + Returns: `dsdl2.mixer.MusicType` enumeration indicating the format type
1050      +/
1051     MusicType type() const @property @trusted {
1052         return cast(MusicType) Mix_GetMusicType(this.mixMusic);
1053     }
1054 
1055     static if (sdlMixerSupport >= SDLMixerSupport.v2_6) {
1056         /++
1057          + Wraps `Mix_GetMusicTitle` (from SDL_mixer 2.6) which gets the title of the music
1058          +
1059          + Returns: title of the music
1060          +/
1061         string title() const @property @trusted
1062         in {
1063             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1064         }
1065         do {
1066             return Mix_GetMusicTitle(this.mixMusic).to!string;
1067         }
1068 
1069         /++
1070          + Wraps `Mix_GetMusicTitleTag` (from SDL_mixer 2.6) which gets the title tag of the music
1071          +
1072          + Returns: title tag of the music
1073          +/
1074         string titleTag() const @property @trusted
1075         in {
1076             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1077         }
1078         do {
1079             return Mix_GetMusicTitleTag(this.mixMusic).to!string;
1080         }
1081 
1082         /++
1083          + Wraps `Mix_GetMusicArtist` (from SDL_mixer 2.6) which gets the artist of the music
1084          +
1085          + Returns: artist of the music
1086          +/
1087         string artistTag() const @property @trusted
1088         in {
1089             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1090         }
1091         do {
1092             return Mix_GetMusicArtistTag(this.mixMusic).to!string;
1093         }
1094 
1095         /++
1096          + Wraps `Mix_GetMusicAlbum` (from SDL_mixer 2.6) which gets the album of the music
1097          +
1098          + Returns: album of the music
1099          +/
1100         string albumTag() const @property @trusted
1101         in {
1102             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1103         }
1104         do {
1105             return Mix_GetMusicAlbumTag(this.mixMusic).to!string;
1106         }
1107 
1108         /++
1109          + Wraps `Mix_GetMusicCopyright` (from SDL_mixer 2.6) which gets the copyright of the music
1110          +
1111          + Returns: copyright of the music
1112          +/
1113         string copyrightTag() const @property @trusted
1114         in {
1115             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1116         }
1117         do {
1118             return Mix_GetMusicCopyrightTag(this.mixMusic).to!string;
1119         }
1120 
1121         /++
1122          + Wraps `Mix_GetMusicVolume` (from SDL_mixer 2.6) which gets the volume of the music
1123          +
1124          + Returns: volume ranging from `0` to `128`
1125          +/
1126         ubyte volume() const @property @trusted
1127         in {
1128             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1129         }
1130         do {
1131             return cast(ubyte) Mix_GetMusicVolume(cast(Mix_Music*) this.mixMusic);
1132         }
1133 
1134         /++
1135          + Wraps `Mix_GetMusicPosition` (from SDL_mixer 2.6) which gets the timestamp of the music in seconds
1136          +
1137          + Returns: position timestamp of the music in seconds; `-1.0` if codec doesn't support
1138          +/
1139         double position() const @property @trusted
1140         in {
1141             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1142         }
1143         do {
1144             return Mix_GetMusicPosition(cast(Mix_Music*) this.mixMusic);
1145         }
1146 
1147         /++
1148          + Wraps `Mix_MusicDuration` (from SDL_mixer 2.6) which gets the duration of the music in seconds
1149          +
1150          + Returns: duration of the music in seconds
1151          + Throws: `dsdl2.SDLException` if failed to get duration
1152          +/
1153         double duration() const @property @trusted
1154         in {
1155             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1156         }
1157         do {
1158             double duration = Mix_MusicDuration(cast(Mix_Music*) this.mixMusic);
1159             if (duration == -1.0) {
1160                 throw new SDLException;
1161             }
1162 
1163             return duration;
1164         }
1165 
1166         /++
1167          + Wraps `Mix_GetMusicLoopStartTime` (from SDL_mixer 2.6) which gets the loop start time of the music
1168          +
1169          + Returns: loop start time of the music; `-1.0` if not used or codec doesn't support
1170          +/
1171         double loopStartTime() const @property @trusted
1172         in {
1173             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1174         }
1175         do {
1176             return Mix_GetMusicLoopStartTime(cast(Mix_Music*) this.mixMusic);
1177         }
1178 
1179         /++
1180          + Wraps `Mix_GetMusicLoopEndTime` (from SDL_mixer 2.6) which gets the loop end time of the music
1181          +
1182          + Returns: loop end time of the music; `-1.0` if not used or codec doesn't support
1183          +/
1184         double loopEndTime() const @property @trusted
1185         in {
1186             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1187         }
1188         do {
1189             return Mix_GetMusicLoopEndTime(cast(Mix_Music*) this.mixMusic);
1190         }
1191 
1192         /++
1193          + Wraps `Mix_GetMusicLoopLengthTime` (from SDL_mixer 2.6) which gets the loop length time of the music
1194          +
1195          + Returns: loop length time of the music; `-1.0` if not used or codec doesn't support
1196          +/
1197         double loopLengthTime() const @property @trusted
1198         in {
1199             assert(dsdl2.mixer.getVersion() >= Version(2, 6));
1200         }
1201         do {
1202             return Mix_GetMusicLoopLengthTime(cast(Mix_Music*) this.mixMusic);
1203         }
1204     }
1205 
1206     /++
1207      + Wraps `Mix_SetMusicPosition` which sets the timestamp position of the currently playing music
1208      +
1209      + Params:
1210      +   newPosition = new timestamp position in seconds
1211      + Throws: `dsdl2.SDLException` if failed to set position
1212      +/
1213     static void position(double newPosition) @property @trusted {
1214         if (Mix_SetMusicPosition(newPosition) != 0) {
1215             throw new SDLException;
1216         }
1217     }
1218 
1219     /++
1220      + Wraps `Mix_VolumeMusic` which gets the volume for music
1221      +
1222      + Returns: volume ranging from `0` to `128`
1223      +/
1224     static ubyte volume() @property @trusted {
1225         return cast(ubyte) Mix_VolumeMusic(-1);
1226     }
1227 
1228     /++
1229      + Wraps `Mix_VolumeMusic` which sets the volume for music
1230      +
1231      + Params:
1232      +   newVolume = new volume ranging from `0` to `128`
1233      +/
1234     static void volume(ubyte newVolume) @property @trusted {
1235         Mix_VolumeMusic(newVolume);
1236     }
1237 
1238     /++
1239      + Wraps `Mix_PausedMusic` which checks whether music is paused
1240      +
1241      + Returns: `true` if music is paused, otherwise `false`
1242      +/
1243     static bool paused() @trusted {
1244         return Mix_PausedMusic() == 1;
1245     }
1246 
1247     /++
1248      + Wraps `Mix_PlayingMusic` which checks whether music is playing
1249      +
1250      + Returns: `true` if music is playing, otherwise `false`
1251      +/
1252     static bool playing() @trusted {
1253         return Mix_PlayingMusic() == 1;
1254     }
1255 
1256     /++
1257      + Wraps `Mix_FadingMusic` which gets the fading stage of the music
1258      +
1259      + Returns: `dsdl2.mixer.Fading` enumeration indicating the music's fading stage
1260      +/
1261     static Fading fading() @property @trusted {
1262         return cast(Fading) Mix_FadingMusic();
1263     }
1264 
1265     /++
1266      + Wraps `Mix_SetMusicCMD` which sets a command to be called when a new music is played
1267      +
1268      + Params:
1269      +   newCommand = trigger command
1270      + Throws: `dsdl2.SDLException` if failed to set command
1271      +/
1272     static void command(string newCommand) @property @trusted {
1273         if (Mix_SetMusicCMD(newCommand.toStringz()) != 0) {
1274             throw new SDLException;
1275         }
1276     }
1277 
1278     /++
1279      + Wraps `Mix_PlayMusic` which plays the `dsdl2.mixer.Music`
1280      +
1281      + Params:
1282      +   loops = how many times the music should be played (`cast(uint) -1` for infinity)
1283      + Throws: `dsdl2.SDLException` if failed to play the music
1284      +/
1285     void play(uint loops = 1) const @trusted {
1286         if (Mix_PlayMusic(cast(Mix_Music*) this.mixMusic, loops.to!int) != 0) {
1287             throw new SDLException;
1288         }
1289     }
1290 
1291     /++
1292      + Wraps `Mix_FadeInMusicPos` which plays the `dsdl2.mixer.Music` with a fade-in effect
1293      +
1294      + Params:
1295      +   loops = how many times the chunk should be played (`cast(uint) -1` for infinity)
1296      +   fadeMs = number of milliseconds the chunk fades in before fully playing at full volume
1297      +   position = start timestamp of the music in seconds
1298      + Throws: `dsdl2.SDLException` if failed to play the music
1299      +/
1300     void fadeIn(uint loops = 1, uint fadeMs = 0, double position = 0) const @trusted {
1301         if (Mix_FadeInMusicPos(cast(Mix_Music*) this.mixMusic, loops, fadeMs, position) != 0) {
1302             throw new SDLException;
1303         }
1304     }
1305 
1306     /++
1307      + Wraps `Mix_FadeOutMusic` which performs fade-out for the current music playing
1308      +
1309      + Params:
1310      +   fadeMs = number of milliseconds to fade-out before fully halting
1311      +/
1312     static void fadeOut(uint fadeMs) @trusted {
1313         Mix_FadeOutMusic(fadeMs.to!int);
1314     }
1315 
1316     /++
1317      + Wraps `Mix_HaltMusic` which halts the playing music
1318      +/
1319     static void halt() @trusted {
1320         Mix_HaltMusic();
1321     }
1322 
1323     /++
1324      + Wraps `Mix_PauseMusic` which pauses music playback
1325      +/
1326     static void pause() @trusted {
1327         Mix_PauseMusic();
1328     }
1329 
1330     /++
1331      + Wraps `Mix_ResumeMusic` which resumes music playback
1332      +/
1333     static void resume() @trusted {
1334         Mix_ResumeMusic();
1335     }
1336 
1337     /++
1338      + Wraps `Mix_RewindMusic` which rewinds music playback
1339      +/
1340     static void rewind() @trusted {
1341         Mix_RewindMusic();
1342     }
1343 }
1344 
1345 /++
1346  + Wraps `Mix_LoadMUS` which loads an audio file from the filesystem to a `dsdl2.mixer.Music`
1347  +
1348  + Params:
1349  +   file = path to the audio file
1350  + Returns: loaded `dsdl2.mixer.Music`
1351  + Throws: `dsdl2.SDLException` if unable to load
1352  +/
1353 Music loadMusic(string file) @trusted {
1354     Mix_Music* mixMusic = Mix_LoadMUS(file.toStringz());
1355     if (mixMusic !is null) {
1356         return new Music(mixMusic);
1357     }
1358     else {
1359         throw new SDLException;
1360     }
1361 }
1362 
1363 /++
1364  + Wraps `Mix_LoadMUS_RW` which loads an audio file from a buffer to a `dsdl2.mixer.Music`
1365  +
1366  + Params:
1367  +   data = buffer of the audio file
1368  + Returns: loaded `dsdl2.mixer.Music`
1369  + Throws: `dsdl2.SDLException` if unable to load
1370  +/
1371 Music loadMusicRaw(const void[] data) @trusted {
1372     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
1373     if (sdlRWops is null) {
1374         throw new SDLException;
1375     }
1376 
1377     Mix_Music* mixMusic = Mix_LoadMUS_RW(sdlRWops, 1);
1378     if (mixMusic !is null) {
1379         return new Music(mixMusic);
1380     }
1381     else {
1382         throw new SDLException;
1383     }
1384 }
1385 
1386 /++
1387  + Wraps `Mix_LoadMUSType_RW` which loads a typed audio file from a buffer to a `dsdl2.mixer.Music`
1388  +
1389  + Params:
1390  +   data = buffer of the audio file
1391  +   type = specified `dsdl2.mixer.MusicType` enumeration of the music
1392  + Returns: loaded `dsdl2.mixer.Music`
1393  + Throws: `dsdl2.SDLException` if unable to load
1394  +/
1395 Music loadMusicRaw(const void[] data, MusicType type) @trusted {
1396     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
1397     if (sdlRWops is null) {
1398         throw new SDLException;
1399     }
1400 
1401     Mix_Music* mixMusic = Mix_LoadMUSType_RW(sdlRWops, type, 1);
1402     if (mixMusic !is null) {
1403         return new Music(mixMusic);
1404     }
1405     else {
1406         throw new SDLException;
1407     }
1408 }