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.audio;
8 @safe:
9 
10 import bindbc.sdl;
11 import dsdl2.sdl;
12 
13 import core.memory : GC;
14 import std.conv : to;
15 import std.format : format;
16 import std.string : toStringz;
17 
18 /++
19  + D enum that wraps `SDL_AudioFormat` defining scalar type per audio sample
20  +/
21 enum AudioFormat {
22     /++
23      + Wraps `AUDIO_*` enumeration constants
24      +/
25     u8 = AUDIO_U8,
26     s8 = AUDIO_S8, /// ditto
27     u16LSB = AUDIO_U16LSB, /// ditto
28     s16LSB = AUDIO_S16LSB, /// ditto
29     u16MSB = AUDIO_U16MSB, /// ditto
30     s16MSB = AUDIO_S16MSB, /// ditto
31     u16 = AUDIO_U16, /// ditto
32     s16 = AUDIO_S16, /// ditto
33     s32LSB = AUDIO_S32LSB, /// ditto
34     s32MSB = AUDIO_S32MSB, /// ditto
35     s32 = AUDIO_S32, /// ditto
36     f32LSB = AUDIO_F32LSB, /// ditto
37     f32MSB = AUDIO_F32MSB, /// ditto
38     f32 = AUDIO_F32, /// ditto
39 
40     u16Sys = AUDIO_U16SYS, /// ditto
41     s16Sys = AUDIO_S16SYS, /// ditto
42     s32Sys = AUDIO_S32SYS, /// ditto
43     f32Sys = AUDIO_F32SYS /// ditto
44 }
45 
46 /++
47  + D enum that wraps `SDL_AUDIO_*` status enumerations
48  +/
49 enum AudioStatus {
50     /++
51      + Wraps `SDL_AUDIO_*` enumeration constants
52      +/
53     stopped = SDL_AUDIO_STOPPED,
54     playing = SDL_AUDIO_PLAYING, /// ditto
55     paused = SDL_AUDIO_PAUSED /// ditto
56 }
57 
58 enum maxVolume = cast(ubyte) SDL_MIX_MAXVOLUME; /// Alias to `SDL_MIX_MAXVOLUME`
59 
60 /++
61  + Wraps `SDL_AudioInit` which initializes the audio subsystem while specifying the audio driver used
62  +
63  + Params:
64  +   driverName = the name of the audio driver
65  + Throws: `dsdl2.SDLException` if the audio driver could not be initialized
66  +/
67 void initAudio(string driverName) @trusted {
68     if (SDL_AudioInit(driverName.toStringz()) != 0) {
69         throw new SDLException;
70     }
71 }
72 
73 /++
74  + Wraps `SDL_AudioQuit` which quits the audio subsystem
75  +/
76 void quitAudio() @trusted {
77     SDL_AudioQuit();
78 }
79 
80 /++
81  + Wraps `SDL_GetNumAudioDrivers` and `SDL_GetAudioDriver` which return a list of available audio drivers
82  +
83  + Returns: names of the available audio drivers
84  + Throws: `dsdl2.SDLException` if failed to get the available audio drivers
85  +/
86 const(string[]) getAudioDrivers() @trusted {
87     int numDrivers = SDL_GetNumAudioDrivers();
88     if (numDrivers <= 0) {
89         throw new SDLException;
90     }
91 
92     static string[] drivers;
93     if (drivers !is null) {
94         size_t originalLength = drivers.length;
95         drivers.length = numDrivers;
96 
97         if (numDrivers > originalLength) {
98             foreach (i; originalLength .. numDrivers) {
99                 drivers[i] = SDL_GetAudioDriver(i.to!int).to!string;
100             }
101         }
102     }
103     else {
104         drivers = new string[](numDrivers);
105 
106         foreach (i; 0 .. numDrivers) {
107             drivers[i] = SDL_GetAudioDriver(i).to!string;
108         }
109     }
110 
111     return drivers;
112 }
113 
114 /++
115  + Wraps `SDL_GetCurrentAudioDriver` which returns the current audio driver
116  +
117  + Returns: name of the current audio driver
118  +/
119 string getCurrentAudioDriver() @trusted {
120     return SDL_GetCurrentAudioDriver().to!string;
121 }
122 
123 // TODO
124 struct AudioSpec {
125     SDL_AudioSpec sdlAudioSpec; /// Internal `SDL_AudioSpec` struct
126 }
127 
128 // TODO
129 final class AudioDevice {
130     const SDL_AudioDeviceID sdlAudioDeviceID; /// Internal `SDL_AudioDeviceID`
131 }
132 
133 private const(string[]) getAudioDeviceNamesRaw(int isCapture)() @trusted {
134     int numDrivers = SDL_GetNumAudioDevices(isCapture);
135     if (numDrivers <= 0) {
136         throw new SDLException;
137     }
138 
139     static string[] drivers;
140     if (drivers !is null) {
141         size_t originalLength = drivers.length;
142         drivers.length = numDrivers;
143 
144         if (numDrivers > originalLength) {
145             foreach (i; originalLength .. numDrivers) {
146                 drivers[i] = SDL_GetAudioDeviceName(i.to!int, isCapture).to!string;
147             }
148         }
149     }
150     else {
151         drivers = new string[](numDrivers);
152 
153         foreach (i; 0 .. numDrivers) {
154             drivers[i] = SDL_GetAudioDeviceName(i, isCapture).to!string;
155         }
156     }
157 
158     return drivers;
159 }
160 
161 /++
162  + Acts as `SDL_GetNumAudioDevices(0)` and `SDL_GetAudioDeviceName(..., 0)` which return a name list of available
163  + non-capturing audio devices
164  +
165  + Returns: names of the available non-capturing audio devices
166  + Throws: `dsdl2.SDLException` if failed to get the available non-capturing audio devices
167  +/
168 const(string[]) getAudioDeviceNames() @trusted {
169     return getAudioDeviceNamesRaw!0;
170 }
171 
172 /++
173  + Acts as `SDL_GetNumAudioDevices(1)` and `SDL_GetAudioDeviceName(..., 1)` which return a name list of available
174  + capturing audio devices
175  +
176  + Returns: names of the available capturing audio devices
177  + Throws: `dsdl2.SDLException` if failed to get the available capturing audio devices
178  +/
179 const(string[]) getCapturingAudioDeviceNames() @trusted {
180     return getAudioDeviceNamesRaw!1;
181 }
182 
183 /++
184  + Wraps `SDL_OpenAudio` which opens the default audio device
185  +
186  + Params:
187  +   desired = desired `dsdl2.AudioSpec`ifications
188  + Returns: obtained `dsdl2.AudioSpec`ifications
189  +/
190 AudioSpec openAudio(AudioSpec desired) @trusted {
191     // TODO: implement handling for `AudioSpec`
192     assert(false, "Not implemented");
193 }
194 
195 /++
196  + Wraps `SDL_CloseAudio` which closes the default audio device
197  +/
198 void closeAudio() @trusted {
199     SDL_CloseAudio();
200 }
201 
202 /++
203  + Wraps `SDL_GetAudioStatus` which returns the current status of the default audio device
204  +
205  + Returns: `dsdl2.AudioStatus` of the default audio device
206  +/
207 AudioStatus getAudioStatus() @trusted {
208     return cast(AudioStatus) SDL_GetAudioStatus();
209 }
210 
211 /++
212  + Wraps `SDL_PauseAudio` which pauses the default audio device
213  +
214  + Params:
215  +   paused = `true` to pause by default; `false` to resume
216  +/
217 void pauseAudio(bool paused = true) @trusted {
218     SDL_PauseAudio(paused ? 1 : 0);
219 }
220 
221 /++
222  + Acts as `SDL_PauseAudio(0)` which resumes the default audio device
223  +/
224 void resumeAudio() @trusted {
225     SDL_PauseAudio(0);
226 }
227 
228 /++
229  + Wraps `SDL_LockAudio` which locks the default audio device
230  +/
231 void lockAudio() @trusted {
232     SDL_LockAudio();
233 }
234 
235 /++
236  + Wraps `SDL_UnlockAudio` which unlocks the default audio device
237  +/
238 void unlockAudio() @trusted {
239     SDL_UnlockAudio();
240 }