001package jmri.jmrit.vsdecoder;
002
003import jmri.AudioException;
004import jmri.AudioManager;
005import jmri.jmrit.audio.AudioBuffer;
006import jmri.jmrit.audio.AudioSource;
007import jmri.util.PhysicalLocation;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * VSD implementation of an audio sound.
013 *
014 * <hr>
015 * This file is part of JMRI.
016 * <p>
017 * JMRI is free software; you can redistribute it and/or modify it under
018 * the terms of version 2 of the GNU General Public License as published
019 * by the Free Software Foundation. See the "COPYING" file for a copy
020 * of this license.
021 * <p>
022 * JMRI is distributed in the hope that it will be useful, but WITHOUT
023 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
024 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
025 * for more details.
026 *
027 * @author Mark Underwood Copyright (C) 2011
028 */
029class SoundBite extends VSDSound {
030
031    public static enum BufferMode {
032
033        BOUND_MODE, QUEUE_MODE
034    }
035
036    String filename, system_name, user_name;
037    AudioBuffer sound_buf;
038    AudioSource sound_src;
039    boolean initialized = false;
040    boolean looped = false;
041    int minloops;
042    int maxloops;
043    float rd;
044    long length;
045    BufferMode bufferMode;
046
047    // Constructor for QUEUE_MODE.
048    public SoundBite(String name) {
049        super(name);
050        system_name = name;
051        user_name = name;
052        bufferMode = BufferMode.QUEUE_MODE;
053        initialized = init(null, bufferMode);
054    }
055
056    // Constructor for BOUND_MODE.
057    public SoundBite(VSDFile vf, String filename, String sname, String uname) {
058        super(uname);
059        this.filename = filename;
060        system_name = sname;
061        user_name = uname;
062        bufferMode = BufferMode.BOUND_MODE;
063        initialized = init(vf, bufferMode);
064    }
065
066    public String getFileName() {
067        return filename;
068    }
069
070    public String getSystemName() {
071        return system_name;
072    }
073
074    public String getUserName() {
075        return user_name;
076    }
077
078    public boolean isInitialized() {
079        return initialized;
080    }
081
082    public final boolean init(VSDFile vf, BufferMode mode) {
083        AudioManager am = jmri.InstanceManager.getDefault(jmri.AudioManager.class);
084        if (!initialized) {
085            try {
086                sound_src = (AudioSource) am.provideAudio(SrcSysNamePrefix + system_name);
087                sound_src.setUserName(SrcUserNamePrefix + user_name);
088                setLooped(false);
089                if (mode == BufferMode.BOUND_MODE) {
090                    sound_buf = (AudioBuffer) am.provideAudio(BufSysNamePrefix + system_name);
091                    sound_buf.setUserName(BufUserNamePrefix + user_name);
092                    if (vf == null) {
093                        log.debug("No VSD File! Filename: {}", filename);
094                        sound_buf.setURL(filename); // Path must be provided by caller.
095                    } else {
096                        java.io.InputStream ins = vf.getInputStream(filename);
097                        if (ins != null) {
098                            sound_buf.setInputStream(ins);
099                        } else {
100                            return false;
101                        }
102                    }
103                    sound_src.setAssignedBuffer(sound_buf);
104                    setLength();
105                }
106            } catch (AudioException | IllegalArgumentException ex) {
107                log.warn("Problem creating SoundBite", ex);
108            }
109        }
110        return true;
111    }
112
113    public void queueBuffer(AudioBuffer b) {
114        if (bufferMode == BufferMode.QUEUE_MODE) {
115            if (b == null) {
116                log.debug("queueAudioBuffer with null buffer input");
117                return;
118            }
119            if (sound_src == null) {
120                log.debug("queueAudioBuffer with null sound_src");
121                return;
122            }
123            log.debug("Queueing Buffer: {}", b.getSystemName());
124            sound_src.queueBuffer(b);
125        } else {
126            log.warn("Attempted to Queue buffer to a Bound SoundBite.");
127        }
128    }
129
130    public void unqueueBuffers() {
131        if (bufferMode == BufferMode.QUEUE_MODE) {
132            sound_src.unqueueBuffers();
133        }
134    }
135
136    public int numQueuedBuffers() {
137        if (bufferMode == BufferMode.QUEUE_MODE) {
138            return sound_src.numQueuedBuffers();
139        } else {
140            return 0;
141        }
142    }
143
144    // Direct access to the underlying source.  use with caution.
145    public AudioSource getSource() {
146        return sound_src;
147    }
148
149    // WARNING: This will go away when we go to shared buffers... or at least it will
150    // have to do the name lookup on behalf of the caller...
151    public AudioBuffer getBuffer() {
152        return sound_buf;
153    }
154
155    // These can(?) be used to get the underlying AudioSource and AudioBuffer objects
156    // from the DefaultAudioManager.
157    public String getSourceSystemName() {
158        return SrcSysNamePrefix + system_name;
159    }
160
161    public String getSourceUserName() {
162        return SrcUserNamePrefix + user_name;
163    }
164
165    public String getBufferSystemName() {
166        return BufSysNamePrefix + system_name;
167    }
168
169    public String getBufferUserName() {
170        return BufUserNamePrefix + user_name;
171    }
172
173    public void setLooped(boolean loop, int minloops, int maxloops) {
174        this.looped = loop;
175        this.minloops = minloops;
176        this.maxloops = maxloops;
177        sound_src.setLooped(looped);
178        sound_src.setMinLoops(minloops);
179        sound_src.setMaxLoops(maxloops);
180    }
181
182    public void setLooped(boolean loop) {
183        if (loop) {
184            this.setLooped(true, AudioSource.LOOP_CONTINUOUS, AudioSource.LOOP_CONTINUOUS);
185        } else {
186            this.setLooped(false, AudioSource.LOOP_NONE, AudioSource.LOOP_NONE);
187        }
188    }
189
190    public boolean isLooped() {
191        return looped;
192    }
193
194    public int getFadeInTime() {
195        return sound_src.getFadeIn();
196    }
197
198    public int getFadeOutTime() {
199        return sound_src.getFadeOut();
200    }
201
202    public void setFadeInTime(int t) {
203        sound_src.setFadeIn(t);
204    }
205
206    public void setFadeOutTime(int t) {
207        sound_src.setFadeOut(t);
208    }
209
210    public void setFadeTimes(int in, int out) {
211        sound_src.setFadeIn(in);
212        sound_src.setFadeOut(out);
213    }
214
215    public float getReferenceDistance() {
216        return sound_src.getReferenceDistance();
217    }
218
219    public void setReferenceDistance(float r) {
220        this.rd = r;
221        sound_src.setReferenceDistance(rd);
222    }
223
224    @Override
225    public void shutdown() {
226    }
227
228    @Override
229    public void mute(boolean m) {
230        if (m) {
231            volume = sound_src.getGain();
232            sound_src.setGain(0);
233        } else {
234            sound_src.setGain(volume);
235        }
236    }
237
238    @Override
239    public void setVolume(float v) {
240        volume = v * gain;
241        sound_src.setGain(volume);
242    }
243
244    @Override
245    public void play() {
246        sound_src.play();
247    }
248
249    @Override
250    public void loop() {
251        sound_src.play();
252    }
253
254    @Override
255    public void stop() {
256        sound_src.stop();
257    }
258
259    public void pause() {
260        sound_src.pause();
261    }
262
263    public void rewind() {
264        sound_src.rewind();
265    }
266
267    @Override
268    public void fadeOut() {
269        // Skip the fade action if the fade out time is zero.
270        if (sound_src.getFadeOut() == 0) {
271            sound_src.stop();
272        } else {
273            sound_src.fadeOut();
274        }
275    }
276
277    @Override
278    public void fadeIn() {
279        // Skip the fade action if the fade in time is zero.
280        if (sound_src.getFadeIn() == 0) {
281            sound_src.play();
282        } else {
283            sound_src.fadeIn();
284        }
285    }
286
287    @Override
288    public void setPosition(PhysicalLocation v) {
289        super.setPosition(v);
290        sound_src.setPosition(v);
291    }
292
293    public void setURL(String filename) {
294        this.filename = filename;
295        sound_buf.setURL(filename); // Path must be provided by caller.
296    }
297
298    public long getLength() {
299        return length;
300    }
301
302    public int getLengthAsInt() {
303        // Note:  this only works for positive lengths...
304        // Timer only takes an int... cap the length at MAXINT
305        if (length > Integer.MAX_VALUE) {
306            return Integer.MAX_VALUE;
307        } else { // small enough to safely cast.
308            return (int) length;
309        }
310    }
311
312    public void setLength(long p) {
313        length = p;
314    }
315
316    public void setLength() {
317        length = calcLength(this);
318    }
319
320    public static long calcLength(SoundBite s) {
321        return calcLength(s.getBuffer());
322    }
323
324    public static long calcLength(AudioBuffer buf) {
325        // Assumes later getBuffer() will find the buffer from AudioManager instead
326        // of the current local reference... that's why I'm not directly using sound_buf here.
327
328        // Required buffer functions not yet implemented
329        long num_frames;
330        int frequency;
331
332        if (buf != null) {
333            num_frames = buf.getLength();
334            frequency = buf.getFrequency();
335        } else {
336            // No buffer attached!
337            num_frames = 0;
338            frequency = 0;
339        }
340
341        /*
342         long num_frames = 1;
343         long frequency = 125;
344         */
345        if (frequency <= 0) {
346            // Protect against divide-by-zero errors
347            return 0L;
348        } else {
349            return (1000 * num_frames) / frequency;
350        }
351    }
352
353    private static final Logger log = LoggerFactory.getLogger(SoundBite.class);
354}