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 }