001package jmri.jmrit.vsdecoder;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import jmri.util.PhysicalLocation;
006import org.jdom2.Element;
007
008/**
009 * Configurable Sound initial version.
010 *
011 * <hr>
012 * This file is part of JMRI.
013 * <p>
014 * JMRI is free software; you can redistribute it and/or modify it under
015 * the terms of version 2 of the GNU General Public License as published
016 * by the Free Software Foundation. See the "COPYING" file for a copy
017 * of this license.
018 * <p>
019 * JMRI is distributed in the hope that it will be useful, but WITHOUT
020 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
021 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
022 * for more details.
023 *
024 * @author Mark Underwood Copyright (C) 2011
025 * @author Klaus Killinger Copyright (C) 2025
026 */
027class ConfigurableSound extends VSDSound {
028
029    protected String start_file;
030    protected String mid_file;
031    protected String end_file;
032    protected String short_file;
033
034    SoundBite start_sound;
035    SoundBite mid_sound;
036    SoundBite end_sound;
037    SoundBite short_sound;
038
039    boolean initialized = false;
040
041    protected boolean use_start_sound = false;
042    protected boolean use_mid_sound = false;
043    protected boolean use_end_sound = false;
044    protected boolean use_short_sound = false;
045
046    private float rd;
047
048    public ConfigurableSound(String name) {
049        super(name);
050    }
051
052    public boolean init() {
053        return this.init(null);
054    }
055
056    boolean init(VSDFile vf) {
057        if (!initialized) {
058            if (use_start_sound) {
059                start_sound = new SoundBite(vf, start_file, name + "_Start", name + "_Start");
060                if (start_sound.isInitialized()) {
061                    start_sound.setLooped(false);
062                    start_sound.setReferenceDistance(rd);
063                    start_sound.setGain(gain);
064                } else {
065                    use_start_sound = false;
066                }
067            }
068            if (use_mid_sound) {
069                mid_sound = new SoundBite(vf, mid_file, name + "_Mid", name + "_Mid");
070                if (mid_sound.isInitialized()) {
071                    mid_sound.setLooped(false);
072                    mid_sound.setReferenceDistance(rd);
073                    mid_sound.setGain(gain);
074                } else {
075                    use_mid_sound = false;
076                }
077            }
078            if (use_end_sound) {
079                end_sound = new SoundBite(vf, end_file, name + "_End", name + "_End");
080                if (end_sound.isInitialized()) {
081                    end_sound.setLooped(false);
082                    end_sound.setReferenceDistance(rd);
083                    end_sound.setGain(gain);
084                } else {
085                    use_end_sound = false;
086                }
087            }
088            if (use_short_sound) {
089                short_sound = new SoundBite(vf, short_file, name + "_Short", name + "_Short");
090                if (short_sound.isInitialized()) {
091                    short_sound.setLooped(false);
092                    short_sound.setReferenceDistance(rd);
093                    short_sound.setGain(gain);
094                } else {
095                    use_short_sound = false;
096                }
097            }
098        }
099        return true;
100    }
101
102    @Override
103    public void play() {
104        if (use_short_sound) {
105            short_sound.play();
106        } else {
107            if (use_start_sound) {
108                t = newTimer(start_sound.getLengthAsInt(), false, new ActionListener() {
109                    @Override
110                    public void actionPerformed(ActionEvent e) {
111                        handleTimerPop(e);
112                    }
113                });
114                start_sound.play();
115                if (use_mid_sound) {
116                    t.start();
117                }
118            } else if (use_mid_sound) {
119                mid_sound.setLooped(true);
120                mid_sound.play();
121            }
122        }
123    }
124
125    @Override
126    public void loop() {
127        if (use_start_sound) {
128            start_sound.setLooped(false);
129            start_sound.play();
130            // The newTimer method in the super class makes sure that the delay value is positive
131            t = newTimer(start_sound.getLengthAsInt() - 100, false, new ActionListener() {
132                @Override
133                public void actionPerformed(ActionEvent e) {
134                    handleTimerPop(e);
135                }
136            });
137            t.setRepeats(false); // timer pop only once to trigger the sustain sound.
138            t.start();
139        } else if (use_mid_sound) {
140            mid_sound.setLooped(true);
141            mid_sound.play();
142        }
143    }
144
145    // Catch the timer pop after the start sound is played and trigger the (looped) sustain sound.
146    protected void handleTimerPop(ActionEvent e) {
147        log.debug("Received timer pop after start sound played.");
148        //TODO: Need to validate that this is the timer pop
149        if (use_mid_sound) {
150            mid_sound.setLooped(true);
151            mid_sound.play();
152        }
153        t.stop();
154    }
155
156    @Override
157    public void stop() {
158        log.debug("Stopping");
159        // make sure the start sound is killed
160        if (use_start_sound) {
161            start_sound.stop();
162        }
163
164        // If the mid sound is used, turn off the looping.
165        // this will allow it to naturally die.
166        if (use_mid_sound) {
167            mid_sound.setLooped(false);
168            mid_sound.fadeOut();
169        }
170
171        // If the timer is running, stop it.
172        if (t != null) {
173            t.stop();
174        }
175
176        // If we're using the end sound, stop the mid sound
177        // and play the end sound.
178        if (use_end_sound) {
179            if (use_mid_sound) {
180                mid_sound.stop();
181            }
182            end_sound.setLooped(false);
183            end_sound.play();
184        }
185    }
186
187    @Override
188    public void fadeIn() {
189        this.play();
190    }
191
192    @Override
193    public void fadeOut() {
194        this.stop();
195    }
196
197    @Override
198    public void shutdown() {
199        if (use_start_sound) {
200            start_sound.stop();
201        }
202        if (use_mid_sound) {
203            mid_sound.stop();
204        }
205        if (use_end_sound) {
206            end_sound.stop();
207        }
208        if (use_short_sound) {
209            short_sound.stop();
210        }
211    }
212
213    @Override
214    public void mute(boolean m) {
215        if (use_start_sound) {
216            start_sound.mute(m);
217        }
218        if (use_mid_sound) {
219            mid_sound.mute(m);
220        }
221        if (use_end_sound) {
222            end_sound.mute(m);
223        }
224        if (use_short_sound) {
225            short_sound.mute(m);
226        }
227    }
228
229    @Override
230    public void setVolume(float v) {
231        if (use_start_sound) {
232            start_sound.setVolume(v);
233        }
234        if (use_mid_sound) {
235            mid_sound.setVolume(v);
236        }
237        if (use_end_sound) {
238            end_sound.setVolume(v);
239        }
240        if (use_short_sound) {
241            short_sound.setVolume(v);
242        }
243    }
244
245    @Override
246    public void setPosition(PhysicalLocation p) {
247        super.setPosition(p);
248        if (use_start_sound) {
249            start_sound.setPosition(p);
250            setTunnelEffect(start_sound);
251        }
252        if (use_mid_sound) {
253            mid_sound.setPosition(p);
254            setTunnelEffect(mid_sound);
255        }
256        if (use_end_sound) {
257            end_sound.setPosition(p);
258            setTunnelEffect(end_sound);
259        }
260        if (use_short_sound) {
261            short_sound.setPosition(p);
262            setTunnelEffect(short_sound);
263        }
264    }
265
266    private void setTunnelEffect(SoundBite sb) {
267        if (this.getTunnel()) {
268            sb.attachSourcesToEffects();
269        } else {
270            sb.detachSourcesToEffects();
271        }
272    }
273
274    @Override
275    public Element getXml() {
276        Element me = new Element("sound");
277
278        if (log.isDebugEnabled()) {
279            log.debug("Configurable Sound:");
280            log.debug("  name: {}", this.getName());
281            log.debug("  start_file: {}", start_file);
282            log.debug("  mid_file: {}", mid_file);
283            log.debug("  end_file: {}", end_file);
284            log.debug("  short_file: {}", short_file);
285            log.debug("  use_start_file: {}", start_file);
286        }
287
288        me.setAttribute("name", this.getName());
289        me.setAttribute("type", "configurable");
290        if (use_start_sound) {
291            me.addContent(new Element("start-file").addContent(start_file));
292        }
293        if (use_mid_sound) {
294            me.addContent(new Element("mid-file").addContent(mid_file));
295        }
296        if (use_end_sound) {
297            me.addContent(new Element("end-file").addContent(end_file));
298        }
299        if (use_short_sound) {
300            me.addContent(new Element("short-file").addContent(short_file));
301        }
302
303        return me;
304    }
305
306    @Override
307    public void setXml(Element e) {
308        this.setXml(e, null);
309    }
310
311    public void setXml(Element e, VSDFile vf) {
312        log.debug("ConfigurableSound: {}", e.getAttributeValue("name"));
313        if (((start_file = e.getChildText("start-file")) != null) && (!start_file.isEmpty())) {
314            use_start_sound = true;
315        } else {
316            use_start_sound = false;
317        }
318        if (((mid_file = e.getChildText("mid-file")) != null) && (!mid_file.isEmpty())) {
319            use_mid_sound = true;
320        } else {
321            use_mid_sound = false;
322        }
323        if (((end_file = e.getChildText("end-file")) != null) && (!end_file.isEmpty())) {
324            use_end_sound = true;
325        } else {
326            use_end_sound = false;
327        }
328        if (((short_file = e.getChildText("short-file")) != null) && (!short_file.isEmpty())) {
329            use_short_sound = true;
330        } else {
331            use_short_sound = false;
332        }
333
334        String g = e.getChildText("gain");
335        if ((g != null) && (!g.isEmpty())) {
336            gain = Float.parseFloat(g);
337        } else {
338            gain = default_gain;
339        }
340
341        String rds = e.getChildText("reference-distance");
342        if ((rds != null) && (!rds.isEmpty())) {
343            rd = Float.parseFloat(rds);
344        } else {
345            rd = default_reference_distance;
346        }
347
348        /*
349         log.debug("Use:  start: {}, mid: {}, end: {}, short: {}", use_start_sound,
350         use_mid_sound, use_end_sound, use_short_sound);
351         */
352        // Reboot the sound
353        initialized = false;
354        this.init(vf);
355    }
356
357    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConfigurableSound.class);
358
359}