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 }