1 /*
2  *  zlib License
3  *  
4  *  (C) 2016 jython234
5  *  
6  *  This software is provided 'as-is', without any express or implied
7  *  warranty.  In no event will the authors be held liable for any damages
8  *  arising from the use of this software.
9  *  
10  *  Permission is granted to anyone to use this software for any purpose,
11  *  including commercial applications, and to alter it and redistribute it
12  *  freely, subject to the following restrictions:
13  *  
14  *  1. The origin of this software must not be misrepresented; you must not
15  *     claim that you wrote the original software. If you use this software
16  *     in a product, an acknowledgment in the product documentation would be
17  *     appreciated but is not required.
18  *  2. Altered source versions must be plainly marked as such, and must not be
19  *     misrepresented as being the original software.
20  *  3. This notice may not be removed or altered from any source distribution.
21 */
22 module blocksound.backend.openal;
23 
24 version(blocksound_ALBackend) {
25 
26     pragma(msg, "-----Using OpenAL backend-----");
27 
28     import blocksound.core;
29     import blocksound.backend.types;
30 
31     import derelict.openal.al;
32     import derelict.sndfile.sndfile;
33 
34     /// Class to manage the OpenAL Audio backend.
35     class ALAudioBackend : AudioBackend {
36         protected ALCdevice* device;
37         protected ALCcontext* context;
38 
39         /// Create a new ALBackend. One per thread.
40         this() @trusted {
41             debug(blocksound_verbose) {
42                 import std.stdio : writeln;
43                 writeln("[BlockSound]: Initializing OpenAL backend...");
44             }
45 
46             device = alcOpenDevice(null); // Open default device.
47             context = alcCreateContext(device, null);
48 
49             alcMakeContextCurrent(context);
50 
51             debug(blocksound_verbose) {
52                 import std.stdio : writeln;
53                 writeln("[BlockSound]: OpenAL Backend initialized.");
54                 writeln("[BlockSound]: AL_VERSION: ", toDString(alGetString(AL_VERSION)), ", AL_VENDOR: ", toDString(alGetString(AL_VENDOR)));
55             }
56         }
57 
58         override {
59             void setListenerLocation(in Vec3 loc) @trusted nothrow {
60                 alListener3f(AL_POSITION, loc.x, loc.y, loc.z);
61             }
62 
63             void setListenerGain(in float gain) @trusted nothrow {
64                 alListenerf(AL_GAIN, gain);
65             }
66 
67             void cleanup() @trusted nothrow {
68                 alcCloseDevice(device);
69             }
70         }
71     }
72 
73     /// OpenAL Source backend
74     class ALSource : Source {
75         private ALuint source;
76 
77         package this() {
78             alGenSources(1, &source);
79         }
80 
81         override {
82             protected void _setSound(Sound sound) @trusted {
83                 if(auto s = cast(ALSound) sound) {
84                     alSourcei(source, AL_BUFFER, s.buffer);
85                 } else {
86                     throw new Exception("Invalid Sound: not instance of ALSound");
87                 }
88             }
89 
90             void setLooping(in bool loop) @trusted {
91                 alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
92             }
93  
94             void play() @trusted nothrow {
95                 alSourcePlay(source);
96             }
97 
98             void pause() @trusted nothrow {
99                 alSourcePause(source);
100             }
101 
102             void stop() @trusted nothrow {
103                 alSourceStop(source);
104             }
105 
106             bool hasFinishedPlaying() @trusted nothrow {
107                 ALenum state;
108                 alGetSourcei(source, AL_SOURCE_STATE, &state);
109                 return state != AL_PLAYING;
110             }
111 
112             protected void _cleanup() @system nothrow {
113                 alDeleteSources(1, &source);
114             }
115         }
116     }
117 
118     /// OpenAL Sound backend
119     class ALSound : Sound {
120         private ALuint _buffer;
121 
122         @property ALuint buffer() @safe nothrow { return _buffer; }
123 
124         protected this(ALuint buffer) @safe nothrow {
125             _buffer = buffer;
126         }
127 
128         static ALSound loadSound(in string filename) @trusted {
129             return new ALSound(loadSoundToBuffer(filename));
130         }
131 
132         override void cleanup() @trusted nothrow {
133             alDeleteBuffers(1, &_buffer);
134         }
135     }
136 
137     /++
138         Loads a sound from a file into an OpenAL buffer.
139         Uses libsndfile for file reading.
140 
141         Params:
142                 filename =  The filename where the sound is located.
143         
144         Throws: Exception if file is not found, or engine is not initialized.
145         Returns: An OpenAL buffer containing the sound.
146     +/
147     ALuint loadSoundToBuffer(in string filename) @system {
148         import std.exception : enforce;
149         import std.file : exists;
150 
151         enforce(INIT, new Exception("BlockSound has not been initialized!"));
152         enforce(exists(filename), new Exception("File \"" ~ filename ~ "\" does not exist!"));
153 
154         SF_INFO info;
155         SNDFILE* file = sf_open(toCString(filename), SFM_READ, &info);
156 
157         // Fix for OGG pops and crackles.
158         sf_command(file, SFC_SET_SCALE_FLOAT_INT_READ, cast(void*) 1, cast(int) byte.sizeof);
159 
160         short[] data;
161         short[] readBuf = new short[4096];
162 
163         long readSize = 0;
164         while((readSize = sf_read_short(file, readBuf.ptr, readBuf.length)) != 0) {
165             data ~= readBuf[0..(cast(size_t) readSize)];
166         }
167 
168         ALuint buffer;
169         alGenBuffers(1, &buffer);
170         alBufferData(buffer, info.channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data.ptr, cast(int) (data.length * short.sizeof), info.samplerate);
171 
172         sf_close(file);
173 
174         return buffer;
175     }
176 
177     /++
178         Loads libraries required by the OpenAL backend.
179         This is called automatically by blocksound's init
180         function.
181 
182         Params:
183                 skipALload =    Skips loading OpenAL from derelict.
184                                 Set this to true if your application loads
185                                 OpenAL itself before blocksound does.
186 
187                 skipSFLoad =    Skips loading libsndfile from derelict.
188                                 Set this to true if your application loads
189                                 libsdnfile itself before blocksound does.
190     +/
191     void loadLibraries(bool skipALload = false, bool skipSFload = false) @system {
192         if(!skipALload) {
193             version(Windows) {
194                 try {
195                     DerelictAL.load(); // Search for system libraries first.
196                     debug(blocksound_verbose) notifyLoadLib("OpenAL");
197                 } catch(Exception e) {
198                     DerelictAL.load("lib\\openal32.dll"); // Try to use provided library.
199                     debug(blocksound_verbose) notifyLoadLib("OpenAL");
200                 }
201             } else {
202                 DerelictAL.load();
203                 debug(blocksound_verbose) notifyLoadLib("OpenAL");
204             }
205         }
206 
207         if(!skipSFload) {
208             version(Windows) {
209                 try {
210                     DerelictSndFile.load(); // Search for system libraries first.
211                     debug(blocksound_verbose) notifyLoadLib("libsndfile");
212                 } catch(Exception e) {
213                     DerelictSndFile.load("lib\\libsndfile-1.dll"); // Try to use provided library.
214                     debug(blocksound_verbose) notifyLoadLib("libsndfile");
215                 }
216             } else {
217                 DerelictSndFile.load();
218                 debug(blocksound_verbose) notifyLoadLib("libsndfile");
219             }
220         }
221     }
222 }