1 module gfm.sdl2.sdlmixer; 2 3 import std.datetime; 4 import std..string; 5 6 import bindbc.sdl, 7 bindbc.sdl.mixer; 8 9 import std.experimental.logger; 10 11 import gfm.sdl2.sdl; 12 13 /// SDL_mixer library wrapper. 14 final class SDLMixer 15 { 16 public 17 { 18 /// Loads the SDL_mixer library and opens audio. 19 /// Throws: $(D SDL2Exception) on error. 20 /// See_also: $(LINK https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer.html#SEC11) 21 this(SDL2 sdl2, 22 int flags = MIX_INIT_FLAC | MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG, 23 int frequency = MIX_DEFAULT_FREQUENCY, 24 ushort format = MIX_DEFAULT_FORMAT, 25 int channels = MIX_DEFAULT_CHANNELS, 26 int chunksize = 1024) 27 { 28 _sdl2 = sdl2; 29 _logger = sdl2._logger; 30 _SDLMixerInitialized = false; 31 32 const ret = loadSDLMixer(); 33 if(ret < sdlMixerSupport) 34 { 35 if(ret == SDLMixerSupport.noLibrary) 36 throwSDL2MixerException("SDL Mixer shared library failed to load"); 37 else if(SDLMixerSupport.badLibrary) 38 // One or more symbols failed to load. The likely cause is that the 39 // shared library is for a lower version than bindbc-sdl was configured 40 // to load (via SDL_201, SDL_202, etc.) 41 throwSDL2MixerException("One or more symbols of SDL Mixer shared library failed to load"); 42 throwSDL2MixerException("The version of the SDL Mixer library on your system is too low. Please upgrade."); 43 } 44 45 if(Mix_Init(flags) != flags) 46 { 47 throwSDL2MixerException("Mix_Init"); 48 } 49 50 if(Mix_OpenAudio(frequency, format, channels, chunksize) != 0) 51 { 52 throwSDL2MixerException("Mix_OpenAudio"); 53 } 54 55 _SDLMixerInitialized = true; 56 } 57 58 /// Releases the SDL_mixer library. 59 ~this() 60 { 61 if(!_SDLMixerInitialized) 62 { 63 debug ensureNotInGC("SDLMixer"); 64 _SDLMixerInitialized = false; 65 Mix_CloseAudio(); 66 Mix_Quit(); 67 } 68 } 69 70 /// Returns: The number of mixing channels currently allocated. 71 int getChannels() 72 { 73 return Mix_AllocateChannels(-1); 74 } 75 76 /// Sets the number of mixing channels. 77 void setChannels(int numChannels) 78 { 79 Mix_AllocateChannels(numChannels); 80 } 81 82 /// Pauses the channel. Passing -1 pauses all channels. 83 void pause(int channel) 84 { 85 Mix_Pause(channel); 86 } 87 88 /// Unpauses the channel. Passing -1 unpauses all channels. 89 void unpause(int channel) 90 { 91 Mix_Resume(channel); 92 } 93 94 /// Returns: Whether the channel is paused. 95 bool isPaused(int channel) 96 { 97 return Mix_Paused(channel) != 0; 98 } 99 100 /// Clears the channel and pauses it. 101 /// Params: 102 /// channel = channel to halt. -1 halts all channels. 103 /// delay = time after which to perform the halt. 104 void halt(int channel, Duration delay = 0.msecs) 105 { 106 Mix_ExpireChannel(channel, cast(int)delay.total!"msecs"); 107 } 108 109 /// Fades out the channel and then halts it. 110 /// Params: 111 /// channel = channel to halt. -1 fades all channels. 112 /// time = time over which the channel is faded out. 113 void fade(int channel, Duration time) 114 { 115 Mix_FadeOutChannel(channel, cast(int)time.total!"msecs"); 116 } 117 118 /// Returns: Fading status of the channel. 119 Mix_Fading getFading(int channel) 120 { 121 return Mix_FadingChannel(channel); 122 } 123 124 /// Returns: Whether the channel is currently playing. 125 bool isPlaying(int channel) 126 { 127 return Mix_Playing(channel) != 0; 128 } 129 130 /// Returns: The volume of the channel. 131 int getVolume(int channel) 132 { 133 return Mix_Volume(channel, -1); 134 } 135 136 /// Sets the volume of the channel. Passing -1 sets volume of all channels. 137 void setVolume(int channel, int volume) 138 { 139 Mix_Volume(channel, volume); 140 } 141 142 /// Sets stereo panning on the channel. The library must have been opened with two output channels. 143 /// See_also: $(LINK https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer.html#SEC80) 144 void setPanning(int channel, ubyte volumeLeft, ubyte volumeRight) 145 { 146 Mix_SetPanning(channel, volumeLeft, volumeRight); 147 } 148 149 /// Sets distance from the listener on the channel, used to simulate attenuation. 150 /// See_also: $(LINK https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer.html#SEC81) 151 void setDistance(int channel, ubyte distance) 152 { 153 Mix_SetDistance(channel, distance); 154 } 155 156 /// Set panning and distance on the channel to simulate positional audio. 157 /// See_also: $(LINK https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer.html#SEC82) 158 void setPosition(int channel, short angle, ubyte distance) 159 { 160 Mix_SetPosition(channel, angle, distance); 161 } 162 163 /// Sets whether reverse stereo is enabled on the channel. 164 void setReverseStereo(int channel, bool reverse) 165 { 166 Mix_SetReverseStereo(channel, reverse); 167 } 168 169 /// Clears all effects from the channel. 170 void clearEffects(int channel) 171 { 172 Mix_UnregisterAllEffects(channel); 173 } 174 } 175 176 private 177 { 178 SDL2 _sdl2; 179 Logger _logger; 180 bool _SDLMixerInitialized; 181 182 void throwSDL2MixerException(string callThatFailed) 183 { 184 string message = format("%s failed: %s", callThatFailed, getErrorString()); 185 throw new SDL2Exception(message); 186 } 187 188 const(char)[] getErrorString() 189 { 190 return fromStringz(Mix_GetError()); 191 } 192 } 193 } 194 195 /// SDL_mixer audio chunk wrapper. 196 final class SDLSample 197 { 198 public 199 { 200 /// Loads a sample from a file. 201 /// Params: 202 /// sdlmixer = library object. 203 /// filename = path to the audio sample. 204 /// Throws: $(D SDL2Exception) on error. 205 this(SDLMixer sdlmixer, string filename) 206 { 207 _sdlmixer = sdlmixer; 208 _chunk = Mix_LoadWAV(toStringz(filename)); 209 if(_chunk is null) 210 _sdlmixer.throwSDL2MixerException("Mix_LoadWAV"); 211 } 212 213 /// Releases the SDL resource. 214 ~this() 215 { 216 if(_chunk !is null) 217 { 218 debug ensureNotInGC("SDLSample"); 219 Mix_FreeChunk(_chunk); 220 _chunk = null; 221 } 222 } 223 224 /// Returns: SDL handle. 225 Mix_Chunk* handle() 226 { 227 return _chunk; 228 } 229 230 /// Plays this sample. 231 /// Params: 232 /// channel = channel to play on. -1 plays on first inactive channel. 233 /// loops = number of times to loop this sample. 234 /// fadeInTime = time over which this sample is faded in. 235 /// Returns: channel the sample is now playing on. 236 int play(int channel, int loops = 0, Duration fadeInTime = 0.seconds) 237 { 238 return Mix_FadeInChannel(channel, _chunk, loops, cast(int)fadeInTime.total!"msecs"); 239 } 240 241 /// Plays this sample only within a certain time limit. 242 /// Params: 243 /// channel = channel to play on. -1 plays on first inactive channel. 244 /// timeLimit = time after which the sample stops playing. 245 /// loops = number of times to loop this sample. 246 /// fadeInTime = time over which this sample is faded in. 247 /// Returns: channel the sample is now playing on. 248 int playTimed(int channel, Duration timeLimit, int loops = 0, Duration fadeInTime = 0.seconds) 249 { 250 return Mix_FadeInChannelTimed(channel, _chunk, loops, cast(int)fadeInTime.total!"msecs", cast(int)timeLimit.total!"msecs"); 251 } 252 253 /// Returns: The volume this sample plays at. 254 int getVolume() 255 { 256 return Mix_VolumeChunk(_chunk, -1); 257 } 258 259 /// Sets the volume this sample plays at. 260 void setVolume(int volume) 261 { 262 Mix_VolumeChunk(_chunk, volume); 263 } 264 } 265 266 private 267 { 268 SDLMixer _sdlmixer; 269 Mix_Chunk* _chunk; 270 } 271 }