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