001package jmri.jmrit.audio; 002 003import com.jogamp.openal.AL; 004import com.jogamp.openal.ALExt; 005import com.jogamp.openal.ALC; 006import com.jogamp.openal.ALCdevice; 007import com.jogamp.openal.ALConstants; 008import com.jogamp.openal.ALException; 009import com.jogamp.openal.ALFactory; 010import com.jogamp.openal.util.ALut; 011import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 012import java.util.SortedSet; 013import java.util.TreeSet; 014import jmri.Audio; 015import jmri.AudioManager; 016import jmri.InstanceManager; 017 018/** 019 * This is the JOAL audio system specific AudioFactory. 020 * <p> 021 * The JOAL sound system supports, where available, full surround-sound with 3D 022 * positioning capabilities. 023 * <p> 024 * When only stereo capable hardware is available, it will automatically create 025 * an approximation of the desired sound-scape. 026 * <p> 027 * This factory initialises JOAL, provides new Joal-specific Audio objects and 028 * deals with clean-up operations. 029 * <br><br><hr><br><b> 030 * This software is based on or using the JOAL Library available from 031 * <a href="http://jogamp.org/joal/www/">http://jogamp.org/joal/www/</a> 032 * </b><br><br> 033 * JOAL is released under the BSD license. The full license terms follow: 034 * <br><i> 035 * Copyright (c) 2003-2006 Sun Microsystems, Inc. All Rights Reserved. 036 * <br> 037 * Redistribution and use in source and binary forms, with or without 038 * modification, are permitted provided that the following conditions are 039 * met: 040 * <br> 041 * - Redistribution of source code must retain the above copyright 042 * notice, this list of conditions and the following disclaimer. 043 * <br> 044 * - Redistribution in binary form must reproduce the above copyright 045 * notice, this list of conditions and the following disclaimer in the 046 * documentation and/or other materials provided with the distribution. 047 * <br> 048 * Neither the name of Sun Microsystems, Inc. or the names of 049 * contributors may be used to endorse or promote products derived from 050 * this software without specific prior written permission. 051 * <br> 052 * This software is provided "AS IS," without a warranty of any kind. ALL 053 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, 054 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A 055 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN 056 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR 057 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 058 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR 059 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR 060 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 061 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, 062 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF 063 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 064 * <br> 065 * You acknowledge that this software is not designed or intended for use 066 * in the design, construction, operation or maintenance of any nuclear 067 * facility. 068 * <br><br><br></i> 069 * <hr> 070 * This file is part of JMRI. 071 * <p> 072 * JMRI is free software; you can redistribute it and/or modify it under the 073 * terms of version 2 of the GNU General Public License as published by the Free 074 * Software Foundation. See the "COPYING" file for a copy of this license. 075 * <p> 076 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 077 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 078 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 079 * 080 * @author Matthew Harris copyright (c) 2009 081 */ 082public class JoalAudioFactory extends AbstractAudioFactory { 083 084 private static AL al; 085 086 private static ALC alc; 087 088 private static ALExt alext; 089 090 private static ALCdevice alcDevice; 091 092 // New Effects Objects 093 // holds id's of effect slots 094 static int[] uiEffectSlots = new int[1]; 095 // holds id's of effects 096 static int[] uiEffect = new int[1]; 097 098 private static boolean initialised = false; 099 100 private JoalAudioListener activeAudioListener; 101 102 /** 103 * Definition of 8-bit quad multi-channel audio format. 104 * <p> 105 * These formats are only supported by certain OpenAL implementations and 106 * support is determined at runtime. 107 * <p> 108 * Initially set format to unknown. 109 */ 110 static int AL_FORMAT_QUAD8 = AudioBuffer.FORMAT_UNKNOWN; 111 112 /** 113 * Definition of 16-bit quad multi-channel audio format. 114 * <p> 115 * These formats are only supported by certain OpenAL implementations and 116 * support is determined at runtime. 117 * <p> 118 * Initially set format to unknown. 119 */ 120 static int AL_FORMAT_QUAD16 = AudioBuffer.FORMAT_UNKNOWN; 121 122 /** 123 * Definition of 8-bit 5.1 multi-channel audio format. 124 * <p> 125 * These formats are only supported by certain OpenAL implementations and 126 * support is determined at runtime. 127 * <p> 128 * Initially set format to unknown. 129 */ 130 static int AL_FORMAT_51CHN8 = AudioBuffer.FORMAT_UNKNOWN; 131 132 /** 133 * Definition of 16-bit 5.1 multi-channel audio format. 134 * <p> 135 * These formats are only supported by certain OpenAL implementations and 136 * support is determined at runtime. 137 * <p> 138 * Initially set format to unknown. 139 */ 140 static int AL_FORMAT_51CHN16 = AudioBuffer.FORMAT_UNKNOWN; 141 142 /** 143 * Definition of 8-bit 6.1 multi-channel audio format. 144 * <p> 145 * These formats are only supported by certain OpenAL implementations and 146 * support is determined at runtime. 147 * <p> 148 * Initially set format to unknown. 149 */ 150 static int AL_FORMAT_61CHN8 = AudioBuffer.FORMAT_UNKNOWN; 151 152 /** 153 * Definition of 16-bit 6.1 multi-channel audio format. 154 * <p> 155 * These formats are only supported by certain OpenAL implementations and 156 * support is determined at runtime. 157 * <p> 158 * Initially set format to unknown. 159 */ 160 static int AL_FORMAT_61CHN16 = AudioBuffer.FORMAT_UNKNOWN; 161 162 /** 163 * Definition of 8-bit 7.1 multi-channel audio format. 164 * <p> 165 * These formats are only supported by certain OpenAL implementations and 166 * support is determined at runtime. 167 * <p> 168 * Initially set format to unknown. 169 */ 170 static int AL_FORMAT_71CHN8 = AudioBuffer.FORMAT_UNKNOWN; 171 172 /** 173 * Definition of 16-bit 7.1 multi-channel audio format. 174 * <p> 175 * These formats are only supported by certain OpenAL implementations and 176 * support is determined at runtime. 177 * <p> 178 * Initially set format to unknown. 179 */ 180 static int AL_FORMAT_71CHN16 = AudioBuffer.FORMAT_UNKNOWN; 181 182 /** 183 * Initialise this JoalAudioFactory and check for multi-channel support. 184 * <p> 185 * Initial values for multi-channel formats are set to unknown as OpenAL 186 * implementations are only guaranteed to support MONO and STEREO Buffers. 187 * <p> 188 * On initialisation, we need to check if this implementation supports 189 * multi-channel formats. 190 * <p> 191 * This is done by making alGetEnumValue calls to request the value of the 192 * Buffer Format Tag Enum (that will be passed to an alBufferData call). 193 * Enum Values are retrieved by string names. The following names are 194 * defined for multi-channel wave formats ... 195 * <ul> 196 * <li>"AL_FORMAT_QUAD8" : 4 Channel, 8 bit data 197 * <li>"AL_FORMAT_QUAD16" : 4 Channel, 16 bit data 198 * <li>"AL_FORMAT_51CHN8" : 5.1 Channel, 8 bit data 199 * <li>"AL_FORMAT_51CHN16" : 5.1 Channel, 16 bit data 200 * <li>"AL_FORMAT_61CHN8" : 6.1 Channel, 8 bit data 201 * <li>"AL_FORMAT_61CHN16" : 6.1 Channel, 16 bit data 202 * <li>"AL_FORMAT_71CHN8" : 7.1 Channel, 8 bit data 203 * <li>"AL_FORMAT_71CHN16" : 7.1 Channel, 16 bit data 204 * </ul> 205 * 206 * @return true, if initialisation successful 207 */ 208 @Override 209 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 210 justification = "OK to write to static variables as we only do so if not initialised") 211 public boolean init() { 212 if (initialised) { 213 return true; 214 } 215 216 // Initialise OpenAL and clear the error bit 217 try { 218// // Open default 'preferred' device 219// alcDevice = alc.alcOpenDevice(null); 220// alcContext = alc.alcCreateContext(alcDevice, null); 221// alc.alcMakeContextCurrent(alcContext); 222// 223 ALut.alutInit(); 224 al = ALFactory.getAL(); 225 al.alGetError(); 226 log.info("Initialised JOAL using OpenAL: vendor - {} version - {}", al.alGetString(AL.AL_VENDOR), al.alGetString(AL.AL_VERSION)); 227 } catch (ALException e) { 228 log.warn("Error initialising JOAL", jmri.util.LoggingUtil.shortenStacktrace(e)); 229 return false; 230 } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { 231 log.warn("Error loading OpenAL libraries: {}", e.getMessage()); 232 return false; 233 } catch (RuntimeException e) { 234 log.warn("Error initialising OpenAL", jmri.util.LoggingUtil.shortenStacktrace(e)); 235 return false; 236 } 237 238 // Check for multi-channel support 239 int checkMultiChannel; 240 241 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_QUAD8"); 242 checkALError(); 243 if (checkMultiChannel != ALConstants.AL_FALSE) { 244 AL_FORMAT_QUAD8 = checkMultiChannel; 245 } 246 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_QUAD16"); 247 checkALError(); 248 if (checkMultiChannel != ALConstants.AL_FALSE) { 249 AL_FORMAT_QUAD16 = checkMultiChannel; 250 } 251 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_51CHN8"); 252 checkALError(); 253 if (checkMultiChannel != ALConstants.AL_FALSE) { 254 AL_FORMAT_51CHN8 = checkMultiChannel; 255 } 256 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_51CHN16"); 257 checkALError(); 258 if (checkMultiChannel != ALConstants.AL_FALSE) { 259 AL_FORMAT_51CHN16 = checkMultiChannel; 260 } 261 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_61CHN8"); 262 checkALError(); 263 if (checkMultiChannel != ALConstants.AL_FALSE) { 264 AL_FORMAT_61CHN8 = checkMultiChannel; 265 } 266 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_61CHN16"); 267 checkALError(); 268 if (checkMultiChannel != ALConstants.AL_FALSE) { 269 AL_FORMAT_61CHN16 = checkMultiChannel; 270 } 271 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_71CHN8"); 272 checkALError(); 273 if (checkMultiChannel != ALConstants.AL_FALSE) { 274 AL_FORMAT_71CHN8 = checkMultiChannel; 275 } 276 checkMultiChannel = al.alGetEnumValue("AL_FORMAT_71CHN16"); 277 checkALError(); 278 if (checkMultiChannel != ALConstants.AL_FALSE) { 279 AL_FORMAT_71CHN16 = checkMultiChannel; 280 } 281 log.debug("8-bit quadrophonic supported? {}", AL_FORMAT_QUAD8 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 282 log.debug("16-bit quadrophonic supported? {}", AL_FORMAT_QUAD16 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 283 log.debug("8-bit 5.1 surround supported? {}", AL_FORMAT_51CHN8 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 284 log.debug("16-bit 5.1 surround supported? {}", AL_FORMAT_51CHN16 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 285 log.debug("8-bit 6.1 surround supported? {}", AL_FORMAT_61CHN8 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 286 log.debug("16-bit 6.1 surround supported? {}", AL_FORMAT_61CHN16 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 287 log.debug("8 bit 7.1 surround supported? {}", AL_FORMAT_71CHN8 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 288 log.debug("16 bit 7.1 surround supported? {}", AL_FORMAT_71CHN16 == AudioBuffer.FORMAT_UNKNOWN ? "No" : "Yes"); 289 290 // Check context 291 alc = ALFactory.getALC(); 292 alext = ALFactory.getALExt(); 293 alcDevice = alc.alcGetContextsDevice(alc.alcGetCurrentContext()); 294 if (!checkALCError(alcDevice)) { 295 int[] size = new int[1]; 296 alc.alcGetIntegerv(alcDevice, ALC.ALC_ATTRIBUTES_SIZE, size.length, size, 0); 297 log.debug("Size of ALC_ATTRIBUTES: {}", size[0]); 298 if (!checkALCError(alcDevice) && size[0] > 0) { 299 int[] attributes = new int[size[0]]; 300 alc.alcGetIntegerv(alcDevice, ALC.ALC_ALL_ATTRIBUTES, attributes.length, attributes, 0); 301 for (int i = 0; i < attributes.length; i++) { 302 if (i % 2 != 0) { 303 continue; 304 } 305 switch (attributes[i]) { 306 case ALC.ALC_INVALID: 307 log.debug("Invalid"); 308 break; 309 case ALC.ALC_MONO_SOURCES: 310 log.debug("Number of mono sources: {}", attributes[i + 1]); 311 break; 312 case ALC.ALC_STEREO_SOURCES: 313 log.debug("Number of stereo sources: {}", attributes[i + 1]); 314 break; 315 case ALC.ALC_FREQUENCY: 316 log.debug("Frequency: {}", attributes[i + 1]); 317 break; 318 case ALC.ALC_REFRESH: 319 log.debug("Refresh: {}", attributes[i + 1]); 320 break; 321 default: 322 log.debug("Attribute {}: {}", i, attributes[i]); 323 } 324 } 325 if (alc.alcIsExtensionPresent(alcDevice, "ALC_EXT_EFX")) { 326 loadEffect(); 327 } else { 328 log.warn("Extension EFX not present"); 329 } 330 } 331 } 332 333 super.init(); 334 initialised = true; 335 return true; 336 } 337 338 static int loadEffect() { 339 al.alGetError(); // Clear error state 340 // Try to create 1 effect slot 341 alext.alGenAuxiliaryEffectSlots(1, uiEffectSlots, 0); 342 if (al.alGetError() != AL.AL_NO_ERROR) { 343 return AL.AL_FALSE; 344 } 345 log.debug("Generated 1 effect slot {}", uiEffectSlots[0]); 346 347 // Try to create 1 effect 348 alext.alGenEffects(1, uiEffect, 0); 349 if (al.alGetError() != AL.AL_NO_ERROR) { 350 return AL.AL_FALSE; 351 } 352 log.debug("Generated 1 effect"); 353 354 // Set Effect Type to Reverb 355 if (alext.alIsEffect(uiEffect[0])) { 356 alext.alEffecti(uiEffect[0], ALExt.AL_EFFECT_TYPE, ALExt.AL_EFFECT_REVERB); 357 if (al.alGetError() != AL.AL_NO_ERROR) { 358 log.warn("ReverbEffect not supported"); 359 return AL.AL_FALSE; 360 } 361 } 362 363 // Load effect into effect slot 364 alext.alAuxiliaryEffectSloti(uiEffectSlots[0], ALExt.AL_EFFECTSLOT_EFFECT, uiEffect[0]); 365 if (al.alGetError() != AL.AL_NO_ERROR) { 366 log.warn("Could not load effect into effect slot"); 367 return AL.AL_FALSE; 368 } 369 log.debug("Successfully loaded effect into effect slot"); 370 return AL.AL_TRUE; 371 } 372 373 @Override 374 public String toString() { 375 if (al == null) return "JoalAudioFactory, using null"; 376 try { 377 return "JoalAudioFactory, using OpenAL:" 378 + " vendor - " + al.alGetString(AL.AL_VENDOR) 379 + " version - " + al.alGetString(AL.AL_VERSION); 380 } catch (NullPointerException e) { 381 log.error("NPE from JoalAudioFactory",e); 382 return "JoalAudioFactory, using Null"; 383 } 384 } 385 386 @Override 387 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 388 justification = "OK to write to static variables to record static library status") 389 public void cleanup() { 390 // Stop the command thread 391 super.cleanup(); 392 393 // Get the active AudioManager 394 AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class); 395 396 // Retrieve list of AudioSource objects and remove the sources 397 SortedSet<Audio> sources = new TreeSet<>(am.getNamedBeanSet(Audio.SOURCE)); 398 for (Audio source: sources) { 399 if (log.isDebugEnabled()) { 400 log.debug("Removing JoalAudioSource: {}", source.getSystemName()); 401 } 402 // Includes cleanup 403 source.dispose(); 404 } 405 406 // Now, retrieve list of AudioBuffer objects and remove the buffers 407 SortedSet<Audio> buffers = new TreeSet<>(am.getNamedBeanSet(Audio.BUFFER)); 408 for (Audio buffer : buffers) { 409 if (log.isDebugEnabled()) { 410 log.debug("Removing JoalAudioBuffer: {}", buffer.getSystemName()); 411 } 412 // Includes cleanup 413 buffer.dispose(); 414 } 415 416 // Lastly, retrieve list of AudioListener objects and remove listener. 417 SortedSet<Audio> listeners = new TreeSet<>(am.getNamedBeanSet(Audio.LISTENER)); 418 for (Audio listener : listeners) { 419 if (log.isDebugEnabled()) { 420 log.debug("Removing JoalAudioListener: {}", listener.getSystemName()); 421 } 422 // Includes cleanup 423 listener.dispose(); 424 } 425 426 // delete and free resources for Auxiliary Effect Slot 427 alext.alDeleteAuxiliaryEffectSlots(1, uiEffectSlots, 0); 428 // delete and free resources for Effect Object 429 alext.alDeleteEffects(1, uiEffect, 0); 430 431 // Finally, shutdown OpenAL and close the output device 432 log.debug("Shutting down OpenAL, initialised: {}", initialised); 433 if (initialised) ALut.alutExit(); 434 initialised = false; 435 } 436 437 @Override 438 public boolean isInitialised() { 439 return initialised; 440 } 441 442 @Override 443 public AudioBuffer createNewBuffer(String systemName, String userName) { 444 return new JoalAudioBuffer(systemName, userName); 445 } 446 447 @Override 448 public AudioListener createNewListener(String systemName, String userName) { 449 activeAudioListener = new JoalAudioListener(systemName, userName); 450 return activeAudioListener; 451 } 452 453 @Override 454 public AudioListener getActiveAudioListener() { 455 return activeAudioListener; 456 } 457 458 @Override 459 public AudioSource createNewSource(String systemName, String userName) { 460 return new JoalAudioSource(systemName, userName); 461 } 462 463 @Override 464 public void setDistanceAttenuated(boolean attenuated) { 465 super.setDistanceAttenuated(attenuated); 466 if (isDistanceAttenuated()) { 467 al.alDistanceModel(ALConstants.AL_INVERSE_DISTANCE_CLAMPED); 468 } else { 469 al.alDistanceModel(ALConstants.AL_NONE); 470 } 471 } 472 473 /** 474 * Return a reference to the active AL object for use by other Joal objects 475 * 476 * @return active AL object 477 */ 478 public static synchronized AL getAL() { 479 return al; 480 } 481 482 /** 483 * Method to check if any error has occurred in the OpenAL sub-system. 484 * <p> 485 * If an error has occurred, log the error as a warning message and return 486 * True. 487 * <p> 488 * If no error has occurred, return False. 489 * 490 * @return True if an error has occurred 491 */ 492 public static boolean checkALError() { 493 return checkALError(""); 494 } 495 496 /** 497 * Method to check if any error has occurred in the OpenAL sub-system. 498 * <p> 499 * If an error has occurred, log the error as a warning message with the 500 * defined message pre-pended and return True. 501 * <p> 502 * If no error has occurred, return False. 503 * 504 * @param msg additional message prepended to the log 505 * @return True if an error has occurred 506 */ 507 public static boolean checkALError(String msg) { 508 // Trim any whitespace then append a space if required 509 msg = msg.trim(); 510 if (msg.length() > 0) { 511 msg = msg + " "; 512 } 513 514 // Check for error 515 switch (al.alGetError()) { 516 case AL.AL_NO_ERROR: 517 return false; 518 case AL.AL_INVALID_NAME: 519 log.warn("{}Invalid name parameter", msg); 520 return true; 521 case AL.AL_INVALID_ENUM: 522 log.warn("{}Invalid enumerated parameter value", msg); 523 return true; 524 case AL.AL_INVALID_VALUE: 525 log.warn("{}Invalid parameter value", msg); 526 return true; 527 case AL.AL_INVALID_OPERATION: 528 log.warn("{}Requested operation is not valid", msg); 529 return true; 530 case AL.AL_OUT_OF_MEMORY: 531 log.warn("{}Out of memory", msg); 532 return true; 533 default: 534 log.warn("{}Unrecognised error occurred", msg); 535 return true; 536 } 537 } 538 539 /** 540 * Method to check if any error has occurred in the OpenAL sub-system. 541 * <p> 542 * If an error has occurred, log the error as a warning message and return 543 * True. 544 * <p> 545 * If no error has occurred, return False. 546 * 547 * @param alcDevice OpenAL context device to check 548 * @return True if an error has occurred 549 */ 550 public static boolean checkALCError(ALCdevice alcDevice) { 551 return checkALCError(alcDevice, ""); 552 } 553 554 /** 555 * Method to check if any error has occurred in the OpenAL context 556 * sub-system. 557 * <p> 558 * If an error has occurred, log the error as a warning message with the 559 * defined message pre-pended and return True. 560 * <p> 561 * If no error has occurred, return False. 562 * 563 * @param alcDevice OpenAL context device to check 564 * @param msg additional message prepended to the log 565 * @return True if an error has occurred 566 */ 567 public static boolean checkALCError(ALCdevice alcDevice, String msg) { 568 // Trim any whitespace then append a space if required 569 msg = msg.trim(); 570 if (msg.length() > 0) { 571 msg = msg + " "; 572 } 573 574 // Check for error 575 switch (alc.alcGetError(alcDevice)) { 576 case ALC.ALC_NO_ERROR: 577 return false; 578 case ALC.ALC_INVALID_DEVICE: 579 log.warn("{}Invalid device", msg); 580 return true; 581 case ALC.ALC_INVALID_CONTEXT: 582 log.warn("{}Invalid context", msg); 583 return true; 584 case ALC.ALC_INVALID_ENUM: 585 log.warn("{}Invalid enumerated parameter value", msg); 586 return true; 587 case ALC.ALC_INVALID_VALUE: 588 log.warn("{}Invalid parameter value", msg); 589 return true; 590 case ALC.ALC_OUT_OF_MEMORY: 591 log.warn("{}Out of memory", msg); 592 return true; 593 default: 594 log.warn("{}Unrecognised error occurred", msg); 595 return true; 596 } 597 } 598 599 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JoalAudioFactory.class); 600 601}