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);