001package jmri.jmrit.audio; 002 003import com.jogamp.openal.AL; 004import com.jogamp.openal.ALExt; 005import java.util.Queue; 006import javax.vecmath.Vector3f; 007 008/** 009 * JOAL implementation of the Audio Source sub-class. 010 * <p> 011 * For now, no system-specific implementations are forseen - this will remain 012 * internal-only 013 * <br><br><hr><br><b> 014 * This software is based on or using the JOAL Library available from 015 * <a href="http://jogamp.org/joal/www/">http://jogamp.org/joal/www/</a> 016 * </b><br><br> 017 * JOAL is released under the BSD license. The full license terms follow: 018 * <br><i> 019 * Copyright (c) 2003-2006 Sun Microsystems, Inc. All Rights Reserved. 020 * <br> 021 * Redistribution and use in source and binary forms, with or without 022 * modification, are permitted provided that the following conditions are 023 * met: 024 * <br> 025 * - Redistribution of source code must retain the above copyright 026 * notice, this list of conditions and the following disclaimer. 027 * <br> 028 * - Redistribution in binary form must reproduce the above copyright 029 * notice, this list of conditions and the following disclaimer in the 030 * documentation and/or other materials provided with the distribution. 031 * <br> 032 * Neither the name of Sun Microsystems, Inc. or the names of 033 * contributors may be used to endorse or promote products derived from 034 * this software without specific prior written permission. 035 * <br> 036 * This software is provided "AS IS," without a warranty of any kind. ALL 037 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, 038 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A 039 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN 040 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR 041 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 042 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR 043 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR 044 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 045 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, 046 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF 047 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 048 * <br> 049 * You acknowledge that this software is not designed or intended for use 050 * in the design, construction, operation or maintenance of any nuclear 051 * facility. 052 * <br><br><br></i> 053 * <hr> 054 * This file is part of JMRI. 055 * <p> 056 * JMRI is free software; you can redistribute it and/or modify it under the 057 * terms of version 2 of the GNU General Public License as published by the Free 058 * Software Foundation. See the "COPYING" file for a copy of this license. 059 * <p> 060 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 061 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 062 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 063 * 064 * @author Matthew Harris copyright (c) 2009 065 */ 066public class JoalAudioSource extends AbstractAudioSource { 067 068 private static AL al = JoalAudioFactory.getAL(); 069 070 private boolean _initialised = false; 071 072 private int[] _source = new int[1]; 073 074 private int[] _alState = new int[1]; 075 076 /** 077 * Constructor for new JoalAudioSource with system name 078 * 079 * @param systemName AudioSource object system name (e.g. IAS1) 080 */ 081 public JoalAudioSource(String systemName) { 082 super(systemName); 083 log.debug("New JoalAudioSource: {}", systemName); 084 _initialised = init(); 085 } 086 087 /** 088 * Constructor for new JoalAudioSource with system name and user name 089 * 090 * @param systemName AudioSource object system name (e.g. IAS1) 091 * @param userName AudioSource object user name 092 */ 093 public JoalAudioSource(String systemName, String userName) { 094 super(systemName, userName); 095 if (log.isDebugEnabled()) { 096 log.debug("New JoalAudioSource: {} ({})", userName, systemName); 097 } 098 _initialised = init(); 099 } 100 101 /** 102 * Initialise this AudioSource 103 * 104 * @return True if initialised 105 */ 106 private boolean init() { 107 // Check that the JoalAudioFactory exists 108 if (al == null) { 109 log.warn("Al Factory not yet initialised"); 110 return false; 111 } 112 113 // Now, check that the audio command thread exists 114 if (!isAudioAlive()) { 115 log.debug("Command thread not yet alive..."); 116 return false; 117 } else { 118 log.debug("Command thread is alive - continue."); 119 } 120 121 // Generate the AudioSource 122 al.alGenSources(1, _source, 0); 123 if (JoalAudioFactory.checkALError()) { 124 log.warn("Error creating JoalSource ({})", this.getSystemName()); 125 _source = null; 126 return false; 127 } 128 return true; 129 } 130 131 /** 132 * Queue a single AudioBuffer on this source. 133 * 134 * (called from DefaultAudioFactory command queue) 135 * 136 * @param audioBuffer AudioBuffer to queue 137 * @return True if successfully queued. 138 */ 139 @Override 140 public boolean queueAudioBuffer(AudioBuffer audioBuffer) { 141 // First check we've been initialised 142 if (!_initialised || !isAudioAlive()) { 143 log.error("Source Not Initialized: {}", this.getSystemName()); 144 return false; 145 } 146 147 if (audioBuffer instanceof JoalAudioBuffer) { 148 int[] bids = new int[1]; 149 // Make an int[] of the buffer ids 150 bids[0] = ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0]; 151 if (log.isDebugEnabled()) { 152 log.debug("Queueing Buffer: {} bid: {} Source: {}", audioBuffer.getSystemName(), ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0], this.getSystemName()); 153 } 154 155 // Bind this AudioSource to the specified AudioBuffer 156 al.alSourceQueueBuffers(_source[0], 1, bids, 0); 157 if (JoalAudioFactory.checkALError()) { 158 log.warn("Error queueing JoalSource ({}) to AudioBuffers ({})", this.getSystemName(), audioBuffer.getDisplayName()); 159 return false; 160 } 161 162 if (log.isDebugEnabled()) { 163 log.debug("Queue JoalAudioBuffer ({}) to JoalAudioSource ({})", audioBuffer.getSystemName(), this.getSystemName()); 164 } 165 return true; 166 } else { 167 throw new IllegalArgumentException(audioBuffer.getSystemName() + " is not a JoalAudioBuffer"); 168 } 169 } 170 171 /** 172 * Queue a list of AudioBuffers on this source. 173 * 174 * (called from DefaultAudioFactory command queue) 175 * 176 * @param audioBuffers AudioBuffers to queue 177 * @return True if successfully queued. 178 */ 179 @Override 180 public boolean queueAudioBuffers(Queue<AudioBuffer> audioBuffers) { 181 // First check we've been initialised 182 if (!_initialised || !isAudioAlive()) { 183 return false; 184 } 185 186 // Make an int[] of the buffer ids 187 int[] bids = new int[1]; 188 int i = 0; 189 // While the list isn't empty, pop elements and process. 190 AudioBuffer b; 191 while ((b = audioBuffers.poll()) != null) { 192 if (b instanceof JoalAudioBuffer) { 193 bids[0] = ((JoalAudioBuffer) b).getDataStorageBuffer()[0]; 194 } else { 195 throw new IllegalArgumentException(b.getSystemName() + " is not a JoalAudioBuffer"); 196 } 197 al.alSourceQueueBuffers(_source[0], 1, bids, 0); 198 if (log.isDebugEnabled()) { 199 log.debug("Queueing Buffer [{}] {}", i, b.getSystemName()); 200 } 201 i++; 202 if (JoalAudioFactory.checkALError()) { 203 log.warn("Error queueing JoalSource ({}) to AudioBuffers ({}) etc.", this.getSystemName(), b.getDisplayName()); 204 return false; 205 } 206 } 207 208 // Bind this AudioSource to the specified AudioBuffer 209 //al.alSourceQueueBuffers(_source[0], bids.length, bids, 0); 210 //al.alSourcei(_source[0], AL.AL_BUFFER, ((JoalAudioBuffer)audioBuffer).getDataStorageBuffer()[0]); 211 return true; 212 } 213 214 /** 215 * Remove all processed AudioBuffers from this Source. 216 * 217 * @return True if successful. 218 */ 219 @Override 220 public boolean unqueueAudioBuffers() { 221 // First check we've been initialised 222 if (!_initialised || !isAudioAlive()) { 223 return false; 224 } 225 226 int[] num_processed = new int[1]; 227 228 // How many processed buffers are there? 229 al.alGetSourcei(_source[0], AL.AL_BUFFERS_PROCESSED, num_processed, 0); 230 if (JoalAudioFactory.checkALError()) { 231 log.warn("Error getting # processed buffers from JoalSource ({})", this.getSystemName()); 232 return false; 233 } 234 235 // Try to unqueue them all. 236 if (num_processed[0] > 0) { 237 int[] bids = new int[num_processed[0]]; 238 al.alSourceUnqueueBuffers(_source[0], num_processed[0], bids, 0); 239 if (JoalAudioFactory.checkALError()) { 240 log.warn("Error removing {} buffers from JoalSource ({})", num_processed[0], this.getSystemName()); 241 return false; 242 } 243 } 244 if (log.isDebugEnabled()) { 245 log.debug("Removed {} buffers from JoalAudioSource ({})", num_processed[0], this.getSystemName()); 246 } 247 return (numQueuedBuffers() != 0); 248 } 249 250 @Override 251 public int numProcessedBuffers() { 252 if (!isAudioAlive()) { 253 return 0; 254 } 255 256 int[] num_processed = new int[1]; 257 258 // How many processed buffers are there? 259 al.alGetSourcei(_source[0], AL.AL_BUFFERS_PROCESSED, num_processed, 0); 260 if (JoalAudioFactory.checkALError()) { 261 log.warn("Error getting # processed buffers from JoalSource ({})", this.getSystemName()); 262 return 0; 263 } 264 return (num_processed[0]); 265 } 266 267 /** 268 * Report the number of AudioBuffers queued to this source. 269 * 270 * @return number of queued buffers. 271 */ 272 @Override 273 public int numQueuedBuffers() { 274 // First check we've been initialised 275 if (!_initialised || !isAudioAlive()) { 276 return 0; 277 } 278 279 int[] num_queued = new int[1]; 280 // How many queued buffers are there? 281 al.alGetSourcei(_source[0], AL.AL_BUFFERS_QUEUED, num_queued, 0); 282 if (JoalAudioFactory.checkALError()) { 283 log.warn("Error getting # queued buffers from JoalSource ({})", this.getSystemName()); 284 return 0; 285 } 286 287 if (log.isDebugEnabled()) { 288 log.debug("Queued {} buffers on JoalAudioSource ({})", num_queued[0], this.getSystemName()); 289 } 290 return (num_queued[0]); 291 } 292 293 @Override 294 boolean bindAudioBuffer(AudioBuffer audioBuffer) { 295 // First check we've been initialised 296 if (!_initialised || !isAudioAlive()) { 297 return false; 298 } 299 300 // Bind this AudioSource to the specified AudioBuffer 301 al.alSourcei(_source[0], AL.AL_BUFFER, ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0]); 302 if (JoalAudioFactory.checkALError()) { 303 log.warn("Error binding JoalSource ({}) to AudioBuffer ({})", this.getSystemName(), this.getAssignedBufferName()); 304 return false; 305 } 306 307 if (log.isDebugEnabled()) { 308 log.debug("Bind JoalAudioSource ({}) to JoalAudioBuffer ({})", this.getSystemName(), audioBuffer.getSystemName()); 309 } 310 return true; 311 } 312 313 @Override 314 protected void changePosition(Vector3f pos) { 315 if (_initialised && isAudioAlive()) { 316 if (_source != null) { 317 al.alSource3f(_source[0], AL.AL_POSITION, pos.x, pos.y, pos.z); 318 if (JoalAudioFactory.checkALError()) { 319 log.warn("Error updating position of JoalAudioSource ({})", this.getSystemName()); 320 } 321 } 322 } 323 } 324 325 @Override 326 public void setPositionRelative(boolean relative) { 327 super.setPositionRelative(relative); 328 if (_initialised && isAudioAlive()) { 329 al.alSourcei(_source[0], AL.AL_SOURCE_RELATIVE, relative ? AL.AL_TRUE : AL.AL_FALSE); 330 if (JoalAudioFactory.checkALError()) { 331 log.warn("Error updating relative position property of JoalAudioSource ({})", this.getSystemName()); 332 } 333 } 334 } 335 336 @Override 337 public void setVelocity(Vector3f vel) { 338 super.setVelocity(vel); 339 if (_initialised && isAudioAlive()) { 340 al.alSource3f(_source[0], AL.AL_VELOCITY, vel.x, vel.y, vel.z); 341 if (JoalAudioFactory.checkALError()) { 342 log.warn("Error updating velocity of JoalAudioSource ({})", this.getSystemName()); 343 } 344 } 345 } 346 347 @Override 348 public void setGain(float gain) { 349 super.setGain(gain); 350 if (_initialised && isAudioAlive()) { 351 calculateGain(); 352 } 353 } 354 355 @Override 356 public void setPitch(float pitch) { 357 super.setPitch(pitch); 358 if (_initialised && isAudioAlive()) { 359 al.alSourcef(_source[0], AL.AL_PITCH, pitch); 360 if (JoalAudioFactory.checkALError()) { 361 log.warn("Error updating pitch of JoalAudioSource ({})", this.getSystemName()); 362 } 363 } 364 } 365 366 @Override 367 public void setReferenceDistance(float referenceDistance) { 368 super.setReferenceDistance(referenceDistance); 369 if (_initialised && isAudioAlive()) { 370 al.alSourcef(_source[0], AL.AL_REFERENCE_DISTANCE, referenceDistance); 371 if (JoalAudioFactory.checkALError()) { 372 log.warn("Error updating reference distance of JoalAudioSource ({})", this.getSystemName()); 373 } 374 } 375 } 376 377 @Override 378 public void setOffset(long offset) { 379 super.setOffset(offset); 380 if (_initialised && isAudioAlive()) { 381 al.alSourcei(_source[0], AL.AL_SAMPLE_OFFSET, (int) getOffset()); 382 if (JoalAudioFactory.checkALError()) { 383 log.warn("Error updating Sample Offset of JoalAudioSource ({})", this.getSystemName()); 384 } 385 } 386 } 387 388 @Override 389 public void setMaximumDistance(float maximumDistance) { 390 super.setMaximumDistance(maximumDistance); 391 if (_initialised && isAudioAlive()) { 392 al.alSourcef(_source[0], AL.AL_MAX_DISTANCE, maximumDistance); 393 if (JoalAudioFactory.checkALError()) { 394 log.warn("Error updating maximum distance of JoalAudioSource ({})", this.getSystemName()); 395 } 396 } 397 } 398 399 @Override 400 public void setRollOffFactor(float rollOffFactor) { 401 super.setRollOffFactor(rollOffFactor); 402 if (_initialised && isAudioAlive()) { 403 al.alSourcef(_source[0], AL.AL_ROLLOFF_FACTOR, rollOffFactor); 404 if (JoalAudioFactory.checkALError()) { 405 log.warn("Error updating roll-off factor of JoalAudioSource ({})", this.getSystemName()); 406 } 407 } 408 } 409 410 @Override 411 public int getState() { 412 if (_source != null && isAudioAlive()) { 413 int old = _alState[0]; 414 al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, _alState, 0); 415 if (_alState[0] != old) { 416 if (_alState[0] == AL.AL_PLAYING) { 417 this.setState(STATE_PLAYING); 418 } else { 419 this.setState(STATE_STOPPED); 420 } 421 } 422 return super.getState(); 423 } else { 424 return STATE_STOPPED; 425 } 426 } 427 428 @Override 429 public void stateChanged(int oldState) { 430 super.stateChanged(oldState); 431 if (_initialised && isAudioAlive()) { 432 if (_source != null) { 433 al.alSourcef(_source[0], AL.AL_PITCH, this.getPitch()); 434 al.alSourcef(_source[0], AL.AL_GAIN, this.getGain()); 435 al.alSource3f(_source[0], AL.AL_POSITION, this.getCurrentPosition().x, this.getCurrentPosition().y, this.getCurrentPosition().z); 436 al.alSource3f(_source[0], AL.AL_VELOCITY, this.getVelocity().x, this.getVelocity().y, this.getVelocity().z); 437 al.alSourcei(_source[0], AL.AL_LOOPING, this.isLooped() ? AL.AL_TRUE : AL.AL_FALSE); 438 if (JoalAudioFactory.checkALError()) { 439 log.warn("Error updating JoalAudioSource ({})", this.getSystemName()); 440 } 441 } 442 } else { 443 _initialised = init(); 444 } 445 } 446 447 @Override 448 public int attachSourcesToEffects() { 449 if (!isAudioAlive()) { 450 return 0; 451 } 452 453 // Connect the source to the effect slot 454 al.alSource3i(_source[0], ALExt.AL_AUXILIARY_SEND_FILTER, 1, 0, 0); 455 if (JoalAudioFactory.checkALError()) { 456 log.warn("Failed to configure source send 1"); 457 return 0; 458 } 459 return 1; 460 } 461 462 @Override 463 public int detachSourcesToEffects() { 464 if (!isAudioAlive()) { 465 return 0; 466 } 467 // Disconnect the source 468 al.alSource3i(_source[0], ALExt.AL_AUXILIARY_SEND_FILTER, 0, 0, 0); 469 470 // Remove filter from source 471 if (JoalAudioFactory.checkALError()) { 472 log.warn("Failed to detach"); 473 return 0; 474 } 475 return 1; 476 } 477 478 @Override 479 protected void doPlay() { 480 log.debug("Play JoalAudioSource ({})", this.getSystemName()); 481 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 482 doRewind(); 483 doResume(); 484 } 485 } 486 487// @SuppressWarnings("SleepWhileInLoop") 488 @Override 489 protected void doStop() { 490 log.debug("Stop JoalAudioSource ({})", this.getSystemName()); 491 if (_source != null) { 492 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 493 al.alSourceStop(_source[0]); 494 doRewind(); 495 } 496 int[] myState = new int[1]; 497 al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, myState, 0); 498 boolean stopped = myState[0] != AL.AL_LOOPING; 499 while (!stopped) { 500 try { 501 Thread.sleep(5); 502 } catch (InterruptedException ex) { 503 } 504 al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, myState, 0); 505 stopped = myState[0] != AL.AL_LOOPING; 506 } 507 this.setState(STATE_STOPPED); 508 } 509 } 510 511 @Override 512 protected void doPause() { 513 log.debug("Pause JoalAudioSource ({})", this.getSystemName()); 514 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 515 al.alSourcePause(_source[0]); 516 } 517 this.setState(STATE_STOPPED); 518 } 519 520 @Override 521 protected void doResume() { 522 log.debug("Resume JoalAudioSource ({})", this.getSystemName()); 523 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 524 calculateGain(); 525 al.alSourcei(_source[0], AL.AL_LOOPING, this.isLooped() ? AL.AL_TRUE : AL.AL_FALSE); 526 al.alSourcePlay(_source[0]); 527 int numLoops = this.getNumLoops(); 528 if (numLoops > 0) { 529 if (log.isDebugEnabled()) { 530 log.debug("Create LoopThread for JoalAudioSource {}", this.getSystemName()); 531 } 532 AudioSourceLoopThread aslt = new AudioSourceLoopThread(this, numLoops); 533 aslt.start(); 534 } 535 } 536 this.setState(STATE_PLAYING); 537 } 538 539 @Override 540 protected void doRewind() { 541 log.debug("Rewind JoalAudioSource ({})", this.getSystemName()); 542 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 543 al.alSourceRewind(_source[0]); 544 } 545 } 546 547 @Override 548 protected void doFadeIn() { 549 log.debug("Fade-in JoalAudioSource ({})", this.getSystemName()); 550 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 551 doPlay(); 552 AudioSourceFadeThread asft = new AudioSourceFadeThread(this); 553 asft.start(); 554 } 555 } 556 557 @Override 558 protected void doFadeOut() { 559 log.debug("Fade-out JoalAudioSource ({})", this.getSystemName()); 560 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 561 AudioSourceFadeThread asft = new AudioSourceFadeThread(this); 562 asft.start(); 563 } 564 } 565 566 @Override 567 protected void cleanup() { 568 log.debug("Cleanup JoalAudioSource ({})", this.getSystemName()); 569 int[] source_type = new int[1]; 570 al.alGetSourcei(_source[0], AL.AL_SOURCE_TYPE, source_type, 0); 571 if (_initialised && (isBound() || isQueued() || source_type[0] == AL.AL_UNDETERMINED) || source_type[0] == AL.AL_STREAMING) { 572 al.alSourceStop(_source[0]); 573 al.alDeleteSources(1, _source, 0); 574 this._source = null; 575 log.debug("...done cleanup"); 576 } 577 } 578 579 @Override 580 protected void calculateGain() { 581 // Adjust gain based on master gain for this source and any 582 // calculated fade gains 583 float currentGain = this.getGain() * this.getFadeGain(); 584 585 // If playing, update the gain 586 if (_initialised && isAudioAlive()) { 587 al.alSourcef(_source[0], AL.AL_GAIN, currentGain); 588 if (JoalAudioFactory.checkALError()) { 589 log.warn("Error updating gain setting of JoalAudioSource ({})", this.getSystemName()); 590 } 591 if (log.isDebugEnabled()) { 592 log.debug("Set current gain of JoalAudioSource {} to {}", this.getSystemName(), currentGain); 593 } 594 } 595 } 596 597 /** 598 * Internal method to return a reference to the OpenAL source buffer 599 * 600 * @return source buffer 601 */ 602 private int[] getSourceBuffer() { 603 return this._source; 604 } 605 606 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JoalAudioSource.class); 607 608 /** 609 * An internal class used to create a new thread to monitor looping as, 610 * unlike JavaSound, OpenAL (and, therefore, JOAL) do not provide a 611 * convenient method to loop a sound a specific number of times. 612 */ 613 private static class AudioSourceLoopThread extends AbstractAudioThread { 614 615 /** 616 * Number of times to loop this source 617 */ 618 private int numLoops; 619 620 /** 621 * Reference to the OpenAL source buffer 622 */ 623 private int[] sourceBuffer; 624 625 /** 626 * Constructor that takes handle to looping AudioSource to monitor 627 * 628 * @param audioSource looping AudioSource to monitor 629 * @param numLoops number of loops for this AudioSource to make 630 */ 631 AudioSourceLoopThread(JoalAudioSource audioSource, int numLoops) { 632 super(); 633 this.setName("loopsrc-" + super.getName()); 634 this.sourceBuffer = audioSource.getSourceBuffer(); 635 this.numLoops = numLoops; 636 if (log.isDebugEnabled()) { 637 log.debug("Created AudioSourceLoopThread for AudioSource {} loopcount {}", 638 audioSource.getSystemName(), this.numLoops); 639 } 640 } 641 642 /** 643 * Main processing loop 644 */ 645 @Override 646 public void run() { 647 648 // Current loop count 649 int loopCount = 0; 650 651 // Previous position 652 float oldPos = 0; 653 654 // Current position 655 float[] newPos = new float[1]; 656 657 // Turn on looping 658 JoalAudioSource.al.alSourcei(sourceBuffer[0], AL.AL_LOOPING, AL.AL_TRUE); 659 660 while (!dying()) { 661 662 // Determine current position 663 JoalAudioSource.al.alGetSourcef(sourceBuffer[0], AL.AL_SEC_OFFSET, newPos, 0); 664 665 // Check if it is smaller than the previous position 666 // If so, we've looped so increment the loop counter 667 if (oldPos > newPos[0]) { 668 loopCount++; 669 log.debug("Loop count {}", loopCount); 670 } 671 oldPos = newPos[0]; 672 673 // Check if we've performed sufficient iterations 674 if (loopCount >= numLoops) { 675 die(); 676 } 677 678 // sleep for a while so as not to overload CPU 679 snooze(20); 680 } 681 682 // Turn off looping 683 JoalAudioSource.al.alSourcei(sourceBuffer[0], AL.AL_LOOPING, AL.AL_FALSE); 684 685 // Finish up 686 if (log.isDebugEnabled()) { 687 log.debug("Clean up thread {}", this.getName()); 688 } 689 cleanup(); 690 } 691 692 /** 693 * Shuts this thread down and clears references to created objects 694 */ 695 @Override 696 protected void cleanup() { 697 // Thread is to shutdown 698 die(); 699 700 // Clear references to objects 701 this.sourceBuffer = null; 702 703 // Finalise cleanup in super-class 704 super.cleanup(); 705 } 706 } 707}