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  * Musics are sounds that are streamed rather than completely loaded in memory.
30  *
31  * This is especially useful for compressed musics that usually take hundreds of
32  * MB when they are uncompressed: by streaming it instead of loading it
33  * entirely, you avoid saturating the memory and have almost no loading delay.
34  *
35  * Apart from that, a $(U Music) has almost the same features as the
36  * $(SOUNDBUFFER_LINK)/$(SOUND_LINK) pair: you can play/pause/stop it, request
37  * its parameters (channels, sample rate), change the way it is played (pitch,
38  * volume, 3D position, ...), etc.
39  *
40  * As a sound stream, a music is played in its own thread in order not to block
41  * the rest of the program. This means that you can leave the music alone after
42  * calling `play()`, it will manage itself very well.
43  *
44  * Example:
45  * ---
46  * // Declare a new music
47  * auto music = new Music();
48  *
49  * // Open it from an audio file
50  * if (!music.openFromFile("music.ogg"))
51  * {
52  *     // error...
53  * }
54  *
55  * // change its 3D position
56  * music.position = Vector3f(0, 1, 10);
57  *
58  * // increase the pitch
59  * music.pitch = 2;
60  *
61  * // reduce the volume
62  * music.volume = 50;
63  *
64  * // make it loop
65  * music.loop = true;
66  *
67  * // Play it
68  * music.play();
69  * ---
70  *
71  * See_Also:
72  * $(SOUND_LINK), $(SOUNDSTREAM_LINK)
73  */
74 module dsfml.audio.music;
75 
76 public import dsfml.system.time;
77 
78 import dsfml.system.mutex;
79 import dsfml.system.inputstream;
80 
81 import dsfml.audio.soundstream;
82 
83 
84 /**
85  * Streamed music played from an audio file.
86  */
87 class Music : SoundStream
88 {
89     import dsfml.audio.inputsoundfile;
90 
91     private
92     {
93         InputSoundFile m_file;
94         Time m_duration;
95         short[] m_samples;
96         Mutex m_mutex;
97     }
98 
99     /// Default constructor.
100     this()
101     {
102         m_file = new InputSoundFile();
103         m_mutex = new Mutex();
104 
105         super();
106     }
107 
108     /// Destructor
109     ~this()
110     {
111         import dsfml.system.config;
112         mixin(destructorOutput);
113 
114         /*
115          * Calling stop() causes a segmentation fault when m_mutex is
116          * destroyed before this destructor has run (which is what happens
117          * during a GC collection).
118          *
119          * It is probably OK to not call it here since it only resets the seek
120          * position on top of what the SoundStream destructor already does.
121          */
122 
123         //stop();
124     }
125 
126     /**
127      * Open a music from an audio file.
128      *
129      * This function doesn't start playing the music (call `play()` to do so).
130      *
131      * The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC. The
132      * supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
133      *
134      * Params:
135      * 		filename =	Path of the music file to open
136      *
137      * Returns: true if loading succeeded, false if it failed.
138      */
139     bool openFromFile(string filename)
140     {
141         //stop music if already playing
142         stop();
143 
144         if(!m_file.openFromFile(filename))
145         {
146             return false;
147         }
148 
149         initialize();
150 
151         return true;
152     }
153 
154     /**
155      * Open a music from an audio file in memory.
156      *
157      * This function doesn't start playing the music (call `play()` to do so).
158      *
159      * The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC. The
160      * supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
161      *
162      * Since the music is not loaded completely but rather streamed
163      * continuously, the data must remain available as long as the music is
164      * playing (ie. you can't deallocate it right after calling this function).
165      *
166      * Params:
167      * 		data =	The array of data
168      *
169      * Returns: true if loading succeeded, false if it failed.
170      */
171     bool openFromMemory(const(void)[] data)
172     {
173         stop();
174 
175         if(!m_file.openFromMemory(data))
176         {
177             return false;
178         }
179 
180         initialize();
181         return true;
182     }
183 
184     /**
185      * Open a music from an audio file in memory.
186      *
187      * This function doesn't start playing the music (call `play()` to do so).
188      *
189      * The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC. The
190      * supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
191      *
192      * Since the music is not loaded completely but rather streamed
193      * continuously, the stream must remain available as long as the music is
194      * playing (ie. you can't deallocate it right after calling this function).
195      *
196      * Params:
197      * 		stream =	Source stream to read from
198      *
199      * Returns: true if loading succeeded, false if it failed.
200      */
201     bool openFromStream(InputStream stream)
202     {
203         stop();
204 
205         if(!m_file.openFromStream(stream))
206         {
207             return false;
208         }
209 
210         initialize();
211 
212         return true;
213     }
214 
215     /**
216      * Get the total duration of the music.
217      *
218      * Returns: Music duration
219      */
220     Time getDuration() const
221     {
222         return m_duration;
223     }
224 
225     protected
226     {
227         /**
228          * Request a new chunk of audio samples from the stream source.
229          *
230          * This function fills the chunk from the next samples to read from the
231          * audio file.
232          *
233          * Params:
234          * 		samples =	Array of samples to fill
235          *
236          * Returns: true to continue playback, false to stop.
237          */
238         override bool onGetData(ref const(short)[] samples)
239         {
240             import dsfml.system.lock;
241 
242             Lock lock = Lock(m_mutex);
243 
244             auto length = cast(size_t)m_file.read(m_samples);
245             samples = m_samples[0..length];
246 
247             return (samples.length == m_samples.length);
248         }
249 
250         /**
251          * Change the current playing position in the stream source.
252          *
253          * Params:
254          * 		timeOffset =   New playing position, from the start of the music
255          *
256          */
257         override void onSeek(Time timeOffset)
258         {
259             import dsfml.system.lock: Lock;
260 
261             auto lock = Lock(m_mutex);
262 
263             m_file.seek(timeOffset.asMicroseconds());
264         }
265     }
266 
267     private
268     {
269         /**
270          * Define the audio stream parameters.
271          *
272          * This function must be called by derived classes as soon as they know
273          * the audio settings of the stream to play. Any attempt to manipulate
274          * the stream (play(), ...) before calling this function will fail.
275          *
276          * It can be called multiple times if the settings of the audio stream
277          * change, but only when the stream is stopped.
278          *
279          * Params:
280          * 		channelCount =	Number of channels of the stream
281          * 		sampleRate =	Sample rate, in samples per second
282          */
283         void initialize()
284         {
285             size_t sampleCount = cast(size_t)m_file.getSampleCount();
286 
287             uint channelCount = m_file.getChannelCount();
288 
289             uint sampleRate = m_file.getSampleRate();
290 
291             // Compute the music duration
292             m_duration = microseconds(sampleCount * 1_000_000 / sampleRate /
293                                 channelCount);
294 
295             // Resize the internal buffer so that it can contain 1 second of audio samples
296             m_samples.length = sampleRate * channelCount;
297 
298             // Initialize the stream
299             super.initialize(channelCount, sampleRate);
300         }
301     }
302 }
303 
304 unittest
305 {
306     version(DSFML_Unittest_Audio)
307     {
308         import std.stdio;
309         import dsfml.system.clock;
310 
311         writeln("Unit test for Music Class");
312 
313         auto music = new Music();
314 
315         //TODO: update this for a real unit test users can run themselves.
316         if(!music.openFromFile("res/TestMusic.ogg"))
317         {
318             return;
319         }
320 
321         auto clock = new Clock();
322 
323         writeln("Playing music for 5 seconds");
324 
325         music.play();
326         while(clock.getElapsedTime().asSeconds() < 5)
327         {
328             //playing music in seoarate thread while main thread is stuck here
329         }
330 
331         music.stop();
332 
333         writeln();
334     }
335 }