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