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}