001package jmri.jmrit.vsdecoder;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.util.ArrayList;
006import java.util.HashMap;
007import java.util.Iterator;
008import jmri.util.PhysicalLocation;
009import org.jdom2.Element;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Diesel Sound initial version.
015 *
016 * <hr>
017 * This file is part of JMRI.
018 * <p>
019 * JMRI is free software; you can redistribute it and/or modify it under 
020 * the terms of version 2 of the GNU General Public License as published 
021 * by the Free Software Foundation. See the "COPYING" file for a copy
022 * of this license.
023 * <p>
024 * JMRI is distributed in the hope that it will be useful, but WITHOUT 
025 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
026 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
027 * for more details.
028 *
029 * @author Mark Underwood Copyright (C) 2011
030 */
031
032// Usage:
033// EngineSound() : constructor
034// play() : plays short horn pop
035// loop() : starts extended sustain horn
036// stop() : ends extended sustain horn (plays end sound)
037class DieselSound extends EngineSound {
038
039    // Engine Sounds
040    HashMap<Integer, SoundBite> notch_sounds;
041    ArrayList<NotchTransition> transition_sounds;
042    SoundBite start_sound;
043    SoundBite shutdown_sound;
044    NotchTransition notch_transition; // used for changing notches
045
046    int current_notch = 1;
047
048    public DieselSound(String name) {
049        super(name);
050    }
051
052    // Note:  Play and Loop do the same thing, since all of the notch sounds are set to loop.
053    @Override
054    public void play() {
055        log.debug("EngineSound Play: current_notch = {}", current_notch);
056        if (notch_sounds.containsKey(current_notch) && (isEngineStarted() || auto_start_engine)) {
057            notch_sounds.get(current_notch).play();
058            is_playing = true;
059        }
060    }
061
062    // Note:  Play and Loop do the same thing, since all of the notch sounds are set to loop.
063    @Override
064    public void loop() {
065        if (notch_sounds.containsKey(current_notch) && (isEngineStarted() || auto_start_engine)) {
066            notch_sounds.get(current_notch).play();
067            is_playing = true;
068        }
069    }
070
071    @Override
072    public void stop() {
073        if (notch_sounds.containsKey(current_notch)) {
074            notch_sounds.get(current_notch).stop();
075        }
076        is_playing = false;
077    }
078
079    @Override
080    public void changeNotch(int new_notch) {
081        log.debug("EngineSound.changeNotch() current = {} new notch = {}", current_notch, new_notch);
082        if (new_notch != current_notch) {
083            if (notch_sounds.containsKey(current_notch) && (isEngineStarted() || auto_start_engine)) {
084                notch_sounds.get(current_notch).fadeOut();
085            }
086
087            notch_transition = findNotchTransient(current_notch, new_notch);
088            if (notch_transition != null) {
089                log.debug("notch transition: name = {} length = {}, fade_length = {}", notch_transition.getFileName(), notch_transition.getLengthAsInt(), fade_length);
090                // Handle notch transition...
091                t = newTimer(notch_transition.getLengthAsInt() - notch_sounds.get(new_notch).getFadeInTime(), false,
092                        new ActionListener() {
093                            @Override
094                            public void actionPerformed(ActionEvent e) {
095                                handleNotchTimerPop(e);
096                            }
097                        });
098                t.start();
099                notch_transition.fadeIn();
100            } else {
101                log.debug("notch transition not found!");
102                if (notch_sounds.containsKey(new_notch) && (isEngineStarted() || auto_start_engine)) {
103                    notch_sounds.get(new_notch).fadeIn();
104                }
105            }
106            current_notch = new_notch;
107        }
108    }
109
110    protected void handleNotchTimerPop(ActionEvent e) {
111        // notch value has already been changed
112        log.debug("Notch timer pop. nt.next_notch = {}, file = {}", notch_transition.getNextNotch(), notch_sounds.get(notch_transition.getNextNotch()).getFileName());
113        if (notch_sounds.containsKey(notch_transition.getNextNotch()) && (isEngineStarted() || auto_start_engine)) {
114            notch_sounds.get(notch_transition.getNextNotch()).fadeIn();
115        }
116        notch_transition.fadeOut();
117    }
118
119    private NotchTransition findNotchTransient(int prev, int next) {
120        log.debug("Looking for Transient: prev = {} next = {}", prev, next);
121        for (NotchTransition nt : transition_sounds) {
122            log.debug("searching: nt.prev = {} nt.next = {}", nt.getPrevNotch(), nt.getNextNotch());
123            if ((nt.getPrevNotch() == prev) && (nt.getNextNotch() == next)) {
124                log.debug("Found transient: prev = {} next = {}", nt.getPrevNotch(), nt.getNextNotch());
125                return nt;
126            }
127        }
128        // If we loop out, there's no transition that matches.
129        return null;
130    }
131
132    @Override
133    public void startEngine() {
134        start_sound.play();
135        current_notch = calcEngineNotch(0.0f);
136        //t = newTimer(4500, false, new ActionListener() { 
137        t = newTimer(start_sound.getLengthAsInt() - start_sound.getFadeOutTime(), false, new ActionListener() {
138            @Override
139            public void actionPerformed(ActionEvent e) {
140                startToIdleAction(e);
141            }
142        });
143        //t.setInitialDelay(4500);
144        t.setInitialDelay(start_sound.getLengthAsInt() - start_sound.getFadeOutTime());
145        t.setRepeats(false);
146        log.debug("Starting Engine");
147        t.start();
148    }
149
150    @Override
151    public void stopEngine() {
152        notch_sounds.get(current_notch).fadeOut();
153        shutdown_sound.play();
154        setEngineStarted(false);
155    }
156
157    private void startToIdleAction(ActionEvent e) {
158        log.debug("Starting idle sound notch = {} sound = {}", current_notch, notch_sounds.get(current_notch));
159        notch_sounds.get(current_notch).loop();
160        setEngineStarted(true);
161    }
162
163    @Override
164    public void shutdown() {
165        for (SoundBite ns : notch_sounds.values()) {
166            ns.stop();
167        }
168        for (NotchTransition nt : transition_sounds) {
169            nt.stop();
170        }
171        if (start_sound != null) {
172            start_sound.stop();
173        }
174        if (shutdown_sound != null) {
175            shutdown_sound.stop();
176        }
177    }
178
179    @Override
180    public void mute(boolean m) {
181        for (SoundBite ns : notch_sounds.values()) {
182            ns.mute(m);
183        }
184        for (NotchTransition nt : transition_sounds) {
185            nt.mute(m);
186        }
187        if (start_sound != null) {
188            start_sound.mute(m);
189        }
190        if (shutdown_sound != null) {
191            shutdown_sound.mute(m);
192        }
193    }
194
195    @Override
196    public void setVolume(float v) {
197        for (SoundBite ns : notch_sounds.values()) {
198            ns.setVolume(v);
199        }
200        for (NotchTransition nt : transition_sounds) {
201            nt.setVolume(v);
202        }
203        if (start_sound != null) {
204            start_sound.setVolume(v);
205        }
206        if (shutdown_sound != null) {
207            shutdown_sound.setVolume(v);
208        }
209    }
210
211    @Override
212    public void setPosition(PhysicalLocation p) {
213        for (SoundBite ns : notch_sounds.values()) {
214            ns.setPosition(p);
215        }
216        for (NotchTransition nt : transition_sounds) {
217            nt.setPosition(p);
218        }
219        if (start_sound != null) {
220            start_sound.setPosition(p);
221        }
222        if (shutdown_sound != null) {
223            shutdown_sound.setPosition(p);
224        }
225    }
226
227    @Override
228    public Element getXml() {
229        Element me = new Element("sound");
230        me.setAttribute("name", this.getName());
231        me.setAttribute("type", "engine");
232        // Do something, eventually...
233        return me;
234    }
235
236    @Override
237    public void setXml(Element e, VSDFile vf) {
238        Element el;
239        //int num_notches;
240        String fn;
241        SoundBite sb;
242
243        // Handle the common stuff.
244        super.setXml(e, vf);
245
246        log.debug("Diesel EngineSound: {}", e.getAttribute("name").getValue());
247        notch_sounds = new HashMap<Integer, SoundBite>();
248        transition_sounds = new ArrayList<NotchTransition>();
249
250        // Get the notch sounds
251        Iterator<Element> itr = (e.getChildren("notch-sound")).iterator();
252        int i = 0;
253        while (itr.hasNext()) {
254            el = itr.next();
255            fn = el.getChildText("file");
256            int nn = Integer.parseInt(el.getChildText("notch"));
257            //log.debug("Notch: {}, File: {}", nn, fn);
258            sb = new SoundBite(vf, fn, "Engine_n" + i, "Engine_" + i);
259            sb.setLooped(true);
260            sb.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
261            sb.setGain(setXMLGain(el));
262            // Store in the list.
263            notch_sounds.put(nn, sb);
264            i++;
265        }
266
267        // Get the notch transitions
268        itr = (e.getChildren("notch-transition")).iterator();
269        i = 0;
270        NotchTransition nt;
271        while (itr.hasNext()) {
272            el = itr.next();
273            fn = el.getChildText("file");
274            nt = new NotchTransition(vf, fn, "Engine_nt" + i, "Engine_nt" + i);
275            nt.setPrevNotch(Integer.parseInt(el.getChildText("prev-notch")));
276            nt.setNextNotch(Integer.parseInt(el.getChildText("next-notch")));
277            //log.debug("Transition - prev: {}, next: {}, file: {}", nt.getPrevNotch(), nt.getNextNotch(), fn);
278            nt.setLooped(false);
279            nt.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
280            // Handle gain
281            nt.setGain(setXMLGain(el));
282            transition_sounds.add(nt);
283            i++;
284        }
285
286        // Get the start and stop sounds
287        el = e.getChild("start-sound");
288        if (el != null) {
289            fn = el.getChild("file").getValue();
290            //log.debug("Start sound: {}", fn);
291            start_sound = new SoundBite(vf, fn, "Engine_start",
292                    "Engine_Start");
293            // Handle gain
294            start_sound.setGain(setXMLGain(el));
295            start_sound.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
296            start_sound.setLooped(false);
297        }
298        el = e.getChild("shutdown-sound");
299        if (el != null) {
300            fn = el.getChild("file").getValue();
301            //log.debug("Shutdown sound: {}", fn);
302            shutdown_sound = new SoundBite(vf, fn, "Engine_shutdown", "Engine_Shutdown");
303            shutdown_sound.setLooped(false);
304            // Handle gain
305            shutdown_sound.setGain(setXMLGain(el));
306            shutdown_sound.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
307        }
308    }
309
310    private static final Logger log = LoggerFactory.getLogger(DieselSound.class);
311
312}