1 /++
2 Module for loading and managing sound (in particular, playback).
3 
4 Macros:
5     LREF = <a href="#$1">$1</a>
6     HREF = <a href="$1">$2</a>
7     PHOBREF = <a href="https://dlang.org/phobos/$1.html#$2">$2</a>
8 
9 Authors: $(HREF https://github.com/TodNaz,TodNaz)
10 Copyright: Copyright (c) 2020 - 2021, TodNaz.
11 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT)
12 +/
13 module tida.sound;
14 
15 import bindbc.openal;
16 import std.exception : enforce;
17 
18 /++
19 Function to load a library of sound and music playback.
20 
21 Throws:
22 $(PHOBREF object,Exception) if the library was not found on the system.
23 +/
24 void initSoundlibrary() @trusted
25 {
26     enforce!Exception(loadOpenAL() != ALSupport.noLibrary,
27     "Library \"OpenAL\" was not found.");
28 }
29 
30 /++
31 Audio playback context. Some global parameters of sound and music are set from it.
32 +/
33 class Device
34 {
35 private:
36     ALCdevice* _device;
37     ALCcontext* _context;
38 
39 public @trusted:
40     /// Device object.
41     @property ALCdevice* device()
42     {
43         return _device;
44     }
45 
46     /// Context object.
47     @property ALCcontext* context()
48     {
49         return _context;
50     }
51 
52     /++
53     Opens and prepares the device for work.
54 
55     Throws:
56     $(PHOBREF object,Exception) If the device was not initialized and
57     an error occurred.
58     +/
59     void open()
60     {
61         _device = alcOpenDevice(null);
62         enforce!Exception(_device, "Device is not a open!");
63 
64         _context = alcCreateContext(_device, null);
65         enforce!Exception(alcMakeContextCurrent(_context), "Context is not a create!");
66 
67         ALfloat[] listenerOri = [ 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f ];
68 
69         alListener3f(AL_POSITION, 0, 0, 0);
70         alListener3f(AL_VELOCITY, 0, 0, 0);
71         alListenerfv(AL_ORIENTATION, cast(float*) listenerOri);
72     }
73 
74     /++
75     Closes the device.
76     (Make sure that all sounds have been cleared from memory before this).
77     +/
78     void close()
79     {
80         alcMakeContextCurrent(null);
81         alcDestroyContext(context);
82         alcCloseDevice(device);
83     }
84 }
85 
86 /++
87 Sound module and also music (if you set the repeat parameter).
88 +/
89 class Sound
90 {
91     import std.datetime;
92     import tida.vector;
93 
94 private:
95     uint _source;
96     uint _buffer;
97     ubyte[] _bufferData;
98     Vecf _position;
99 
100     bool _isPlay;
101 
102 public @trusted:
103     /++
104     Buffer identificator.
105     +/
106     @property uint bufferID()
107     {
108         return _buffer;
109     }
110 
111     /++
112     Buffer identificator.
113     +/
114     @property void bufferID(uint value)
115     {
116         _buffer = value;
117     }
118 
119     /// Allocates space for the source and buffer.
120     this()
121     {
122         allocateBuffer();
123         allocateSource();
124     }
125 
126     /// Allocates space for the buffer.
127     void allocateBuffer()
128     {
129         alGenBuffers(1, &_buffer);
130     }
131 
132     /// Allocates space for the source.
133     void allocateSource()
134     {
135         alGenSources(1, &_source);
136 
137         alSourcef(_source, AL_PITCH, 1.0f);
138         alSourcef(_source, AL_GAIN, 1.0f);
139         alSource3f(_source, AL_POSITION, 0, 0, 0);
140         alSource3f(_source, AL_VELOCITY, 0, 0, 0);
141         alSourcei(_source, AL_LOOPING, AL_FALSE);
142     }
143 
144     /++
145     Inserts music data into the buffer for further playback.
146 
147     Params:
148         wave = Sound data.
149     +/
150     void bind(Wav wave)
151     {
152         const format = wave.numChannels > 1 ?
153             (wave.bitsPerSample == 8 ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16) :
154             (wave.bitsPerSample == 8 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16);
155 
156         _bufferData = wave.data;
157 
158         alBufferData(_buffer, format, cast(void*) _bufferData,
159         cast(int) _bufferData.length, wave.sampleRate);
160     }
161 
162     /++
163     The source enters a buffer of sound or music for further playback.
164     +/
165     void inSourceBindBuffer()
166     {
167         alSourcei(_source, AL_BUFFER, _buffer);
168     }
169 
170     /++
171     Load sound from file.
172 
173     Params:
174         path = The path to the sound file.
175     +/
176     void load(string path)
177     {
178         import std.path;
179 
180         switch (path.extension)
181         {
182             case ".wav":
183                 Wav wave = new Wav();
184                 wave.load(path);
185                 bind(wave);
186                 inSourceBindBuffer();
187             break;
188 
189             case ".mp3":
190                 Wav wave = new MP3();
191                 wave.load(path);
192                 bind(wave);
193                 inSourceBindBuffer();
194             break;
195 
196             default:
197                 return;
198         }
199     }
200 
201     /++
202     Sound source volume control.
203 
204     Params:
205         volume = Volume source [0 .. 100].
206     +/
207     @property void volume(uint volume)
208     in(volume <= 100)
209     do
210     {
211         alSourcef(_source, AL_GAIN, cast(float) volume / 100);
212     }
213 
214     /++
215     Do I need to repeat the sample.
216     +/
217     @property void loop(bool value)
218     {
219         alSourcei(_source, AL_LOOPING, value ? AL_TRUE : AL_FALSE);
220     }
221 
222     /++
223     Starts playing the sound
224     (if the sound is already played, then the sound will start acting back).
225     +/
226     void play()
227     {
228         alSourcePlay(_source);
229     }
230 
231     /++
232     Stops playing sound or music.
233     +/
234     void stop()
235     {
236         alSourceStop(_source);
237     }
238 
239     /++
240     Pauses playback sound, keeping its position.
241     +/
242     void pause()
243     {
244         alSourcePause(_source);
245     }
246 
247     /++
248     Resume playback sound. (Alias $(LREF play))
249     +/
250     void resume()
251     {
252         alSourcePlay(_source);
253     }
254 
255     /++
256     Estimated sound time (duration) in seconds.
257     +/
258     @property Duration duration()
259     {
260         int size;
261         int chn;
262         int bits;
263         int fq;
264 
265         alGetBufferi(_buffer,AL_SIZE,&size);
266         alGetBufferi(_buffer,AL_CHANNELS,&chn);
267         alGetBufferi(_buffer,AL_BITS,&bits);
268         alGetBufferi(_buffer,AL_FREQUENCY,&fq);
269 
270         return dur!"seconds"(
271             (size * 8 / (chn * bits)) / fq
272         );
273     }
274 
275     /++
276     The current position of the music in seconds.
277     +/
278     @property Duration currentDuration()
279     {
280         int chn;
281         int bits;
282         int fq;
283         float currSample = 0.0f;
284 
285         alGetBufferi(_buffer, AL_CHANNELS, &chn);
286         alGetBufferi(_buffer, AL_BITS, &bits);
287         alGetBufferi(_buffer, AL_FREQUENCY, &fq);
288 
289         alGetSourcef(_source, AL_SAMPLE_OFFSET, &currSample);
290 
291         return dur!"seconds"(
292             ((cast(int) currSample) * 32 / (chn * bits)) / fq
293         );
294     }
295 
296     /// ditto
297     @property void currentDuration(Duration duration) @disable
298     {
299         int chn;
300         int bits;
301         int fq;
302 
303         alGetBufferi(_buffer, AL_CHANNELS, &chn);
304         alGetBufferi(_buffer, AL_BITS, &bits);
305         alGetBufferi(_buffer, AL_FREQUENCY, &fq);
306 
307         float offset = (duration.total!"seconds") / ((32 / chn * bits) / fq);
308         alSourcef(_source, AL_SAMPLE_OFFSET, offset);
309     }
310 
311     /// Sound distance effect (range 0 .. 1)
312     @property void distance(float value)
313     {
314         alSourcef(_source, AL_GAIN, value);
315     }
316 
317     /// Sound speed (0.25 .. 2)
318     @property void pitch(float value)
319     {
320         alSourcef(_source, AL_PITCH, value);
321     }
322 
323     /++
324     Shows if music is currently playing.
325     +/
326     bool isPlay()
327     {
328         int state;
329         alGetSourcei(_source,AL_SOURCE_STATE,&state);
330 
331         return state == AL_PLAYING;
332     }
333 
334     /++
335     shows if music is currently paused.
336     +/
337     bool isPaused()
338     {
339         int state;
340         alGetSourcei(_source,AL_SOURCE_STATE,&state);
341 
342         return state == AL_PAUSED;
343     }
344 
345     /++
346     Based on sound, reproduces a different sound using the same buffer,
347     but with different sources. This makes it possible to play the same
348     sound without stopping each other.
349     +/
350     Sound copySource()
351     {
352         Sound sound = new Sound();
353         sound.bufferID = bufferID;
354         sound.inSourceBindBuffer();
355 
356         return sound;
357     }
358 }
359 
360 private T byteTo(T)(ubyte[] bytes) @trusted
361 {
362     T data = T.init;
363     foreach(i; 0 .. bytes.length) data = cast(T) (((data & 0xFF) << 8) + bytes[i]);
364 
365     return data;
366 }
367 
368 /++
369 Object describing the audio format WAV.
370 +/
371 class Wav
372 {
373     import std.file;
374 
375     public
376     {
377         ubyte audioFormat; /// Audio format.
378         ushort numChannels; /// The number of channels.
379         uint sampleRate; /// Channel speed.
380         uint byteRate; /// Channel speed. in bytes.
381         ushort blockAlign; /// Block Align
382         ushort bitsPerSample; /// Significant Bits Per Sample
383         ubyte[] data; /// Sound data
384     }
385 
386     /++
387     Loads an audio format from a file.
388 
389     Params:
390         file = The path to the file.
391     +/
392     void load(string file) @trusted
393     {
394         ubyte[] dat = cast(ubyte[]) read(file);
395 
396         if(cast(string) dat[0 .. 4] != "RIFF")
397             throw new Exception("It not a sound file!");
398 
399         ubyte[] cDat = dat[20 .. 36];
400         audioFormat = cDat[0 .. 1].byteTo!ubyte;
401         numChannels = cDat[1 .. 3].byteTo!ushort;
402         sampleRate = cDat[3 .. 7].byteTo!uint;
403         byteRate = cDat[7 .. 11].byteTo!uint;
404         blockAlign = cDat[11 .. 13].byteTo!ushort;
405         bitsPerSample = cDat[13 .. 15].byteTo!ushort;
406 
407         data = dat[44 .. $];
408     }
409 
410     /// Dynamic copy
411     Wav dup() @safe
412     {
413         Wav dupped = new Wav();
414         dupped.audioFormat = audioFormat;
415         dupped.numChannels = numChannels;
416         dupped.sampleRate = sampleRate;
417         dupped.byteRate = byteRate;
418         dupped.blockAlign = blockAlign;
419         dupped.bitsPerSample = bitsPerSample;
420         dupped.data = data.dup;
421 
422         return dupped;
423     }
424 }
425 
426 /++
427 Decoder for mp3.
428 +/
429 class MP3 : Wav
430 {
431     import mp3decoder;
432     import mp3decoderex;
433 
434     override void load(string path) @trusted
435     {
436         import core.stdc.stdlib : free;
437 
438         mp3dec_t mp3d;
439         mp3dec_file_info_t info = mp3dec_load(mp3d,path,null,null);
440 
441         audioFormat = 1;
442         numChannels = cast(ushort) info.channels;
443         sampleRate = info.hz;
444         bitsPerSample = cast(ushort) info.avg_bitrate_kbps;
445 
446         data = cast(ubyte[]) info.buffer[0 .. info.samples].dup;
447         free(cast(void*) info.buffer);
448     }
449 }