001package jmri.jmrit.audio;
002
003import java.io.InputStream;
004import java.nio.ByteBuffer;
005import jmri.implementation.AbstractAudio;
006import jmri.util.FileUtil;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Base implementation of the AudioBuffer class.
012 * <p>
013 * Specific implementations will extend this base class.
014 * <br>
015 * <hr>
016 * This file is part of JMRI.
017 * <p>
018 * JMRI is free software; you can redistribute it and/or modify it under the
019 * terms of version 2 of the GNU General Public License as published by the Free
020 * Software Foundation. See the "COPYING" file for a copy of this license.
021 * <p>
022 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
024 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
025 *
026 * @author Matthew Harris copyright (c) 2009, 2011
027 */
028public abstract class AbstractAudioBuffer extends AbstractAudio implements AudioBuffer {
029
030    /**
031     * Holds the location of the sound sample used in this buffer
032     */
033    private String url = "";
034
035    /**
036     * Start loop point for this buffer represented as a number of samples
037     */
038    private long startLoopPoint;
039
040    /**
041     * End loop point for this buffer represented as a number of samples
042     */
043    private long endLoopPoint;
044
045    /**
046     * Flag to determine if this buffer is to be streamed from file
047     */
048    private boolean streamed = false;
049
050    /**
051     * Flag to determine if streaming has been forced
052     */
053    private boolean streamedForced = false;
054
055//    /**
056//     *
057//     */
058//    private WaveFileReader waveFile;
059    /**
060     * Identifier of start loop point
061     */
062    protected static final int LOOP_POINT_START = 0x01;
063
064    /**
065     * Identifier of end loop point
066     */
067    protected static final int LOOP_POINT_END = 0x02;
068
069    /**
070     * Identifier of both loop points
071     */
072    protected static final int LOOP_POINT_BOTH = 0x03;
073
074    /**
075     * Abstract constructor for new AudioBuffer with system name
076     *
077     * @param systemName AudioBuffer object system name (e.g. IAB4)
078     */
079    public AbstractAudioBuffer(String systemName) {
080        super(systemName);
081        this.setState(STATE_EMPTY);
082    }
083
084    /**
085     * Abstract constructor for new AudioBuffer with system name and user name
086     *
087     * @param systemName AudioBuffer object system name (e.g. IAB4)
088     * @param userName   AudioBuffer object user name
089     */
090    public AbstractAudioBuffer(String systemName, String userName) {
091        super(systemName, userName);
092        this.setState(STATE_EMPTY);
093    }
094
095    @Override
096    public char getSubType() {
097        return BUFFER;
098    }
099
100    @Override
101    public String getURL() {
102        return this.url;
103    }
104
105    @Override
106    public void setURL(String url) {
107        this.url = FileUtil.getPortableFilename(url);
108
109        // Run the loadBuffer method on the main AWT thread to avoid any
110        // potential issues with interrupted exceptions if run on the audio
111        // command thread
112        loadBuffer();
113        if (log.isDebugEnabled()) {
114            log.debug("Set url of Buffer {} to {}", this.getSystemName(), url);
115        }
116    }
117
118    @Override
119    public void setInputStream(InputStream stream) {
120        this.url = "stream";
121
122        loadBuffer(stream);
123        if (log.isDebugEnabled()) {
124            log.debug("Set inputstream of Buffer {} to stream", this.getSystemName());
125        }
126    }
127
128    @Override
129    public int getFrameSize() {
130        switch (this.getFormat()) {
131            case AudioBuffer.FORMAT_8BIT_MONO:
132                return 1;
133            case AudioBuffer.FORMAT_8BIT_STEREO:
134                return 2;
135            case AudioBuffer.FORMAT_8BIT_QUAD:
136                return 4;
137            case AudioBuffer.FORMAT_8BIT_5DOT1:
138                return 6;
139            case AudioBuffer.FORMAT_8BIT_6DOT1:
140                return 7;
141            case AudioBuffer.FORMAT_8BIT_7DOT1:
142                return 8;
143            case AudioBuffer.FORMAT_16BIT_MONO:
144                return 2;
145            case AudioBuffer.FORMAT_16BIT_STEREO:
146                return 4;
147            case AudioBuffer.FORMAT_16BIT_QUAD:
148                return 8;
149            case AudioBuffer.FORMAT_16BIT_5DOT1:
150                return 12;
151            case AudioBuffer.FORMAT_16BIT_6DOT1:
152                return 14;
153            case AudioBuffer.FORMAT_16BIT_7DOT1:
154                return 16;
155            default: //AudioBuffer.FORMAT_UNKNOWN:
156                return 0;
157        }
158    }
159
160    /**
161     * Method used to load the actual sound data into the buffer
162     *
163     * @return True if successful; False if not
164     */
165    abstract protected boolean loadBuffer();
166
167    /**
168     * Method used to load the actual sound data from an InputStream into the
169     * buffer
170     *
171     * @param s InputStream containing sound data
172     * @return True if successful; False if not
173     */
174    abstract protected boolean loadBuffer(InputStream s);
175
176    @Override
177    public void setStartLoopPoint(long startLoopPoint) {
178        this.setStartLoopPoint(startLoopPoint, true);
179    }
180
181    // Can be made abstract later.
182    @Override
183    public boolean loadBuffer(ByteBuffer b, int format, int frequency) {
184        return false;
185    }
186
187    /**
188     * Internal method used to set the start loop point of this buffer with
189     * optional generation of loop buffers
190     *
191     * @param startLoopPoint      position of start loop point in samples
192     * @param generateLoopBuffers True if loop buffers to be generated
193     */
194    protected void setStartLoopPoint(long startLoopPoint, boolean generateLoopBuffers) {
195        this.startLoopPoint = startLoopPoint;
196        if (generateLoopBuffers) {
197            generateLoopBuffers(LOOP_POINT_START);
198        }
199        if (log.isDebugEnabled()) {
200            log.debug("Set start loop point of Buffer {} to {}", this.getSystemName(), startLoopPoint);
201        }
202    }
203
204    @Override
205    public long getStartLoopPoint() {
206        return this.startLoopPoint;
207    }
208
209    @Override
210    public void setEndLoopPoint(long endLoopPoint) {
211        this.setEndLoopPoint(endLoopPoint, true);
212    }
213
214    /**
215     * Internal method used to set the end loop point of this buffer with
216     * optional generation of loop buffers
217     *
218     * @param endLoopPoint        position of end loop point in samples
219     * @param generateLoopBuffers True if loop buffers to be generated
220     */
221    protected void setEndLoopPoint(long endLoopPoint, boolean generateLoopBuffers) {
222        this.endLoopPoint = endLoopPoint;
223        if (generateLoopBuffers) {
224            generateLoopBuffers(LOOP_POINT_END);
225        }
226        if (log.isDebugEnabled()) {
227            log.debug("Set end loop point of Buffer {} to {}", this.getSystemName(), endLoopPoint);
228        }
229    }
230
231    @Override
232    public long getEndLoopPoint() {
233        return this.endLoopPoint;
234    }
235
236    @Override
237    public void setStreamed(boolean streamed) {
238        if (streamed) {
239            log.warn("Streaming not yet supported!!");
240            streamed = !streamed;
241        }
242        boolean changed = this.streamed != streamed;
243        this.streamed = this.streamedForced == true ? true : streamed;
244        if (log.isDebugEnabled()) {
245            log.debug("Set streamed property of Buffer {} to {}; changed = {}", this.getSystemName(), streamed, changed);
246        }
247        if (streamed && changed) {
248            generateStreamingBuffers();
249        } else if (!streamed && changed) {
250            removeStreamingBuffers();
251        }
252    }
253
254    @Override
255    public boolean isStreamed() {
256        return this.streamed;
257    }
258
259    /**
260     * Protected method used internally to modify the forced streaming flag
261     *
262     * @param streamedForced True if required; False if not
263     */
264    protected void setStreamedForced(boolean streamedForced) {
265        boolean changed = this.streamedForced == false && streamedForced == true;
266        this.streamedForced = streamedForced;
267        if (log.isDebugEnabled()) {
268            log.debug("Set streamedForced property of Buffer {} to {}; changed = {}", this.getSystemName(), streamedForced, changed);
269        }
270        this.setStreamed(streamedForced == true ? true : this.streamed);
271        if (changed) {
272            this.generateLoopBuffers(LOOP_POINT_BOTH);
273        }
274    }
275
276    @Override
277    public boolean isStreamedForced() {
278        return this.streamedForced;
279    }
280
281    /**
282     * Method used to generate any necessary loop buffers.
283     *
284     * @param which the loop buffer to generate:
285     * <br>{@link #LOOP_POINT_START} for the start loop buffer
286     * <br>{@link #LOOP_POINT_END} for the end loop buffer
287     * <br>{@link #LOOP_POINT_BOTH} for both loop buffers
288     */
289    abstract protected void generateLoopBuffers(int which);
290
291    /**
292     * Internal method used to generate buffers for streaming
293     *
294     * @return True if successful; False if not
295     */
296    abstract protected boolean generateStreamingBuffers();
297
298    /**
299     * Internal method used to remove streaming buffers
300     */
301    abstract protected void removeStreamingBuffers();
302
303    @Override
304    public void stateChanged(int oldState) {
305        // Move along... nothing to see here...
306    }
307
308    private static final Logger log = LoggerFactory.getLogger(AbstractAudioBuffer.class);
309}