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        is_playing = true;
248    }
249
250    @Override
251    public void loop() {
252        sound_src.play();
253        is_playing = true;
254    }
255
256    @Override
257    public void stop() {
258        sound_src.stop();
259        is_playing = false;
260    }
261
262    public void pause() {
263        sound_src.pause();
264        is_playing = false;
265    }
266
267    public void rewind() {
268        sound_src.rewind();
269    }
270
271    @Override
272    public void fadeOut() {
273        // Skip the fade action if the fade out time is zero.
274        if (sound_src.getFadeOut() == 0) {
275            sound_src.stop();
276        } else {
277            sound_src.fadeOut();
278        }
279        is_playing = false;
280    }
281
282    @Override
283    public void fadeIn() {
284        // Skip the fade action if the fade in time is zero.
285        if (sound_src.getFadeIn() == 0) {
286            sound_src.play();
287        } else {
288            sound_src.fadeIn();
289        }
290        is_playing = true;
291    }
292
293    @Override
294    public void setPosition(PhysicalLocation v) {
295        super.setPosition(v);
296        sound_src.setPosition(v);
297    }
298
299    public void setURL(String filename) {
300        this.filename = filename;
301        sound_buf.setURL(filename); // Path must be provided by caller.
302    }
303
304    public long getLength() {
305        return length;
306    }
307
308    public int getLengthAsInt() {
309        // Note:  this only works for positive lengths...
310        // Timer only takes an int... cap the length at MAXINT
311        if (length > Integer.MAX_VALUE) {
312            return Integer.MAX_VALUE;
313        } else { // small enough to safely cast.
314            return (int) length;
315        }
316    }
317
318    public void setLength(long p) {
319        length = p;
320    }
321
322    public void setLength() {
323        length = calcLength(this);
324    }
325
326    public static long calcLength(SoundBite s) {
327        return calcLength(s.getBuffer());
328    }
329
330    public static long calcLength(AudioBuffer buf) {
331        // Assumes later getBuffer() will find the buffer from AudioManager instead
332        // of the current local reference... that's why I'm not directly using sound_buf here.
333
334        // Required buffer functions not yet implemented
335        long num_frames;
336        int frequency;
337
338        if (buf != null) {
339            num_frames = buf.getLength();
340            frequency = buf.getFrequency();
341        } else {
342            // No buffer attached!
343            num_frames = 0;
344            frequency = 0;
345        }
346
347        /*
348         long num_frames = 1;
349         long frequency = 125;
350         */
351        if (frequency <= 0) {
352            // Protect against divide-by-zero errors
353            return 0L;
354        } else {
355            return (1000 * num_frames) / frequency;
356        }
357    }
358
359    private static final Logger log = LoggerFactory.getLogger(SoundBite.class);
360}