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  * Sound is the class used to play sounds.
30  *
31  * It provides:
32  * $(UL
33  * $(LI Control (play, pause, stop))
34  * $(LI Ability to modify output parameters in real-time (pitch, volume, ...))
35  * $(LI 3D spatial features (position, attenuation, ...)))
36  *
37  * $(PARA
38  * Sound is perfect for playing short sounds that can fit in memory and require
39  * no latency, like foot steps or gun shots. For longer sounds, like background
40  * musics or long speeches, rather see Music (which is based on streaming).
41  *
42  * In order to work, a sound must be given a buffer of audio data to play. Audio
43  * data (samples) is stored in SoundBuffer, and attached to a sound with the
44  * setBuffer() function. The buffer object attached to a sound must remain alive
45  * as long as the sound uses it. Note that multiple sounds can use the same
46  * sound buffer at the same time.
47  *)
48  *
49  * Example:
50  * ---
51  * auto buffer = new SoundBuffer();
52  * buffer.loadFromFile("sound.wav");
53  *
54  * auto sound = new Sound();
55  * sound.setBuffer(buffer);
56  * sound.play();
57  * ---
58  *
59  * See_Also:
60  * $(SOUNDBUFFER_LINK), $(MUSIC_LINK)
61  */
62 module dsfml.audio.sound;
63 
64 public import dsfml.system.time;
65 
66 import dsfml.audio.soundbuffer;
67 import dsfml.audio.soundsource;
68 
69 import dsfml.system.vector3;
70 
71 /**
72  * Regular sound that can be played in the audio environment.
73  */
74 class Sound : SoundSource
75 {
76     import std.typecons:Rebindable;
77 
78     //Const AND able to be rebound. Word.
79     private Rebindable!(const(SoundBuffer)) m_buffer;
80     package sfSound* sfPtr;
81 
82     /// Default constructor.
83     this()
84     {
85         sfPtr = sfSound_construct();
86     }
87 
88     /**
89      * Construct the sound with a buffer.
90      *
91      * Params:
92      *	buffer = Sound buffer containing the audio data to play with the sound
93      */
94     this(const(SoundBuffer) buffer)
95     {
96         this();
97 
98         setBuffer(buffer);
99     }
100 
101     /// Destructor.
102     ~this()
103     {
104         import dsfml.system.config;
105         mixin(destructorOutput);
106         //stop the sound
107         stop();
108 
109         sfSound_destroy(sfPtr);
110     }
111 
112     @property
113     {
114         /**
115          * Whether or not the sound should loop after reaching the end.
116          *
117          * If set, the sound will restart from beginning after reaching the end and
118          * so on, until it is stopped or setLoop(false) is called.
119          *
120          * The default looping state for sound is false.
121          */
122         void isLooping(bool loop)
123         {
124             sfSound_setLoop(sfPtr, loop);
125         }
126 
127         /// ditto
128         bool isLooping() const
129         {
130             return sfSound_getLoop(sfPtr);
131         }
132     }
133 
134     @property
135     {
136         /**
137          * Change the current playing position (from the beginning) of the sound.
138          *
139          * The playing position can be changed when the sound is either paused or
140          * playing.
141          */
142         void playingOffset(Time offset)
143         {
144             sfSound_setPlayingOffset(sfPtr, offset.asMicroseconds());
145         }
146 
147         /// ditto
148         Time playingOffset() const
149         {
150             return microseconds(sfSound_getPlayingOffset(sfPtr));
151         }
152     }
153 
154     @property
155     {
156         /**
157          * Get the current status of the sound (stopped, paused, playing).
158          */
159         Status status() const
160         {
161             return cast(Status)sfSound_getStatus(sfPtr);
162         }
163     }
164 
165     //from SoundSource
166     @property
167     {
168         /**
169          * The pitch of the sound.
170          *
171          * The pitch represents the perceived fundamental frequency of a sound; thus
172          * you can make a sound more acute or grave by changing its pitch. A side
173          * effect of changing the pitch is to modify the playing speed of the sound
174          * as well. The default value for the pitch is 1.
175          */
176         void pitch(float newPitch)
177         {
178             sfSound_setPitch(sfPtr, newPitch);
179         }
180 
181         /// ditto
182         float pitch() const
183         {
184             return sfSound_getPitch(sfPtr);
185         }
186     }
187 
188     @property
189     {
190         /**
191          * The volume of the sound.
192          *
193          * The volume is a value between 0 (mute) and 100 (full volume). The
194          * default value for the volume is 100.
195          */
196         void volume(float newVolume)
197         {
198             sfSound_setVolume(sfPtr, newVolume);
199         }
200 
201         /// ditto
202         float volume() const
203         {
204             return sfSound_getVolume(sfPtr);
205         }
206     }
207 
208     @property
209     {
210         /**
211          * The 3D position of the sound in the audio scene.
212          *
213          * Only sounds with one channel (mono sounds) can be spatialized. The
214          * default position of a sound is (0, 0, 0).
215          */
216         void position(Vector3f newPosition)
217         {
218             sfSound_setPosition(sfPtr, newPosition.x, newPosition.y,
219                                 newPosition.z);
220         }
221 
222         /// ditto
223         Vector3f position() const
224         {
225             Vector3f temp;
226             sfSound_getPosition(sfPtr, &temp.x, &temp.y, &temp.z);
227             return temp;
228         }
229     }
230 
231     @property
232     {
233         /**
234          * Make the sound's position relative to the listener (true) or absolute
235          * (false).
236          *
237          * Making a sound relative to the listener will ensure that it will always
238          * be played the same way regardless the position of the listener.  This can
239          * be useful for non-spatialized sounds, sounds that are produced by the
240          * listener, or sounds attached to it. The default value is false
241          * (position is absolute).
242          */
243         void relativeToListener(bool relative)
244         {
245             sfSound_setRelativeToListener(sfPtr, relative);
246         }
247 
248         /// ditto
249         bool relativeToListener() const
250         {
251             return sfSound_isRelativeToListener(sfPtr);
252         }
253     }
254 
255     @property
256     {
257         /**
258          * The minimum distance of the sound.
259          *
260          * The "minimum distance" of a sound is the maximum distance at which it is
261          * heard at its maximum volume. Further than the minimum distance, it will
262          * start to fade out according to its attenuation factor. A value of 0
263          * ("inside the head of the listener") is an invalid value and is forbidden.
264          * The default value of the minimum distance is 1.
265          */
266         void minDistance(float distance)
267         {
268             sfSound_setMinDistance(sfPtr, distance);
269         }
270 
271         /// ditto
272         float minDistance() const
273         {
274             return sfSound_getMinDistance(sfPtr);
275         }
276     }
277 
278     @property
279     {
280         /**
281          * The attenuation factor of the sound.
282          *
283          * The attenuation is a multiplicative factor which makes the sound more or
284          * less loud according to its distance from the listener. An attenuation of
285          * 0 will produce a non-attenuated sound, i.e. its volume will always be the
286          * same whether it is heard from near or from far.
287          *
288          * On the other hand, an attenuation value such as 100 will make the sound
289          * fade out very quickly as it gets further from the listener. The default
290          * value of the attenuation is 1.
291          */
292         void attenuation(float newAttenuation)
293         {
294             sfSound_setAttenuation(sfPtr, newAttenuation);
295         }
296 
297         /// ditto
298         float attenuation() const
299         {
300             return sfSound_getAttenuation(sfPtr);
301         }
302     }
303 
304     /*
305      * Set the source buffer containing the audio data to play.
306      *
307      * It is important to note that the sound buffer is not copied, thus the
308      * SoundBuffer instance must remain alive as long as it is attached to the
309      * sound.
310      *
311      * Params:
312      * 		buffer =	Sound buffer to attach to the sound
313      */
314     void setBuffer(const(SoundBuffer) buffer)
315     {
316         m_buffer = buffer;
317         sfSound_setBuffer(sfPtr, buffer.sfPtr);
318     }
319 
320     /**
321      * Pause the sound.
322      *
323      * This function pauses the sound if it was playing, otherwise
324      * (sound already paused or stopped) it has no effect.
325      */
326     void pause()
327     {
328         sfSound_pause(sfPtr);
329     }
330 
331     /**
332      * Start or resume playing the sound.
333      *
334      * This function starts the stream if it was stopped, resumes it if it was
335      * paused, and restarts it from beginning if it was it already playing.
336      *
337      * This function uses its own thread so that it doesn't block the rest of
338      * the program while the sound is played.
339      */
340     void play()
341     {
342         sfSound_play(sfPtr);
343     }
344 
345     /**
346      * Stop playing the sound.
347      *
348      * This function stops the sound if it was playing or paused, and does
349      * nothing if it was already stopped. It also resets the playing position
350      * (unlike `pause()`).
351      */
352     void stop()
353     {
354         sfSound_stop(sfPtr);
355     }
356 }
357 
358 unittest
359 {
360     version(DSFML_Unittest_Audio)
361     {
362         import std.stdio;
363         import dsfml.system.clock;
364 
365         writeln("Unit test for Sound class");
366 
367         //first, get a sound buffer
368 
369         auto soundbuffer = new SoundBuffer();
370 
371         if(!soundbuffer.loadFromFile("res/TestSound.ogg"))
372         {
373             //error
374             return;
375         }
376 
377         float duration = soundbuffer.getDuration().asSeconds();
378 
379         auto sound = new Sound(soundbuffer);
380 
381         //sound.relativeToListener(true);
382 
383         auto clock = new Clock();
384         //play the sound!
385         sound.play();
386 
387 
388         while(clock.getElapsedTime().asSeconds() < duration)
389         {
390             //wait for sound to finish
391         }
392 
393         //clock.restart();
394 
395         //sound.relativeToListener(false);
396 
397 
398 
399         writeln();
400     }
401 }
402 
403 private extern(C):
404 
405 struct sfSound;
406 
407 sfSound* sfSound_construct();
408 
409 sfSound* sfSound_copy(const sfSound* sound);
410 
411 void sfSound_destroy(sfSound* sound);
412 
413 void sfSound_play(sfSound* sound);
414 
415 void sfSound_pause(sfSound* sound);
416 
417 void sfSound_stop(sfSound* sound);
418 
419 void sfSound_setBuffer(sfSound* sound, const sfSoundBuffer* buffer);
420 
421 void sfSound_setLoop(sfSound* sound, bool loop);
422 
423 bool sfSound_getLoop(const sfSound* sound);
424 
425 int sfSound_getStatus(const sfSound* sound);
426 
427 void sfSound_setPitch(sfSound* sound, float pitch);
428 
429 void sfSound_setVolume(sfSound* sound, float volume);
430 
431 void sfSound_setPosition(sfSound* sound, float positionX, float positionY, float positionZ);
432 
433 void sfSound_setRelativeToListener(sfSound* sound, bool relative);
434 
435 void sfSound_setMinDistance(sfSound* sound, float distance);
436 
437 void sfSound_setAttenuation(sfSound* sound, float attenuation);
438 
439 void sfSound_setPlayingOffset(sfSound* sound, long timeOffset);
440 
441 float sfSound_getPitch(const sfSound* sound);
442 
443 float sfSound_getVolume(const sfSound* sound);
444 
445 void sfSound_getPosition(const sfSound* sound, float* positionX, float* positionY, float* positionZ);
446 
447 bool sfSound_isRelativeToListener(const sfSound* sound);
448 
449 float sfSound_getMinDistance(const sfSound* sound);
450 
451 float sfSound_getAttenuation(const sfSound* sound);
452 
453 long sfSound_getPlayingOffset(const sfSound* sound);