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 }