001package jmri.jmrit.audio; 002 003import java.util.LinkedList; 004import java.util.Queue; 005import java.util.concurrent.ThreadLocalRandom; 006import javax.annotation.Nonnull; 007import javax.vecmath.Vector3f; 008import jmri.Audio; 009import jmri.AudioManager; 010import jmri.InstanceManager; 011import jmri.implementation.AbstractAudio; 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015/** 016 * Base implementation of the AudioSource class. 017 * <p> 018 * Specific implementations will extend this base class. 019 * <br> 020 * <hr> 021 * This file is part of JMRI. 022 * <p> 023 * JMRI is free software; you can redistribute it and/or modify it under the 024 * terms of version 2 of the GNU General Public License as published by the Free 025 * Software Foundation. See the "COPYING" file for a copy of this license. 026 * <p> 027 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 028 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 029 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 030 * 031 * @author Matthew Harris copyright (c) 2009 032 */ 033public abstract class AbstractAudioSource extends AbstractAudio implements AudioSource { 034 035 private Vector3f position = new Vector3f(0.0f, 0.0f, 0.0f); 036 private Vector3f currentPosition = new Vector3f(0.0f, 0.0f, 0.0f); 037 private Vector3f velocity = new Vector3f(0.0f, 0.0f, 0.0f); 038 private float gain = 1.0f; 039 private float pitch = 1.0f; 040 private float referenceDistance = 1.0f; 041 private float maximumDistance = Audio.MAX_DISTANCE; 042 private float rollOffFactor = 1.0f; 043 private int minLoops = LOOP_NONE; 044 private int maxLoops = LOOP_NONE; 045 private int numLoops = 0; 046 // private int minLoopDelay = 0; 047 // private int maxLoopDelay = 0; 048 // private int loopDelay = 0; 049 private int fadeInTime = 1000; 050 private int fadeOutTime = 1000; 051 private float fadeGain = 1.0f; 052 private long timeOfLastFadeCheck = 0; 053 private long timeOfLastPositionCheck = 0; 054 private int fading = Audio.FADE_NONE; 055 private boolean bound = false; 056 private boolean positionRelative = false; 057 private boolean queued = false; 058 private long offset = 0; 059 private AudioBuffer buffer; 060 // private AudioSourceDelayThread asdt = null; 061 private LinkedList<AudioBuffer> pendingBufferQueue = new LinkedList<>(); 062 063 private static final AudioFactory activeAudioFactory = InstanceManager.getDefault(jmri.AudioManager.class).getActiveAudioFactory(); 064 private static float metersPerUnit; 065 066 /** 067 * Abstract constructor for new AudioSource with system name 068 * 069 * @param systemName AudioSource object system name (e.g. IAS1) 070 */ 071 public AbstractAudioSource(String systemName) { 072 super(systemName); 073 AudioListener al = activeAudioFactory.getActiveAudioListener(); 074 if (al != null) { 075 storeMetersPerUnit(al.getMetersPerUnit()); 076 } 077 } 078 079 /** 080 * Abstract constructor for new AudioSource with system name and user name 081 * 082 * @param systemName AudioSource object system name (e.g. IAS1) 083 * @param userName AudioSource object user name 084 */ 085 public AbstractAudioSource(String systemName, String userName) { 086 super(systemName, userName); 087 AudioListener al = activeAudioFactory.getActiveAudioListener(); 088 if (al != null) { 089 storeMetersPerUnit(al.getMetersPerUnit()); 090 } 091 } 092 093 private static void storeMetersPerUnit(float newVal) { 094 metersPerUnit = newVal; 095 } 096 097 public boolean isAudioAlive() { 098 return ((AudioThread) activeAudioFactory.getCommandThread()).isThreadAlive(); 099 } 100 101 @Override 102 public char getSubType() { 103 return SOURCE; 104 } 105 106 @Override 107 public boolean queueBuffers(Queue<AudioBuffer> audioBuffers) { 108 // Note: Cannot queue buffers to a Source that has a bound buffer. 109 if (!bound) { 110 this.pendingBufferQueue = new LinkedList<>(audioBuffers); 111 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_QUEUE_BUFFERS)); 112 activeAudioFactory.getCommandThread().interrupt(); 113 if (log.isDebugEnabled() && (audioBuffers.peek() != null)) { 114 log.debug("Queued Buffer {} to Source {}", audioBuffers.peek().getSystemName(), this.getSystemName()); 115 } 116 return true; 117 } else { 118 if (audioBuffers.peek() != null) { 119 log.error("Attempted to queue buffers {} (etc) to Bound Source {}", audioBuffers.peek().getSystemName(), this.getSystemName()); 120 } 121 return false; 122 } 123 } 124 125 @Override 126 public boolean queueBuffer(AudioBuffer audioBuffer) { 127 if (!bound) { 128 //this.pendingBufferQueue.clear(); 129 this.pendingBufferQueue.add(audioBuffer); 130 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_QUEUE_BUFFERS)); 131 activeAudioFactory.getCommandThread().interrupt(); 132 if (log.isDebugEnabled()) { 133 log.debug("Queued Buffer {} to Source {}", audioBuffer.getSystemName(), this.getSystemName()); 134 } 135 return true; 136 } else { 137 log.error("Attempted to queue buffer {} to Bound Source {}", audioBuffer.getSystemName(), this.getSystemName()); 138 return false; 139 } 140 } 141 142 @Override 143 public boolean unqueueBuffers() { 144 if (bound) { 145 log.error("Attempted to unqueue buffers on Bound Source {}", this.getSystemName()); 146 return false; 147 } else if (queued) { 148 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_UNQUEUE_BUFFERS)); 149 activeAudioFactory.getCommandThread().interrupt(); 150 if (log.isDebugEnabled()) { 151 log.debug("Unqueued Processed Buffers on Source {}", this.getSystemName()); 152 } 153 return true; 154 } else { 155 log.debug("Source neither queued nor bound. Not an error. {}", this.getSystemName()); 156 return false; 157 } 158 } 159 160 public Queue<AudioBuffer> getQueuedBuffers() { 161 return this.pendingBufferQueue; 162 } 163 164 @Override 165 public void setAssignedBuffer(AudioBuffer audioBuffer) { 166 if (!queued) { 167 this.buffer = audioBuffer; 168 // Ensure that the source is stopped 169 this.stop(false); 170 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_BIND_BUFFER)); 171 activeAudioFactory.getCommandThread().interrupt(); 172 if (log.isDebugEnabled()) { 173 log.debug("Assigned Buffer {} to Source {}", audioBuffer.getSystemName(), this.getSystemName()); 174 } 175 } else { 176 log.error("Attempted to assign buffer {} to Queued Source {}", audioBuffer.getSystemName(), this.getSystemName()); 177 } 178 } 179 180 @Override 181 public void setAssignedBuffer(String bufferSystemName) { 182 if (!queued) { 183 AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class); 184 Audio a = am.getBySystemName(bufferSystemName); 185 if (a != null && a.getSubType() == Audio.BUFFER) { 186 setAssignedBuffer((AudioBuffer) a); 187 } else { 188 log.warn("Attempt to assign incorrect object type to buffer - AudioBuffer expected."); 189 this.buffer = null; 190 this.bound = false; 191 } 192 } else { 193 log.error("Attempted to assign buffer {} to Queued Source {}", bufferSystemName, this.getSystemName()); 194 } 195 } 196 197 @Override 198 public AudioBuffer getAssignedBuffer() { 199 return this.buffer; 200 } 201 202 @Override 203 public String getAssignedBufferName() { 204 return (buffer != null) ? buffer.getSystemName() : "[none]"; 205 } 206 207 @Override 208 public void setPosition(Vector3f pos) { 209 this.position = pos; 210 this.currentPosition = pos; 211 changePosition(pos); 212 if (log.isDebugEnabled()) { 213 log.debug("Set position of Source {} to {}", this.getSystemName(), pos); 214 } 215 } 216 217 @Override 218 public void setPosition(float x, float y, float z) { 219 this.setPosition(new Vector3f(x, y, z)); 220 } 221 222 @Override 223 public void setPosition(float x, float y) { 224 this.setPosition(new Vector3f(x, y, 0.0f)); 225 } 226 227 @Override 228 public Vector3f getPosition() { 229 return this.position; 230 } 231 232 @Override 233 public Vector3f getCurrentPosition() { 234 return this.currentPosition; 235 } 236 237 @Override 238 public void setPositionRelative(boolean relative) { 239 this.positionRelative = relative; 240 } 241 242 @Override 243 public boolean isPositionRelative() { 244 return this.positionRelative; 245 } 246 247 @Override 248 public void setVelocity(Vector3f vel) { 249 this.velocity = vel; 250 if (log.isDebugEnabled()) { 251 log.debug("Set velocity of Source {} to {}", this.getSystemName(), vel); 252 } 253 } 254 255 @Override 256 public Vector3f getVelocity() { 257 return this.velocity; 258 } 259 260 /** 261 * Calculate current position based on velocity. 262 */ 263 protected void calculateCurrentPosition() { 264 265 // Calculate how long it's been since we lasted checked position 266 long currentTime = System.currentTimeMillis(); 267 float timePassed = (currentTime - this.timeOfLastPositionCheck); 268 this.timeOfLastPositionCheck = currentTime; 269 270 log.debug("timePassed = {} metersPerUnit = {} source = {} state = {}", timePassed, metersPerUnit, this.getSystemName(), this.getState()); 271 if (this.velocity.length() != 0) { 272 this.currentPosition.scaleAdd((timePassed / 1000) * metersPerUnit, 273 this.velocity, 274 this.currentPosition); 275 changePosition(this.currentPosition); 276 if (log.isDebugEnabled()) { 277 log.debug("Set current position of Source {} to {}", this.getSystemName(), this.currentPosition); 278 } 279 } 280 } 281 282 @Override 283 public void resetCurrentPosition() { 284 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_RESET_POSITION)); 285 activeAudioFactory.getCommandThread().interrupt(); 286 } 287 288 /** 289 * Reset the current position. 290 */ 291 protected void doResetCurrentPosition() { 292 this.currentPosition = this.position; 293 } 294 295 /** 296 * Change the current position of this source. 297 * 298 * @param pos new position 299 */ 300 abstract protected void changePosition(Vector3f pos); 301 302 @Override 303 public void setGain(float gain) { 304 this.gain = gain; 305 if (log.isDebugEnabled()) { 306 log.debug("Set gain of Source {} to {}", this.getSystemName(), gain); 307 } 308 } 309 310 @Override 311 public float getGain() { 312 return this.gain; 313 } 314 315 /** 316 * Calculate the gain of this AudioSource based on distance from 317 * listener and fade levels. 318 */ 319 abstract protected void calculateGain(); 320 321 @Override 322 public void setPitch(float pitch) { 323 if (pitch < 0.5f) { 324 pitch = 0.5f; 325 } 326 if (pitch > 2.0f) { 327 pitch = 2.0f; 328 } 329 this.pitch = pitch; 330 if (log.isDebugEnabled()) { 331 log.debug("Set pitch of Source {} to {}", this.getSystemName(), pitch); 332 } 333 } 334 335 @Override 336 public float getPitch() { 337 return this.pitch; 338 } 339 340 @Override 341 public void setReferenceDistance(float referenceDistance) { 342 if (referenceDistance < 0.0f) { 343 referenceDistance = 0.0f; 344 } 345 this.referenceDistance = referenceDistance; 346 if (log.isDebugEnabled()) { 347 log.debug("Set reference distance of Source {} to {}", this.getSystemName(), referenceDistance); 348 } 349 } 350 351 @Override 352 public float getReferenceDistance() { 353 return this.referenceDistance; 354 } 355 356 @Override 357 public void setOffset(long offset) { 358 if (offset < 0) { 359 offset = 0; 360 } 361 if (offset > this.buffer.getLength()) { 362 offset = this.buffer.getLength(); 363 } 364 this.offset = offset; 365 if (log.isDebugEnabled()) { 366 log.debug("Set byte offset of Source {}to {}", this.getSystemName(), offset); 367 } 368 } 369 370 @Override 371 public long getOffset() { 372 return this.offset; 373 } 374 375 @Override 376 public void setMaximumDistance(float maximumDistance) { 377 if (maximumDistance < 0.0f) { 378 maximumDistance = 0.0f; 379 } 380 this.maximumDistance = maximumDistance; 381 if (log.isDebugEnabled()) { 382 log.debug("Set maximum distance of Source {} to {}", this.getSystemName(), maximumDistance); 383 } 384 } 385 386 @Override 387 public float getMaximumDistance() { 388 return this.maximumDistance; 389 } 390 391 @Override 392 public void setRollOffFactor(float rollOffFactor) { 393 this.rollOffFactor = rollOffFactor; 394 if (log.isDebugEnabled()) { 395 log.debug("Set roll-off factor of Source {} to {}", this.getSystemName(), rollOffFactor); 396 } 397 } 398 399 @Override 400 public float getRollOffFactor() { 401 return this.rollOffFactor; 402 } 403 404 @Override 405 public void setLooped(boolean loop) { 406 if (loop) { 407 this.minLoops = LOOP_CONTINUOUS; 408 this.maxLoops = LOOP_CONTINUOUS; 409 } else { 410 this.minLoops = LOOP_NONE; 411 this.maxLoops = LOOP_NONE; 412 } 413 calculateLoops(); 414 } 415 416 @Override 417 public boolean isLooped() { 418 return (this.minLoops != LOOP_NONE || this.maxLoops != LOOP_NONE); 419 } 420 421 @Override 422 public void setMinLoops(int loops) { 423 if (this.maxLoops < loops) { 424 this.maxLoops = loops; 425 } 426 this.minLoops = loops; 427 calculateLoops(); 428 } 429 430 @Override 431 public int getMinLoops() { 432 return this.minLoops; 433 } 434 435 @Override 436 public void setMaxLoops(int loops) { 437 if (this.minLoops > loops) { 438 this.minLoops = loops; 439 } 440 this.maxLoops = loops; 441 calculateLoops(); 442 } 443 444 /** 445 * Calculate the number of times to loop playback of this sound. 446 */ 447 protected void calculateLoops() { 448 if (this.minLoops != this.maxLoops) { 449 this.numLoops = this.minLoops + ThreadLocalRandom.current().nextInt(this.maxLoops - this.minLoops); 450 } else { 451 this.numLoops = this.minLoops; 452 } 453 } 454 455 @Override 456 public int getMaxLoops() { 457 return this.maxLoops; 458 } 459 460 @Override 461 public int getNumLoops() { 462 // Call the calculate method each time so as to ensure 463 // randomness when min and max are not equal 464 calculateLoops(); 465 return this.numLoops; 466 } 467 468 @Override 469 public int getLastNumLoops() { 470 return this.numLoops; 471 } 472 473// public void setMinLoopDelay(int loopDelay) { 474// if (this.maxLoopDelay < loopDelay) { 475// this.maxLoopDelay = loopDelay; 476// } 477// this.minLoopDelay = loopDelay; 478// calculateLoopDelay(); 479// } 480// 481// public int getMinLoopDelay() { 482// return this.minLoopDelay; 483// } 484// 485// public void setMaxLoopDelay(int loopDelay) { 486// if (this.minLoopDelay > loopDelay) { 487// this.minLoopDelay = loopDelay; 488// } 489// this.maxLoopDelay = loopDelay; 490// calculateLoopDelay(); 491// } 492// 493// public int getMaxLoopDelay() { 494// return this.maxLoopDelay; 495// } 496// 497// public int getLoopDelay() { 498// // Call the calculate method each time so as to ensure 499// // randomness when min and max are not equal 500// calculateLoopDelay(); 501// return this.loopDelay; 502// } 503// 504// /** 505// * Method to calculate the delay between subsequent loops of this source 506// */ 507// protected void calculateLoopDelay() { 508// if (this.minLoopDelay != this.maxLoopDelay) { 509// Random r = new Random(); 510// this.loopDelay = this.minLoopDelay + r.nextInt(this.maxLoopDelay-this.minLoopDelay); 511// } else { 512// this.loopDelay = this.minLoopDelay; 513// } 514// } 515 @Override 516 public void setFadeIn(int fadeInTime) { 517 this.fadeInTime = fadeInTime; 518 } 519 520 @Override 521 public int getFadeIn() { 522 return this.fadeInTime; 523 } 524 525 @Override 526 public void setFadeOut(int fadeOutTime) { 527 this.fadeOutTime = fadeOutTime; 528 } 529 530 @Override 531 public int getFadeOut() { 532 return this.fadeOutTime; 533 } 534 535 /** 536 * Used to return the current calculated fade gain for this AudioSource. 537 * 538 * @return current fade gain 539 */ 540 protected float getFadeGain() { 541 return this.fadeGain; 542 } 543 544 /** 545 * Calculate the fade gains. 546 */ 547 protected void calculateFades() { 548 549 // Calculate how long it's been since we lasted checked fade gains 550 long currentTime = System.currentTimeMillis(); 551 long timePassed = currentTime - this.timeOfLastFadeCheck; 552 this.timeOfLastFadeCheck = currentTime; 553 554 switch (this.fading) { 555 case Audio.FADE_NONE: 556 // Reset fade gain 557 this.fadeGain = 1.0f; 558 break; 559 case Audio.FADE_OUT: 560 // Calculate fade-out gain 561 this.fadeGain -= roundDecimal(timePassed) / (this.getFadeOut()); 562 563 // Ensure that fade-out gain is not less than 0.0f 564 if (this.fadeGain < 0.0f) { 565 this.fadeGain = 0.0f; 566 567 // If so, we're done fading 568 this.fading = Audio.FADE_NONE; 569 } 570 if (log.isDebugEnabled()) { 571 log.debug("Set fade out gain of AudioSource {} to {}", this.getSystemName(), this.fadeGain); 572 } 573 break; 574 case Audio.FADE_IN: 575 // Calculate fade-in gain 576 this.fadeGain += roundDecimal(timePassed) / (this.getFadeIn()); 577 578 // Ensure that fade-in gain is not greater than 1.0f 579 if (this.fadeGain >= 1.0f) { 580 this.fadeGain = 1.0f; 581 582 // If so, we're done fading 583 this.fading = Audio.FADE_NONE; 584 } 585 if (log.isDebugEnabled()) { 586 log.debug("Set fade in gain of AudioSource {} to {}", this.getSystemName(), this.fadeGain); 587 } 588 break; 589 default: 590 throw new IllegalArgumentException(); 591 } 592 } 593 594 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 595 // types to implement this (yet). So default to failing. 596 public boolean queueAudioBuffers(Queue<AudioBuffer> audioBuffers) { 597 log.debug("Abstract queueAudioBuffers() called."); 598 return false; 599 } 600 601 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 602 // types to implement this (yet). So default to failing. 603 public boolean queueAudioBuffer(AudioBuffer audioBuffer) { 604 return false; 605 } 606 607 public boolean unqueueAudioBuffers() { 608 return false; 609 } 610 611 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 612 // types to implement this (yet). So default to failing. 613 @Override 614 public int numQueuedBuffers() { 615 return 0; 616 } 617 618 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 619 // types to implement this (yet). So default to failing. 620 @Override 621 public int numProcessedBuffers() { 622 return 0; 623 } 624 625 /** 626 * Binds this AudioSource with the specified AudioBuffer. 627 * <p> 628 * Applies only to sub-types: 629 * <ul> 630 * <li>Source 631 * </ul> 632 * 633 * @param buffer The AudioBuffer to bind to this AudioSource 634 * @return true if successful 635 */ 636 abstract boolean bindAudioBuffer(AudioBuffer buffer); 637 638 /** 639 * Method to define if this AudioSource has been bound to an AudioBuffer. 640 * 641 * @param bound True if bound to an AudioBufferr 642 */ 643 protected void setBound(boolean bound) { 644 this.bound = bound; 645 } 646 647 protected void setQueued(boolean queued) { 648 this.queued = queued; 649 } 650 651 @Override 652 public boolean isBound() { 653 return this.bound; 654 } 655 656 @Override 657 public boolean isQueued() { 658 return this.queued; 659 } 660 661 @Override 662 public void stateChanged(int oldState) { 663 // Get the current state 664 int i = this.getState(); 665 666 // Check if the current state has changed to playing 667 if (i != oldState && i == STATE_PLAYING) { 668 // We've changed to playing so start the move thread 669 this.timeOfLastPositionCheck = System.currentTimeMillis(); 670 AudioSourceMoveThread asmt = new AudioSourceMoveThread(this); 671 asmt.start(); 672 } 673 674// // Check if the current state has changed to stopped 675// if (i!=oldState && i==STATE_STOPPED) { 676// // We've changed to stopped so determine if we need to start the 677// // loop delay thread 678// if (isLooped() && getMinLoops()!=LOOP_CONTINUOUS) { 679// // Yes, we need to 680// if (asdt!=null) { 681// asdt.cleanup(); 682// asdt = null; 683// } 684// asdt = new AudioSourceDelayThread(this); 685// asdt.start(); 686// } 687// } 688 } 689 690 @Override 691 public void play() { 692 this.fading = Audio.FADE_NONE; 693// if (asdt!=null) { 694// asdt.interrupt(); 695// } 696 if (this.getState() != STATE_PLAYING) { 697 this.setState(STATE_PLAYING); 698 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PLAY)); 699 activeAudioFactory.getCommandThread().interrupt(); 700 } 701 } 702 703 /** 704 * Play the clip from the beginning. If looped, start looping. 705 */ 706 abstract protected void doPlay(); 707 708 @Override 709 public void stop() { 710 stop(true); 711 } 712 713 private void stop(boolean interruptThread) { 714 this.fading = Audio.FADE_NONE; 715 this.setState(STATE_STOPPED); 716 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_STOP)); 717 if (interruptThread) { 718 activeAudioFactory.getCommandThread().interrupt(); 719 } 720 } 721 722 /** 723 * Stop playing the clip and rewind to the beginning. 724 */ 725 abstract protected void doStop(); 726 727 @Override 728 public void togglePlay() { 729 this.fading = Audio.FADE_NONE; 730 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PLAY_TOGGLE)); 731 activeAudioFactory.getCommandThread().interrupt(); 732 } 733 734 /** 735 * Toggle the current playing status. Will always start at/return to the 736 * beginning of the sample. 737 */ 738 protected void doTogglePlay() { 739 if (this.getState() == STATE_PLAYING) { 740 stop(); 741 } else { 742 play(); 743 } 744 } 745 746 @Override 747 public void pause() { 748 this.fading = Audio.FADE_NONE; 749 this.setState(STATE_STOPPED); 750 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PAUSE)); 751 activeAudioFactory.getCommandThread().interrupt(); 752 } 753 754 /** 755 * Stop playing the clip but retain the current position. 756 */ 757 abstract protected void doPause(); 758 759 @Override 760 public void resume() { 761 this.fading = Audio.FADE_NONE; 762 this.setState(STATE_PLAYING); 763 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_RESUME)); 764 activeAudioFactory.getCommandThread().interrupt(); 765 } 766 767 /** 768 * Play the clip from the current position. 769 */ 770 abstract protected void doResume(); 771 772 @Override 773 public void togglePause() { 774 this.fading = Audio.FADE_NONE; 775 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PAUSE_TOGGLE)); 776 activeAudioFactory.getCommandThread().interrupt(); 777 } 778 779 /** 780 * Toggle the current playing status. Will retain the playback position of 781 * the sample. 782 */ 783 protected void doTogglePause() { 784 if (this.getState() == STATE_PLAYING) { 785 pause(); 786 } else { 787 resume(); 788 } 789 } 790 791 @Override 792 public void rewind() { 793 this.fading = Audio.FADE_NONE; 794 this.setState(STATE_STOPPED); 795 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_REWIND)); 796 activeAudioFactory.getCommandThread().interrupt(); 797 } 798 799 /** 800 * Rewind clip to the beginning. 801 */ 802 abstract protected void doRewind(); 803 804 @Override 805 public void fadeIn() { 806 if (this.getState() != STATE_PLAYING && this.fading != Audio.FADE_IN) { 807 this.fading = Audio.FADE_IN; 808 this.fadeGain = 0.0f; 809 this.timeOfLastFadeCheck = System.currentTimeMillis(); 810 this.setState(STATE_PLAYING); 811 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_FADE_IN)); 812 activeAudioFactory.getCommandThread().interrupt(); 813 } 814 } 815 816 /** 817 * Fade in then play this AudioSource. 818 */ 819 abstract protected void doFadeIn(); 820 821 @Override 822 public void fadeOut() { 823 if (this.getState() == STATE_PLAYING && this.fading != Audio.FADE_OUT) { 824 this.fading = Audio.FADE_OUT; 825 this.fadeGain = 1.0f; 826 this.timeOfLastFadeCheck = System.currentTimeMillis(); 827 this.setState(STATE_PLAYING); 828 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_FADE_OUT)); 829 activeAudioFactory.getCommandThread().interrupt(); 830 } 831 } 832 833 /** 834 * Fade out then stop this AudioSource. 835 */ 836 abstract protected void doFadeOut(); 837 838 /** 839 * Get the current fading status. 840 * 841 * @return fading status 842 */ 843 protected int getFading() { 844 return this.fading; 845 } 846 847 @Override 848 @Nonnull 849 public String getDebugString() { 850 return "Pos: " + this.getPosition().toString() 851 + ", bound to: " + this.getAssignedBufferName() 852 + ", loops: " 853 + ((this.getMinLoops() == LOOP_CONTINUOUS) ? "infinite" 854 : ((!this.isLooped()) ? "none" 855 : "(min=" + this.getMinLoops() + " max=" + this.getMaxLoops() + ")")); 856 } 857 858 private static final Logger log = LoggerFactory.getLogger(AbstractAudioSource.class); 859 860 /** 861 * An internal class used to create a new thread to monitor and maintain 862 * fade in and fade out levels. 863 * <p> 864 * Will exist only as long as this source is in the process of fading in or 865 * out. 866 */ 867 protected static class AudioSourceFadeThread extends AbstractAudioThread { 868 869 /** 870 * Reference to the AudioSource object being monitored. 871 */ 872 private AbstractAudioSource audioSource; 873 874 /** 875 * Internal variable to hold the fade direction. 876 */ 877 private final int fadeDirection; 878 879 /** 880 * Constructor that takes handle to looping AudioSource to monitor. 881 * 882 * @param audioSource looping AudioSource to monitor 883 */ 884 AudioSourceFadeThread(AbstractAudioSource audioSource) { 885 super(); 886 this.setName("fadesrc-" + super.getName()); 887 this.audioSource = audioSource; 888 this.fadeDirection = audioSource.getFading(); 889 if (log.isDebugEnabled()) { 890 log.debug("Created AudioSourceFadeThread for AudioSource {}", audioSource.getSystemName()); 891 } 892 } 893 894 /** 895 * Main processing loop. 896 */ 897 @Override 898 public void run() { 899 900 while (!dying()) { 901 902 // Recalculate the fade levels 903 audioSource.calculateFades(); 904 905 // Recalculate the gain levels 906 audioSource.calculateGain(); 907 908 // Check if we've done fading 909 if (audioSource.getFading() == Audio.FADE_NONE) { 910 die(); 911 } 912 913 // sleep for a while so as not to overload CPU 914 snooze(20); 915 } 916 917 // Reset fades 918 audioSource.calculateFades(); 919 920 // Check if we were fading out and, if so, stop. 921 // Otherwise reset gain 922 if (this.fadeDirection == Audio.FADE_OUT) { 923 audioSource.doStop(); 924 } else { 925 audioSource.calculateGain(); 926 } 927 928 // Finish up 929 if (log.isDebugEnabled()) { 930 log.debug("Clean up thread {}", this.getName()); 931 } 932 cleanup(); 933 } 934 935 /** 936 * Shut down this thread and clear references to created objects. 937 */ 938 @Override 939 protected void cleanup() { 940 // Thread is to shutdown 941 die(); 942 943 // Clear references to objects 944 this.audioSource = null; 945 946 // Finalise cleanup in super-class 947 super.cleanup(); 948 } 949 } 950 951 /** 952 * An internal class used to create a new thread to monitor and maintain 953 * current source position with respect to velocity. 954 */ 955 protected static class AudioSourceMoveThread extends AbstractAudioThread { 956 957 /** 958 * Reference to the AudioSource object being monitored. 959 */ 960 private AbstractAudioSource audioSource; 961 962 /** 963 * Constructor that takes handle to looping AudioSource to monitor. 964 * 965 * @param audioSource looping AudioSource to monitor 966 */ 967 AudioSourceMoveThread(AbstractAudioSource audioSource) { 968 super(); 969 this.setName("movesrc-" + super.getName()); 970 this.audioSource = audioSource; 971 if (log.isDebugEnabled()) { 972 log.debug("Created AudioSourceMoveThread for AudioSource {}", audioSource.getSystemName()); 973 } 974 } 975 976 /** 977 * Main processing loop. 978 */ 979 @Override 980 public void run() { 981 982 while (!dying()) { 983 984 // Recalculate the position 985 audioSource.calculateCurrentPosition(); 986 987 // Check state and die if not playing 988 if (audioSource.getState() != STATE_PLAYING) { 989 die(); 990 } 991 992 // sleep for a while so as not to overload CPU 993 snooze(100); 994 } 995 996// // Reset the current position 997// audioSource.resetCurrentPosition(); 998 // Finish up 999 if (log.isDebugEnabled()) { 1000 log.debug("Clean up thread {}", this.getName()); 1001 } 1002 cleanup(); 1003 } 1004 1005 /** 1006 * Shuts this thread down and clears references to created objects. 1007 */ 1008 @Override 1009 protected void cleanup() { 1010 // Thread is to shutdown 1011 die(); 1012 1013 // Clear references to objects 1014 this.audioSource = null; 1015 1016 // Finalise cleanup in super-class 1017 super.cleanup(); 1018 } 1019 } 1020 1021// /** 1022// * An internal class used to create a new thread to delay subsequent 1023// * playbacks of a non-continuous looped source. 1024// */ 1025// private class AudioSourceDelayThread extends Thread { 1026// 1027// /** 1028// * Reference to the AudioSource object being monitored 1029// */ 1030// private AbstractAudioSource audioSource; 1031// 1032// /** 1033// * Constructor that takes handle to looping AudioSource to monitor 1034// * 1035// * @param audioSource looping AudioSource to monitor 1036// */ 1037// AudioSourceDelayThread(AbstractAudioSource audioSource) { 1038// super(); 1039// this.setName("delaysrc-"+super.getName()); 1040// this.audioSource = audioSource; 1041// if (log.isDebugEnabled()) log.debug("Created AudioSourceDelayThread for AudioSource " + audioSource.getSystemName()); 1042// } 1043// 1044// /** 1045// * Main processing loop 1046// */ 1047// @Override 1048// public void run() { 1049// 1050// // Sleep for the required period of time 1051// try { 1052// Thread.sleep(audioSource.getLoopDelay()); 1053// } catch (InterruptedException ex) {} 1054// 1055// // Restart playing this AudioSource 1056// this.audioSource.play(); 1057// 1058// // Finish up 1059// if (log.isDebugEnabled()) log.debug("Clean up thread " + this.getName()); 1060// cleanup(); 1061// } 1062// 1063// /** 1064// * Shuts this thread down and clears references to created objects 1065// */ 1066// protected void cleanup() { 1067// // Clear references to objects 1068// this.audioSource = null; 1069// } 1070// } 1071 1072}