1 module polyplex.core.audio.music; 2 import polyplex.core.audio; 3 import polyplex.core.audio.effect; 4 import ppc.types.audio; 5 import openal; 6 import ppc.backend.cfile; 7 import polyplex.math; 8 import std.concurrency; 9 import std.stdio; 10 import polyplex.utils.logging; 11 import core.thread; 12 import core.time; 13 import core.sync.mutex; 14 15 // This is set to true on application close, so that threads know to quit. 16 protected __gshared bool shouldStop; 17 protected __gshared int openMusicChannels; 18 19 package(polyplex) void stopMusicThread() { 20 shouldStop = true; 21 22 Logger.Info("Cleaning up music threads... {0}", openMusicChannels); 23 while (openMusicChannels > 0) {} 24 } 25 26 private void iHandlerLaunch() { 27 synchronized { 28 openMusicChannels++; 29 } 30 } 31 32 private void iHandlerStop() { 33 synchronized { 34 openMusicChannels--; 35 } 36 } 37 38 protected void MusicHandlerThread(Music iMus) { 39 try { 40 iHandlerLaunch; 41 int buffersProcessed; 42 ALuint deqBuff; 43 ALint state; 44 byte[] streamBuffer = new byte[iMus.bufferSize]; 45 46 size_t startBufferSize = Music.BufferSize; 47 48 // Stop thread if requested. 49 while (iMus !is null && &shouldStop !is null && !shouldStop && !iMus.shouldStopInternal) { 50 51 // Sleep needed to make the CPU NOT run at 100% at all times 52 Thread.sleep(10.msecs); 53 54 // Check if we have any buffers to fill 55 alGetSourcei(iMus.source, AL_BUFFERS_PROCESSED, &buffersProcessed); 56 iMus.totalProcessed += buffersProcessed; 57 while(buffersProcessed > 0) { 58 59 // Find the ID of the buffer to fill 60 deqBuff = 0; 61 alSourceUnqueueBuffers(iMus.source, 1, &deqBuff); 62 63 // Resize buffers if needed 64 if (startBufferSize != Music.BufferSize) { 65 streamBuffer.length = Music.BufferSize; 66 startBufferSize = Music.BufferSize; 67 iMus.bufferSize = cast(int)Music.BufferSize; 68 } 69 70 // Read audio from stream in 71 size_t readData = iMus.stream.read(streamBuffer.ptr, iMus.bufferSize); 72 if (readData == 0) { 73 if (!iMus.looping) { 74 iMus.playing = false; 75 iHandlerStop; 76 return; 77 } 78 iMus.stream.seek; 79 readData = iMus.stream.read(streamBuffer.ptr, iMus.bufferSize); 80 } 81 82 // Send to OpenAL 83 alBufferData(deqBuff, iMus.fFormat, streamBuffer.ptr, cast(int)readData, cast(int)iMus.stream.info.bitrate); 84 alSourceQueueBuffers(iMus.source, 1, &deqBuff); 85 86 buffersProcessed--; 87 } 88 89 // OpenAL will stop the stream if it runs out of buffers 90 // Ensure that if OpenAL stops, we start it again. 91 alGetSourcei(iMus.source, AL_SOURCE_STATE, &state); 92 if (state != AL_PLAYING) { 93 iMus.xruns++; 94 Logger.Warn("Music buffer X-run!"); 95 alSourcePlay(iMus.source); 96 } 97 } 98 } catch(Exception ex) { 99 Logger.Err("MusicHandlerException: {0}", ex.message); 100 } catch (Error err) { 101 Logger.Err("MusicHandlerError: {0}", err.message); 102 } 103 iHandlerStop; 104 } 105 106 public class Music { 107 private: 108 // Internal thread-communication data. 109 size_t totalProcessed; 110 Thread musicThread; 111 bool shouldStopInternal; 112 113 // Buffer 114 Audio stream; 115 ALuint[] buffer; 116 AudioRenderFormats fFormat; 117 118 // Buffer sizing 119 int bufferSize; 120 int buffers; 121 122 // Settings 123 bool looping; 124 bool paused; 125 bool playing; 126 127 int xruns; 128 129 // Effects & filters 130 AudioEffect attachedEffect; 131 AudioFilter attachedFilter; 132 133 void applyEffectsAndFilters() { 134 ALuint efId = attachedEffect !is null ? attachedEffect.Id : AL_EFFECTSLOT_NULL; 135 ALuint flId = attachedFilter !is null ? attachedFilter.Id : AL_FILTER_NULL; 136 137 Logger.Debug("Applying effect {0} and filter {1} on Music {2}...", efId, flId, source); 138 139 alSource3i(source, AL_AUXILIARY_SEND_FILTER, efId, 0, flId); 140 alSourcei(source, AL_DIRECT_FILTER, flId); 141 ErrCodes err = cast(ErrCodes)alGetError(); 142 143 import std.conv; 144 if (cast(ALint)err != AL_NO_ERROR) throw new Exception("Failed to attach effect and/or filter to SoundEffect instance "~err.to!string); 145 } 146 147 // Source 148 ALuint source; 149 150 public: 151 152 /// Changes the size of the buffers for music playback. 153 /// Changes take effect immidiately 154 static size_t BufferSize = 48_000; 155 156 /// Amount of buffers to provision per audio track 157 /// Takes effect on new audio load. 158 static size_t BufferCount = 16; 159 160 /// Constructs a Music via an Audio source. 161 // TODO: Optimize enough so that we can have fewer buffers 162 this(Audio audio, AudioRenderFormats format = AudioRenderFormats.Auto) { 163 stream = audio; 164 165 // Select format if told to. 166 if (format == AudioRenderFormats.Auto) { 167 import std.conv; 168 if (stream.info.channels == 1) fFormat = AudioRenderFormats.Mono16; 169 else if (stream.info.channels == 2) fFormat = AudioRenderFormats.Stereo16; 170 else throw new Exception("Unsupported amount of channels! " ~ stream.info.channels.to!string); 171 } 172 173 // Clear errors 174 alGetError(); 175 176 // Prepare buffer arrays 177 this.buffers = cast(int)BufferCount; 178 buffer = new ALuint[buffers]; 179 180 this.bufferSize = (cast(int)BufferSize)*stream.info.channels; 181 182 alGenBuffers(buffers, buffer.ptr); 183 184 // Prepare sources 185 alGenSources(1, &source); 186 187 prestream(); 188 189 Logger.VerboseDebug("Created new Music! source={3} buffers={0} bufferSize={1} ids={2}", buffers, bufferSize, buffer, source); 190 } 191 192 ~this() { 193 cleanup(); 194 } 195 196 private void cleanup() { 197 // Cleanup procedure 198 alDeleteSources(1, &source); 199 alDeleteBuffers(buffers, buffer.ptr); 200 } 201 202 private void prestream() { 203 byte[] streamBuffer = new byte[bufferSize]; 204 205 // Pre-read some data. 206 foreach (i; 0 .. buffers) { 207 208 // Read audio from stream in 209 size_t read = stream.read(streamBuffer.ptr, bufferSize); 210 211 // Send to OpenAL 212 alBufferData(buffer[i], this.fFormat, streamBuffer.ptr, cast(int)read, cast(int)stream.info.bitrate); 213 alSourceQueueBuffers(source, 1, &buffer[i]); 214 215 } 216 } 217 218 // Spawns player thread. 219 private void spawnHandler() { 220 if (musicThread !is null && musicThread.isRunning) return; 221 222 musicThread = new Thread({ 223 MusicHandlerThread(this); 224 }); 225 musicThread.start(); 226 } 227 228 /// Play music 229 void Play(bool isLooping = false) { 230 synchronized { 231 if (musicThread is null || !musicThread.isRunning) spawnHandler(); 232 233 this.Looping = isLooping; 234 alSourcePlay(source); 235 236 paused = false; 237 playing = true; 238 } 239 } 240 241 /// Stop playing music 242 /// Does nothing if music isn't playing. 243 void Stop() { 244 synchronized { 245 if (musicThread is null) return; 246 247 // Tell thread to die repeatedly, until it does. 248 while(musicThread.isRunning) shouldStopInternal = true; 249 250 // Stop source and prepare for playing again 251 alSourceStop(source); 252 stream.seek(); 253 prestream(); 254 255 playing = false; 256 } 257 } 258 259 /// Pause music 260 void Pause() { 261 synchronized { 262 alSourcePause(source); 263 paused = true; 264 playing = false; 265 } 266 } 267 268 /// Gets the current position in the music stream (in samples) 269 size_t Tell() { 270 synchronized { 271 return stream.tell; 272 } 273 } 274 275 /// Seeks the music stream (in samples) 276 void Seek(size_t position = 0) { 277 synchronized { 278 if (position >= Length) position = Length-1; 279 // Stop stream 280 alSourceStop(source); 281 282 // Unqueue everything 283 alSourceUnqueueBuffers(source, buffers, buffer.ptr); 284 285 // Refill buffers 286 stream.seekSample(position); 287 prestream(); 288 289 // Resume 290 if (playing) alSourcePlay(source); 291 } 292 } 293 294 /// Gets length of music (in samples) 295 size_t Length() { 296 return stream.info.pcmLength; 297 } 298 299 /// Get amount of unhandled XRuns that has happened. 300 int XRuns() { 301 synchronized { 302 return xruns; 303 } 304 } 305 306 /// Mark XRuns as handled. 307 void HandledXRun() { 308 synchronized { 309 xruns = 0; 310 } 311 } 312 313 /// Gets wether the music is paused. 314 bool Paused() { 315 return paused; 316 } 317 318 /// Gets wether the music is playing 319 bool Playing() { 320 return playing; 321 } 322 323 @property AudioEffect Effect() { 324 return this.attachedEffect; 325 } 326 327 @property void Effect(AudioEffect effect) { 328 attachedEffect = effect; 329 applyEffectsAndFilters(); 330 } 331 332 @property AudioFilter Filter() { 333 return this.attachedFilter; 334 } 335 336 @property void Filter(AudioFilter filter) { 337 attachedFilter = filter; 338 applyEffectsAndFilters(); 339 } 340 341 @property bool Looping() { 342 return looping; 343 } 344 @property void Looping(bool val) { looping = val; } 345 346 @property int ByteOffset() { 347 int v = 0; 348 alGetSourcei(source, AL_BYTE_OFFSET, &v); 349 return v; 350 } 351 352 @property int SecondOffset() { 353 int v = 0; 354 alGetSourcei(source, AL_SEC_OFFSET, &v); 355 return v; 356 } 357 358 @property int SampleOffset() { 359 int v = 0; 360 alGetSourcei(source, AL_SAMPLE_OFFSET, &v); 361 return v; 362 } 363 364 /* 365 PITCH 366 */ 367 @property float Pitch() { 368 float v = 0f; 369 alGetSourcef(source, AL_PITCH, &v); 370 return v; 371 } 372 @property void Pitch(float val) { alSourcef(source, AL_PITCH, val); } 373 374 /* 375 GAIN 376 */ 377 @property float Gain() { 378 float v = 0f; 379 alGetSourcef(source, AL_GAIN, &v); 380 return v; 381 } 382 @property void Gain(float val) { alSourcef(source, AL_GAIN, val); } 383 384 /* 385 MIN GAIN 386 */ 387 @property float MinGain() { 388 float v = 0f; 389 alGetSourcef(source, AL_MIN_GAIN, &v); 390 return v; 391 } 392 @property void MinGain(float val) { alSourcef(source, AL_MIN_GAIN, val); } 393 394 /* 395 MAX GAIN 396 */ 397 @property float MaxGain() { 398 float v = 0f; 399 alGetSourcef(source, AL_MAX_GAIN, &v); 400 return v; 401 } 402 @property void MaxGain(float val) { alSourcef(source, AL_MAX_GAIN, val); } 403 404 /* 405 MAX DISTANCE 406 */ 407 @property float MaxDistance() { 408 float v = 0f; 409 alGetSourcef(source, AL_MAX_DISTANCE, &v); 410 return v; 411 } 412 @property void MaxDistance(float val) { alSourcef(source, AL_MAX_DISTANCE, val); } 413 414 /* 415 ROLLOFF FACTOR 416 */ 417 @property float RolloffFactor() { 418 float v = 0f; 419 alGetSourcef(source, AL_ROLLOFF_FACTOR, &v); 420 return v; 421 } 422 @property void RolloffFactor(float val) { alSourcef(source, AL_ROLLOFF_FACTOR, val); } 423 424 /* 425 CONE OUTER GAIN 426 */ 427 @property float ConeOuterGain() { 428 float v = 0f; 429 alGetSourcef(source, AL_CONE_OUTER_GAIN, &v); 430 return v; 431 } 432 @property void ConeOuterGain(float val) { alSourcef(source, AL_CONE_OUTER_GAIN, val); } 433 434 /* 435 CONE INNER ANGLE 436 */ 437 @property float ConeInnerAngle() { 438 float v = 0f; 439 alGetSourcef(source, AL_CONE_INNER_ANGLE, &v); 440 return v; 441 } 442 @property void ConeInnerAngle(float val) { alSourcef(source, AL_CONE_INNER_ANGLE, val); } 443 444 /* 445 CONE OUTER ANGLE 446 */ 447 @property float ConeOuterAngle() { 448 float v = 0f; 449 alGetSourcef(source, AL_CONE_OUTER_ANGLE, &v); 450 return v; 451 } 452 @property void ConeOuterAngle(float val) { alSourcef(source, AL_CONE_OUTER_ANGLE, val); } 453 454 /* 455 REFERENCE DISTANCE 456 */ 457 @property float ReferenceDistance() { 458 float v = 0f; 459 alGetSourcef(source, AL_REFERENCE_DISTANCE, &v); 460 return v; 461 } 462 @property void ReferenceDistance(float val) { alSourcef(source, AL_REFERENCE_DISTANCE, val); } 463 464 /* 465 POSITION 466 */ 467 @property Vector3 Position() { 468 Vector3 v = 0f; 469 alGetSourcefv(source, AL_POSITION, v.ptr); 470 return v; 471 } 472 @property void Position(Vector3 val) { alSourcefv(source, AL_POSITION, val.ptr); } 473 474 /* 475 VELOCITY 476 */ 477 @property Vector3 Velocity() { 478 Vector3 v = 0f; 479 alGetSourcefv(source, AL_VELOCITY, v.ptr); 480 return v; 481 } 482 @property void Velocity(Vector3 val) { alSourcefv(source, AL_VELOCITY, val.ptr); } 483 484 /* 485 DIRECTION 486 */ 487 @property Vector3 Direction() { 488 Vector3 v = 0f; 489 alGetSourcefv(source, AL_DIRECTION, v.ptr); 490 return v; 491 } 492 @property void Direction(Vector3 val) { alSourcefv(source, AL_DIRECTION, val.ptr); } 493 }