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.lwjgl; 34 35 import com.jme3.audio.AudioNode.Status; 36 import com.jme3.audio.*; 37 import com.jme3.math.Vector3f; 38 import com.jme3.util.BufferUtils; 39 import com.jme3.util.NativeObjectManager; 40 import java.nio.ByteBuffer; 41 import java.nio.FloatBuffer; 42 import java.nio.IntBuffer; 43 import java.util.ArrayList; 44 import java.util.concurrent.atomic.AtomicBoolean; 45 import java.util.logging.Level; 46 import java.util.logging.Logger; 47 import org.lwjgl.LWJGLException; 48 import static org.lwjgl.openal.AL10.*; 49 import org.lwjgl.openal.*; 50 51 public class LwjglAudioRenderer implements AudioRenderer, Runnable { 52 53 private static final Logger logger = Logger.getLogger(LwjglAudioRenderer.class.getName()); 54 55 private final NativeObjectManager objManager = new NativeObjectManager(); 56 57 // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 58 // which is exactly 1 second of audio. 59 private static final int BUFFER_SIZE = 35280; 60 private static final int STREAMING_BUFFER_COUNT = 5; 61 62 private final static int MAX_NUM_CHANNELS = 64; 63 private IntBuffer ib = BufferUtils.createIntBuffer(1); 64 private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); 65 private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); 66 private final byte[] arrayBuf = new byte[BUFFER_SIZE]; 67 68 private int[] channels; 69 private AudioNode[] chanSrcs; 70 private int nextChan = 0; 71 private ArrayList<Integer> freeChans = new ArrayList<Integer>(); 72 73 private Listener listener; 74 private boolean audioDisabled = false; 75 76 private boolean supportEfx = false; 77 private int auxSends = 0; 78 private int reverbFx = -1; 79 private int reverbFxSlot = -1; 80 81 // Update audio 20 times per second 82 private static final float UPDATE_RATE = 0.05f; 83 84 private final Thread audioThread = new Thread(this, "jME3 Audio Thread"); 85 private final AtomicBoolean threadLock = new AtomicBoolean(false); 86 87 public LwjglAudioRenderer(){ 88 } 89 90 public void initialize(){ 91 if (!audioThread.isAlive()){ 92 audioThread.setDaemon(true); 93 audioThread.setPriority(Thread.NORM_PRIORITY+1); 94 audioThread.start(); 95 }else{ 96 throw new IllegalStateException("Initialize already called"); 97 } 98 } 99 100 private void checkDead(){ 101 if (audioThread.getState() == Thread.State.TERMINATED) 102 throw new IllegalStateException("Audio thread is terminated"); 103 } 104 105 public void run(){ 106 initInThread(); 107 synchronized (threadLock){ 108 threadLock.set(true); 109 threadLock.notifyAll(); 110 } 111 112 long updateRateNanos = (long) (UPDATE_RATE * 1000000000); 113 mainloop: while (true){ 114 long startTime = System.nanoTime(); 115 116 if (Thread.interrupted()) 117 break; 118 119 synchronized (threadLock){ 120 updateInThread(UPDATE_RATE); 121 } 122 123 long endTime = System.nanoTime(); 124 long diffTime = endTime - startTime; 125 126 if (diffTime < updateRateNanos){ 127 long desiredEndTime = startTime + updateRateNanos; 128 while (System.nanoTime() < desiredEndTime){ 129 try{ 130 Thread.sleep(1); 131 }catch (InterruptedException ex){ 132 break mainloop; 133 } 134 } 135 } 136 } 137 138 synchronized (threadLock){ 139 cleanupInThread(); 140 } 141 } 142 143 public void initInThread(){ 144 try{ 145 if (!AL.isCreated()){ 146 AL.create(); 147 } 148 }catch (OpenALException ex){ 149 logger.log(Level.SEVERE, "Failed to load audio library", ex); 150 audioDisabled = true; 151 return; 152 }catch (LWJGLException ex){ 153 logger.log(Level.SEVERE, "Failed to load audio library", ex); 154 audioDisabled = true; 155 return; 156 } catch (UnsatisfiedLinkError ex){ 157 logger.log(Level.SEVERE, "Failed to load audio library", ex); 158 audioDisabled = true; 159 return; 160 } 161 162 ALCdevice device = AL.getDevice(); 163 String deviceName = ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER); 164 165 logger.log(Level.FINER, "Audio Device: {0}", deviceName); 166 logger.log(Level.FINER, "Audio Vendor: {0}", alGetString(AL_VENDOR)); 167 logger.log(Level.FINER, "Audio Renderer: {0}", alGetString(AL_RENDERER)); 168 logger.log(Level.FINER, "Audio Version: {0}", alGetString(AL_VERSION)); 169 170 // Find maximum # of sources supported by this implementation 171 ArrayList<Integer> channelList = new ArrayList<Integer>(); 172 for (int i = 0; i < MAX_NUM_CHANNELS; i++){ 173 int chan = alGenSources(); 174 if (alGetError() != 0){ 175 break; 176 }else{ 177 channelList.add(chan); 178 } 179 } 180 181 channels = new int[channelList.size()]; 182 for (int i = 0; i < channels.length; i++){ 183 channels[i] = channelList.get(i); 184 } 185 186 ib = BufferUtils.createIntBuffer(channels.length); 187 chanSrcs = new AudioNode[channels.length]; 188 189 logger.log(Level.INFO, "AudioRenderer supports {0} channels", channels.length); 190 191 supportEfx = ALC10.alcIsExtensionPresent(device, "ALC_EXT_EFX"); 192 if (supportEfx){ 193 ib.position(0).limit(1); 194 ALC10.alcGetInteger(device, EFX10.ALC_EFX_MAJOR_VERSION, ib); 195 int major = ib.get(0); 196 ib.position(0).limit(1); 197 ALC10.alcGetInteger(device, EFX10.ALC_EFX_MINOR_VERSION, ib); 198 int minor = ib.get(0); 199 logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{major, minor}); 200 201 ALC10.alcGetInteger(device, EFX10.ALC_MAX_AUXILIARY_SENDS, ib); 202 auxSends = ib.get(0); 203 logger.log(Level.INFO, "Audio max auxilary sends: {0}", auxSends); 204 205 // create slot 206 ib.position(0).limit(1); 207 EFX10.alGenAuxiliaryEffectSlots(ib); 208 reverbFxSlot = ib.get(0); 209 210 // create effect 211 ib.position(0).limit(1); 212 EFX10.alGenEffects(ib); 213 reverbFx = ib.get(0); 214 EFX10.alEffecti(reverbFx, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB); 215 216 // attach reverb effect to effect slot 217 EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); 218 }else{ 219 logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work."); 220 } 221 } 222 223 public void cleanupInThread(){ 224 if (audioDisabled){ 225 AL.destroy(); 226 return; 227 } 228 229 // stop any playing channels 230 for (int i = 0; i < chanSrcs.length; i++){ 231 if (chanSrcs[i] != null){ 232 clearChannel(i); 233 } 234 } 235 236 // delete channel-based sources 237 ib.clear(); 238 ib.put(channels); 239 ib.flip(); 240 alDeleteSources(ib); 241 242 // delete audio buffers and filters 243 objManager.deleteAllObjects(this); 244 245 if (supportEfx){ 246 ib.position(0).limit(1); 247 ib.put(0, reverbFx); 248 EFX10.alDeleteEffects(ib); 249 250 // If this is not allocated, why is it deleted? 251 // Commented out to fix native crash in OpenAL. 252 ib.position(0).limit(1); 253 ib.put(0, reverbFxSlot); 254 EFX10.alDeleteAuxiliaryEffectSlots(ib); 255 } 256 257 AL.destroy(); 258 } 259 260 public void cleanup(){ 261 // kill audio thread 262 if (audioThread.isAlive()){ 263 audioThread.interrupt(); 264 } 265 } 266 267 private void updateFilter(Filter f){ 268 int id = f.getId(); 269 if (id == -1){ 270 ib.position(0).limit(1); 271 EFX10.alGenFilters(ib); 272 id = ib.get(0); 273 f.setId(id); 274 275 objManager.registerForCleanup(f); 276 } 277 278 if (f instanceof LowPassFilter){ 279 LowPassFilter lpf = (LowPassFilter) f; 280 EFX10.alFilteri(id, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS); 281 EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAIN, lpf.getVolume()); 282 EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); 283 }else{ 284 throw new UnsupportedOperationException("Filter type unsupported: "+ 285 f.getClass().getName()); 286 } 287 288 f.clearUpdateNeeded(); 289 } 290 291 public void updateSourceParam(AudioNode src, AudioParam param){ 292 checkDead(); 293 synchronized (threadLock){ 294 while (!threadLock.get()){ 295 try { 296 threadLock.wait(); 297 } catch (InterruptedException ex) { 298 } 299 } 300 if (audioDisabled) 301 return; 302 303 // There is a race condition in AudioNode that can 304 // cause this to be called for a node that has been 305 // detached from its channel. For example, setVolume() 306 // called from the render thread may see that that AudioNode 307 // still has a channel value but the audio thread may 308 // clear that channel before setVolume() gets to call 309 // updateSourceParam() (because the audio stopped playing 310 // on its own right as the volume was set). In this case, 311 // it should be safe to just ignore the update 312 if (src.getChannel() < 0) 313 return; 314 315 assert src.getChannel() >= 0; 316 317 int id = channels[src.getChannel()]; 318 switch (param){ 319 case Position: 320 if (!src.isPositional()) 321 return; 322 323 Vector3f pos = src.getWorldTranslation(); 324 alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); 325 break; 326 case Velocity: 327 if (!src.isPositional()) 328 return; 329 330 Vector3f vel = src.getVelocity(); 331 alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); 332 break; 333 case MaxDistance: 334 if (!src.isPositional()) 335 return; 336 337 alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); 338 break; 339 case RefDistance: 340 if (!src.isPositional()) 341 return; 342 343 alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); 344 break; 345 case ReverbFilter: 346 if (!supportEfx || !src.isPositional() || !src.isReverbEnabled()) 347 return; 348 349 int filter = EFX10.AL_FILTER_NULL; 350 if (src.getReverbFilter() != null){ 351 Filter f = src.getReverbFilter(); 352 if (f.isUpdateNeeded()){ 353 updateFilter(f); 354 } 355 filter = f.getId(); 356 } 357 AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); 358 break; 359 case ReverbEnabled: 360 if (!supportEfx || !src.isPositional()) 361 return; 362 363 if (src.isReverbEnabled()){ 364 updateSourceParam(src, AudioParam.ReverbFilter); 365 }else{ 366 AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); 367 } 368 break; 369 case IsPositional: 370 if (!src.isPositional()){ 371 // play in headspace 372 alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); 373 alSource3f(id, AL_POSITION, 0,0,0); 374 alSource3f(id, AL_VELOCITY, 0,0,0); 375 }else{ 376 alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); 377 updateSourceParam(src, AudioParam.Position); 378 updateSourceParam(src, AudioParam.Velocity); 379 updateSourceParam(src, AudioParam.MaxDistance); 380 updateSourceParam(src, AudioParam.RefDistance); 381 updateSourceParam(src, AudioParam.ReverbEnabled); 382 } 383 break; 384 case Direction: 385 if (!src.isDirectional()) 386 return; 387 388 Vector3f dir = src.getDirection(); 389 alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); 390 break; 391 case InnerAngle: 392 if (!src.isDirectional()) 393 return; 394 395 alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); 396 break; 397 case OuterAngle: 398 if (!src.isDirectional()) 399 return; 400 401 alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); 402 break; 403 case IsDirectional: 404 if (src.isDirectional()){ 405 updateSourceParam(src, AudioParam.Direction); 406 updateSourceParam(src, AudioParam.InnerAngle); 407 updateSourceParam(src, AudioParam.OuterAngle); 408 alSourcef(id, AL_CONE_OUTER_GAIN, 0); 409 }else{ 410 alSourcef(id, AL_CONE_INNER_ANGLE, 360); 411 alSourcef(id, AL_CONE_OUTER_ANGLE, 360); 412 alSourcef(id, AL_CONE_OUTER_GAIN, 1f); 413 } 414 break; 415 case DryFilter: 416 if (!supportEfx) 417 return; 418 419 if (src.getDryFilter() != null){ 420 Filter f = src.getDryFilter(); 421 if (f.isUpdateNeeded()){ 422 updateFilter(f); 423 424 // NOTE: must re-attach filter for changes to apply. 425 alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); 426 } 427 }else{ 428 alSourcei(id, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); 429 } 430 break; 431 case Looping: 432 if (src.isLooping()){ 433 if (!(src.getAudioData() instanceof AudioStream)){ 434 alSourcei(id, AL_LOOPING, AL_TRUE); 435 } 436 }else{ 437 alSourcei(id, AL_LOOPING, AL_FALSE); 438 } 439 break; 440 case Volume: 441 alSourcef(id, AL_GAIN, src.getVolume()); 442 break; 443 case Pitch: 444 alSourcef(id, AL_PITCH, src.getPitch()); 445 break; 446 } 447 } 448 } 449 450 private void setSourceParams(int id, AudioNode src, boolean forceNonLoop){ 451 if (src.isPositional()){ 452 Vector3f pos = src.getWorldTranslation(); 453 Vector3f vel = src.getVelocity(); 454 alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); 455 alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); 456 alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); 457 alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); 458 alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); 459 460 if (src.isReverbEnabled() && supportEfx){ 461 int filter = EFX10.AL_FILTER_NULL; 462 if (src.getReverbFilter() != null){ 463 Filter f = src.getReverbFilter(); 464 if (f.isUpdateNeeded()){ 465 updateFilter(f); 466 } 467 filter = f.getId(); 468 } 469 AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); 470 } 471 }else{ 472 // play in headspace 473 alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); 474 alSource3f(id, AL_POSITION, 0,0,0); 475 alSource3f(id, AL_VELOCITY, 0,0,0); 476 } 477 478 if (src.getDryFilter() != null && supportEfx){ 479 Filter f = src.getDryFilter(); 480 if (f.isUpdateNeeded()){ 481 updateFilter(f); 482 483 // NOTE: must re-attach filter for changes to apply. 484 alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); 485 } 486 } 487 488 if (forceNonLoop){ 489 alSourcei(id, AL_LOOPING, AL_FALSE); 490 }else{ 491 alSourcei(id, AL_LOOPING, src.isLooping() ? AL_TRUE : AL_FALSE); 492 } 493 alSourcef(id, AL_GAIN, src.getVolume()); 494 alSourcef(id, AL_PITCH, src.getPitch()); 495 alSourcef(id, AL11.AL_SEC_OFFSET, src.getTimeOffset()); 496 497 if (src.isDirectional()){ 498 Vector3f dir = src.getDirection(); 499 alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); 500 alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); 501 alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); 502 alSourcef(id, AL_CONE_OUTER_GAIN, 0); 503 }else{ 504 alSourcef(id, AL_CONE_INNER_ANGLE, 360); 505 alSourcef(id, AL_CONE_OUTER_ANGLE, 360); 506 alSourcef(id, AL_CONE_OUTER_GAIN, 1f); 507 } 508 } 509 510 public void updateListenerParam(Listener listener, ListenerParam param){ 511 checkDead(); 512 synchronized (threadLock){ 513 while (!threadLock.get()){ 514 try { 515 threadLock.wait(); 516 } catch (InterruptedException ex) { 517 } 518 } 519 if (audioDisabled) 520 return; 521 522 switch (param){ 523 case Position: 524 Vector3f pos = listener.getLocation(); 525 alListener3f(AL_POSITION, pos.x, pos.y, pos.z); 526 break; 527 case Rotation: 528 Vector3f dir = listener.getDirection(); 529 Vector3f up = listener.getUp(); 530 fb.rewind(); 531 fb.put(dir.x).put(dir.y).put(dir.z); 532 fb.put(up.x).put(up.y).put(up.z); 533 fb.flip(); 534 alListener(AL_ORIENTATION, fb); 535 break; 536 case Velocity: 537 Vector3f vel = listener.getVelocity(); 538 alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); 539 break; 540 case Volume: 541 alListenerf(AL_GAIN, listener.getVolume()); 542 break; 543 } 544 } 545 } 546 547 private void setListenerParams(Listener listener){ 548 Vector3f pos = listener.getLocation(); 549 Vector3f vel = listener.getVelocity(); 550 Vector3f dir = listener.getDirection(); 551 Vector3f up = listener.getUp(); 552 553 alListener3f(AL_POSITION, pos.x, pos.y, pos.z); 554 alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); 555 fb.rewind(); 556 fb.put(dir.x).put(dir.y).put(dir.z); 557 fb.put(up.x).put(up.y).put(up.z); 558 fb.flip(); 559 alListener(AL_ORIENTATION, fb); 560 alListenerf(AL_GAIN, listener.getVolume()); 561 } 562 563 private int newChannel(){ 564 if (freeChans.size() > 0) 565 return freeChans.remove(0); 566 else if (nextChan < channels.length){ 567 return nextChan++; 568 }else{ 569 return -1; 570 } 571 } 572 573 private void freeChannel(int index){ 574 if (index == nextChan-1){ 575 nextChan--; 576 } else{ 577 freeChans.add(index); 578 } 579 } 580 581 public void setEnvironment(Environment env){ 582 checkDead(); 583 synchronized (threadLock){ 584 while (!threadLock.get()){ 585 try { 586 threadLock.wait(); 587 } catch (InterruptedException ex) { 588 } 589 } 590 if (audioDisabled || !supportEfx) 591 return; 592 593 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DENSITY, env.getDensity()); 594 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DIFFUSION, env.getDiffusion()); 595 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAIN, env.getGain()); 596 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAINHF, env.getGainHf()); 597 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_TIME, env.getDecayTime()); 598 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); 599 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); 600 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); 601 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); 602 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); 603 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); 604 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); 605 606 // attach effect to slot 607 EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); 608 } 609 } 610 611 private boolean fillBuffer(AudioStream stream, int id){ 612 int size = 0; 613 int result; 614 615 while (size < arrayBuf.length){ 616 result = stream.readSamples(arrayBuf, size, arrayBuf.length - size); 617 618 if(result > 0){ 619 size += result; 620 }else{ 621 break; 622 } 623 } 624 625 if(size == 0) 626 return false; 627 628 nativeBuf.clear(); 629 nativeBuf.put(arrayBuf, 0, size); 630 nativeBuf.flip(); 631 632 alBufferData(id, convertFormat(stream), nativeBuf, stream.getSampleRate()); 633 634 return true; 635 } 636 637 private boolean fillStreamingSource(int sourceId, AudioStream stream){ 638 if (!stream.isOpen()) 639 return false; 640 641 boolean active = true; 642 int processed = alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); 643 644 // while((processed--) != 0){ 645 if (processed > 0){ 646 int buffer; 647 648 ib.position(0).limit(1); 649 alSourceUnqueueBuffers(sourceId, ib); 650 buffer = ib.get(0); 651 652 active = fillBuffer(stream, buffer); 653 654 ib.position(0).limit(1); 655 ib.put(0, buffer); 656 alSourceQueueBuffers(sourceId, ib); 657 } 658 659 if (!active && stream.isOpen()) 660 stream.close(); 661 662 return active; 663 } 664 665 private boolean attachStreamToSource(int sourceId, AudioStream stream){ 666 boolean active = true; 667 for (int id : stream.getIds()){ 668 active = fillBuffer(stream, id); 669 ib.position(0).limit(1); 670 ib.put(id).flip(); 671 alSourceQueueBuffers(sourceId, ib); 672 } 673 return active; 674 } 675 676 private boolean attachBufferToSource(int sourceId, AudioBuffer buffer){ 677 alSourcei(sourceId, AL_BUFFER, buffer.getId()); 678 return true; 679 } 680 681 private boolean attachAudioToSource(int sourceId, AudioData data){ 682 if (data instanceof AudioBuffer){ 683 return attachBufferToSource(sourceId, (AudioBuffer) data); 684 }else if (data instanceof AudioStream){ 685 return attachStreamToSource(sourceId, (AudioStream) data); 686 } 687 throw new UnsupportedOperationException(); 688 } 689 690 private void clearChannel(int index){ 691 // make room at this channel 692 if (chanSrcs[index] != null){ 693 AudioNode src = chanSrcs[index]; 694 695 int sourceId = channels[index]; 696 alSourceStop(sourceId); 697 698 if (src.getAudioData() instanceof AudioStream){ 699 AudioStream str = (AudioStream) src.getAudioData(); 700 ib.position(0).limit(STREAMING_BUFFER_COUNT); 701 ib.put(str.getIds()).flip(); 702 alSourceUnqueueBuffers(sourceId, ib); 703 }else if (src.getAudioData() instanceof AudioBuffer){ 704 alSourcei(sourceId, AL_BUFFER, 0); 705 } 706 707 if (src.getDryFilter() != null && supportEfx){ 708 // detach filter 709 alSourcei(sourceId, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); 710 } 711 if (src.isPositional()){ 712 AudioNode pas = (AudioNode) src; 713 if (pas.isReverbEnabled() && supportEfx) { 714 AL11.alSource3i(sourceId, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); 715 } 716 } 717 718 chanSrcs[index] = null; 719 } 720 } 721 722 public void update(float tpf){ 723 // does nothing 724 } 725 726 public void updateInThread(float tpf){ 727 if (audioDisabled) 728 return; 729 730 for (int i = 0; i < channels.length; i++){ 731 AudioNode src = chanSrcs[i]; 732 if (src == null) 733 continue; 734 735 int sourceId = channels[i]; 736 737 // is the source bound to this channel 738 // if false, it's an instanced playback 739 boolean boundSource = i == src.getChannel(); 740 741 // source's data is streaming 742 boolean streaming = src.getAudioData() instanceof AudioStream; 743 744 // only buffered sources can be bound 745 assert (boundSource && streaming) || (!streaming); 746 747 int state = alGetSourcei(sourceId, AL_SOURCE_STATE); 748 boolean wantPlaying = src.getStatus() == Status.Playing; 749 boolean stopped = state == AL_STOPPED; 750 751 if (streaming && wantPlaying){ 752 AudioStream stream = (AudioStream) src.getAudioData(); 753 if (stream.isOpen()){ 754 fillStreamingSource(sourceId, stream); 755 if (stopped) 756 alSourcePlay(sourceId); 757 }else{ 758 if (stopped){ 759 // became inactive 760 src.setStatus(Status.Stopped); 761 src.setChannel(-1); 762 clearChannel(i); 763 freeChannel(i); 764 765 // And free the audio since it cannot be 766 // played again anyway. 767 deleteAudioData(stream); 768 } 769 } 770 }else if (!streaming){ 771 boolean paused = state == AL_PAUSED; 772 773 // make sure OAL pause state & source state coincide 774 assert (src.getStatus() == Status.Paused && paused) || (!paused); 775 776 if (stopped){ 777 if (boundSource){ 778 src.setStatus(Status.Stopped); 779 src.setChannel(-1); 780 } 781 clearChannel(i); 782 freeChannel(i); 783 } 784 } 785 } 786 787 // Delete any unused objects. 788 objManager.deleteUnused(this); 789 } 790 791 public void setListener(Listener listener) { 792 checkDead(); 793 synchronized (threadLock){ 794 while (!threadLock.get()){ 795 try { 796 threadLock.wait(); 797 } catch (InterruptedException ex) { 798 } 799 } 800 if (audioDisabled) 801 return; 802 803 if (this.listener != null){ 804 // previous listener no longer associated with current 805 // renderer 806 this.listener.setRenderer(null); 807 } 808 809 this.listener = listener; 810 this.listener.setRenderer(this); 811 setListenerParams(listener); 812 } 813 } 814 815 public void playSourceInstance(AudioNode src){ 816 checkDead(); 817 synchronized (threadLock){ 818 while (!threadLock.get()){ 819 try { 820 threadLock.wait(); 821 } catch (InterruptedException ex) { 822 } 823 } 824 if (audioDisabled) 825 return; 826 827 if (src.getAudioData() instanceof AudioStream) 828 throw new UnsupportedOperationException( 829 "Cannot play instances " + 830 "of audio streams. Use playSource() instead."); 831 832 if (src.getAudioData().isUpdateNeeded()){ 833 updateAudioData(src.getAudioData()); 834 } 835 836 // create a new index for an audio-channel 837 int index = newChannel(); 838 if (index == -1) 839 return; 840 841 int sourceId = channels[index]; 842 843 clearChannel(index); 844 845 // set parameters, like position and max distance 846 setSourceParams(sourceId, src, true); 847 attachAudioToSource(sourceId, src.getAudioData()); 848 chanSrcs[index] = src; 849 850 // play the channel 851 alSourcePlay(sourceId); 852 } 853 } 854 855 856 public void playSource(AudioNode src) { 857 checkDead(); 858 synchronized (threadLock){ 859 while (!threadLock.get()){ 860 try { 861 threadLock.wait(); 862 } catch (InterruptedException ex) { 863 } 864 } 865 if (audioDisabled) 866 return; 867 868 //assert src.getStatus() == Status.Stopped || src.getChannel() == -1; 869 870 if (src.getStatus() == Status.Playing){ 871 return; 872 }else if (src.getStatus() == Status.Stopped){ 873 874 // allocate channel to this source 875 int index = newChannel(); 876 if (index == -1) { 877 logger.log(Level.WARNING, "No channel available to play {0}", src); 878 return; 879 } 880 clearChannel(index); 881 src.setChannel(index); 882 883 AudioData data = src.getAudioData(); 884 if (data.isUpdateNeeded()) 885 updateAudioData(data); 886 887 chanSrcs[index] = src; 888 setSourceParams(channels[index], src, false); 889 attachAudioToSource(channels[index], data); 890 } 891 892 alSourcePlay(channels[src.getChannel()]); 893 src.setStatus(Status.Playing); 894 } 895 } 896 897 898 public void pauseSource(AudioNode src) { 899 checkDead(); 900 synchronized (threadLock){ 901 while (!threadLock.get()){ 902 try { 903 threadLock.wait(); 904 } catch (InterruptedException ex) { 905 } 906 } 907 if (audioDisabled) 908 return; 909 910 if (src.getStatus() == Status.Playing){ 911 assert src.getChannel() != -1; 912 913 alSourcePause(channels[src.getChannel()]); 914 src.setStatus(Status.Paused); 915 } 916 } 917 } 918 919 920 public void stopSource(AudioNode src) { 921 synchronized (threadLock){ 922 while (!threadLock.get()){ 923 try { 924 threadLock.wait(); 925 } catch (InterruptedException ex) { 926 } 927 } 928 if (audioDisabled) 929 return; 930 931 if (src.getStatus() != Status.Stopped){ 932 int chan = src.getChannel(); 933 assert chan != -1; // if it's not stopped, must have id 934 935 src.setStatus(Status.Stopped); 936 src.setChannel(-1); 937 clearChannel(chan); 938 freeChannel(chan); 939 940 if (src.getAudioData() instanceof AudioStream) { 941 AudioStream stream = (AudioStream)src.getAudioData(); 942 if (stream.isOpen()) { 943 stream.close(); 944 } 945 946 // And free the audio since it cannot be 947 // played again anyway. 948 deleteAudioData(src.getAudioData()); 949 } 950 } 951 } 952 } 953 954 private int convertFormat(AudioData ad){ 955 switch (ad.getBitsPerSample()){ 956 case 8: 957 if (ad.getChannels() == 1) 958 return AL_FORMAT_MONO8; 959 else if (ad.getChannels() == 2) 960 return AL_FORMAT_STEREO8; 961 962 break; 963 case 16: 964 if (ad.getChannels() == 1) 965 return AL_FORMAT_MONO16; 966 else 967 return AL_FORMAT_STEREO16; 968 } 969 throw new UnsupportedOperationException("Unsupported channels/bits combination: "+ 970 "bits="+ad.getBitsPerSample()+", channels="+ad.getChannels()); 971 } 972 973 private void updateAudioBuffer(AudioBuffer ab){ 974 int id = ab.getId(); 975 if (ab.getId() == -1){ 976 ib.position(0).limit(1); 977 alGenBuffers(ib); 978 id = ib.get(0); 979 ab.setId(id); 980 981 objManager.registerForCleanup(ab); 982 } 983 984 ab.getData().clear(); 985 alBufferData(id, convertFormat(ab), ab.getData(), ab.getSampleRate()); 986 ab.clearUpdateNeeded(); 987 } 988 989 private void updateAudioStream(AudioStream as){ 990 if (as.getIds() != null){ 991 deleteAudioData(as); 992 } 993 994 int[] ids = new int[STREAMING_BUFFER_COUNT]; 995 ib.position(0).limit(STREAMING_BUFFER_COUNT); 996 alGenBuffers(ib); 997 ib.position(0).limit(STREAMING_BUFFER_COUNT); 998 ib.get(ids); 999 1000 // Not registered with object manager. 1001 // AudioStreams can be handled without object manager 1002 // since their lifecycle is known to the audio renderer. 1003 1004 as.setIds(ids); 1005 as.clearUpdateNeeded(); 1006 } 1007 1008 private void updateAudioData(AudioData ad){ 1009 if (ad instanceof AudioBuffer){ 1010 updateAudioBuffer((AudioBuffer) ad); 1011 }else if (ad instanceof AudioStream){ 1012 updateAudioStream((AudioStream) ad); 1013 } 1014 } 1015 1016 public void deleteFilter(Filter filter) { 1017 int id = filter.getId(); 1018 if (id != -1){ 1019 EFX10.alDeleteFilters(id); 1020 } 1021 } 1022 1023 public void deleteAudioData(AudioData ad){ 1024 synchronized (threadLock){ 1025 while (!threadLock.get()){ 1026 try { 1027 threadLock.wait(); 1028 } catch (InterruptedException ex) { 1029 } 1030 } 1031 if (audioDisabled) 1032 return; 1033 1034 if (ad instanceof AudioBuffer){ 1035 AudioBuffer ab = (AudioBuffer) ad; 1036 int id = ab.getId(); 1037 if (id != -1){ 1038 ib.put(0,id); 1039 ib.position(0).limit(1); 1040 alDeleteBuffers(ib); 1041 ab.resetObject(); 1042 } 1043 }else if (ad instanceof AudioStream){ 1044 AudioStream as = (AudioStream) ad; 1045 int[] ids = as.getIds(); 1046 if (ids != null){ 1047 ib.clear(); 1048 ib.put(ids).flip(); 1049 alDeleteBuffers(ib); 1050 as.resetObject(); 1051 } 1052 } 1053 } 1054 } 1055 1056 } 1057