1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.audio; 34 35 import com.jme3.asset.AssetManager; 36 import com.jme3.asset.AssetNotFoundException; 37 import com.jme3.export.InputCapsule; 38 import com.jme3.export.JmeExporter; 39 import com.jme3.export.JmeImporter; 40 import com.jme3.export.OutputCapsule; 41 import com.jme3.math.Vector3f; 42 import com.jme3.scene.Node; 43 import com.jme3.util.PlaceholderAssets; 44 import java.io.IOException; 45 import java.util.logging.Level; 46 import java.util.logging.Logger; 47 48 /** 49 * An <code>AudioNode</code> is used in jME3 for playing audio files. 50 * <br/> 51 * First, an {@link AudioNode} is loaded from file, and then assigned 52 * to an audio node for playback. Once the audio node is attached to the 53 * scene, its location will influence the position it is playing from relative 54 * to the {@link Listener}. 55 * <br/> 56 * An audio node can also play in "headspace", meaning its location 57 * or velocity does not influence how it is played. 58 * The "positional" property of an AudioNode can be set via 59 * {@link AudioNode#setPositional(boolean) }. 60 * 61 * @author normenhansen 62 * @author Kirill Vainer 63 */ 64 public class AudioNode extends Node { 65 66 protected boolean loop = false; 67 protected float volume = 1; 68 protected float pitch = 1; 69 protected float timeOffset = 0; 70 protected Filter dryFilter; 71 protected AudioKey audioKey; 72 protected transient AudioData data = null; 73 protected transient volatile Status status = Status.Stopped; 74 protected transient volatile int channel = -1; 75 protected Vector3f velocity = new Vector3f(); 76 protected boolean reverbEnabled = true; 77 protected float maxDistance = 200; // 200 meters 78 protected float refDistance = 10; // 10 meters 79 protected Filter reverbFilter; 80 private boolean directional = false; 81 protected Vector3f direction = new Vector3f(0, 0, 1); 82 protected float innerAngle = 360; 83 protected float outerAngle = 360; 84 protected boolean positional = true; 85 86 /** 87 * <code>Status</code> indicates the current status of the audio node. 88 */ 89 public enum Status { 90 /** 91 * The audio node is currently playing. This will be set if 92 * {@link AudioNode#play() } is called. 93 */ 94 Playing, 95 96 /** 97 * The audio node is currently paused. 98 */ 99 Paused, 100 101 /** 102 * The audio node is currently stopped. 103 * This will be set if {@link AudioNode#stop() } is called 104 * or the audio has reached the end of the file. 105 */ 106 Stopped, 107 } 108 109 /** 110 * Creates a new <code>AudioNode</code> without any audio data set. 111 */ 112 public AudioNode() { 113 } 114 115 /** 116 * Creates a new <code>AudioNode</code> without any audio data set. 117 * 118 * @param audioRenderer The audio renderer to use for playing. Cannot be null. 119 * 120 * @deprecated AudioRenderer parameter is ignored. 121 */ 122 public AudioNode(AudioRenderer audioRenderer) { 123 } 124 125 /** 126 * Creates a new <code>AudioNode</code> with the given data and key. 127 * 128 * @param audioRenderer The audio renderer to use for playing. Cannot be null. 129 * @param audioData The audio data contains the audio track to play. 130 * @param audioKey The audio key that was used to load the AudioData 131 * 132 * @deprecated AudioRenderer parameter is ignored. 133 */ 134 public AudioNode(AudioRenderer audioRenderer, AudioData audioData, AudioKey audioKey) { 135 setAudioData(audioData, audioKey); 136 } 137 138 /** 139 * Creates a new <code>AudioNode</code> with the given data and key. 140 * 141 * @param audioData The audio data contains the audio track to play. 142 * @param audioKey The audio key that was used to load the AudioData 143 */ 144 public AudioNode(AudioData audioData, AudioKey audioKey) { 145 setAudioData(audioData, audioKey); 146 } 147 148 /** 149 * Creates a new <code>AudioNode</code> with the given audio file. 150 * 151 * @param audioRenderer The audio renderer to use for playing. Cannot be null. 152 * @param assetManager The asset manager to use to load the audio file 153 * @param name The filename of the audio file 154 * @param stream If true, the audio will be streamed gradually from disk, 155 * otherwise, it will be buffered. 156 * @param streamCache If stream is also true, then this specifies if 157 * the stream cache is used. When enabled, the audio stream will 158 * be read entirely but not decoded, allowing features such as 159 * seeking, looping and determining duration. 160 * 161 * @deprecated AudioRenderer parameter is ignored. 162 */ 163 public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream, boolean streamCache) { 164 this.audioKey = new AudioKey(name, stream, streamCache); 165 this.data = (AudioData) assetManager.loadAsset(audioKey); 166 } 167 168 /** 169 * Creates a new <code>AudioNode</code> with the given audio file. 170 * 171 * @param assetManager The asset manager to use to load the audio file 172 * @param name The filename of the audio file 173 * @param stream If true, the audio will be streamed gradually from disk, 174 * otherwise, it will be buffered. 175 * @param streamCache If stream is also true, then this specifies if 176 * the stream cache is used. When enabled, the audio stream will 177 * be read entirely but not decoded, allowing features such as 178 * seeking, looping and determining duration. 179 */ 180 public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) { 181 this.audioKey = new AudioKey(name, stream, streamCache); 182 this.data = (AudioData) assetManager.loadAsset(audioKey); 183 } 184 185 /** 186 * Creates a new <code>AudioNode</code> with the given audio file. 187 * 188 * @param audioRenderer The audio renderer to use for playing. Cannot be null. 189 * @param assetManager The asset manager to use to load the audio file 190 * @param name The filename of the audio file 191 * @param stream If true, the audio will be streamed gradually from disk, 192 * otherwise, it will be buffered. 193 * 194 * @deprecated AudioRenderer parameter is ignored. 195 */ 196 public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream) { 197 this(audioRenderer, assetManager, name, stream, false); 198 } 199 200 /** 201 * Creates a new <code>AudioNode</code> with the given audio file. 202 * 203 * @param assetManager The asset manager to use to load the audio file 204 * @param name The filename of the audio file 205 * @param stream If true, the audio will be streamed gradually from disk, 206 * otherwise, it will be buffered. 207 */ 208 public AudioNode(AssetManager assetManager, String name, boolean stream) { 209 this(assetManager, name, stream, false); 210 } 211 212 /** 213 * Creates a new <code>AudioNode</code> with the given audio file. 214 * 215 * @param audioRenderer The audio renderer to use for playing. Cannot be null. 216 * @param assetManager The asset manager to use to load the audio file 217 * @param name The filename of the audio file 218 * 219 * @deprecated AudioRenderer parameter is ignored. 220 */ 221 public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) { 222 this(assetManager, name, false); 223 } 224 225 /** 226 * Creates a new <code>AudioNode</code> with the given audio file. 227 * 228 * @param assetManager The asset manager to use to load the audio file 229 * @param name The filename of the audio file 230 */ 231 public AudioNode(AssetManager assetManager, String name) { 232 this(assetManager, name, false); 233 } 234 235 protected AudioRenderer getRenderer() { 236 AudioRenderer result = AudioContext.getAudioRenderer(); 237 if( result == null ) 238 throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." ); 239 return result; 240 } 241 242 /** 243 * Start playing the audio. 244 */ 245 public void play(){ 246 getRenderer().playSource(this); 247 } 248 249 /** 250 * Start playing an instance of this audio. This method can be used 251 * to play the same <code>AudioNode</code> multiple times. Note 252 * that changes to the parameters of this AudioNode will not effect the 253 * instances already playing. 254 */ 255 public void playInstance(){ 256 getRenderer().playSourceInstance(this); 257 } 258 259 /** 260 * Stop playing the audio that was started with {@link AudioNode#play() }. 261 */ 262 public void stop(){ 263 getRenderer().stopSource(this); 264 } 265 266 /** 267 * Pause the audio that was started with {@link AudioNode#play() }. 268 */ 269 public void pause(){ 270 getRenderer().pauseSource(this); 271 } 272 273 /** 274 * Do not use. 275 */ 276 public final void setChannel(int channel) { 277 if (status != Status.Stopped) { 278 throw new IllegalStateException("Can only set source id when stopped"); 279 } 280 281 this.channel = channel; 282 } 283 284 /** 285 * Do not use. 286 */ 287 public int getChannel() { 288 return channel; 289 } 290 291 /** 292 * @return The {#link Filter dry filter} that is set. 293 * @see AudioNode#setDryFilter(com.jme3.audio.Filter) 294 */ 295 public Filter getDryFilter() { 296 return dryFilter; 297 } 298 299 /** 300 * Set the dry filter to use for this audio node. 301 * 302 * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used, 303 * the dry filter will only influence the "dry" portion of the audio, 304 * e.g. not the reverberated parts of the AudioNode playing. 305 * 306 * See the relevent documentation for the {@link Filter} to determine 307 * the effect. 308 * 309 * @param dryFilter The filter to set, or null to disable dry filter. 310 */ 311 public void setDryFilter(Filter dryFilter) { 312 this.dryFilter = dryFilter; 313 if (channel >= 0) 314 getRenderer().updateSourceParam(this, AudioParam.DryFilter); 315 } 316 317 /** 318 * Set the audio data to use for the audio. Note that this method 319 * can only be called once, if for example the audio node was initialized 320 * without an {@link AudioData}. 321 * 322 * @param audioData The audio data contains the audio track to play. 323 * @param audioKey The audio key that was used to load the AudioData 324 */ 325 public void setAudioData(AudioData audioData, AudioKey audioKey) { 326 if (data != null) { 327 throw new IllegalStateException("Cannot change data once its set"); 328 } 329 330 data = audioData; 331 this.audioKey = audioKey; 332 } 333 334 /** 335 * @return The {@link AudioData} set previously with 336 * {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) } 337 * or any of the constructors that initialize the audio data. 338 */ 339 public AudioData getAudioData() { 340 return data; 341 } 342 343 /** 344 * @return The {@link Status} of the audio node. 345 * The status will be changed when either the {@link AudioNode#play() } 346 * or {@link AudioNode#stop() } methods are called. 347 */ 348 public Status getStatus() { 349 return status; 350 } 351 352 /** 353 * Do not use. 354 */ 355 public final void setStatus(Status status) { 356 this.status = status; 357 } 358 359 /** 360 * @return True if the audio will keep looping after it is done playing, 361 * otherwise, false. 362 * @see AudioNode#setLooping(boolean) 363 */ 364 public boolean isLooping() { 365 return loop; 366 } 367 368 /** 369 * Set the looping mode for the audio node. The default is false. 370 * 371 * @param loop True if the audio should keep looping after it is done playing. 372 */ 373 public void setLooping(boolean loop) { 374 this.loop = loop; 375 if (channel >= 0) 376 getRenderer().updateSourceParam(this, AudioParam.Looping); 377 } 378 379 /** 380 * @return The pitch of the audio, also the speed of playback. 381 * 382 * @see AudioNode#setPitch(float) 383 */ 384 public float getPitch() { 385 return pitch; 386 } 387 388 /** 389 * Set the pitch of the audio, also the speed of playback. 390 * The value must be between 0.5 and 2.0. 391 * 392 * @param pitch The pitch to set. 393 * @throws IllegalArgumentException If pitch is not between 0.5 and 2.0. 394 */ 395 public void setPitch(float pitch) { 396 if (pitch < 0.5f || pitch > 2.0f) { 397 throw new IllegalArgumentException("Pitch must be between 0.5 and 2.0"); 398 } 399 400 this.pitch = pitch; 401 if (channel >= 0) 402 getRenderer().updateSourceParam(this, AudioParam.Pitch); 403 } 404 405 /** 406 * @return The volume of this audio node. 407 * 408 * @see AudioNode#setVolume(float) 409 */ 410 public float getVolume() { 411 return volume; 412 } 413 414 /** 415 * Set the volume of this audio node. 416 * 417 * The volume is specified as gain. 1.0 is the default. 418 * 419 * @param volume The volume to set. 420 * @throws IllegalArgumentException If volume is negative 421 */ 422 public void setVolume(float volume) { 423 if (volume < 0f) { 424 throw new IllegalArgumentException("Volume cannot be negative"); 425 } 426 427 this.volume = volume; 428 if (channel >= 0) 429 getRenderer().updateSourceParam(this, AudioParam.Volume); 430 } 431 432 /** 433 * @return The time offset in seconds when the sound will start playing. 434 */ 435 public float getTimeOffset() { 436 return timeOffset; 437 } 438 439 /** 440 * Set the time offset in seconds when the sound will start playing. 441 * 442 * @param timeOffset The time offset 443 * @throws IllegalArgumentException If timeOffset is negative 444 */ 445 public void setTimeOffset(float timeOffset) { 446 if (timeOffset < 0f) { 447 throw new IllegalArgumentException("Time offset cannot be negative"); 448 } 449 450 this.timeOffset = timeOffset; 451 if (data instanceof AudioStream) { 452 System.out.println("request setTime"); 453 ((AudioStream) data).setTime(timeOffset); 454 }else if(status == Status.Playing){ 455 stop(); 456 play(); 457 } 458 } 459 460 /** 461 * @return The velocity of the audio node. 462 * 463 * @see AudioNode#setVelocity(com.jme3.math.Vector3f) 464 */ 465 public Vector3f getVelocity() { 466 return velocity; 467 } 468 469 /** 470 * Set the velocity of the audio node. The velocity is expected 471 * to be in meters. Does nothing if the audio node is not positional. 472 * 473 * @param velocity The velocity to set. 474 * @see AudioNode#setPositional(boolean) 475 */ 476 public void setVelocity(Vector3f velocity) { 477 this.velocity.set(velocity); 478 if (channel >= 0) 479 getRenderer().updateSourceParam(this, AudioParam.Velocity); 480 } 481 482 /** 483 * @return True if reverb is enabled, otherwise false. 484 * 485 * @see AudioNode#setReverbEnabled(boolean) 486 */ 487 public boolean isReverbEnabled() { 488 return reverbEnabled; 489 } 490 491 /** 492 * Set to true to enable reverberation effects for this audio node. 493 * Does nothing if the audio node is not positional. 494 * <br/> 495 * When enabled, the audio environment set with 496 * {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) } 497 * will apply a reverb effect to the audio playing from this audio node. 498 * 499 * @param reverbEnabled True to enable reverb. 500 */ 501 public void setReverbEnabled(boolean reverbEnabled) { 502 this.reverbEnabled = reverbEnabled; 503 if (channel >= 0) 504 getRenderer().updateSourceParam(this, AudioParam.ReverbEnabled); 505 } 506 507 /** 508 * @return Filter for the reverberations of this audio node. 509 * 510 * @see AudioNode#setReverbFilter(com.jme3.audio.Filter) 511 */ 512 public Filter getReverbFilter() { 513 return reverbFilter; 514 } 515 516 /** 517 * Set the reverb filter for this audio node. 518 * <br/> 519 * The reverb filter will influence the reverberations 520 * of the audio node playing. This only has an effect if 521 * reverb is enabled. 522 * 523 * @param reverbFilter The reverb filter to set. 524 * @see AudioNode#setDryFilter(com.jme3.audio.Filter) 525 */ 526 public void setReverbFilter(Filter reverbFilter) { 527 this.reverbFilter = reverbFilter; 528 if (channel >= 0) 529 getRenderer().updateSourceParam(this, AudioParam.ReverbFilter); 530 } 531 532 /** 533 * @return Max distance for this audio node. 534 * 535 * @see AudioNode#setMaxDistance(float) 536 */ 537 public float getMaxDistance() { 538 return maxDistance; 539 } 540 541 /** 542 * Set the maximum distance for the attenuation of the audio node. 543 * Does nothing if the audio node is not positional. 544 * <br/> 545 * The maximum distance is the distance beyond which the audio 546 * node will no longer be attenuated. Normal attenuation is logarithmic 547 * from refDistance (it reduces by half when the distance doubles). 548 * Max distance sets where this fall-off stops and the sound will never 549 * get any quieter than at that distance. If you want a sound to fall-off 550 * very quickly then set ref distance very short and leave this distance 551 * very long. 552 * 553 * @param maxDistance The maximum playing distance. 554 * @throws IllegalArgumentException If maxDistance is negative 555 */ 556 public void setMaxDistance(float maxDistance) { 557 if (maxDistance < 0) { 558 throw new IllegalArgumentException("Max distance cannot be negative"); 559 } 560 561 this.maxDistance = maxDistance; 562 if (channel >= 0) 563 getRenderer().updateSourceParam(this, AudioParam.MaxDistance); 564 } 565 566 /** 567 * @return The reference playing distance for the audio node. 568 * 569 * @see AudioNode#setRefDistance(float) 570 */ 571 public float getRefDistance() { 572 return refDistance; 573 } 574 575 /** 576 * Set the reference playing distance for the audio node. 577 * Does nothing if the audio node is not positional. 578 * <br/> 579 * The reference playing distance is the distance at which the 580 * audio node will be exactly half of its volume. 581 * 582 * @param refDistance The reference playing distance. 583 * @throws IllegalArgumentException If refDistance is negative 584 */ 585 public void setRefDistance(float refDistance) { 586 if (refDistance < 0) { 587 throw new IllegalArgumentException("Reference distance cannot be negative"); 588 } 589 590 this.refDistance = refDistance; 591 if (channel >= 0) 592 getRenderer().updateSourceParam(this, AudioParam.RefDistance); 593 } 594 595 /** 596 * @return True if the audio node is directional 597 * 598 * @see AudioNode#setDirectional(boolean) 599 */ 600 public boolean isDirectional() { 601 return directional; 602 } 603 604 /** 605 * Set the audio node to be directional. 606 * Does nothing if the audio node is not positional. 607 * <br/> 608 * After setting directional, you should call 609 * {@link AudioNode#setDirection(com.jme3.math.Vector3f) } 610 * to set the audio node's direction. 611 * 612 * @param directional If the audio node is directional 613 */ 614 public void setDirectional(boolean directional) { 615 this.directional = directional; 616 if (channel >= 0) 617 getRenderer().updateSourceParam(this, AudioParam.IsDirectional); 618 } 619 620 /** 621 * @return The direction of this audio node. 622 * 623 * @see AudioNode#setDirection(com.jme3.math.Vector3f) 624 */ 625 public Vector3f getDirection() { 626 return direction; 627 } 628 629 /** 630 * Set the direction of this audio node. 631 * Does nothing if the audio node is not directional. 632 * 633 * @param direction 634 * @see AudioNode#setDirectional(boolean) 635 */ 636 public void setDirection(Vector3f direction) { 637 this.direction = direction; 638 if (channel >= 0) 639 getRenderer().updateSourceParam(this, AudioParam.Direction); 640 } 641 642 /** 643 * @return The directional audio node, cone inner angle. 644 * 645 * @see AudioNode#setInnerAngle(float) 646 */ 647 public float getInnerAngle() { 648 return innerAngle; 649 } 650 651 /** 652 * Set the directional audio node cone inner angle. 653 * Does nothing if the audio node is not directional. 654 * 655 * @param innerAngle The cone inner angle. 656 */ 657 public void setInnerAngle(float innerAngle) { 658 this.innerAngle = innerAngle; 659 if (channel >= 0) 660 getRenderer().updateSourceParam(this, AudioParam.InnerAngle); 661 } 662 663 /** 664 * @return The directional audio node, cone outer angle. 665 * 666 * @see AudioNode#setOuterAngle(float) 667 */ 668 public float getOuterAngle() { 669 return outerAngle; 670 } 671 672 /** 673 * Set the directional audio node cone outer angle. 674 * Does nothing if the audio node is not directional. 675 * 676 * @param outerAngle The cone outer angle. 677 */ 678 public void setOuterAngle(float outerAngle) { 679 this.outerAngle = outerAngle; 680 if (channel >= 0) 681 getRenderer().updateSourceParam(this, AudioParam.OuterAngle); 682 } 683 684 /** 685 * @return True if the audio node is positional. 686 * 687 * @see AudioNode#setPositional(boolean) 688 */ 689 public boolean isPositional() { 690 return positional; 691 } 692 693 /** 694 * Set the audio node as positional. 695 * The position, velocity, and distance parameters effect positional 696 * audio nodes. Set to false if the audio node should play in "headspace". 697 * 698 * @param positional True if the audio node should be positional, otherwise 699 * false if it should be headspace. 700 */ 701 public void setPositional(boolean positional) { 702 this.positional = positional; 703 if (channel >= 0) 704 getRenderer().updateSourceParam(this, AudioParam.IsPositional); 705 } 706 707 @Override 708 public void updateGeometricState(){ 709 boolean updatePos = false; 710 if ((refreshFlags & RF_TRANSFORM) != 0){ 711 updatePos = true; 712 } 713 714 super.updateGeometricState(); 715 716 if (updatePos && channel >= 0) 717 getRenderer().updateSourceParam(this, AudioParam.Position); 718 } 719 720 @Override 721 public AudioNode clone(){ 722 AudioNode clone = (AudioNode) super.clone(); 723 724 clone.direction = direction.clone(); 725 clone.velocity = velocity.clone(); 726 727 return clone; 728 } 729 730 @Override 731 public void write(JmeExporter ex) throws IOException { 732 super.write(ex); 733 OutputCapsule oc = ex.getCapsule(this); 734 oc.write(audioKey, "audio_key", null); 735 oc.write(loop, "looping", false); 736 oc.write(volume, "volume", 1); 737 oc.write(pitch, "pitch", 1); 738 oc.write(timeOffset, "time_offset", 0); 739 oc.write(dryFilter, "dry_filter", null); 740 741 oc.write(velocity, "velocity", null); 742 oc.write(reverbEnabled, "reverb_enabled", false); 743 oc.write(reverbFilter, "reverb_filter", null); 744 oc.write(maxDistance, "max_distance", 20); 745 oc.write(refDistance, "ref_distance", 10); 746 747 oc.write(directional, "directional", false); 748 oc.write(direction, "direction", null); 749 oc.write(innerAngle, "inner_angle", 360); 750 oc.write(outerAngle, "outer_angle", 360); 751 752 oc.write(positional, "positional", false); 753 } 754 755 @Override 756 public void read(JmeImporter im) throws IOException { 757 super.read(im); 758 InputCapsule ic = im.getCapsule(this); 759 760 // NOTE: In previous versions of jME3, audioKey was actually 761 // written with the name "key". This has been changed 762 // to "audio_key" in case Spatial's key will be written as "key". 763 if (ic.getSavableVersion(AudioNode.class) == 0){ 764 audioKey = (AudioKey) ic.readSavable("key", null); 765 }else{ 766 audioKey = (AudioKey) ic.readSavable("audio_key", null); 767 } 768 769 loop = ic.readBoolean("looping", false); 770 volume = ic.readFloat("volume", 1); 771 pitch = ic.readFloat("pitch", 1); 772 timeOffset = ic.readFloat("time_offset", 0); 773 dryFilter = (Filter) ic.readSavable("dry_filter", null); 774 775 velocity = (Vector3f) ic.readSavable("velocity", null); 776 reverbEnabled = ic.readBoolean("reverb_enabled", false); 777 reverbFilter = (Filter) ic.readSavable("reverb_filter", null); 778 maxDistance = ic.readFloat("max_distance", 20); 779 refDistance = ic.readFloat("ref_distance", 10); 780 781 directional = ic.readBoolean("directional", false); 782 direction = (Vector3f) ic.readSavable("direction", null); 783 innerAngle = ic.readFloat("inner_angle", 360); 784 outerAngle = ic.readFloat("outer_angle", 360); 785 786 positional = ic.readBoolean("positional", false); 787 788 if (audioKey != null) { 789 try { 790 data = im.getAssetManager().loadAudio(audioKey); 791 } catch (AssetNotFoundException ex){ 792 Logger.getLogger(AudioNode.class.getName()).log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key}); 793 data = PlaceholderAssets.getPlaceholderAudio(); 794 } 795 } 796 } 797 798 @Override 799 public String toString() { 800 String ret = getClass().getSimpleName() 801 + "[status=" + status; 802 if (volume != 1f) { 803 ret += ", vol=" + volume; 804 } 805 if (pitch != 1f) { 806 ret += ", pitch=" + pitch; 807 } 808 return ret + "]"; 809 } 810 } 811