001package jmri.jmrit.vsdecoder; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import jmri.util.PhysicalLocation; 006import org.jdom2.Element; 007 008/** 009 * Configurable Sound initial version. 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) 2025 026 */ 027class ConfigurableSound extends VSDSound { 028 029 protected String start_file; 030 protected String mid_file; 031 protected String end_file; 032 protected String short_file; 033 034 SoundBite start_sound; 035 SoundBite mid_sound; 036 SoundBite end_sound; 037 SoundBite short_sound; 038 039 boolean initialized = false; 040 041 protected boolean use_start_sound = false; 042 protected boolean use_mid_sound = false; 043 protected boolean use_end_sound = false; 044 protected boolean use_short_sound = false; 045 046 private float rd; 047 048 public ConfigurableSound(String name) { 049 super(name); 050 } 051 052 public boolean init() { 053 return this.init(null); 054 } 055 056 boolean init(VSDFile vf) { 057 if (!initialized) { 058 if (use_start_sound) { 059 start_sound = new SoundBite(vf, start_file, name + "_Start", name + "_Start"); 060 if (start_sound.isInitialized()) { 061 start_sound.setLooped(false); 062 start_sound.setReferenceDistance(rd); 063 start_sound.setGain(gain); 064 } else { 065 use_start_sound = false; 066 } 067 } 068 if (use_mid_sound) { 069 mid_sound = new SoundBite(vf, mid_file, name + "_Mid", name + "_Mid"); 070 if (mid_sound.isInitialized()) { 071 mid_sound.setLooped(false); 072 mid_sound.setReferenceDistance(rd); 073 mid_sound.setGain(gain); 074 } else { 075 use_mid_sound = false; 076 } 077 } 078 if (use_end_sound) { 079 end_sound = new SoundBite(vf, end_file, name + "_End", name + "_End"); 080 if (end_sound.isInitialized()) { 081 end_sound.setLooped(false); 082 end_sound.setReferenceDistance(rd); 083 end_sound.setGain(gain); 084 } else { 085 use_end_sound = false; 086 } 087 } 088 if (use_short_sound) { 089 short_sound = new SoundBite(vf, short_file, name + "_Short", name + "_Short"); 090 if (short_sound.isInitialized()) { 091 short_sound.setLooped(false); 092 short_sound.setReferenceDistance(rd); 093 short_sound.setGain(gain); 094 } else { 095 use_short_sound = false; 096 } 097 } 098 } 099 return true; 100 } 101 102 @Override 103 public void play() { 104 if (use_short_sound) { 105 short_sound.play(); 106 } else { 107 if (use_start_sound) { 108 t = newTimer(start_sound.getLengthAsInt(), false, new ActionListener() { 109 @Override 110 public void actionPerformed(ActionEvent e) { 111 handleTimerPop(e); 112 } 113 }); 114 start_sound.play(); 115 if (use_mid_sound) { 116 t.start(); 117 } 118 } else if (use_mid_sound) { 119 mid_sound.setLooped(true); 120 mid_sound.play(); 121 } 122 } 123 } 124 125 @Override 126 public void loop() { 127 if (use_start_sound) { 128 start_sound.setLooped(false); 129 start_sound.play(); 130 // The newTimer method in the super class makes sure that the delay value is positive 131 t = newTimer(start_sound.getLengthAsInt() - 100, false, new ActionListener() { 132 @Override 133 public void actionPerformed(ActionEvent e) { 134 handleTimerPop(e); 135 } 136 }); 137 t.setRepeats(false); // timer pop only once to trigger the sustain sound. 138 t.start(); 139 } else if (use_mid_sound) { 140 mid_sound.setLooped(true); 141 mid_sound.play(); 142 } 143 } 144 145 // Catch the timer pop after the start sound is played and trigger the (looped) sustain sound. 146 protected void handleTimerPop(ActionEvent e) { 147 log.debug("Received timer pop after start sound played."); 148 //TODO: Need to validate that this is the timer pop 149 if (use_mid_sound) { 150 mid_sound.setLooped(true); 151 mid_sound.play(); 152 } 153 t.stop(); 154 } 155 156 @Override 157 public void stop() { 158 log.debug("Stopping"); 159 // make sure the start sound is killed 160 if (use_start_sound) { 161 start_sound.stop(); 162 } 163 164 // If the mid sound is used, turn off the looping. 165 // this will allow it to naturally die. 166 if (use_mid_sound) { 167 mid_sound.setLooped(false); 168 mid_sound.fadeOut(); 169 } 170 171 // If the timer is running, stop it. 172 if (t != null) { 173 t.stop(); 174 } 175 176 // If we're using the end sound, stop the mid sound 177 // and play the end sound. 178 if (use_end_sound) { 179 if (use_mid_sound) { 180 mid_sound.stop(); 181 } 182 end_sound.setLooped(false); 183 end_sound.play(); 184 } 185 } 186 187 @Override 188 public void fadeIn() { 189 this.play(); 190 } 191 192 @Override 193 public void fadeOut() { 194 this.stop(); 195 } 196 197 @Override 198 public void shutdown() { 199 if (use_start_sound) { 200 start_sound.stop(); 201 } 202 if (use_mid_sound) { 203 mid_sound.stop(); 204 } 205 if (use_end_sound) { 206 end_sound.stop(); 207 } 208 if (use_short_sound) { 209 short_sound.stop(); 210 } 211 } 212 213 @Override 214 public void mute(boolean m) { 215 if (use_start_sound) { 216 start_sound.mute(m); 217 } 218 if (use_mid_sound) { 219 mid_sound.mute(m); 220 } 221 if (use_end_sound) { 222 end_sound.mute(m); 223 } 224 if (use_short_sound) { 225 short_sound.mute(m); 226 } 227 } 228 229 @Override 230 public void setVolume(float v) { 231 if (use_start_sound) { 232 start_sound.setVolume(v); 233 } 234 if (use_mid_sound) { 235 mid_sound.setVolume(v); 236 } 237 if (use_end_sound) { 238 end_sound.setVolume(v); 239 } 240 if (use_short_sound) { 241 short_sound.setVolume(v); 242 } 243 } 244 245 @Override 246 public void setPosition(PhysicalLocation p) { 247 super.setPosition(p); 248 if (use_start_sound) { 249 start_sound.setPosition(p); 250 setTunnelEffect(start_sound); 251 } 252 if (use_mid_sound) { 253 mid_sound.setPosition(p); 254 setTunnelEffect(mid_sound); 255 } 256 if (use_end_sound) { 257 end_sound.setPosition(p); 258 setTunnelEffect(end_sound); 259 } 260 if (use_short_sound) { 261 short_sound.setPosition(p); 262 setTunnelEffect(short_sound); 263 } 264 } 265 266 private void setTunnelEffect(SoundBite sb) { 267 if (this.getTunnel()) { 268 sb.attachSourcesToEffects(); 269 } else { 270 sb.detachSourcesToEffects(); 271 } 272 } 273 274 @Override 275 public Element getXml() { 276 Element me = new Element("sound"); 277 278 if (log.isDebugEnabled()) { 279 log.debug("Configurable Sound:"); 280 log.debug(" name: {}", this.getName()); 281 log.debug(" start_file: {}", start_file); 282 log.debug(" mid_file: {}", mid_file); 283 log.debug(" end_file: {}", end_file); 284 log.debug(" short_file: {}", short_file); 285 log.debug(" use_start_file: {}", start_file); 286 } 287 288 me.setAttribute("name", this.getName()); 289 me.setAttribute("type", "configurable"); 290 if (use_start_sound) { 291 me.addContent(new Element("start-file").addContent(start_file)); 292 } 293 if (use_mid_sound) { 294 me.addContent(new Element("mid-file").addContent(mid_file)); 295 } 296 if (use_end_sound) { 297 me.addContent(new Element("end-file").addContent(end_file)); 298 } 299 if (use_short_sound) { 300 me.addContent(new Element("short-file").addContent(short_file)); 301 } 302 303 return me; 304 } 305 306 @Override 307 public void setXml(Element e) { 308 this.setXml(e, null); 309 } 310 311 public void setXml(Element e, VSDFile vf) { 312 log.debug("ConfigurableSound: {}", e.getAttributeValue("name")); 313 if (((start_file = e.getChildText("start-file")) != null) && (!start_file.isEmpty())) { 314 use_start_sound = true; 315 } else { 316 use_start_sound = false; 317 } 318 if (((mid_file = e.getChildText("mid-file")) != null) && (!mid_file.isEmpty())) { 319 use_mid_sound = true; 320 } else { 321 use_mid_sound = false; 322 } 323 if (((end_file = e.getChildText("end-file")) != null) && (!end_file.isEmpty())) { 324 use_end_sound = true; 325 } else { 326 use_end_sound = false; 327 } 328 if (((short_file = e.getChildText("short-file")) != null) && (!short_file.isEmpty())) { 329 use_short_sound = true; 330 } else { 331 use_short_sound = false; 332 } 333 334 String g = e.getChildText("gain"); 335 if ((g != null) && (!g.isEmpty())) { 336 gain = Float.parseFloat(g); 337 } else { 338 gain = default_gain; 339 } 340 341 String rds = e.getChildText("reference-distance"); 342 if ((rds != null) && (!rds.isEmpty())) { 343 rd = Float.parseFloat(rds); 344 } else { 345 rd = default_reference_distance; 346 } 347 348 /* 349 log.debug("Use: start: {}, mid: {}, end: {}, short: {}", use_start_sound, 350 use_mid_sound, use_end_sound, use_short_sound); 351 */ 352 // Reboot the sound 353 initialized = false; 354 this.init(vf); 355 } 356 357 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConfigurableSound.class); 358 359}