1 /* 2 * DSFML - The Simple and Fast Multimedia Library for D 3 * 4 * Copyright (c) 2013 - 2017 Jeremy DeHaan (dehaan.jeremiah@gmail.com) 5 * 6 * This software is provided 'as-is', without any express or implied warranty. 7 * In no event will the authors be held liable for any damages arising from the 8 * use of this software. 9 * 10 * Permission is granted to anyone to use this software for any purpose, 11 * including commercial applications, and to alter it and redistribute it 12 * freely, subject to the following restrictions: 13 * 14 * 1. The origin of this software must not be misrepresented; you must not claim 15 * that you wrote the original software. If you use this software in a product, 16 * an acknowledgment in the product documentation would be appreciated but is 17 * not required. 18 * 19 * 2. Altered source versions must be plainly marked as such, and must not be 20 * misrepresented as being the original software. 21 * 22 * 3. This notice may not be removed or altered from any source distribution 23 */ 24 25 /** 26 * Unlike audio buffers (see $(SOUNDBUFFER_LINK)), audio streams are never 27 * completely loaded in memory. Instead, the audio data is acquired continuously 28 * while the stream is playing. This behaviour allows to play a sound with no 29 * loading delay, and keeps the memory consumption very low. 30 * 31 * Sound sources that need to be streamed are usually big files (compressed 32 * audio musics that would eat hundreds of MB in memory) or files that would 33 * take a lot of time to be received (sounds played over the network). 34 * 35 * $(U SoundStream) is a base class that doesn't care about the stream source, 36 * which is left to the derived class. SFML provides a built-in specialization 37 * for big files (see $(MUSIC_LINK)). No network stream source is provided, but 38 * you can write your own by combining this class with the network module. 39 * 40 * A derived class has to override two virtual functions: 41 * $(UL 42 * $(LI `onGetData` fills a new chunk of audio data to be played) 43 * $(LI `onSeek` changes the current playing position in the source)) 44 * 45 * $(PARA 46 * It is important to note that each $(U SoundStream) is played in its own 47 * separate thread, so that the streaming loop doesn't block the rest of the 48 * program. In particular, the `onGetData` and `onSeek` virtual functions may 49 * sometimes be called from this separate thread. It is important to keep this 50 * in mind, because you may have to take care of synchronization issues if you 51 * share data between threads.) 52 * 53 * Example: 54 * --- 55 * class CustomStream : SoundStream 56 * { 57 * 58 * bool open(const(char)[] location) 59 * { 60 * // Open the source and get audio settings 61 * ... 62 * uint channelCount = ...; 63 * unint sampleRate = ...; 64 * 65 * // Initialize the stream -- important! 66 * initialize(channelCount, sampleRate); 67 * } 68 * 69 * protected: 70 * override bool onGetData(ref const(short)[] samples) 71 * { 72 * // Fill the chunk with audio data from the stream source 73 * // (note: must not be empty if you want to continue playing) 74 * 75 * // Return true to continue playing 76 * return true; 77 * } 78 * 79 * override void onSeek(Uint32 timeOffset) 80 * { 81 * // Change the current position in the stream source 82 * ... 83 * } 84 * } 85 * 86 * // Usage 87 * auto stream = CustomStream(); 88 * stream.open("path/to/stream"); 89 * stream.play(); 90 * --- 91 * 92 * See_Also: 93 * $(MUSIC_LINK) 94 */ 95 module dsfml.audio.soundstream; 96 97 98 import core.thread; 99 import core.time; 100 101 import dsfml.audio.soundsource; 102 103 import dsfml.system.vector3; 104 105 import dsfml.system.err; 106 107 108 /** 109 * Abstract base class for streamed audio sources. 110 */ 111 class SoundStream : SoundSource 112 { 113 package sfSoundStream* sfPtr; 114 private SoundStreamCallBacks callBacks; 115 116 /// Internal constructor required to set up callbacks. 117 protected this() 118 { 119 callBacks = new SoundStreamCallBacks(this); 120 sfPtr = sfSoundStream_construct(callBacks); 121 } 122 123 /// Destructor. 124 ~this() 125 { 126 import dsfml.system.config; 127 mixin(destructorOutput); 128 sfSoundStream_destroy(sfPtr); 129 } 130 131 /** 132 * Define the audio stream parameters. 133 * 134 * This function must be called by derived classes as soon as they know the 135 * audio settings of the stream to play. Any attempt to manipulate the 136 * stream (`play()`, ...) before calling this function will fail. It can be 137 * called multiple times if the settings of the audio stream change, but 138 * only when the stream is stopped. 139 * 140 * Params: 141 * channelCount = Number of channels of the stream 142 * sampleRate = Sample rate, in samples per second 143 */ 144 protected void initialize(uint channelCount, uint sampleRate) 145 { 146 import dsfml.system..string; 147 148 sfSoundStream_initialize(sfPtr, channelCount, sampleRate); 149 150 err.write(dsfml.system..string.toString(sfErr_getOutput())); 151 } 152 153 154 @property 155 { 156 /** 157 * The pitch of the sound. 158 * 159 * The pitch represents the perceived fundamental frequency of a sound; thus 160 * you can make a sound more acute or grave by changing its pitch. A side 161 * effect of changing the pitch is to modify the playing speed of the sound 162 * as well. The default value for the pitch is 1. 163 */ 164 void pitch(float newPitch) 165 { 166 sfSoundStream_setPitch(sfPtr, newPitch); 167 } 168 /// ditto 169 float pitch() 170 { 171 return sfSoundStream_getPitch(sfPtr); 172 } 173 } 174 175 @property 176 { 177 /** 178 * The volume of the sound. 179 * 180 * The volume is a vlue between 0 (mute) and 100 (full volume). The default 181 * value for the volume is 100. 182 */ 183 void volume(float newVolume) 184 { 185 sfSoundStream_setVolume(sfPtr, newVolume); 186 } 187 /// ditto 188 float volume() 189 { 190 return sfSoundStream_getVolume(sfPtr); 191 } 192 } 193 194 @property 195 { 196 /** 197 * The 3D position of the sound in the audio scene. 198 * 199 * Only sounds with one channel (mono sounds) can be spatialized. The 200 * default position of a sound is (0, 0, 0). 201 */ 202 void position(Vector3f newPosition) 203 { 204 sfSoundStream_setPosition(sfPtr, newPosition.x, newPosition.y, newPosition.z); 205 } 206 /// ditto 207 Vector3f position() 208 { 209 Vector3f temp; 210 sfSoundStream_getPosition(sfPtr, &temp.x, &temp.y, &temp.z); 211 return temp; 212 } 213 } 214 215 @property 216 { 217 /** 218 * Whether or not the stream should loop after reaching the end. 219 * 220 * If set, the stream will restart from the beginning after reaching the end 221 * and so on, until it is stopped or looping is set to false. 222 * 223 * Default looping state for streams is false. 224 */ 225 void isLooping(bool loop) 226 { 227 sfSoundStream_setLoop(sfPtr, loop); 228 } 229 /// ditto 230 bool isLooping() 231 { 232 return sfSoundStream_getLoop(sfPtr); 233 } 234 } 235 236 @property 237 { 238 /** 239 * The current playing position (from the beginning) of the stream. 240 * 241 * The playing position can be changed when the stream is either paused or 242 * playing. 243 */ 244 void playingOffset(Duration offset) 245 { 246 sfSoundStream_setPlayingOffset(sfPtr, offset.total!"usecs"); 247 248 } 249 /// ditto 250 Duration playingOffset() 251 { 252 return usecs(sfSoundStream_getPlayingOffset(sfPtr)); 253 } 254 } 255 256 @property 257 { 258 /** 259 * Make the sound's position relative to the listener (true) or absolute 260 * (false). 261 * 262 * Making a sound relative to the listener will ensure that it will always 263 * be played the same way regardless the position of the listener. This can 264 * be useful for non-spatialized sounds, sounds that are produced by the 265 * listener, or sounds attached to it. The default value is false (position 266 * is absolute). 267 */ 268 void relativeToListener(bool relative) 269 { 270 sfSoundStream_setRelativeToListener(sfPtr, relative); 271 } 272 /// ditto 273 bool relativeToListener() 274 { 275 return sfSoundStream_isRelativeToListener(sfPtr); 276 } 277 } 278 279 @property 280 { 281 /** 282 * The minimum distance of the sound. 283 * 284 * The "minimum distance" of a sound is the maximum distance at which it is 285 * heard at its maximum volume. Further than the minimum distance, it will 286 * start to fade out according to its attenuation factor. A value of 0 287 * ("inside the head of the listener") is an invalid value and is forbidden. 288 * The default value of the minimum distance is 1. 289 */ 290 void minDistance(float distance) 291 { 292 sfSoundStream_setMinDistance(sfPtr, distance); 293 } 294 /// ditto 295 float minDistance() 296 { 297 return sfSoundStream_getMinDistance(sfPtr); 298 } 299 } 300 301 @property 302 { 303 /** 304 * The attenuation factor of the sound. 305 * 306 * The attenuation is a multiplicative factor which makes the sound more or 307 * less loud according to its distance from the listener. An attenuation of 308 * 0 will produce a non-attenuated sound, i.e. its volume will always be the 309 * same whether it is heard from near or from far. 310 * 311 * On the other hand, an attenuation value such as 100 will make the sound 312 * fade out very quickly as it gets further from the listener. The default 313 * value of the attenuation is 1. 314 */ 315 void attenuation(float newAttenuation) 316 { 317 sfSoundStream_setAttenuation(sfPtr, newAttenuation); 318 } 319 /// ditto 320 float attenuation() 321 { 322 return sfSoundStream_getAttenuation(sfPtr); 323 } 324 } 325 326 327 @property 328 { 329 /** 330 * The number of channels of the stream. 331 * 332 * 1 channel means mono sound, 2 means stereo, etc. 333 */ 334 uint channelCount() 335 { 336 return sfSoundStream_getChannelCount(sfPtr); 337 } 338 } 339 340 @property 341 { 342 /** 343 * The stream sample rate of the stream 344 * 345 * The sample rate is the number of audio samples played per second. The 346 * higher, the better the quality. 347 */ 348 uint sampleRate() 349 { 350 return sfSoundStream_getSampleRate(sfPtr); 351 } 352 } 353 354 @property 355 { 356 /// The current status of the stream (stopped, paused, playing) 357 Status status() 358 { 359 return cast(Status)sfSoundStream_getStatus(sfPtr); 360 } 361 } 362 363 /** 364 * Start or resume playing the audio stream. 365 * 366 * This function starts the stream if it was stopped, resumes it if it was 367 * paused, and restarts it from the beginning if it was already playing. This 368 * function uses its own thread so that it doesn't block the rest of the 369 * program while the stream is played. 370 */ 371 void play() 372 { 373 import dsfml.system..string; 374 375 sfSoundStream_play(sfPtr); 376 377 err.write(dsfml.system..string.toString(sfErr_getOutput())); 378 } 379 380 /** 381 * Pause the audio stream. 382 * 383 * This function pauses the stream if it was playing, otherwise (stream 384 * already paused or stopped) it has no effect. 385 */ 386 void pause() 387 { 388 sfSoundStream_pause(sfPtr); 389 } 390 391 /** 392 * Stop playing the audio stream. 393 * 394 * This function stops the stream if it was playing or paused, and does 395 * nothing if it was already stopped. It also resets the playing position 396 * (unlike pause()). 397 */ 398 void stop() 399 { 400 sfSoundStream_stop(sfPtr); 401 } 402 403 /** 404 * Request a new chunk of audio samples from the stream source. 405 * 406 * This function must be overridden by derived classes to provide the audio 407 * samples to play. It is called continuously by the streaming loop, in a 408 * separate thread. The source can choose to stop the streaming loop at any 409 * time, by returning false to the caller. If you return true (i.e. continue 410 * streaming) it is important that the returned array of samples is not 411 * empty; this would stop the stream due to an internal limitation. 412 * 413 * Params: 414 * samples = Array of samples to fill 415 */ 416 protected abstract bool onGetData(ref const(short)[] samples); 417 418 /** 419 * Change the current playing position in the stream source. 420 * 421 * This function must be overridden by derived classes to allow random 422 * seeking into the stream source. 423 * 424 * Params: 425 * timeOffset = New playing position, relative to the start of the stream 426 */ 427 protected abstract void onSeek(Duration timeOffset); 428 429 430 } 431 432 private extern(C++) 433 { 434 struct Chunk 435 { 436 const(short)* samples; 437 size_t sampleCount; 438 } 439 } 440 441 private extern(C++) interface sfmlSoundStreamCallBacks 442 { 443 public: 444 bool onGetData(Chunk* chunk); 445 void onSeek(long time); 446 } 447 448 449 class SoundStreamCallBacks: sfmlSoundStreamCallBacks 450 { 451 SoundStream m_stream; 452 453 this(SoundStream stream) 454 { 455 m_stream = stream; 456 } 457 458 extern(C++) bool onGetData(Chunk* chunk) 459 { 460 const(short)[] samples; 461 462 auto ret = m_stream.onGetData(samples); 463 464 (*chunk).samples = samples.ptr; 465 (*chunk).sampleCount = samples.length; 466 467 return ret; 468 469 } 470 471 extern(C++) void onSeek(long time) 472 { 473 m_stream.onSeek(usecs(time)); 474 } 475 476 477 478 } 479 480 private extern(C): 481 482 483 484 struct sfSoundStream; 485 486 487 sfSoundStream* sfSoundStream_construct(sfmlSoundStreamCallBacks callBacks); 488 489 void sfSoundStream_destroy(sfSoundStream* soundStream); 490 491 void sfSoundStream_initialize(sfSoundStream* soundStream, uint channelCount, uint sampleRate); 492 493 void sfSoundStream_play(sfSoundStream* soundStream); 494 495 void sfSoundStream_pause(sfSoundStream* soundStream); 496 497 void sfSoundStream_stop(sfSoundStream* soundStream); 498 499 int sfSoundStream_getStatus(const sfSoundStream* soundStream); 500 501 uint sfSoundStream_getChannelCount(const sfSoundStream* soundStream); 502 503 uint sfSoundStream_getSampleRate(const sfSoundStream* soundStream); 504 505 void sfSoundStream_setPitch(sfSoundStream* soundStream, float pitch); 506 507 void sfSoundStream_setVolume(sfSoundStream* soundStream, float volume); 508 509 void sfSoundStream_setPosition(sfSoundStream* soundStream, float positionX, float positionY, float positionZ); 510 511 void sfSoundStream_setRelativeToListener(sfSoundStream* soundStream, bool relative); 512 513 void sfSoundStream_setMinDistance(sfSoundStream* soundStream, float distance); 514 515 void sfSoundStream_setAttenuation(sfSoundStream* soundStream, float attenuation); 516 517 void sfSoundStream_setPlayingOffset(sfSoundStream* soundStream, long timeOffset); 518 519 void sfSoundStream_setLoop(sfSoundStream* soundStream, bool loop); 520 521 float sfSoundStream_getPitch(const sfSoundStream* soundStream); 522 523 float sfSoundStream_getVolume(const sfSoundStream* soundStream); 524 525 void sfSoundStream_getPosition(const sfSoundStream* soundStream, float* positionX, float* positionY, float* positionZ); 526 527 bool sfSoundStream_isRelativeToListener(const sfSoundStream* soundStream); 528 529 float sfSoundStream_getMinDistance(const sfSoundStream* soundStream); 530 531 float sfSoundStream_getAttenuation(const sfSoundStream* soundStream); 532 533 bool sfSoundStream_getLoop(const sfSoundStream* soundStream); 534 535 long sfSoundStream_getPlayingOffset(const sfSoundStream* soundStream); 536 537 const(char)* sfErr_getOutput();