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