1 /* 2 ******************************************************************************************* 3 * Dgame (a D game framework) - Copyright (c) Randy Schütt 4 * 5 * This software is provided 'as-is', without any express or implied warranty. 6 * In no event will the authors be held liable for any damages arising from 7 * the use of this software. 8 * 9 * Permission is granted to anyone to use this software for any purpose, 10 * including commercial applications, and to alter it and redistribute it 11 * freely, subject to the following restrictions: 12 * 13 * 1. The origin of this software must not be misrepresented; you must not claim 14 * that you wrote the original software. If you use this software in a product, 15 * an acknowledgment in the product documentation would be appreciated but is 16 * not required. 17 * 18 * 2. Altered source versions must be plainly marked as such, and must not be 19 * misrepresented as being the original software. 20 * 21 * 3. This notice may not be removed or altered from any source distribution. 22 ******************************************************************************************* 23 */ 24 module Dgame.Audio.Sound; 25 26 private { 27 import std.algorithm : endsWith; 28 import std.exception : enforce; 29 30 import derelict.openal.al; 31 32 import Dgame.Internal.Log; 33 import Dgame.Math.Vector3; 34 import Dgame.Audio.SoundFile; 35 import Dgame.Audio.VorbisFile; 36 import Dgame.Audio.WaveFile; 37 } 38 39 @safe 40 private char toLower(char ch) pure nothrow { 41 return ch | 32; 42 } 43 44 @safe 45 private string toLower(string str) pure nothrow { 46 char[] s = new char[str.length]; 47 48 for (uint i = 0; i < str.length; i++) { 49 s[i] = toLower(str[i]); 50 } 51 52 return s; 53 } 54 55 /** 56 * Channel struct which stores the channel information. 57 */ 58 struct Channel { 59 /** 60 * The channel type. Mono or Stereo. 61 */ 62 enum Type : ubyte { 63 Mono, /** Channel type is Mono */ 64 Stereo /** Channel type is Stereo */ 65 } 66 67 ubyte bits; /** How many bits has this channel */ 68 Type type; /** Which type is this channel */ 69 70 /** 71 * CTor 72 */ 73 this(ubyte bits, Type type) { 74 this.bits = bits; 75 this.type = type; 76 } 77 78 /** 79 * Short set function. 80 * 81 * Example: 82 * --- 83 * Channel ch; 84 * ch(8, Channel.Type.Stereo); // set bits and type for this channel. 85 * --- 86 */ 87 void opCall(ubyte bits, Type type) { 88 this.bits = bits; 89 this.type = type; 90 } 91 } 92 93 private struct ALChunk { 94 ALuint source; 95 ALuint buffer; 96 97 /** 98 * Create and initialize the buffers 99 */ 100 void init() { 101 alGenBuffers(1, &this.buffer); 102 alGenSources(1, &this.source); 103 } 104 105 /** 106 * Free / Release the source and buffer of the Sound 107 */ 108 void free() { 109 if (this.source && this.buffer) { 110 alDeleteSources(1, &this.source); 111 alDeleteBuffers(1, &this.buffer); 112 } 113 } 114 115 @disable 116 this(this); 117 } 118 119 private ALChunk*[] _ALFinalizer; 120 121 static ~this() { 122 debug Log.info("Finalize Sound (%d)", _ALFinalizer.length); 123 124 for (size_t i = 0; i < _ALFinalizer.length; i++) { 125 if (_ALFinalizer[i]) { 126 debug Log.info(" -> Sound finalized: %d", i); 127 _ALFinalizer[i].free(); 128 } 129 } 130 131 _ALFinalizer = null; 132 133 debug Log.info(" >> Sound Finalized"); 134 } 135 136 /** 137 * Sound represents the functionality to manipulate loaded sounds. 138 * That means with this class you can get informations about the sound or 139 * you can play, stop or pause it. 140 * 141 * Author: rschuett 142 */ 143 class Sound { 144 /** 145 * Represents the current status. 146 */ 147 enum Status : ubyte { 148 None, /** No information */ 149 Stopped, /** Sound is stopped */ 150 Paused, /** Sound is paused */ 151 Playing /** Sound is playing */ 152 } 153 154 private: 155 ALChunk _alChunk; 156 ALenum _format; 157 158 uint _frequency; 159 float _volume; 160 bool _looping; 161 162 Vector3f _sourcePos; 163 Vector3f _sourceVel; 164 165 Status _status; 166 Channel _channel; 167 168 BaseSoundFile _soundfile; 169 170 private: 171 static Sound[string] _soundInstances; 172 173 public: 174 final: 175 /** 176 * CTor 177 */ 178 this() { 179 this._alChunk.init(); 180 this._status = Status.None; 181 182 _ALFinalizer ~= &this._alChunk; 183 } 184 185 /** 186 * CTor 187 */ 188 this(BaseSoundFile soundfile) { 189 this(); 190 this.loadFromFile(soundfile); 191 } 192 193 /** 194 * CTor 195 */ 196 this(string filename) { 197 this(); 198 this.loadFromFile(filename); 199 } 200 201 /** 202 * Free / Release the source and buffer of the Sound 203 */ 204 void free() { 205 this._alChunk.free(); 206 } 207 208 /** 209 * Load a soundfile and stores the sound object in a static field. 210 * Then returns the sound object. 211 * If you try to load the same file on more time, you get the same sound object as before. 212 */ 213 static Sound loadOnce(BaseSoundFile soundfile) in { 214 assert(soundfile !is null, "Soundfile is null."); 215 } body { 216 return Sound.loadOnce(soundfile.getFilename()); 217 } 218 219 /** 220 * Load a soundfile from his path and stores the sound object in a static field. 221 * Then returns the sound object. 222 * If you try to load the same file on more time, you get the same sound object as before. 223 */ 224 static Sound loadOnce(string filename) { 225 if (Sound* s = filename in _soundInstances) 226 return *s; 227 228 Sound s = new Sound(filename); 229 _soundInstances[filename] = s; 230 231 return s; 232 } 233 234 /** 235 * Load from a soundfile. 236 * 237 * Example: 238 * --- 239 * Sound s1 = new Sound(); 240 * s1.loadFromFile(new VorbisFile("samples/audio/orchestral.ogg")); 241 * --- 242 * 243 * See: BaseSoundFile 244 * See: VorbisFile 245 * See: WaveFile 246 */ 247 void loadFromFile(BaseSoundFile soundfile) in { 248 assert(soundfile !is null, "Soundfile is null."); 249 } body { 250 const SoundFile* sFile = &soundfile.getData(); 251 this._soundfile = soundfile; 252 253 /// Load 254 Channel ch = void; 255 switch (sFile.bits) { 256 case 8: 257 if (sFile.channels == 1) 258 ch(8, Channel.Type.Mono); 259 else 260 ch(8, Channel.Type.Stereo); 261 break; 262 263 case 16: 264 if (sFile.channels == 1) 265 ch(16, Channel.Type.Mono); 266 else 267 ch(16, Channel.Type.Stereo); 268 break; 269 270 default: 271 debug Log.info("Bits: %d", sFile.bits); 272 Log.error("Switch error."); 273 } 274 275 this.loadFromMemory(soundfile.getBuffer().ptr, sFile.dataSize, sFile.rate, ch); 276 } 277 278 /** 279 * Load a soundfile from his path. 280 * 281 * Example: 282 * --- 283 * Sound s1 = new Sound(); 284 * if (s1.loadFromFile("samples/audio/orchestral.ogg") is null) 285 * throw new Exception("Could not load file."); 286 * --- 287 * 288 * See: BaseSoundFile 289 * See: VorbisFile 290 * See: WaveFile 291 */ 292 BaseSoundFile loadFromFile(string filename) { 293 enforce(exists(filename), "Soundfile " ~ filename ~ " does not exist."); 294 295 BaseSoundFile sFile; 296 297 Lagain: 298 if (filename.endsWith(".ogg") || filename.endsWith(".vorbis")) 299 sFile = new VorbisFile(filename); 300 else if (filename.endsWith(".wav") || filename.endsWith(".wave")) 301 sFile = new WaveFile(filename); 302 else { 303 const string lower = toLower(filename); // for e.g. *.WAVE 304 if (lower != filename) { 305 filename = lower; 306 goto Lagain; 307 } 308 } 309 310 if (sFile !is null) 311 this.loadFromFile(sFile); 312 313 return sFile; 314 } 315 316 /** 317 * Load from memory. 318 */ 319 void loadFromMemory(const void* buffer, uint dataSize, uint frequency, ref const Channel ch) in { 320 assert(buffer !is null, "Buffer is null."); 321 } body { 322 switch (ch.bits) { 323 case 8: 324 if (ch.type == Channel.Type.Mono) 325 this._format = AL_FORMAT_MONO8; 326 else 327 this._format = AL_FORMAT_STEREO8; 328 break; 329 330 case 16: 331 if (ch.type == Channel.Type.Mono) 332 this._format = AL_FORMAT_MONO16; 333 else 334 this._format = AL_FORMAT_STEREO16; 335 break; 336 default: Log.error("Switch error."); 337 } 338 339 this._frequency = frequency; 340 this._channel = ch; 341 342 alBufferData(this._alChunk.buffer, this._format, buffer, dataSize, this._frequency); 343 344 this._sourcePos = Vector3f(0, 0, 0); 345 this._sourceVel = Vector3f(0, 0, 0); 346 347 this._looping = false; 348 this._volume = 1f; 349 this._status = Status.None; 350 351 const float[3] pos = this._sourcePos.asArray(); 352 const float[3] vel = this._sourceVel.asArray(); 353 354 // Source 355 alSourcei(this._alChunk.source, AL_BUFFER, this._alChunk.buffer); 356 alSourcef(this._alChunk.source, AL_PITCH, 1.0); 357 alSourcef(this._alChunk.source, AL_GAIN, this._volume); 358 alSourcefv(this._alChunk.source, AL_POSITION, &pos[0]); 359 alSourcefv(this._alChunk.source, AL_VELOCITY, &vel[0]); 360 alSourcei(this._alChunk.source, AL_LOOPING, this._looping); 361 } 362 363 /** 364 * Returns the interal Soundfile which contains the filename, the music type and the length in seconds. 365 */ 366 BaseSoundFile getSoundFile() pure nothrow { 367 return this._soundfile; 368 } 369 370 /** 371 * Returns the current filename. 372 */ 373 string getFilename() const pure nothrow { 374 return this._soundfile !is null ? this._soundfile.getFilename() : null; 375 } 376 377 /** 378 * Returns the length in seconds. 379 */ 380 float getLength() const pure nothrow { 381 return this._soundfile !is null ? this._soundfile.getLength() : 0f; 382 } 383 384 /** 385 * Returns the music type. 386 */ 387 MusicType getType() const pure nothrow { 388 return this._soundfile !is null ? this._soundfile.getType() : MusicType.None; 389 } 390 391 /** 392 * Returns the Format. 393 */ 394 ALenum getFormat() const pure nothrow { 395 return this._format; 396 } 397 398 /** 399 * Returns the current status. 400 * 401 * See: Status enum 402 */ 403 Status getStatus() const pure nothrow { 404 return this._status; 405 } 406 407 /** 408 * Returns the current Channel. 409 * 410 * See: Channel struct 411 */ 412 ref const(Channel) getChannel() const pure nothrow { 413 return this._channel; 414 } 415 416 /** 417 * Returns the current frequency. 418 */ 419 uint getFreqeuency() const pure nothrow { 420 return this._frequency; 421 } 422 423 /** 424 * Set the volume. 425 */ 426 void setVolume(float volume) { 427 this._volume = volume; 428 429 alSourcef(this._alChunk.source, AL_GAIN, this._volume); 430 } 431 432 /** 433 * Set the volume. 434 */ 435 void setVolume(ubyte volume) { 436 this.setVolume(volume / 255f); 437 } 438 439 /** 440 * Returns the current volume. 441 */ 442 ubyte getVolume() const pure nothrow { 443 return cast(ubyte)(this._volume * ubyte.max); 444 } 445 446 /** 447 * Enable looping. 448 */ 449 void setLooping(bool enable) { 450 this._looping = enable; 451 452 alSourcei(this._alChunk.source, AL_LOOPING, this._looping); 453 } 454 455 /** 456 * Returns if the looping is enabled or not. 457 */ 458 bool getLooping() const pure nothrow { 459 return this._looping; 460 } 461 462 /** 463 * Activate the playing. 464 * If enable is true, the looping is also activated. 465 */ 466 void play(bool enable) { 467 this.setLooping(enable); 468 this.play(); 469 } 470 471 /** 472 * Activate the playing. 473 */ 474 void play() { 475 alSourcePlay(this._alChunk.source); 476 this._status = Status.Playing; 477 } 478 479 /** 480 * Stop current playing. 481 */ 482 void stop() { 483 alSourceStop(this._alChunk.source); 484 this._status = Status.Stopped; 485 } 486 487 /** 488 * Rewind playing. 489 */ 490 void rewind() { 491 alSourceRewind(this._alChunk.source); 492 this._status = Status.Playing; 493 } 494 495 /** 496 * Pause playing. 497 */ 498 void pause() { 499 alSourcePause(this._alChunk.source); 500 this._status = Status.Paused; 501 } 502 503 /** 504 * Set the position. 505 */ 506 void setPosition(ref const Vector3f vpos) { 507 this._sourcePos = vpos; 508 this._updatePosition(); 509 } 510 511 /** 512 * Set the position. 513 */ 514 void setPosition(float x, float y, float z = 0) { 515 this._sourcePos.set(x, y, z); 516 this._updatePosition(); 517 } 518 519 private void _updatePosition() const { 520 const float[3] pos = this._sourcePos.asArray(); 521 522 alSourcefv(this._alChunk.source, AL_POSITION, &pos[0]); 523 } 524 525 /** 526 * Returns the current position. 527 */ 528 ref const(Vector3f) getPosition() const pure nothrow { 529 return this._sourcePos; 530 } 531 532 /** 533 * Set the velocity. 534 */ 535 void setVelocity(ref const Vector3f vvel) { 536 this._sourceVel = vvel; 537 this._updateVelocity(); 538 } 539 540 /** 541 * Set the velocity. 542 */ 543 void setVelocity(float x, float y, float z = 0) { 544 this._sourceVel.set(x, y, z); 545 this._updateVelocity(); 546 } 547 548 private void _updateVelocity() const { 549 const float[3] vel = this._sourceVel.asArray(); 550 551 alSourcefv(this._alChunk.source, AL_VELOCITY, &vel[0]); 552 } 553 554 /** 555 * Returns the current velocity. 556 */ 557 ref const(Vector3f) getVelocity() const pure nothrow { 558 return this._sourceVel; 559 } 560 }