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