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 }