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