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 }