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 */
031class DieselSound extends EngineSound {
032
033    // Engine Sounds
034    HashMap<Integer, SoundBite> notch_sounds;
035    ArrayList<NotchTransition> transition_sounds;
036    SoundBite start_sound;
037    SoundBite shutdown_sound;
038    NotchTransition notch_transition; // used for changing notches
039
040    int current_notch = 1;
041
042    public DieselSound(String name) {
043        super(name);
044    }
045
046    // Note:  Play and Loop do the same thing, since all of the notch sounds are set to loop.
047    @Override
048    public void play() {
049        log.debug("EngineSound Play: current_notch = {}", current_notch);
050        if (notch_sounds.containsKey(current_notch) && (isEngineStarted() || auto_start_engine)) {
051            notch_sounds.get(current_notch).play();
052        }
053    }
054
055    // Note:  Play and Loop do the same thing, since all of the notch sounds are set to loop.
056    @Override
057    public void loop() {
058        if (notch_sounds.containsKey(current_notch) && (isEngineStarted() || auto_start_engine)) {
059            notch_sounds.get(current_notch).play();
060        }
061    }
062
063    @Override
064    public void stop() {
065        if (notch_sounds.containsKey(current_notch)) {
066            notch_sounds.get(current_notch).stop();
067        }
068    }
069
070    @Override
071    public void changeNotch(int new_notch) {
072        log.debug("EngineSound.changeNotch() current = {} new notch = {}", current_notch, new_notch);
073        if (new_notch != current_notch) {
074            if (notch_sounds.containsKey(current_notch) && (isEngineStarted() || auto_start_engine)) {
075                notch_sounds.get(current_notch).fadeOut();
076            }
077
078            notch_transition = findNotchTransient(current_notch, new_notch);
079            if (notch_transition != null) {
080                log.debug("notch transition: name = {} length = {}, fade_length = {}", notch_transition.getFileName(), notch_transition.getLengthAsInt(), fade_length);
081                // Handle notch transition...
082                t = newTimer(notch_transition.getLengthAsInt() - notch_sounds.get(new_notch).getFadeInTime(), false,
083                        new ActionListener() {
084                            @Override
085                            public void actionPerformed(ActionEvent e) {
086                                handleNotchTimerPop(e);
087                            }
088                        });
089                t.start();
090                notch_transition.fadeIn();
091            } else {
092                log.debug("notch transition not found!");
093                if (notch_sounds.containsKey(new_notch) && (isEngineStarted() || auto_start_engine)) {
094                    notch_sounds.get(new_notch).fadeIn();
095                }
096            }
097            current_notch = new_notch;
098        }
099    }
100
101    protected void handleNotchTimerPop(ActionEvent e) {
102        // notch value has already been changed
103        log.debug("Notch timer pop. nt.next_notch = {}, file = {}", notch_transition.getNextNotch(), notch_sounds.get(notch_transition.getNextNotch()).getFileName());
104        if (notch_sounds.containsKey(notch_transition.getNextNotch()) && (isEngineStarted() || auto_start_engine)) {
105            notch_sounds.get(notch_transition.getNextNotch()).fadeIn();
106        }
107        notch_transition.fadeOut();
108    }
109
110    private NotchTransition findNotchTransient(int prev, int next) {
111        log.debug("Looking for Transient: prev = {} next = {}", prev, next);
112        for (NotchTransition nt : transition_sounds) {
113            log.debug("searching: nt.prev = {} nt.next = {}", nt.getPrevNotch(), nt.getNextNotch());
114            if ((nt.getPrevNotch() == prev) && (nt.getNextNotch() == next)) {
115                log.debug("Found transient: prev = {} next = {}", nt.getPrevNotch(), nt.getNextNotch());
116                return nt;
117            }
118        }
119        // If we loop out, there's no transition that matches.
120        return null;
121    }
122
123    @Override
124    public void startEngine() {
125        start_sound.play();
126        current_notch = calcEngineNotch(0.0f);
127        //t = newTimer(4500, false, new ActionListener() {
128        t = newTimer(start_sound.getLengthAsInt() - start_sound.getFadeOutTime(), false, new ActionListener() {
129            @Override
130            public void actionPerformed(ActionEvent e) {
131                startToIdleAction(e);
132            }
133        });
134        //t.setInitialDelay(4500);
135        t.setInitialDelay(start_sound.getLengthAsInt() - start_sound.getFadeOutTime());
136        t.setRepeats(false);
137        log.debug("Starting Engine");
138        t.start();
139    }
140
141    @Override
142    public void stopEngine() {
143        notch_sounds.get(current_notch).fadeOut();
144        shutdown_sound.play();
145        setEngineStarted(false);
146    }
147
148    private void startToIdleAction(ActionEvent e) {
149        log.debug("Starting idle sound notch = {} sound = {}", current_notch, notch_sounds.get(current_notch));
150        notch_sounds.get(current_notch).loop();
151        setEngineStarted(true);
152    }
153
154    @Override
155    public void shutdown() {
156        for (SoundBite ns : notch_sounds.values()) {
157            ns.stop();
158        }
159        for (NotchTransition nt : transition_sounds) {
160            nt.stop();
161        }
162        if (start_sound != null) {
163            start_sound.stop();
164        }
165        if (shutdown_sound != null) {
166            shutdown_sound.stop();
167        }
168    }
169
170    @Override
171    public void mute(boolean m) {
172        for (SoundBite ns : notch_sounds.values()) {
173            ns.mute(m);
174        }
175        for (NotchTransition nt : transition_sounds) {
176            nt.mute(m);
177        }
178        if (start_sound != null) {
179            start_sound.mute(m);
180        }
181        if (shutdown_sound != null) {
182            shutdown_sound.mute(m);
183        }
184    }
185
186    @Override
187    public void setVolume(float v) {
188        for (SoundBite ns : notch_sounds.values()) {
189            ns.setVolume(v);
190        }
191        for (NotchTransition nt : transition_sounds) {
192            nt.setVolume(v);
193        }
194        if (start_sound != null) {
195            start_sound.setVolume(v);
196        }
197        if (shutdown_sound != null) {
198            shutdown_sound.setVolume(v);
199        }
200    }
201
202    @Override
203    public void setPosition(PhysicalLocation p) {
204        for (SoundBite ns : notch_sounds.values()) {
205            ns.setPosition(p);
206        }
207        for (NotchTransition nt : transition_sounds) {
208            nt.setPosition(p);
209        }
210        if (start_sound != null) {
211            start_sound.setPosition(p);
212        }
213        if (shutdown_sound != null) {
214            shutdown_sound.setPosition(p);
215        }
216    }
217
218    @Override
219    public Element getXml() {
220        Element me = new Element("sound");
221        me.setAttribute("name", this.getName());
222        me.setAttribute("type", "engine");
223        // Do something, eventually...
224        return me;
225    }
226
227    @Override
228    public void setXml(Element e, VSDFile vf) {
229        Element el;
230        //int num_notches;
231        String fn;
232        SoundBite sb;
233
234        // Handle the common stuff.
235        super.setXml(e, vf);
236
237        log.debug("Diesel EngineSound: {}", e.getAttribute("name").getValue());
238        notch_sounds = new HashMap<Integer, SoundBite>();
239        transition_sounds = new ArrayList<NotchTransition>();
240
241        // Get the notch sounds
242        Iterator<Element> itr = (e.getChildren("notch-sound")).iterator();
243        int i = 0;
244        while (itr.hasNext()) {
245            el = itr.next();
246            fn = el.getChildText("file");
247            int nn = Integer.parseInt(el.getChildText("notch"));
248            sb = new SoundBite(vf, fn, "Engine_n" + i, "Engine_" + i);
249            sb.setLooped(true);
250            sb.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
251            sb.setGain(setXMLGain(el));
252            // Store in the list.
253            notch_sounds.put(nn, sb);
254            i++;
255        }
256
257        // Get the notch transitions
258        itr = (e.getChildren("notch-transition")).iterator();
259        i = 0;
260        NotchTransition nt;
261        while (itr.hasNext()) {
262            el = itr.next();
263            fn = el.getChildText("file");
264            nt = new NotchTransition(vf, fn, "Engine_nt" + i, "Engine_nt" + i);
265            nt.setPrevNotch(Integer.parseInt(el.getChildText("prev-notch")));
266            nt.setNextNotch(Integer.parseInt(el.getChildText("next-notch")));
267            nt.setLooped(false);
268            nt.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
269            // Handle gain
270            nt.setGain(setXMLGain(el));
271            transition_sounds.add(nt);
272            i++;
273        }
274
275        // Get the start and stop sounds
276        el = e.getChild("start-sound");
277        if (el != null) {
278            fn = el.getChild("file").getValue();
279            start_sound = new SoundBite(vf, fn, "Engine_start",
280                    "Engine_Start");
281            // Handle gain
282            start_sound.setGain(setXMLGain(el));
283            start_sound.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
284            start_sound.setLooped(false);
285        }
286        el = e.getChild("shutdown-sound");
287        if (el != null) {
288            fn = el.getChild("file").getValue();
289            shutdown_sound = new SoundBite(vf, fn, "Engine_shutdown", "Engine_Shutdown");
290            shutdown_sound.setLooped(false);
291            // Handle gain
292            shutdown_sound.setGain(setXMLGain(el));
293            shutdown_sound.setFadeTimes(this.getFadeInTime(), this.getFadeOutTime());
294        }
295    }
296
297    private static final Logger log = LoggerFactory.getLogger(DieselSound.class);
298
299}