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        is_playing = false;
052    }
053
054    public boolean init() {
055        return this.init(null);
056    }
057
058    public boolean init(VSDFile vf) {
059        if (!initialized) {
060            if (use_start_sound) {
061                start_sound = new SoundBite(vf, start_file, name + "_Start", name + "_Start");
062                if (start_sound.isInitialized()) {
063                    start_sound.setLooped(false);
064                    start_sound.setReferenceDistance(rd);
065                    start_sound.setGain(gain);
066                } else {
067                    use_start_sound = false;
068                }
069            }
070            if (use_mid_sound) {
071                mid_sound = new SoundBite(vf, mid_file, name + "_Mid", name + "_Mid");
072                if (mid_sound.isInitialized()) {
073                    mid_sound.setLooped(false);
074                    mid_sound.setReferenceDistance(rd);
075                    mid_sound.setGain(gain);
076                } else {
077                    use_mid_sound = false;
078                }
079            }
080            if (use_end_sound) {
081                end_sound = new SoundBite(vf, end_file, name + "_End", name + "_End");
082                if (end_sound.isInitialized()) {
083                    end_sound.setLooped(false);
084                    end_sound.setReferenceDistance(rd);
085                    end_sound.setGain(gain);
086                } else {
087                    use_end_sound = false;
088                }
089            }
090            if (use_short_sound) {
091                short_sound = new SoundBite(vf, short_file, name + "_Short", name + "_Short");
092                if (short_sound.isInitialized()) {
093                    short_sound.setLooped(false);
094                    short_sound.setReferenceDistance(rd);
095                    short_sound.setGain(gain);
096                } else {
097                    use_short_sound = false;
098                }
099            }
100        }
101        return true;
102    }
103
104    @Override
105    public boolean isPlaying() {
106        return is_playing;
107    }
108
109    @Override
110    public void play() {
111        if (use_short_sound) {
112            short_sound.play();
113            is_playing = false; // short sound, won't be playing long...
114        } else {
115            if (use_start_sound) {
116                t = newTimer(start_sound.getLengthAsInt(), false, new ActionListener() {
117                    @Override
118                    public void actionPerformed(ActionEvent e) {
119                        handleTimerPop(e);
120                    }
121                });
122                start_sound.play();
123                if (use_mid_sound) {
124                    t.start();
125                    is_playing = true;
126                }
127            } else if (use_mid_sound) {
128                mid_sound.setLooped(true);
129                mid_sound.play();
130            }
131        }
132    }
133
134    @Override
135    public void loop() {
136        if (use_start_sound) {
137            start_sound.setLooped(false);
138            start_sound.play();
139            // The newTimer method in the super class makes sure that the delay value is positive
140            t = newTimer(start_sound.getLengthAsInt() - 100, false, new ActionListener() {
141                @Override
142                public void actionPerformed(ActionEvent e) {
143                    handleTimerPop(e);
144                }
145            });
146            t.setRepeats(false); // timer pop only once to trigger the sustain sound.
147            t.start();
148        } else if (use_mid_sound) {
149            mid_sound.setLooped(true);
150            mid_sound.play();
151        }
152        is_playing = true;
153    }
154
155    // Catch the timer pop after the start sound is played and trigger the (looped) sustain sound.
156    protected void handleTimerPop(ActionEvent e) {
157        log.debug("Received timer pop after start sound played.");
158        //TODO: Need to validate that this is the timer pop
159        if (use_mid_sound) {
160            mid_sound.setLooped(true);
161            mid_sound.play();
162        }
163        t.stop();
164    }
165
166    @Override
167    public void stop() {
168        log.debug("Stopping");
169        // make sure the start sound is killed
170        if (use_start_sound) {
171            start_sound.stop();
172        }
173
174        // If the mid sound is used, turn off the looping.
175        // this will allow it to naturally die.
176        if (use_mid_sound) {
177            mid_sound.setLooped(false);
178            mid_sound.fadeOut();
179        }
180
181        // If the timer is running, stop it.
182        if (t != null) {
183            t.stop();
184        }
185
186        // If we're using the end sound, stop the mid sound
187        // and play the end sound.
188        if (use_end_sound) {
189            if (use_mid_sound) {
190                mid_sound.stop();
191            }
192            end_sound.setLooped(false);
193            end_sound.play();
194        }
195        is_playing = false;
196    }
197
198    @Override
199    public void fadeIn() {
200        this.play();
201    }
202
203    @Override
204    public void fadeOut() {
205        this.stop();
206    }
207
208    @Override
209    public void shutdown() {
210        if (use_start_sound) {
211            start_sound.stop();
212        }
213        if (use_mid_sound) {
214            mid_sound.stop();
215        }
216        if (use_end_sound) {
217            end_sound.stop();
218        }
219        if (use_short_sound) {
220            short_sound.stop();
221        }
222    }
223
224    @Override
225    public void mute(boolean m) {
226        if (use_start_sound) {
227            start_sound.mute(m);
228        }
229        if (use_mid_sound) {
230            mid_sound.mute(m);
231        }
232        if (use_end_sound) {
233            end_sound.mute(m);
234        }
235        if (use_short_sound) {
236            short_sound.mute(m);
237        }
238    }
239
240    @Override
241    public void setVolume(float v) {
242        if (use_start_sound) {
243            start_sound.setVolume(v);
244        }
245        if (use_mid_sound) {
246            mid_sound.setVolume(v);
247        }
248        if (use_end_sound) {
249            end_sound.setVolume(v);
250        }
251        if (use_short_sound) {
252            short_sound.setVolume(v);
253        }
254    }
255
256    @Override
257    public void setPosition(PhysicalLocation p) {
258        super.setPosition(p);
259        if (use_start_sound) {
260            start_sound.setPosition(p);
261        }
262        if (use_mid_sound) {
263            mid_sound.setPosition(p);
264        }
265        if (use_end_sound) {
266            end_sound.setPosition(p);
267        }
268        if (use_short_sound) {
269            short_sound.setPosition(p);
270        }
271    }
272
273    @Override
274    public Element getXml() {
275        Element me = new Element("sound");
276
277        if (log.isDebugEnabled()) {
278            log.debug("Configurable Sound:");
279            log.debug("  name: {}", this.getName());
280            log.debug("  start_file: {}", start_file);
281            log.debug("  mid_file: {}", mid_file);
282            log.debug("  end_file: {}", end_file);
283            log.debug("  short_file: {}", short_file);
284            log.debug("  use_start_file: {}", start_file);
285        }
286
287        me.setAttribute("name", this.getName());
288        me.setAttribute("type", "configurable");
289        if (use_start_sound) {
290            me.addContent(new Element("start-file").addContent(start_file));
291        }
292        if (use_mid_sound) {
293            me.addContent(new Element("mid-file").addContent(mid_file));
294        }
295        if (use_end_sound) {
296            me.addContent(new Element("end-file").addContent(end_file));
297        }
298        if (use_short_sound) {
299            me.addContent(new Element("short-file").addContent(short_file));
300        }
301
302        return me;
303    }
304
305    @Override
306    public void setXml(Element e) {
307        this.setXml(e, null);
308    }
309
310    public void setXml(Element e, VSDFile vf) {
311        log.debug("ConfigurableSound: {}", e.getAttributeValue("name"));
312        if (((start_file = e.getChildText("start-file")) != null) && (!start_file.isEmpty())) {
313            use_start_sound = true;
314        } else {
315            use_start_sound = false;
316        }
317        if (((mid_file = e.getChildText("mid-file")) != null) && (!mid_file.isEmpty())) {
318            use_mid_sound = true;
319        } else {
320            use_mid_sound = false;
321        }
322        if (((end_file = e.getChildText("end-file")) != null) && (!end_file.isEmpty())) {
323            use_end_sound = true;
324        } else {
325            use_end_sound = false;
326        }
327        if (((short_file = e.getChildText("short-file")) != null) && (!short_file.isEmpty())) {
328            use_short_sound = true;
329        } else {
330            use_short_sound = false;
331        }
332
333        String g = e.getChildText("gain");
334        if ((g != null) && (!g.isEmpty())) {
335            gain = Float.parseFloat(g);
336        } else {
337            gain = default_gain;
338        }
339
340        String rds = e.getChildText("reference-distance");
341        if ((rds != null) && (!rds.isEmpty())) {
342            rd = Float.parseFloat(rds);
343        } else {
344            rd = default_reference_distance;
345        }
346
347        /*
348         log.debug("Use:  start: {}, mid: {}, end: {}, short: {}", use_start_sound, 
349         use_mid_sound, use_end_sound, use_short_sound);
350         */
351        // Reboot the sound
352        initialized = false;
353        this.init(vf);
354    }
355
356    private final static Logger log = LoggerFactory.getLogger(ConfigurableSound.class);
357
358}