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}