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 }