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