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