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