001package jmri.jmrit.vsdecoder;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import javax.swing.SwingUtilities;
006import org.jdom2.Element;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Superclass for Steam, Diesel and Electric Sound.
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 * @author Klaus Killinger Copyright (C) 2018, 2021
028 */
029public class EngineSound extends VSDSound {
030
031    boolean initialized = false;
032    boolean engine_started = false;
033    boolean auto_start_engine = false;
034    boolean is_auto_start; // Can be used in config.xml
035    boolean is_first = false;
036
037    int fade_length = 100;
038    int fade_in_time = 100;
039    int fade_out_time = 100;
040
041    float engine_rd;
042    float engine_gain;
043    int sleep_interval;
044    private int default_sleep_interval = 50; // time in ms
045
046    EnginePane engine_pane;
047
048    public EngineSound(String name) {
049        super(name);
050        is_playing = false;
051        engine_started = false;
052        initialized = init();
053    }
054
055    public boolean init() {
056        auto_start_engine = VSDecoderManager.instance().getVSDecoderPreferences().isAutoStartingEngine();
057        return true;
058    }
059
060    // Note:  Play and Loop do the same thing, since all of the notch sounds are set to loop.
061    @Override
062    public void play() {
063        log.debug("EngineSound Play");
064    }
065
066    // Note:  Play and Loop do the same thing, since all of the notch sounds are set to loop.
067    @Override
068    public void loop() {
069        log.debug("EngineSound Loop");
070    }
071
072    @Override
073    public void stop() {
074        log.info("Emergency Stop called!");
075        is_playing = false;
076    }
077
078    @Override
079    public void fadeIn() {
080        this.play();
081    }
082
083    @Override
084    public void fadeOut() {
085        this.stop();
086    }
087
088    public int getFadeInTime() {
089        return this.fade_in_time;
090    }
091
092    public int getFadeOutTime() {
093        return this.fade_out_time;
094    }
095
096    protected void setFadeInTime(int t) {
097        this.fade_in_time = t;
098    }
099
100    protected void setFadeInTime(String s) {
101        if (s == null) {
102            log.debug("setFadeInTime null string");
103            return;
104        }
105        try {
106            this.setFadeInTime(Integer.parseInt(s));
107        } catch (NumberFormatException e) {
108            log.debug("setFadeInTime Failed to parse int from: {}", s);
109        }
110    }
111
112    protected void setFadeOutTime(int t) {
113        this.fade_out_time = t;
114    }
115
116    protected void setFadeOutTime(String s) {
117        if (s == null) {
118            log.debug("setFadeInTime null string");
119            return;
120        }
121
122        try {
123            this.setFadeOutTime(Integer.parseInt(s));
124        } catch (NumberFormatException e) {
125            log.debug("setFadeOutTime Failed to parse int from: {}", s);
126        }
127    }
128
129    static final public int calcEngineNotch(final float throttle) {
130        // This will convert to a value 0-8.
131        int notch = ((int) Math.rint(throttle * 8)) + 1;
132        if (notch < 1) {
133            notch = 1;
134        }
135        log.debug("Throttle: {}, Notch: {}", throttle, notch);
136        return notch;
137    }
138
139    static final public int calcEngineNotch(final double throttle) {
140        // This will convert from a % to a value 0-8.
141        int notch = ((int) Math.rint(throttle * 8)) + 1;
142        if (notch < 1) {
143            notch = 1;
144        }
145        return notch;
146    }
147
148    // This is the default behavior.  Subclasses can do fancier things
149    // if they want.
150    public void handleSpeedChange(Float s, EnginePane e) {
151        engine_pane = e;
152        engine_pane.setSpeed(s);
153    }
154
155    void setFirstSpeed(boolean f) {
156        is_first = f;
157    }
158
159    boolean getFirstSpeed() {
160        return is_first;
161    }
162
163    public void startEngine() {
164        log.debug("Starting Engine");
165    }
166
167    public void stopEngine() {
168    }
169
170    public boolean isEngineStarted() {
171        return engine_started;
172    }
173
174    public void setEngineStarted(boolean es) {
175        engine_started = es;
176    }
177
178    public void functionKey(String e, boolean v, String n) {
179    }
180
181    public void changeLocoDirection(int d) {
182    }
183
184    @Override
185    public void shutdown() {
186        // do nothing.
187    }
188
189    @Override
190    public void mute(boolean m) {
191        // do nothing.
192    }
193
194    @Override
195    public void setVolume(float v) {
196        // do nothing.
197    }
198
199    // Note: We have to invoke engine_pane later because everything's not really setup yet
200    // Need some more time to get the speed from the assigned throttle
201    void autoStartCheck() {
202        if (auto_start_engine || is_auto_start) {
203            SwingUtilities.invokeLater(() -> {
204                t = newTimer(40, false, new ActionListener() {
205                    @Override
206                    public void actionPerformed(ActionEvent e) {
207                        if (engine_pane != null && getFirstSpeed()) {
208                            engine_pane.startButtonClick();
209                        } else {
210                            log.warn("engine pane or speed not found");
211                        }
212                    }
213                });
214                t.start();
215            });
216        }
217    }
218
219    protected boolean setXMLAutoStart(Element e) {
220        String a = e.getChildText("auto-start");
221        if ((a != null) && (a.equals("yes"))) {
222            return true;
223        } else {
224            return false;
225        }
226    }
227
228    protected float setXMLGain(Element e) {
229        String g = e.getChildText("gain");
230        log.debug("  gain: {}", g);
231        if ((g != null) && !(g.isEmpty())) {
232            return Float.parseFloat(g);
233        } else {
234            return default_gain;
235        }
236    }
237
238    protected float setXMLReferenceDistance(Element e) {
239        String a = e.getChildText("reference-distance");
240        if ((a != null) && (!a.isEmpty())) {
241            return Float.parseFloat(a);
242        } else {
243            return default_reference_distance;
244        }
245    }
246
247    protected float setXMLEngineReferenceDistance(Element e) {
248        String a = e.getChildText("engine-reference-distance");
249        if ((a != null) && (!a.isEmpty())) {
250            return Float.parseFloat(a);
251        } else {
252            return default_reference_distance;
253        }
254    }
255
256    protected int setXMLSleepInterval(Element e) {
257        String a = e.getChildText("sleep-interval");
258        if ((a != null) && (!a.isEmpty())) {
259            // Make some restrictions, since the variable is used for calculations later
260            int sleep_interval = Integer.parseInt(a);
261            if ((sleep_interval < 38) || (sleep_interval > 55)) {
262                log.info("Invalid sleep-interval {} was set to default {}", sleep_interval, default_sleep_interval);
263                return default_sleep_interval;
264            } else {
265                return sleep_interval;
266            }
267        } else {
268            return default_sleep_interval;
269        }
270    }
271
272    @Override
273    public Element getXml() {
274        Element me = new Element("sound");
275        me.setAttribute("name", this.getName());
276        me.setAttribute("type", "engine");
277        // Do something, eventually...
278        return me;
279    }
280
281    public void setXml(Element e, VSDFile vf) {
282        // Do only the stuff common...
283        if (this.getName() == null) {
284            this.setName(e.getAttributeValue("name"));
285        }
286        this.setFadeInTime(e.getChildText("fade-in-time"));
287        this.setFadeOutTime(e.getChildText("fade-out-time"));
288        log.debug("Name: {}, Fade-In-Time: {}, Fade-Out-Time: {}", this.getName(),
289            this.getFadeInTime(), this.getFadeOutTime());
290    }
291
292    private static final Logger log = LoggerFactory.getLogger(EngineSound.class);
293
294}