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