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()).alive(); 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// public void setMinLoopDelay(int loopDelay) { 469// if (this.maxLoopDelay < loopDelay) { 470// this.maxLoopDelay = loopDelay; 471// } 472// this.minLoopDelay = loopDelay; 473// calculateLoopDelay(); 474// } 475// 476// public int getMinLoopDelay() { 477// return this.minLoopDelay; 478// } 479// 480// public void setMaxLoopDelay(int loopDelay) { 481// if (this.minLoopDelay > loopDelay) { 482// this.minLoopDelay = loopDelay; 483// } 484// this.maxLoopDelay = loopDelay; 485// calculateLoopDelay(); 486// } 487// 488// public int getMaxLoopDelay() { 489// return this.maxLoopDelay; 490// } 491// 492// public int getLoopDelay() { 493// // Call the calculate method each time so as to ensure 494// // randomness when min and max are not equal 495// calculateLoopDelay(); 496// return this.loopDelay; 497// } 498// 499// /** 500// * Method to calculate the delay between subsequent loops of this source 501// */ 502// protected void calculateLoopDelay() { 503// if (this.minLoopDelay != this.maxLoopDelay) { 504// Random r = new Random(); 505// this.loopDelay = this.minLoopDelay + r.nextInt(this.maxLoopDelay-this.minLoopDelay); 506// } else { 507// this.loopDelay = this.minLoopDelay; 508// } 509// } 510 @Override 511 public void setFadeIn(int fadeInTime) { 512 this.fadeInTime = fadeInTime; 513 } 514 515 @Override 516 public int getFadeIn() { 517 return this.fadeInTime; 518 } 519 520 @Override 521 public void setFadeOut(int fadeOutTime) { 522 this.fadeOutTime = fadeOutTime; 523 } 524 525 @Override 526 public int getFadeOut() { 527 return this.fadeOutTime; 528 } 529 530 /** 531 * Used to return the current calculated fade gain for this AudioSource. 532 * 533 * @return current fade gain 534 */ 535 protected float getFadeGain() { 536 return this.fadeGain; 537 } 538 539 /** 540 * Calculate the fade gains. 541 */ 542 protected void calculateFades() { 543 544 // Calculate how long it's been since we lasted checked fade gains 545 long currentTime = System.currentTimeMillis(); 546 long timePassed = currentTime - this.timeOfLastFadeCheck; 547 this.timeOfLastFadeCheck = currentTime; 548 549 switch (this.fading) { 550 case Audio.FADE_NONE: 551 // Reset fade gain 552 this.fadeGain = 1.0f; 553 break; 554 case Audio.FADE_OUT: 555 // Calculate fade-out gain 556 this.fadeGain -= roundDecimal(timePassed) / (this.getFadeOut()); 557 558 // Ensure that fade-out gain is not less than 0.0f 559 if (this.fadeGain < 0.0f) { 560 this.fadeGain = 0.0f; 561 562 // If so, we're done fading 563 this.fading = Audio.FADE_NONE; 564 } 565 if (log.isDebugEnabled()) { 566 log.debug("Set fade out gain of AudioSource {} to {}", this.getSystemName(), this.fadeGain); 567 } 568 break; 569 case Audio.FADE_IN: 570 // Calculate fade-in gain 571 this.fadeGain += roundDecimal(timePassed) / (this.getFadeIn()); 572 573 // Ensure that fade-in gain is not greater than 1.0f 574 if (this.fadeGain >= 1.0f) { 575 this.fadeGain = 1.0f; 576 577 // If so, we're done fading 578 this.fading = Audio.FADE_NONE; 579 } 580 if (log.isDebugEnabled()) { 581 log.debug("Set fade in gain of AudioSource {} to {}", this.getSystemName(), this.fadeGain); 582 } 583 break; 584 default: 585 throw new IllegalArgumentException(); 586 } 587 } 588 589 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 590 // types to implement this (yet). So default to failing. 591 public boolean queueAudioBuffers(Queue<AudioBuffer> audioBuffers) { 592 log.debug("Abstract queueAudioBuffers() called."); 593 return false; 594 } 595 596 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 597 // types to implement this (yet). So default to failing. 598 public boolean queueAudioBuffer(AudioBuffer audioBuffer) { 599 return false; 600 } 601 602 public boolean unqueueAudioBuffers() { 603 return false; 604 } 605 606 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 607 // types to implement this (yet). So default to failing. 608 @Override 609 public int numQueuedBuffers() { 610 return 0; 611 } 612 613 // Probably aught to be abstract, but I don't want to force the non-JOAL Source 614 // types to implement this (yet). So default to failing. 615 @Override 616 public int numProcessedBuffers() { 617 return 0; 618 } 619 620 /** 621 * Binds this AudioSource with the specified AudioBuffer. 622 * <p> 623 * Applies only to sub-types: 624 * <ul> 625 * <li>Source 626 * </ul> 627 * 628 * @param buffer The AudioBuffer to bind to this AudioSource 629 * @return true if successful 630 */ 631 abstract boolean bindAudioBuffer(AudioBuffer buffer); 632 633 /** 634 * Method to define if this AudioSource has been bound to an AudioBuffer. 635 * 636 * @param bound True if bound to an AudioBufferr 637 */ 638 protected void setBound(boolean bound) { 639 this.bound = bound; 640 } 641 642 protected void setQueued(boolean queued) { 643 this.queued = queued; 644 } 645 646 @Override 647 public boolean isBound() { 648 return this.bound; 649 } 650 651 @Override 652 public boolean isQueued() { 653 return this.queued; 654 } 655 656 @Override 657 public void stateChanged(int oldState) { 658 // Get the current state 659 int i = this.getState(); 660 661 // Check if the current state has changed to playing 662 if (i != oldState && i == STATE_PLAYING) { 663 // We've changed to playing so start the move thread 664 this.timeOfLastPositionCheck = System.currentTimeMillis(); 665 AudioSourceMoveThread asmt = new AudioSourceMoveThread(this); 666 asmt.start(); 667 } 668 669// // Check if the current state has changed to stopped 670// if (i!=oldState && i==STATE_STOPPED) { 671// // We've changed to stopped so determine if we need to start the 672// // loop delay thread 673// if (isLooped() && getMinLoops()!=LOOP_CONTINUOUS) { 674// // Yes, we need to 675// if (asdt!=null) { 676// asdt.cleanup(); 677// asdt = null; 678// } 679// asdt = new AudioSourceDelayThread(this); 680// asdt.start(); 681// } 682// } 683 } 684 685 @Override 686 public void play() { 687 this.fading = Audio.FADE_NONE; 688// if (asdt!=null) { 689// asdt.interrupt(); 690// } 691 if (this.getState() != STATE_PLAYING) { 692 this.setState(STATE_PLAYING); 693 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PLAY)); 694 activeAudioFactory.getCommandThread().interrupt(); 695 } 696 } 697 698 /** 699 * Play the clip from the beginning. If looped, start looping. 700 */ 701 abstract protected void doPlay(); 702 703 @Override 704 public void stop() { 705 stop(true); 706 } 707 708 private void stop(boolean interruptThread) { 709 this.fading = Audio.FADE_NONE; 710 this.setState(STATE_STOPPED); 711 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_STOP)); 712 if (interruptThread) { 713 activeAudioFactory.getCommandThread().interrupt(); 714 } 715 } 716 717 /** 718 * Stop playing the clip and rewind to the beginning. 719 */ 720 abstract protected void doStop(); 721 722 @Override 723 public void togglePlay() { 724 this.fading = Audio.FADE_NONE; 725 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PLAY_TOGGLE)); 726 activeAudioFactory.getCommandThread().interrupt(); 727 } 728 729 /** 730 * Toggle the current playing status. Will always start at/return to the 731 * beginning of the sample. 732 */ 733 protected void doTogglePlay() { 734 if (this.getState() == STATE_PLAYING) { 735 stop(); 736 } else { 737 play(); 738 } 739 } 740 741 @Override 742 public void pause() { 743 this.fading = Audio.FADE_NONE; 744 this.setState(STATE_STOPPED); 745 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PAUSE)); 746 activeAudioFactory.getCommandThread().interrupt(); 747 } 748 749 /** 750 * Stop playing the clip but retain the current position. 751 */ 752 abstract protected void doPause(); 753 754 @Override 755 public void resume() { 756 this.fading = Audio.FADE_NONE; 757 this.setState(STATE_PLAYING); 758 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_RESUME)); 759 activeAudioFactory.getCommandThread().interrupt(); 760 } 761 762 /** 763 * Play the clip from the current position. 764 */ 765 abstract protected void doResume(); 766 767 @Override 768 public void togglePause() { 769 this.fading = Audio.FADE_NONE; 770 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_PAUSE_TOGGLE)); 771 activeAudioFactory.getCommandThread().interrupt(); 772 } 773 774 /** 775 * Toggle the current playing status. Will retain the playback position of 776 * the sample. 777 */ 778 protected void doTogglePause() { 779 if (this.getState() == STATE_PLAYING) { 780 pause(); 781 } else { 782 resume(); 783 } 784 } 785 786 @Override 787 public void rewind() { 788 this.fading = Audio.FADE_NONE; 789 this.setState(STATE_STOPPED); 790 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_REWIND)); 791 activeAudioFactory.getCommandThread().interrupt(); 792 } 793 794 /** 795 * Rewind clip to the beginning. 796 */ 797 abstract protected void doRewind(); 798 799 @Override 800 public void fadeIn() { 801 if (this.getState() != STATE_PLAYING && this.fading != Audio.FADE_IN) { 802 this.fading = Audio.FADE_IN; 803 this.fadeGain = 0.0f; 804 this.timeOfLastFadeCheck = System.currentTimeMillis(); 805 this.setState(STATE_PLAYING); 806 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_FADE_IN)); 807 activeAudioFactory.getCommandThread().interrupt(); 808 } 809 } 810 811 /** 812 * Fade in then play this AudioSource. 813 */ 814 abstract protected void doFadeIn(); 815 816 @Override 817 public void fadeOut() { 818 if (this.getState() == STATE_PLAYING && this.fading != Audio.FADE_OUT) { 819 this.fading = Audio.FADE_OUT; 820 this.fadeGain = 1.0f; 821 this.timeOfLastFadeCheck = System.currentTimeMillis(); 822 this.setState(STATE_PLAYING); 823 activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_FADE_OUT)); 824 activeAudioFactory.getCommandThread().interrupt(); 825 } 826 } 827 828 /** 829 * Fade out then stop this AudioSource. 830 */ 831 abstract protected void doFadeOut(); 832 833 /** 834 * Get the current fading status. 835 * 836 * @return fading status 837 */ 838 protected int getFading() { 839 return this.fading; 840 } 841 842 @Override 843 @Nonnull 844 public String getDebugString() { 845 return "Pos: " + this.getPosition().toString() 846 + ", bound to: " + this.getAssignedBufferName() 847 + ", loops: " 848 + ((this.getMinLoops() == LOOP_CONTINUOUS) ? "infinite" 849 : ((!this.isLooped()) ? "none" 850 : "(min=" + this.getMinLoops() + " max=" + this.getMaxLoops() + ")")); 851 } 852 853 private static final Logger log = LoggerFactory.getLogger(AbstractAudioSource.class); 854 855 /** 856 * An internal class used to create a new thread to monitor and maintain 857 * fade in and fade out levels. 858 * <p> 859 * Will exist only as long as this source is in the process of fading in or 860 * out. 861 */ 862 protected static class AudioSourceFadeThread extends AbstractAudioThread { 863 864 /** 865 * Reference to the AudioSource object being monitored. 866 */ 867 private AbstractAudioSource audioSource; 868 869 /** 870 * Internal variable to hold the fade direction. 871 */ 872 private final int fadeDirection; 873 874 /** 875 * Constructor that takes handle to looping AudioSource to monitor. 876 * 877 * @param audioSource looping AudioSource to monitor 878 */ 879 AudioSourceFadeThread(AbstractAudioSource audioSource) { 880 super(); 881 this.setName("fadesrc-" + super.getName()); 882 this.audioSource = audioSource; 883 this.fadeDirection = audioSource.getFading(); 884 if (log.isDebugEnabled()) { 885 log.debug("Created AudioSourceFadeThread for AudioSource {}", audioSource.getSystemName()); 886 } 887 } 888 889 /** 890 * Main processing loop. 891 */ 892 @Override 893 public void run() { 894 895 while (!dying()) { 896 897 // Recalculate the fade levels 898 audioSource.calculateFades(); 899 900 // Recalculate the gain levels 901 audioSource.calculateGain(); 902 903 // Check if we've done fading 904 if (audioSource.getFading() == Audio.FADE_NONE) { 905 die(); 906 } 907 908 // sleep for a while so as not to overload CPU 909 snooze(20); 910 } 911 912 // Reset fades 913 audioSource.calculateFades(); 914 915 // Check if we were fading out and, if so, stop. 916 // Otherwise reset gain 917 if (this.fadeDirection == Audio.FADE_OUT) { 918 audioSource.doStop(); 919 } else { 920 audioSource.calculateGain(); 921 } 922 923 // Finish up 924 if (log.isDebugEnabled()) { 925 log.debug("Clean up thread {}", this.getName()); 926 } 927 cleanup(); 928 } 929 930 /** 931 * Shut down this thread and clear references to created objects. 932 */ 933 @Override 934 protected void cleanup() { 935 // Thread is to shutdown 936 die(); 937 938 // Clear references to objects 939 this.audioSource = null; 940 941 // Finalise cleanup in super-class 942 super.cleanup(); 943 } 944 } 945 946 /** 947 * An internal class used to create a new thread to monitor and maintain 948 * current source position with respect to velocity. 949 */ 950 protected static class AudioSourceMoveThread extends AbstractAudioThread { 951 952 /** 953 * Reference to the AudioSource object being monitored. 954 */ 955 private AbstractAudioSource audioSource; 956 957 /** 958 * Constructor that takes handle to looping AudioSource to monitor. 959 * 960 * @param audioSource looping AudioSource to monitor 961 */ 962 AudioSourceMoveThread(AbstractAudioSource audioSource) { 963 super(); 964 this.setName("movesrc-" + super.getName()); 965 this.audioSource = audioSource; 966 if (log.isDebugEnabled()) { 967 log.debug("Created AudioSourceMoveThread for AudioSource {}", audioSource.getSystemName()); 968 } 969 } 970 971 /** 972 * Main processing loop. 973 */ 974 @Override 975 public void run() { 976 977 while (!dying()) { 978 979 // Recalculate the position 980 audioSource.calculateCurrentPosition(); 981 982 // Check state and die if not playing 983 if (audioSource.getState() != STATE_PLAYING) { 984 die(); 985 } 986 987 // sleep for a while so as not to overload CPU 988 snooze(100); 989 } 990 991// // Reset the current position 992// audioSource.resetCurrentPosition(); 993 // Finish up 994 if (log.isDebugEnabled()) { 995 log.debug("Clean up thread {}", this.getName()); 996 } 997 cleanup(); 998 } 999 1000 /** 1001 * Shuts this thread down and clears references to created objects. 1002 */ 1003 @Override 1004 protected void cleanup() { 1005 // Thread is to shutdown 1006 die(); 1007 1008 // Clear references to objects 1009 this.audioSource = null; 1010 1011 // Finalise cleanup in super-class 1012 super.cleanup(); 1013 } 1014 } 1015 1016// /** 1017// * An internal class used to create a new thread to delay subsequent 1018// * playbacks of a non-continuous looped source. 1019// */ 1020// private class AudioSourceDelayThread extends Thread { 1021// 1022// /** 1023// * Reference to the AudioSource object being monitored 1024// */ 1025// private AbstractAudioSource audioSource; 1026// 1027// /** 1028// * Constructor that takes handle to looping AudioSource to monitor 1029// * 1030// * @param audioSource looping AudioSource to monitor 1031// */ 1032// AudioSourceDelayThread(AbstractAudioSource audioSource) { 1033// super(); 1034// this.setName("delaysrc-"+super.getName()); 1035// this.audioSource = audioSource; 1036// if (log.isDebugEnabled()) log.debug("Created AudioSourceDelayThread for AudioSource " + audioSource.getSystemName()); 1037// } 1038// 1039// /** 1040// * Main processing loop 1041// */ 1042// @Override 1043// public void run() { 1044// 1045// // Sleep for the required period of time 1046// try { 1047// Thread.sleep(audioSource.getLoopDelay()); 1048// } catch (InterruptedException ex) {} 1049// 1050// // Restart playing this AudioSource 1051// this.audioSource.play(); 1052// 1053// // Finish up 1054// if (log.isDebugEnabled()) log.debug("Clean up thread " + this.getName()); 1055// cleanup(); 1056// } 1057// 1058// /** 1059// * Shuts this thread down and clears references to created objects 1060// */ 1061// protected void cleanup() { 1062// // Clear references to objects 1063// this.audioSource = null; 1064// } 1065// } 1066 1067}