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