001package jmri.jmrit.vsdecoder; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.HashMap; 008import java.util.LinkedHashMap; 009import java.util.Iterator; 010import java.awt.geom.Point2D; 011import jmri.Audio; 012import jmri.LocoAddress; 013import jmri.Throttle; 014import jmri.jmrit.display.layoutEditor.*; 015import jmri.jmrit.operations.locations.Location; 016import jmri.jmrit.operations.routes.RouteLocation; 017import jmri.jmrit.operations.routes.Route; 018import jmri.jmrit.operations.trains.Train; 019import jmri.jmrit.operations.trains.TrainManager; 020import jmri.jmrit.roster.RosterEntry; 021import jmri.jmrit.vsdecoder.swing.VSDControl; 022import jmri.jmrit.vsdecoder.swing.VSDManagerFrame; 023import jmri.util.PhysicalLocation; 024 025import org.jdom2.Element; 026 027/** 028 * Implements a software "decoder" that responds to throttle inputs and 029 * generates sounds in responds to them. 030 * <p> 031 * Each VSDecoder implements exactly one Sound Profile (describes a particular 032 * type of locomotive, say, an EMD GP7). 033 * <hr> 034 * This file is part of JMRI. 035 * <p> 036 * JMRI is free software; you can redistribute it and/or modify it under the 037 * terms of version 2 of the GNU General Public License as published by the Free 038 * Software Foundation. See the "COPYING" file for a copy of this license. 039 * <p> 040 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 041 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 042 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 043 * 044 * @author Mark Underwood Copyright (C) 2011 045 * @author Klaus Killinger Copyright (C) 2018-2023 046 */ 047public class VSDecoder implements PropertyChangeListener { 048 049 boolean initialized = false; // This decoder has been initialized 050 boolean enabled = false; // This decoder is enabled 051 private boolean create_xy_series = false; // Create xy coordinates in console 052 053 private VSDConfig config; 054 055 // For use in VSDecoderManager 056 int dirfn = 1; 057 PhysicalLocation posToSet; 058 PhysicalLocation lastPos; 059 PhysicalLocation startPos; 060 int topspeed; 061 int topspeed_rev; 062 float lastspeed; 063 float avgspeed; 064 int setup_index; // Can be set by a Route 065 boolean is_muted; 066 VSDSound savedSound; 067 068 double distanceOnTrack; 069 float distanceMeter; 070 double distance; // how far to travel this frame 071 private double returnDistance; // used by a direction change 072 private Point2D location; 073 private LayoutTrack lastTrack; // the layout track we were on previously 074 private LayoutTrack layoutTrack; // which layout track we're on 075 private LayoutTrack returnTrack; 076 private LayoutTrack returnLastTrack; 077 LayoutTrack nextLayoutTrack; 078 private double directionRAD; // directionRAD we're headed (in radians) 079 private LayoutEditor models; 080 private VSDNavigation navigation; 081 082 HashMap<String, VSDSound> sound_list; // list of sounds 083 LinkedHashMap<String, SoundEvent> event_list; // list of events 084 085 /** 086 * Construct a VSDecoder with the given system name (id) and configuration 087 * (config) 088 * 089 * @param cfg (VSDConfig) Configuration 090 */ 091 public VSDecoder(VSDConfig cfg) { 092 config = cfg; 093 094 sound_list = new HashMap<>(); 095 event_list = new LinkedHashMap<>(); 096 097 // Force re-initialization 098 initialized = _init(); 099 100 try { 101 VSDFile vsdfile = new VSDFile(config.getVSDPath()); 102 if (vsdfile.isInitialized()) { 103 log.debug("Constructor: vsdfile init OK, loading XML..."); 104 this.setXml(vsdfile, config.getProfileName()); 105 } else { 106 log.debug("Constructor: vsdfile init FAILED."); 107 initialized = false; 108 } 109 } catch (java.util.zip.ZipException e) { 110 log.error("ZipException loading VSDecoder from {}", config.getVSDPath()); 111 // would be nice to pop up a dialog here... 112 } catch (java.io.IOException ioe) { 113 log.error("IOException loading VSDecoder from {}", config.getVSDPath()); 114 // would be nice to pop up a dialog here... 115 } 116 117 // Since the Config already has the address set, we need to call 118 // our own setAddress() to register the throttle listener 119 this.setAddress(config.getLocoAddress()); 120 this.enable(); 121 122 // Handle Advanced Location Following (if the parameter file is OK) 123 if (VSDecoderManager.instance().geofile_ok) { 124 // ALF1 needs this 125 this.setup_index = 0; 126 // create a navigator for this VSDecoder 127 if (VSDecoderManager.instance().alf_version == 2) { 128 navigation = new VSDNavigation(this); 129 } 130 } 131 132 if (log.isDebugEnabled()) { 133 log.debug("VSDecoder Init Complete. Audio Objects Created:"); 134 jmri.InstanceManager.getDefault(jmri.AudioManager.class).getNamedBeanSet(Audio.SOURCE).forEach((s) -> { 135 log.debug("\tSource: {}", s); 136 }); 137 jmri.InstanceManager.getDefault(jmri.AudioManager.class).getNamedBeanSet(Audio.BUFFER).forEach((s) -> { 138 log.debug("\tBuffer: {}", s); 139 }); 140 } 141 } 142 143 /** 144 * Construct a VSDecoder with the given system name (id), profile name and 145 * VSD file path 146 * 147 * @param id (String) System name for this VSDecoder 148 * @param name (String) Profile name 149 * @param path (String) Path to a VSD file to pull the given Profile from 150 */ 151 public VSDecoder(String id, String name, String path) { 152 153 config = new VSDConfig(); 154 config.setProfileName(name); 155 config.setId(id); 156 157 sound_list = new HashMap<>(); 158 event_list = new LinkedHashMap<>(); 159 160 // Force re-initialization 161 initialized = _init(); 162 163 config.setVSDPath(path); 164 165 try { 166 VSDFile vsdfile = new VSDFile(path); 167 if (vsdfile.isInitialized()) { 168 log.debug("Constructor: vsdfile init OK, loading XML..."); 169 this.setXml(vsdfile, name); 170 } else { 171 log.debug("Constructor: vsdfile init FAILED."); 172 initialized = false; 173 } 174 } catch (java.util.zip.ZipException e) { 175 log.error("ZipException loading VSDecoder from {}", path); 176 // would be nice to pop up a dialog here... 177 } catch (java.io.IOException ioe) { 178 log.error("IOException loading VSDecoder from {}", path); 179 // would be nice to pop up a dialog here... 180 } 181 } 182 183 private boolean _init() { 184 // Do nothing for now 185 this.enable(); 186 return true; 187 } 188 189 /** 190 * Get the ID (System Name) of this VSDecoder 191 * 192 * @return (String) system name of this VSDecoder 193 */ 194 public String getId() { 195 return config.getId(); 196 } 197 198 /** 199 * Check whether this VSDecoder has completed initialization 200 * 201 * @return (boolean) true if initialization is complete. 202 */ 203 public boolean isInitialized() { 204 return initialized; 205 } 206 207 /** 208 * Set the VSD File path for this VSDecoder to use 209 * 210 * @param p (String) path to VSD File 211 */ 212 public void setVSDFilePath(String p) { 213 config.setVSDPath(p); 214 } 215 216 /** 217 * Get the current VSD File path for this VSDecoder 218 * 219 * @return (String) path to VSD file 220 */ 221 public String getVSDFilePath() { 222 return config.getVSDPath(); 223 } 224 225 /** 226 * Shut down this VSDecoder and all of its associated sounds. 227 */ 228 public void shutdown() { 229 log.debug("Shutting down sounds..."); 230 for (VSDSound vs : sound_list.values()) { 231 log.debug("Stopping sound: {}", vs.getName()); 232 vs.shutdown(); 233 } 234 } 235 236 /** 237 * Handle the details of responding to a PropertyChangeEvent from a 238 * throttle. 239 * 240 * @param event (PropertyChangeEvent) Throttle event to respond to 241 */ 242 protected void throttlePropertyChange(PropertyChangeEvent event) { 243 // WARNING: FRAGILE CODE 244 // This will break if the return type of the event.getOld/NewValue() changes. 245 246 String eventName = event.getPropertyName(); 247 248 // Skip this if disabled 249 if (!enabled) { 250 log.debug("VSDecoder disabled. Take no action."); 251 return; 252 } 253 254 log.debug("VSDecoder throttle property change: {}", eventName); 255 256 if (eventName.equals("throttleAssigned")) { 257 Float s = (Float) jmri.InstanceManager.throttleManagerInstance().getThrottleInfo(config.getDccAddress(), Throttle.SPEEDSETTING); 258 if (s != null) { 259 this.getEngineSound().setFirstSpeed(true); // Auto-start needs this 260 // Mimic a throttlePropertyChange to propagate the current (init) speed setting of the throttle. 261 log.debug("Existing DCC Throttle found. Speed: {}", s); 262 this.throttlePropertyChange(new PropertyChangeEvent(this, Throttle.SPEEDSETTING, null, s)); 263 } 264 265 // Check for an existing throttle and get loco direction if it exists. 266 Boolean b = (Boolean) jmri.InstanceManager.throttleManagerInstance().getThrottleInfo(config.getDccAddress(), Throttle.ISFORWARD); 267 if (b != null) { 268 dirfn = b ? 1 : -1; 269 log.debug("Existing DCC Throttle found. IsForward is {}", b); 270 log.debug("Initial dirfn: {} for {}", dirfn, config.getDccAddress()); 271 this.throttlePropertyChange(new PropertyChangeEvent(this, Throttle.ISFORWARD, null, b)); 272 } else { 273 log.warn("No existing DCC throttle found."); 274 } 275 276 // Check for an existing throttle and get ENGINE throttle function key status if it exists. 277 // For all function keys used in config.xml (sound-event name="ENGINE") this will send an initial value! This could be ON or OFF. 278 if (event_list.get("ENGINE") != null) { 279 for (Trigger t : event_list.get("ENGINE").trigger_list.values()) { 280 log.debug("ENGINE trigger Name: {}, Event: {}, t: {}", t.getName(), t.getEventName(), t); 281 if (t.getEventName().startsWith("F")) { 282 log.debug("F-Key trigger found: {}, name: {}, event: {}", t, t.getName(), t.getEventName()); 283 // Don't send an initial value if trigger is ENGINE_STARTSTOP, because that would work against auto-start; BRAKE_KEY would play a sound 284 if (!t.getName().equals("ENGINE_STARTSTOP") && !t.getName().equals("BRAKE_KEY")) { 285 b = (Boolean) jmri.InstanceManager.throttleManagerInstance().getThrottleInfo(config.getDccAddress(), t.getEventName()); 286 if (b != null) { 287 this.throttlePropertyChange(new PropertyChangeEvent(this, t.getEventName(), null, b)); 288 } 289 } 290 } 291 } 292 } 293 } 294 295 // Iterate through the list of sound events, forwarding the propertyChange event. 296 for (SoundEvent t : event_list.values()) { 297 t.propertyChange(event); 298 } 299 300 if (eventName.equals(Throttle.ISFORWARD)) { 301 dirfn = (Boolean) event.getNewValue() ? 1 : -1; 302 } 303 } 304 305 /** 306 * Set this VSDecoder's LocoAddress, and register to follow events from the 307 * throttle with this address. 308 * 309 * @param l (LocoAddress) LocoAddress to be followed 310 */ 311 public void setAddress(LocoAddress l) { 312 // Hack for ThrottleManager Dcc dependency 313 config.setLocoAddress(l); 314 jmri.InstanceManager.throttleManagerInstance().attachListener(config.getDccAddress(), 315 new PropertyChangeListener() { 316 @Override 317 public void propertyChange(PropertyChangeEvent event) { 318 log.debug("property change name: {}, old: {}, new: {}", event.getPropertyName(), event.getOldValue(), event.getNewValue()); 319 throttlePropertyChange(event); 320 } 321 }); 322 log.debug("VSDecoder: Address set to {}", config.getLocoAddress()); 323 } 324 325 /** 326 * Get the currently assigned LocoAddress 327 * 328 * @return the currently assigned LocoAddress 329 */ 330 public LocoAddress getAddress() { 331 return config.getLocoAddress(); 332 } 333 334 public RosterEntry getRosterEntry() { 335 return config.getRosterEntry(); 336 } 337 338 /** 339 * Get the current decoder volume setting for this VSDecoder 340 * 341 * @return (float) volume level (0.0 - 1.0) 342 */ 343 public float getDecoderVolume() { 344 return config.getVolume(); 345 } 346 347 private void forwardMasterVolume(float volume) { 348 log.debug("VSD config id: {}, Master volume: {}, Decoder volume: {}", config.getId(), volume, config.getVolume()); 349 for (VSDSound vs : sound_list.values()) { 350 vs.setVolume(volume * config.getVolume()); 351 } 352 } 353 354 /** 355 * Set the decoder volume for this VSDecoder 356 * 357 * @param decoder_volume (float) volume level (0.0 - 1.0) 358 */ 359 public void setDecoderVolume(float decoder_volume) { 360 config.setVolume(decoder_volume); 361 float master_vol = 0.01f * VSDecoderManager.instance().getMasterVolume(); 362 log.debug("config set decoder volume to {}, master volume adjusted: {}", decoder_volume, master_vol); 363 for (VSDSound vs : sound_list.values()) { 364 vs.setVolume(master_vol * decoder_volume); 365 } 366 } 367 368 /** 369 * Is this VSDecoder muted? 370 * 371 * @return true if muted 372 */ 373 public boolean isMuted() { 374 return getMuteState(); 375 } 376 377 /** 378 * Mute or un-mute this VSDecoder 379 * 380 * @param m (boolean) true to mute, false to un-mute 381 */ 382 public void mute(boolean m) { 383 for (VSDSound vs : sound_list.values()) { 384 vs.mute(m); 385 } 386 } 387 388 private void setMuteState(boolean m) { 389 is_muted = m; 390 } 391 392 private boolean getMuteState() { 393 return is_muted; 394 } 395 396 /** 397 * set the x/y/z position in the soundspace of this VSDecoder Translates the 398 * given position to a position relative to the listener for the component 399 * VSDSounds. 400 * <p> 401 * The idea is that the user-preference Listener Position (relative to the 402 * USER's chosen origin) is always the OpenAL Context's origin. 403 * 404 * @param p (PhysicalLocation) location relative to the user's chosen 405 * Origin. 406 */ 407 public void setPosition(PhysicalLocation p) { 408 // Store the actual position relative to the user's Origin locally. 409 config.setPhysicalLocation(p); 410 if (create_xy_series) { 411 log.info("setPosition {}: {}\t{}", this.getAddress(), (float) Math.round(p.x*10000)/10000, p.y); 412 } 413 log.debug("address {} set Position: {}", this.getAddress(), p); 414 415 this.lastPos = p; // save this position 416 417 // Give all of the VSDSound objects the position translated relative to the listener position. 418 // This is a workaround for OpenAL requiring the listener position to always be at (0,0,0). 419 /* 420 * PhysicalLocation ref = VSDecoderManager.instance().getVSDecoderPreferences().getListenerPhysicalLocation(); 421 * if (ref == null) ref = PhysicalLocation.Origin; 422 */ 423 for (VSDSound s : sound_list.values()) { 424 // s.setPosition(PhysicalLocation.translate(p, ref)); 425 s.setPosition(p); 426 } 427 428 // Set (relative) volume for this location (in case we're in a tunnel) 429 float tv = 0.01f * VSDecoderManager.instance().getMasterVolume() * getDecoderVolume(); 430 log.debug("current master volume: {}, decoder volume: {}", VSDecoderManager.instance().getMasterVolume(), getDecoderVolume()); 431 if (savedSound.getTunnel()) { 432 tv *= VSDSound.tunnel_volume; 433 log.debug("VSD: In tunnel, volume: {}", tv); 434 } else { 435 log.debug("VSD: Not in tunnel, volume: {}", tv); 436 } 437 if (! getMuteState()) { 438 for (VSDSound vs : sound_list.values()) { 439 vs.setVolume(tv); 440 } 441 } 442 } 443 444 /** 445 * Get the current x/y/z position in the soundspace of this VSDecoder 446 * 447 * @return PhysicalLocation location of this VSDecoder 448 */ 449 public PhysicalLocation getPosition() { 450 return config.getPhysicalLocation(); 451 } 452 453 /** 454 * Respond to property change events from this VSDecoder's GUI 455 * 456 * @param evt (PropertyChangeEvent) event to respond to 457 */ 458 @Override 459 public void propertyChange(PropertyChangeEvent evt) { 460 String property = evt.getPropertyName(); 461 // Respond to events from the new GUI. 462 if (evt.getSource() instanceof VSDControl) { 463 if (property.equals(VSDControl.OPTION_CHANGE)) { 464 Train selected_train = jmri.InstanceManager.getDefault(TrainManager.class).getTrainByName((String) evt.getNewValue()); 465 if (selected_train != null) { 466 selected_train.addPropertyChangeListener(this); 467 // Handle Advanced Location Following (if the parameter file is OK) 468 if (VSDecoderManager.instance().geofile_ok) { 469 Route r = selected_train.getRoute(); 470 if (r != null) { 471 log.info("Train \"{}\" selected for {} - Route is now \"{}\"", selected_train, this.getAddress(), r.getName()); 472 if (r.getName().equals("VSDRoute1")) { 473 this.setup_index = 0; 474 } else if (r.getName().equals("VSDRoute2") && VSDecoderManager.instance().num_setups > 1) { 475 this.setup_index = 1; 476 } else if (r.getName().equals("VSDRoute3") && VSDecoderManager.instance().num_setups > 2) { 477 this.setup_index = 2; 478 } else if (r.getName().equals("VSDRoute4") && VSDecoderManager.instance().num_setups > 3) { 479 this.setup_index = 3; 480 } else { 481 log.warn("\"{}\" is not suitable for VSD Advanced Location Following", r.getName()); 482 } 483 } else { 484 log.warn("Train \"{}\" is without Route", selected_train); 485 } 486 } 487 } 488 } 489 return; 490 } 491 492 if (property.equals(VSDManagerFrame.MUTE)) { 493 // GUI Mute button 494 log.debug("VSD: Mute change. value: {}", evt.getNewValue()); 495 setMuteState((boolean) evt.getNewValue()); 496 this.mute(getMuteState()); 497 } else if (property.equals(VSDManagerFrame.VOLUME_CHANGE)) { 498 // GUI Volume slider (Master Volume) 499 log.debug("VSD: Volume change. value: {}", evt.getOldValue()); 500 // Slider gives integer 0-100. Need to change that to a float 0.0-1.0 501 this.forwardMasterVolume((0.01f * (Integer) evt.getOldValue())); 502 } else if (property.equals(Train.TRAIN_LOCATION_CHANGED_PROPERTY)) { 503 // Train Location Move 504 PhysicalLocation p = getTrainPosition((Train) evt.getSource()); 505 if (p != null) { 506 this.setPosition(getTrainPosition((Train) evt.getSource())); 507 } else { 508 log.debug("Train has null position"); 509 this.setPosition(new PhysicalLocation()); 510 } 511 } else if (property.equals(Train.STATUS_CHANGED_PROPERTY)) { 512 // Train Status change 513 String status = (String) evt.getOldValue(); 514 log.debug("Train status changed: {}", status); 515 log.debug("New Location: {}", getTrainPosition((Train) evt.getSource())); 516 if ((status.startsWith(Train.BUILT)) || (status.startsWith(Train.PARTIAL_BUILT))) { 517 log.debug("Train built. status: {}", status); 518 PhysicalLocation p = getTrainPosition((Train) evt.getSource()); 519 if (p != null) { 520 this.setPosition(getTrainPosition((Train) evt.getSource())); 521 } else { 522 log.debug("Train has null position"); 523 this.setPosition(new PhysicalLocation()); 524 } 525 } 526 } 527 } 528 529 // Methods for handling location tracking based on JMRI Operations 530 /** 531 * Get the physical location of the given Operations Train 532 * 533 * @param t (Train) the Train to interrogate 534 * @return PhysicalLocation location of the train 535 */ 536 protected PhysicalLocation getTrainPosition(Train t) { 537 if (t == null) { 538 log.debug("Train is null."); 539 return null; 540 } 541 RouteLocation rloc = t.getCurrentRouteLocation(); 542 if (rloc == null) { 543 log.debug("RouteLocation is null."); 544 return null; 545 } 546 Location loc = rloc.getLocation(); 547 if (loc == null) { 548 log.debug("Location is null."); 549 return null; 550 } 551 return loc.getPhysicalLocation(); 552 } 553 554 // Methods for handling the underlying sounds 555 /** 556 * Retrieve the VSDSound with the given system name 557 * 558 * @param name (String) System name of the requested VSDSound 559 * @return VSDSound the requested sound 560 */ 561 public VSDSound getSound(String name) { 562 return sound_list.get(name); 563 } 564 565 // Java Bean set/get Functions 566 /** 567 * Set the profile name to the given string 568 * 569 * @param pn (String) : name of the profile to set 570 */ 571 public void setProfileName(String pn) { 572 config.setProfileName(pn); 573 } 574 575 /** 576 * get the currently selected profile name 577 * 578 * @return (String) name of the currently selected profile 579 */ 580 public String getProfileName() { 581 return config.getProfileName(); 582 } 583 584 /** 585 * Enable this VSDecoder. 586 */ 587 public void enable() { 588 enabled = true; 589 } 590 591 /** 592 * Disable this VSDecoder. 593 */ 594 public void disable() { 595 enabled = false; 596 } 597 598 /** 599 * Get a reference to the EngineSound associated with this VSDecoder 600 * 601 * @return EngineSound The EngineSound reference for this VSDecoder or null 602 */ 603 public EngineSound getEngineSound() { 604 return (EngineSound) sound_list.get("ENGINE"); 605 } 606 607 /** 608 * Get a Collection of SoundEvents associated with this VSDecoder 609 * 610 * @return {@literal Collection<SoundEvent>} collection of SoundEvents 611 */ 612 public Collection<SoundEvent> getEventList() { 613 return event_list.values(); 614 } 615 616 /** 617 * Get an XML representation of this VSDecoder Includes a subtree of 618 * Elements for all of the associated SoundEvents, Triggers, VSDSounds, etc. 619 * 620 * @return Element XML Element for this VSDecoder 621 */ 622 public Element getXml() { 623 Element me = new Element("vsdecoder"); 624 ArrayList<Element> le = new ArrayList<>(); 625 626 me.setAttribute("name", this.config.getProfileName()); 627 628 for (SoundEvent se : event_list.values()) { 629 le.add(se.getXml()); 630 } 631 632 for (VSDSound vs : sound_list.values()) { 633 le.add(vs.getXml()); 634 } 635 636 me.addContent(le); 637 638 // Need to add whatever else here. 639 return me; 640 } 641 642 /** 643 * Build this VSDecoder from an XML representation 644 * 645 * @param vf (VSDFile) : VSD File to pull the XML from 646 * @param pn (String) : Parameter Name to find within the VSD File. 647 */ 648 public void setXml(VSDFile vf, String pn) { 649 Iterator<Element> itr; 650 Element e = null; 651 Element el = null; 652 SoundEvent se; 653 String n; 654 655 if (vf == null) { 656 log.debug("Null VSD File Name"); 657 return; 658 } 659 660 log.debug("VSD File Name: {}, profile: {}", vf.getName(), pn); 661 // need to choose one. 662 this.setVSDFilePath(vf.getName()); 663 664 // Find the <profile/> element that matches the name pn 665 // List<Element> profiles = vf.getRoot().getChildren("profile"); 666 // java.util.Iterator i = profiles.iterator(); 667 java.util.Iterator<Element> i = vf.getRoot().getChildren("profile").iterator(); 668 while (i.hasNext()) { 669 e = i.next(); 670 if (e.getAttributeValue("name").equals(pn)) { 671 break; 672 } 673 } 674 // E is now the first <profile/> in vsdfile that matches pn. 675 676 if (e == null) { 677 // No matching profile name found. 678 return; 679 } 680 681 // Set this decoder's name. 682 this.setProfileName(e.getAttributeValue("name")); 683 log.debug("Decoder Name: {}", e.getAttributeValue("name")); 684 685 // Check for a flag element to create xy-position-coordinates. 686 n = e.getChildText("create-xy-series"); 687 if ((n != null) && (n.equals("yes"))) { 688 create_xy_series = true; 689 log.debug("Profile {}: xy-position-coordinates will be created in JMRI System Console", getProfileName()); 690 } else { 691 create_xy_series = false; 692 log.debug("Profile {}: xy-position-coordinates will NOT be created in JMRI System Console", getProfileName()); 693 } 694 695 // Check for an optional sound start-position. 696 n = e.getChildText("start-position"); 697 if (n != null) { 698 startPos = PhysicalLocation.parse(n); 699 } else { 700 startPos = null; 701 } 702 log.debug("Start position: {}", startPos); 703 704 // +++ DEBUG 705 // Log and print all of the child elements. 706 itr = (e.getChildren()).iterator(); 707 while (itr.hasNext()) { 708 // Pull each element from the XML file. 709 el = itr.next(); 710 log.debug("Element: {}", el); 711 if (el.getAttribute("name") != null) { 712 log.debug(" Name: {}", el.getAttributeValue("name")); 713 log.debug(" type: {}", el.getAttributeValue("type")); 714 } 715 } 716 // --- DEBUG 717 718 // First, the sounds. 719 String prefix = "" + this.getId() + ":"; 720 log.debug("VSDecoder {}, prefix: {}", this.getId(), prefix); 721 itr = (e.getChildren("sound")).iterator(); 722 while (itr.hasNext()) { 723 el = itr.next(); 724 if (el.getAttributeValue("type") == null) { 725 // Empty sound. Skip. 726 log.debug("Skipping empty Sound."); 727 continue; 728 } else if (el.getAttributeValue("type").equals("configurable")) { 729 // Handle configurable sounds. 730 ConfigurableSound cs = new ConfigurableSound(prefix + el.getAttributeValue("name")); 731 cs.setXml(el, vf); 732 sound_list.put(el.getAttributeValue("name"), cs); 733 } else if (el.getAttributeValue("type").equals("diesel")) { 734 // Handle a diesel Engine sound 735 DieselSound es = new DieselSound(prefix + el.getAttributeValue("name")); 736 es.setXml(el, vf); 737 sound_list.put(el.getAttributeValue("name"), es); 738 } else if (el.getAttributeValue("type").equals("diesel3")) { 739 // Handle a diesel3 Engine sound 740 Diesel3Sound es = new Diesel3Sound(prefix + el.getAttributeValue("name")); 741 savedSound = es; 742 es.setXml(el, vf); 743 sound_list.put(el.getAttributeValue("name"), es); 744 topspeed = es.top_speed; 745 topspeed_rev = topspeed; 746 } else if (el.getAttributeValue("type").equals("steam")) { 747 // Handle a steam Engine sound 748 SteamSound es = new SteamSound(prefix + el.getAttributeValue("name")); 749 savedSound = es; 750 es.setXml(el, vf); 751 sound_list.put(el.getAttributeValue("name"), es); 752 topspeed = es.top_speed; 753 topspeed_rev = topspeed; 754 } else if (el.getAttributeValue("type").equals("steam1")) { 755 // Handle a steam1 Engine sound 756 Steam1Sound es = new Steam1Sound(prefix + el.getAttributeValue("name")); 757 savedSound = es; 758 es.setXml(el, vf); 759 sound_list.put(el.getAttributeValue("name"), es); 760 topspeed = es.top_speed; 761 topspeed_rev = es.top_speed_reverse; 762 //} else { 763 // TODO: Some type other than configurable sound. Handle appropriately 764 } 765 } 766 767 // Next, grab all of the SoundEvents 768 // Have to do the sounds first because the SoundEvent's setXml() will 769 // expect to be able to look it up. 770 itr = (e.getChildren("sound-event")).iterator(); 771 while (itr.hasNext()) { 772 el = itr.next(); 773 switch (SoundEvent.ButtonType.valueOf(el.getAttributeValue("buttontype").toUpperCase())) { 774 case MOMENTARY: 775 se = new MomentarySoundEvent(el.getAttributeValue("name")); 776 break; 777 case TOGGLE: 778 se = new ToggleSoundEvent(el.getAttributeValue("name")); 779 break; 780 case ENGINE: 781 se = new EngineSoundEvent(el.getAttributeValue("name")); 782 break; 783 case NONE: 784 default: 785 se = new SoundEvent(el.getAttributeValue("name")); 786 } 787 se.setParent(this); 788 se.setXml(el, vf); 789 event_list.put(se.getName(), se); 790 } 791 // Handle other types of children similarly here. 792 } 793 794 // VSDNavigation accessors 795 // 796 // Code from George Warner's LENavigator 797 // 798 void setLocation(Point2D location) { 799 this.location = location; 800 } 801 802 Point2D getLocation() { 803 return location; 804 } 805 806 LayoutTrack getLastTrack() { 807 return lastTrack; 808 } 809 810 void setLastTrack(LayoutTrack lastTrack) { 811 this.lastTrack = lastTrack; 812 } 813 814 void setLayoutTrack(LayoutTrack layoutTrack) { 815 this.layoutTrack = layoutTrack; 816 } 817 818 LayoutTrack getLayoutTrack() { 819 return layoutTrack; 820 } 821 822 void setReturnTrack(LayoutTrack returnTrack) { 823 this.returnTrack = returnTrack; 824 } 825 826 LayoutTrack getReturnTrack() { 827 return returnTrack; 828 } 829 830 void setReturnLastTrack(LayoutTrack returnLastTrack) { 831 this.returnLastTrack = returnLastTrack; 832 } 833 834 LayoutTrack getReturnLastTrack() { 835 return returnLastTrack; 836 } 837 838 double getDistance() { 839 return distance; 840 } 841 842 void setDistance(double distance) { 843 this.distance = distance; 844 } 845 846 double getReturnDistance() { 847 return returnDistance; 848 } 849 850 void setReturnDistance(double returnDistance) { 851 this.returnDistance = returnDistance; 852 } 853 854 double getDirectionRAD() { 855 return directionRAD; 856 } 857 858 void setDirectionRAD(double directionRAD) { 859 this.directionRAD = directionRAD; 860 } 861 862 void setDirectionDEG(double directionDEG) { 863 this.directionRAD = Math.toRadians(directionDEG); 864 } 865 866 LayoutEditor getModels() { 867 return models; 868 } 869 870 void setModels(LayoutEditor models) { 871 this.models = models; 872 } 873 874 void navigate() { 875 boolean result = false; 876 do { 877 if (this.getLayoutTrack() instanceof TrackSegment) { 878 result = navigation.navigateTrackSegment(); 879 } else if (this.getLayoutTrack() instanceof LayoutSlip) { 880 result = navigation.navigateLayoutSlip(); 881 } else if (this.getLayoutTrack() instanceof LayoutTurnout) { 882 result = navigation.navigateLayoutTurnout(); 883 } else if (this.getLayoutTrack() instanceof PositionablePoint) { 884 result = navigation.navigatePositionalPoint(); 885 } else if (this.getLayoutTrack() instanceof LevelXing) { 886 result = navigation.navigateLevelXing(); 887 } else if (this.getLayoutTrack() instanceof LayoutTurntable) { 888 result = navigation.navigateLayoutTurntable(); 889 } else { 890 log.warn("Track type not supported"); 891 setReturnDistance(0); 892 setReturnTrack(getLastTrack()); 893 result = false; 894 } 895 } while (result); 896 } 897 898 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(VSDecoder.class); 899 900}