001package jmri.jmrit.audio; 002 003import com.jogamp.openal.AL; 004import com.jogamp.openal.ALException; 005import com.jogamp.openal.util.ALut; 006import java.io.InputStream; 007import java.nio.ByteBuffer; 008import jmri.util.FileUtil; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * JOAL implementation of the Audio Buffer sub-class. 014 * <p> 015 * For now, no system-specific implementations are forseen - this will remain 016 * internal-only 017 * <br><br><hr><br><b> 018 * This software is based on or using the JOAL Library available from 019 * <a href="http://jogamp.org/joal/www/">http://jogamp.org/joal/www/</a> 020 * </b><br><br> 021 * JOAL is released under the BSD license. The full license terms follow: 022 * <br><i> 023 * Copyright (c) 2003-2006 Sun Microsystems, Inc. All Rights Reserved. 024 * <br> 025 * Redistribution and use in source and binary forms, with or without 026 * modification, are permitted provided that the following conditions are 027 * met: 028 * <br> 029 * - Redistribution of source code must retain the above copyright 030 * notice, this list of conditions and the following disclaimer. 031 * <br> 032 * - Redistribution in binary form must reproduce the above copyright 033 * notice, this list of conditions and the following disclaimer in the 034 * documentation and/or other materials provided with the distribution. 035 * <br> 036 * Neither the name of Sun Microsystems, Inc. or the names of 037 * contributors may be used to endorse or promote products derived from 038 * this software without specific prior written permission. 039 * <br> 040 * This software is provided "AS IS," without a warranty of any kind. ALL 041 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, 042 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A 043 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN 044 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR 045 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 046 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR 047 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR 048 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 049 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, 050 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF 051 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 052 * <br> 053 * You acknowledge that this software is not designed or intended for use 054 * in the design, construction, operation or maintenance of any nuclear 055 * facility. 056 * <br><br><br></i> 057 * <hr> 058 * This file is part of JMRI. 059 * <p> 060 * JMRI is free software; you can redistribute it and/or modify it under the 061 * terms of version 2 of the GNU General Public License as published by the Free 062 * Software Foundation. See the "COPYING" file for a copy of this license. 063 * <p> 064 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 065 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 066 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 067 * 068 * @author Matthew Harris copyright (c) 2009, 2011 069 */ 070public class JoalAudioBuffer extends AbstractAudioBuffer { 071 072 private static AL al = JoalAudioFactory.getAL(); 073 074 // Arrays to hold various .wav file information 075 private int[] format = new int[1]; 076 private int[] dataStorageBuffer = new int[1]; 077 private ByteBuffer[] data = new ByteBuffer[1]; 078 private int[] size = new int[1]; 079 private int[] freq = new int[1]; 080 private int[] loop = new int[1]; 081 082 private boolean initialised = false; 083 084 /** 085 * Constructor for new JoalAudioBuffer with system name 086 * 087 * @param systemName AudioBuffer object system name (e.g. IAB4) 088 */ 089 public JoalAudioBuffer(String systemName) { 090 super(systemName); 091 log.debug("New JoalAudioBuffer: {}", systemName); 092 initialised = init(); 093 } 094 095 /** 096 * Constructor for new JoalAudioBuffer with system name and user name 097 * 098 * @param systemName AudioBuffer object system name (e.g. IAB4) 099 * @param userName AudioBuffer object user name 100 */ 101 public JoalAudioBuffer(String systemName, String userName) { 102 super(systemName, userName); 103 log.debug("New JoalAudioBuffer: {} ({})", userName, systemName); 104 initialised = init(); 105 } 106 107 /** 108 * Initialise this JoalAudioBuffer. 109 * 110 * @return true, if initialisation successful 111 */ 112 private boolean init() { 113 // Check that the JoalAudioFactory exists 114 if (al == null) { 115 log.warn("Al Factory not yet initialised"); 116 return false; 117 } 118 119 // Try to create an empty buffer that will hold the actual sound data 120 al.alGenBuffers(1, dataStorageBuffer, 0); 121 if (JoalAudioFactory.checkALError()) { 122 log.warn("Error creating JoalAudioBuffer ({})", this.getSystemName()); 123 return false; 124 } 125 126 this.setState(STATE_EMPTY); 127 return true; 128 } 129 130 /** 131 * Return reference to the DataStorageBuffer integer array 132 * <p> 133 * Applies only to sub-types: 134 * <ul> 135 * <li>Buffer 136 * </ul> 137 * 138 * @return buffer[] reference to DataStorageBuffer 139 */ 140 protected int[] getDataStorageBuffer() { 141 return dataStorageBuffer; 142 } 143 144 /** 145 * Internal method to return a string representation of the audio format 146 * 147 * @return string representation 148 */ 149 private String parseFormat() { 150 switch (this.format[0]) { 151 case AL.AL_FORMAT_MONO8: 152 return "8-bit mono"; 153 case AL.AL_FORMAT_MONO16: 154 return "16-bit mono"; 155 case AL.AL_FORMAT_STEREO8: 156 return "8-bit stereo"; 157 case AL.AL_FORMAT_STEREO16: 158 return "16-bit stereo"; 159 default: 160 log.error("Unhandled audio format type: {}", this.format[0]); 161 } 162 if (this.format[0] == JoalAudioFactory.AL_FORMAT_QUAD8 163 && JoalAudioFactory.AL_FORMAT_QUAD8 != FORMAT_UNKNOWN) { 164 return "8-bit quadrophonic"; 165 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_QUAD16 166 && JoalAudioFactory.AL_FORMAT_QUAD16 != FORMAT_UNKNOWN) { 167 return "16-bit quadrophonic"; 168 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_51CHN8 169 && JoalAudioFactory.AL_FORMAT_51CHN8 != FORMAT_UNKNOWN) { 170 return "8-bit 5.1 surround"; 171 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_51CHN16 172 && JoalAudioFactory.AL_FORMAT_51CHN16 != FORMAT_UNKNOWN) { 173 return "16-bit 5.1 surround"; 174 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_61CHN8 175 && JoalAudioFactory.AL_FORMAT_61CHN8 != FORMAT_UNKNOWN) { 176 return "8-bit 6.1 surround"; 177 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_61CHN16 178 && JoalAudioFactory.AL_FORMAT_61CHN16 != FORMAT_UNKNOWN) { 179 return "16-bit 6.1 surround"; 180 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_71CHN8 181 && JoalAudioFactory.AL_FORMAT_71CHN8 != FORMAT_UNKNOWN) { 182 return "8 bit 7.1 surround"; 183 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_71CHN16 184 && JoalAudioFactory.AL_FORMAT_71CHN16 != FORMAT_UNKNOWN) { 185 return "16 bit 7.1 surround"; 186 } else { 187 return "unknown format"; 188 } 189 } 190 191 @Override 192 protected boolean loadBuffer(InputStream stream) { 193 if (!initialised) { 194 return false; 195 } 196 // Reset buffer state 197 // Use internal methods to postpone loop buffer generation 198 setStartLoopPoint(0, false); 199 setEndLoopPoint(0, false); 200 this.setState(STATE_EMPTY); 201 202 // Load the specified .wav file into data arrays 203 try { 204 ALut.alutLoadWAVFile(stream, format, data, size, freq, loop); 205 } catch (ALException e) { 206 log.warn("Exception loading JoalAudioBuffer from stream: {}", e.toString()); 207 return false; 208 } 209 210 return (this.processBuffer()); 211 } 212 213 @Override 214 protected boolean loadBuffer() { 215 if (!initialised) { 216 return false; 217 } 218 // Reset buffer state 219 // Use internal methods to postpone loop buffer generation 220 setStartLoopPoint(0, false); 221 setEndLoopPoint(0, false); 222 this.setState(STATE_EMPTY); 223 224 // Load the specified .wav file into data arrays 225 try { 226 ALut.alutLoadWAVFile(FileUtil.getExternalFilename(this.getURL()), format, data, size, freq, loop); 227 } catch (ALException e) { 228 log.warn("Exception loading JoalAudioBuffer from file: {}", e.toString()); 229 return false; 230 } 231 232 return (this.processBuffer()); 233 } 234 235 @Override 236 public boolean loadBuffer(ByteBuffer b, int format, int freq) { 237 if (!initialised) { 238 return false; 239 } 240 241 // Reset buffer state 242 // Use internal methods to postpone loop buffer generation 243 setStartLoopPoint(0, false); 244 setEndLoopPoint(0, false); 245 this.setState(STATE_EMPTY); 246 247 // Load the buffer data. 248 this.data[0] = b; 249 this.format[0] = format; 250 this.freq[0] = freq; 251 this.size[0] = b.limit(); 252 253 return (this.processBuffer()); 254 255 } 256 257 private boolean processBuffer() { 258 // Processing steps common to both loadBuffer(InputStream) and loadBuffer() 259 260 // Store the actual data in the buffer 261 al.alBufferData(dataStorageBuffer[0], format[0], data[0], size[0], freq[0]); 262 263 // Set initial loop points 264 // Use internal methods to postpone loop buffer generation 265 setStartLoopPoint(0, false); 266 setEndLoopPoint(size[0], false); 267 generateLoopBuffers(LOOP_POINT_BOTH); 268 269 // All done 270 this.setState(STATE_LOADED); 271 if (log.isDebugEnabled()) { 272 log.debug("Loaded buffer: {}", this.getSystemName()); 273 log.debug(" from file: {}", this.getURL()); 274 log.debug(" format: {}, {} Hz", parseFormat(), freq[0]); 275 log.debug(" length: {}", size[0]); 276 } 277 return true; 278 } 279 280 @Override 281 protected boolean generateStreamingBuffers() { 282 // TODO: Actually write this bit 283 if (log.isDebugEnabled()) { 284 log.debug("Method generateStreamingBuffers() called for JoalAudioBuffer {}", this.getSystemName()); 285 } 286 return true; 287 } 288 289 @Override 290 protected void removeStreamingBuffers() { 291 // TODO: Actually write this bit 292 if (log.isDebugEnabled()) { 293 log.debug("Method removeStreamingBuffers() called for JoalAudioBuffer {}", this.getSystemName()); 294 } 295 } 296 297 @Override 298// @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "CF_USELESS_CONTROL_FLOW", 299// justification = "TODO fill out the actions in these clauses") 300 protected void generateLoopBuffers(int which) { 301// if ((which == LOOP_POINT_START) || (which == LOOP_POINT_BOTH)) { 302// // Create start loop buffer 303// // TODO: Actually write this bit 304// } 305// if ((which == LOOP_POINT_END) || (which == LOOP_POINT_BOTH)) { 306// // Create end loop buffer 307// // TODO: Actually write this bit 308// } 309 if (log.isDebugEnabled()) { 310 log.debug("Method generateLoopBuffers() called for JoalAudioBuffer {}", this.getSystemName()); 311 } 312 } 313 314 @Override 315 public int getFormat() { 316 switch (this.format[0]) { 317 case AL.AL_FORMAT_MONO8: 318 return FORMAT_8BIT_MONO; 319 case AL.AL_FORMAT_MONO16: 320 return FORMAT_16BIT_MONO; 321 case AL.AL_FORMAT_STEREO8: 322 return FORMAT_8BIT_STEREO; 323 case AL.AL_FORMAT_STEREO16: 324 return FORMAT_16BIT_STEREO; 325 default: 326 log.error("Unhandled audio format type {}", this.format[0]); 327 break; 328 } 329 if (this.format[0] == JoalAudioFactory.AL_FORMAT_QUAD8 330 && JoalAudioFactory.AL_FORMAT_QUAD8 != FORMAT_UNKNOWN) { 331 return FORMAT_8BIT_QUAD; 332 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_QUAD16 333 && JoalAudioFactory.AL_FORMAT_QUAD16 != FORMAT_UNKNOWN) { 334 return FORMAT_16BIT_QUAD; 335 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_51CHN8 336 && JoalAudioFactory.AL_FORMAT_51CHN8 != FORMAT_UNKNOWN) { 337 return FORMAT_8BIT_5DOT1; 338 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_51CHN16 339 && JoalAudioFactory.AL_FORMAT_51CHN16 != FORMAT_UNKNOWN) { 340 return FORMAT_16BIT_5DOT1; 341 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_61CHN8 342 && JoalAudioFactory.AL_FORMAT_61CHN8 != FORMAT_UNKNOWN) { 343 return FORMAT_8BIT_6DOT1; 344 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_61CHN16 345 && JoalAudioFactory.AL_FORMAT_61CHN16 != FORMAT_UNKNOWN) { 346 return FORMAT_16BIT_6DOT1; 347 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_71CHN8 348 && JoalAudioFactory.AL_FORMAT_71CHN8 != FORMAT_UNKNOWN) { 349 return FORMAT_8BIT_7DOT1; 350 } else if (this.format[0] == JoalAudioFactory.AL_FORMAT_71CHN16 351 && JoalAudioFactory.AL_FORMAT_71CHN16 != FORMAT_UNKNOWN) { 352 return FORMAT_16BIT_7DOT1; 353 } else { 354 return FORMAT_UNKNOWN; 355 } 356 } 357 358 @Override 359 public long getLength() { 360 if (this.getFrameSize() == 0) { 361 return (0); 362 } else { 363 return (long) this.size[0] / this.getFrameSize(); 364 } 365 } 366 367 @Override 368 public int getFrequency() { 369 return this.freq[0]; 370 } 371 372 @Override 373 protected void cleanup() { 374 if (initialised) { 375 al.alDeleteBuffers(1, dataStorageBuffer, 0); 376 } 377 if (log.isDebugEnabled()) { 378 log.debug("Cleanup JoalAudioBuffer ({})", this.getSystemName()); 379 } 380 } 381 382 private static final Logger log = LoggerFactory.getLogger(JoalAudioBuffer.class); 383 384}