001package jmri.managers.configurexml; 002 003import java.util.List; 004import java.util.SortedSet; 005import javax.vecmath.Vector3f; 006import jmri.Audio; 007import jmri.AudioException; 008import jmri.AudioManager; 009import jmri.InstanceManager; 010import jmri.jmrit.audio.AudioBuffer; 011import jmri.jmrit.audio.AudioFactory; 012import jmri.jmrit.audio.AudioListener; 013import jmri.jmrit.audio.AudioSource; 014import jmri.util.FileUtil; 015import org.jdom2.Attribute; 016import org.jdom2.Element; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020/** 021 * Provides the abstract base and store functionality for configuring 022 * AudioManagers, working with AbstractAudioManagers. 023 * <p> 024 * Typically, a subclass will just implement the load(Element audio) class, 025 * relying on implementation here to load the individual Audio objects. Note 026 * that these are stored explicitly, so the resolution mechanism doesn't need to 027 * see *Xml classes for each specific Audio or AbstractAudio subclass at store 028 * time. 029 * 030 * <hr> 031 * This file is part of JMRI. 032 * <p> 033 * JMRI is free software; you can redistribute it and/or modify it under the 034 * terms of version 2 of the GNU General Public License as published by the Free 035 * Software Foundation. See the "COPYING" file for a copy of this license. 036 * <p> 037 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 038 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 039 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 040 * 041 * @author Bob Jacobsen Copyright: Copyright (c) 2002, 2008 042 * @author Matthew Harris copyright (c) 2009, 2011 043 */ 044public abstract class AbstractAudioManagerConfigXML extends AbstractNamedBeanManagerConfigXML { 045 046 /** 047 * Default constructor 048 */ 049 public AbstractAudioManagerConfigXML() { 050 } 051 052 /** 053 * Default implementation for storing the contents of an AudioManager. 054 * 055 * @param o Object to store, of type AudioManager 056 * @return Element containing the complete info 057 */ 058 @Override 059 public Element store(Object o) { 060 Element audios = new Element("audio"); 061 setStoreElementClass(audios); 062 AudioManager am = (AudioManager) o; 063 if (am != null) { 064 SortedSet<Audio> audioList = am.getNamedBeanSet(); 065 // don't return an element if there are no audios to include 066 if (audioList.isEmpty()) { 067 return null; 068 } 069 // also, don't store if we don't have any Sources or Buffers 070 // (no need to store the automatically created Listener object by itself) 071 if (am.getNamedBeanSet(Audio.SOURCE).isEmpty() 072 && am.getNamedBeanSet(Audio.BUFFER).isEmpty()) { 073 return null; 074 } 075 // finally, don't store if the only Sources and Buffers are for the 076 // virtual sound decoder (VSD) 077 int vsdObjectCount = 0; 078 079 // count all VSD objects 080 for (Audio a : audioList) { 081 String aName = a.getSystemName(); 082 log.debug("Check if {} is a VSD object", aName); 083 if (aName.length() >= 8 && aName.substring(3, 8).equalsIgnoreCase("$VSD:")) { 084 log.debug("...yes"); 085 vsdObjectCount++; 086 } 087 } 088 089 log.debug("Found {} VSD objects of {} objects", vsdObjectCount, 090 am.getNamedBeanSet(Audio.SOURCE).size() + am.getNamedBeanSet(Audio.BUFFER).size() 091 ); 092 093 // check if the total number of Sources and Buffers is equal to 094 // the number of VSD objects - if so, exit. 095 if (am.getNamedBeanSet(Audio.SOURCE).size() 096 + am.getNamedBeanSet(Audio.BUFFER).size() == vsdObjectCount) { 097 log.debug("Only VSD objects - nothing to store"); 098 return null; 099 } 100 101 // store global information 102 AudioFactory audioFact = am.getActiveAudioFactory(); 103 if (audioFact != null) { 104 audios.setAttribute("distanceattenuated", audioFact.isDistanceAttenuated() ? "yes" : "no"); 105 } 106 // store the audios 107 for (Audio a : audioList) { 108 String aName = a.getSystemName(); 109 log.debug("system name is {}", aName); 110 111 if (aName.length() >= 8 && aName.substring(3, 8).equalsIgnoreCase("$VSD:")) { 112 log.debug("Skipping storage of VSD object {}", aName); 113 continue; 114 } 115 116 // Transient objects for current element and any children 117 Element e = null; 118 Element ce = null; 119 120 int type = a.getSubType(); 121 if (type == Audio.BUFFER) { 122 AudioBuffer ab = (AudioBuffer) a; 123 e = new Element("audiobuffer").setAttribute("systemName", aName); 124 e.addContent(new Element("systemName").addContent(aName)); 125 126 // store common part 127 storeCommon(ab, e); 128 129 // store sub-type specific data 130 String url = ab.getURL(); 131 ce = new Element("url") 132 .addContent("" + (url.isEmpty() ? "" : FileUtil.getPortableFilename(url))); 133 e.addContent(ce); 134 135 ce = new Element("looppoint"); 136 ce.setAttribute("start", "" + ab.getStartLoopPoint()); 137 ce.setAttribute("end", "" + ab.getEndLoopPoint()); 138 e.addContent(ce); 139 140 ce = new Element("streamed"); 141 ce.addContent("" + (ab.isStreamed() ? "yes" : "no")); 142 e.addContent(ce); 143 } else if (type == Audio.LISTENER) { 144 AudioListener al = (AudioListener) a; 145 e = new Element("audiolistener").setAttribute("systemName", aName); 146 e.addContent(new Element("systemName").addContent(aName)); 147 148 // store common part 149 storeCommon(al, e); 150 151 // store sub-type specific data 152 ce = new Element("position"); 153 ce.setAttribute("x", "" + al.getPosition().x); 154 ce.setAttribute("y", "" + al.getPosition().y); 155 ce.setAttribute("z", "" + al.getPosition().z); 156 e.addContent(ce); 157 158 ce = new Element("velocity"); 159 ce.setAttribute("x", "" + al.getVelocity().x); 160 ce.setAttribute("y", "" + al.getVelocity().y); 161 ce.setAttribute("z", "" + al.getVelocity().z); 162 e.addContent(ce); 163 164 ce = new Element("orientation"); 165 ce.setAttribute("atX", "" + al.getOrientation(Audio.AT).x); 166 ce.setAttribute("atY", "" + al.getOrientation(Audio.AT).y); 167 ce.setAttribute("atZ", "" + al.getOrientation(Audio.AT).z); 168 ce.setAttribute("upX", "" + al.getOrientation(Audio.UP).x); 169 ce.setAttribute("upY", "" + al.getOrientation(Audio.UP).y); 170 ce.setAttribute("upZ", "" + al.getOrientation(Audio.UP).z); 171 e.addContent(ce); 172 173 ce = new Element("gain"); 174 ce.addContent("" + al.getGain()); 175 e.addContent(ce); 176 177 ce = new Element("metersperunit"); 178 ce.addContent("" + al.getMetersPerUnit()); 179 e.addContent(ce); 180 } else if (type == Audio.SOURCE) { 181 AudioSource as = (AudioSource) a; 182 e = new Element("audiosource") 183 .setAttribute("systemName", aName); 184 e.addContent(new Element("systemName").addContent(aName)); 185 186 // store common part 187 storeCommon(as, e); 188 189 // store sub-type specific data 190 ce = new Element("position"); 191 ce.setAttribute("x", "" + as.getPosition().x); 192 ce.setAttribute("y", "" + as.getPosition().y); 193 ce.setAttribute("z", "" + as.getPosition().z); 194 e.addContent(ce); 195 196 ce = new Element("velocity"); 197 ce.setAttribute("x", "" + as.getVelocity().x); 198 ce.setAttribute("y", "" + as.getVelocity().y); 199 ce.setAttribute("z", "" + as.getVelocity().z); 200 e.addContent(ce); 201 202 ce = new Element("assignedbuffer"); 203 if (as.getAssignedBuffer() != null) { 204 ce.addContent("" + as.getAssignedBufferName()); 205 } 206 e.addContent(ce); 207 208 ce = new Element("gain"); 209 ce.addContent("" + as.getGain()); 210 e.addContent(ce); 211 212 ce = new Element("pitch"); 213 ce.addContent("" + as.getPitch()); 214 e.addContent(ce); 215 216 ce = new Element("distances"); 217 ce.setAttribute("ref", "" + as.getReferenceDistance()); 218 float f = as.getMaximumDistance(); 219 ce.setAttribute("max", "" + f); 220 e.addContent(ce); 221 222 ce = new Element("loops"); 223 ce.setAttribute("min", "" + as.getMinLoops()); 224 ce.setAttribute("max", "" + as.getMaxLoops()); 225// ce.setAttribute("mindelay", ""+as.getMinLoopDelay()); 226// ce.setAttribute("maxdelay", ""+as.getMaxLoopDelay()); 227 e.addContent(ce); 228 229 ce = new Element("fadetimes"); 230 ce.setAttribute("in", "" + as.getFadeIn()); 231 ce.setAttribute("out", "" + as.getFadeOut()); 232 e.addContent(ce); 233 234 ce = new Element("positionrelative"); 235 ce.addContent("" + (as.isPositionRelative() ? "yes" : "no")); 236 e.addContent(ce); 237 } 238 239 log.debug("store Audio {}", aName); 240 audios.addContent(e); 241 } 242 } 243 return audios; 244 } 245 246 /** 247 * Subclass provides implementation to create the correct top element, 248 * including the type information. Default implementation is to use the 249 * local class here. 250 * 251 * @param audio The top-level element being created 252 */ 253 abstract public void setStoreElementClass(Element audio); 254 255 /** 256 * Utility method to load the individual Audio objects. If there's no 257 * additional info needed for a specific Audio type, invoke this with the 258 * parent of the set of Audio elements. 259 * 260 * @param audio Element containing the Audio elements to load. 261 */ 262 public void loadAudio(Element audio) { 263 264 AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class); 265 266 // Count number of loaded Audio objects 267 int loadedObjects = 0; 268 269 // Load buffers first 270 List<Element> audioList = audio.getChildren("audiobuffer"); 271 log.debug("Found {} Audio Buffer objects", audioList.size()); 272 273 for (Element au : audioList) { 274 275 String sysName = getSystemName(au); 276 if (sysName == null) { 277 log.warn("unexpected null in systemName {} {}", (au), (au).getAttributes()); 278 break; 279 } 280 281 String userName = getUserName(au); 282 log.debug("create Audio: ({})({})", sysName, (userName == null ? "<null>" : userName)); 283 284 try { 285 AudioBuffer ab = (AudioBuffer) am.newAudio(sysName, userName); 286 287 // load common parts 288 loadCommon(ab, au); 289 290 // load sub-type specific parts 291 // Transient objects for reading child elements 292 Element ce; 293 String value; 294 295 if ((ce = au.getChild("url")) != null) { 296 ab.setURL(ce.getValue()); 297 } 298 299 if ((ce = au.getChild("looppoint")) != null) { 300 if ((value = ce.getAttributeValue("start")) != null) { 301 ab.setStartLoopPoint(Integer.parseInt(value)); 302 } 303 if ((value = ce.getAttributeValue("end")) != null) { 304 ab.setEndLoopPoint(Integer.parseInt(value)); 305 } 306 } 307 308 if ((ce = au.getChild("streamed")) != null) { 309 ab.setStreamed(ce.getValue().equals("yes")); 310 } 311 312 } catch (AudioException ex) { 313 log.error("Error loading AudioBuffer ({}): ", sysName, ex); 314 } 315 } 316 loadedObjects += audioList.size(); 317 318 // Now load sources 319 audioList = audio.getChildren("audiosource"); 320 log.debug("Found {} Audio Source objects", audioList.size()); 321 322 for (Element au : audioList) { 323 324 String sysName = getSystemName(au); 325 if (sysName == null) { 326 log.warn("unexpected null in systemName {} {}", (au), (au).getAttributes()); 327 break; 328 } 329 330 String userName = getUserName(au); 331 log.debug("create Audio: ({})({})", sysName, (userName == null ? "<null>" : userName)); 332 333 try { 334 AudioSource as = (AudioSource) am.newAudio(sysName, userName); 335 336 // load common parts 337 loadCommon(as, au); 338 339 // load sub-type specific parts 340 // Transient objects for reading child elements 341 Element ce; 342 String value; 343 344 if ((ce = au.getChild("position")) != null) { 345 as.setPosition( 346 new Vector3f( 347 Float.parseFloat(ce.getAttributeValue("x")), 348 Float.parseFloat(ce.getAttributeValue("y")), 349 Float.parseFloat(ce.getAttributeValue("z")))); 350 } 351 352 if ((ce = au.getChild("velocity")) != null) { 353 as.setVelocity( 354 new Vector3f( 355 Float.parseFloat(ce.getAttributeValue("x")), 356 Float.parseFloat(ce.getAttributeValue("y")), 357 Float.parseFloat(ce.getAttributeValue("z")))); 358 } 359 360 if ((ce = au.getChild("assignedbuffer")) != null) { 361 if (ce.getValue().length() != 0 && !ce.getValue().equals("null")) { 362 as.setAssignedBuffer(ce.getValue()); 363 } 364 } 365 366 if ((ce = au.getChild("gain")) != null && ce.getValue().length() != 0) { 367 as.setGain(Float.parseFloat(ce.getValue())); 368 } 369 370 if ((ce = au.getChild("pitch")) != null && ce.getValue().length() != 0) { 371 as.setPitch(Float.parseFloat(ce.getValue())); 372 } 373 374 if ((ce = au.getChild("distances")) != null) { 375 if ((value = ce.getAttributeValue("ref")) != null) { 376 as.setReferenceDistance(Float.parseFloat(value)); 377 } 378 if ((value = ce.getAttributeValue("max")) != null) { 379 as.setMaximumDistance(Float.parseFloat(value)); 380 } 381 } 382 383 if ((ce = au.getChild("loops")) != null) { 384 if ((value = ce.getAttributeValue("min")) != null) { 385 as.setMinLoops(Integer.parseInt(value)); 386 } 387 if ((value = ce.getAttributeValue("max")) != null) { 388 as.setMaxLoops(Integer.parseInt(value)); 389 } 390// if ((value = ce.getAttributeValue("mindelay"))!=null) 391// as.setMinLoopDelay(Integer.parseInt(value)); 392// if ((value = ce.getAttributeValue("maxdelay"))!=null) 393// as.setMaxLoopDelay(Integer.parseInt(value)); 394 } 395 396 if ((ce = au.getChild("fadetimes")) != null) { 397 if ((value = ce.getAttributeValue("in")) != null) { 398 as.setFadeIn(Integer.parseInt(value)); 399 } 400 if ((value = ce.getAttributeValue("out")) != null) { 401 as.setFadeOut(Integer.parseInt(value)); 402 } 403 } 404 405 if ((ce = au.getChild("positionrelative")) != null) { 406 as.setPositionRelative(ce.getValue().equals("yes")); 407 } 408 409 } catch (AudioException ex) { 410 log.error("Error loading AudioSource ({}): ", sysName, ex); 411 } 412 } 413 loadedObjects += audioList.size(); 414 415 // Finally, load Listeners if needed 416 if (loadedObjects > 0) { 417 audioList = audio.getChildren("audiolistener"); 418 log.debug("Found {} Audio Listener objects", audioList.size()); 419 420 for (Element au : audioList) { 421 422 String sysName = getSystemName(au); 423 if (sysName == null) { 424 log.warn("unexpected null in systemName {} {}", (au), (au).getAttributes()); 425 break; 426 } 427 428 String userName = getUserName(au); 429 log.debug("create Audio: ({})({})", sysName, (userName == null ? "<null>" : userName)); 430 431 try { 432 AudioListener al = (AudioListener) am.newAudio(sysName, userName); 433 434 // load common parts 435 loadCommon(al, au); 436 437 // load sub-type specific parts 438 // Transient object for reading child elements 439 Element ce; 440 441 if ((ce = au.getChild("position")) != null) { 442 al.setPosition( 443 new Vector3f( 444 Float.parseFloat(ce.getAttributeValue("x")), 445 Float.parseFloat(ce.getAttributeValue("y")), 446 Float.parseFloat(ce.getAttributeValue("z")))); 447 } 448 449 if ((ce = au.getChild("velocity")) != null) { 450 al.setVelocity( 451 new Vector3f( 452 Float.parseFloat(ce.getAttributeValue("x")), 453 Float.parseFloat(ce.getAttributeValue("y")), 454 Float.parseFloat(ce.getAttributeValue("z")))); 455 } 456 457 if ((ce = au.getChild("orientation")) != null) { 458 al.setOrientation( 459 new Vector3f( 460 Float.parseFloat(ce.getAttributeValue("atX")), 461 Float.parseFloat(ce.getAttributeValue("atY")), 462 Float.parseFloat(ce.getAttributeValue("atZ"))), 463 new Vector3f( 464 Float.parseFloat(ce.getAttributeValue("upX")), 465 Float.parseFloat(ce.getAttributeValue("upY")), 466 Float.parseFloat(ce.getAttributeValue("upZ")))); 467 } 468 469 if ((ce = au.getChild("gain")) != null) { 470 al.setGain(Float.parseFloat(ce.getValue())); 471 } 472 473 if ((ce = au.getChild("metersperunit")) != null) { 474 al.setMetersPerUnit(Float.parseFloat((ce.getValue()))); 475 } 476 477 } catch (AudioException ex) { 478 log.error("Error loading AudioListener ({}): ", sysName, ex); 479 } 480 } 481 Attribute da = audio.getAttribute("distanceattenuated"); 482 if (da != null) { 483 AudioFactory audioFact = am.getActiveAudioFactory(); 484 if (audioFact != null) { 485 audioFact.setDistanceAttenuated(da.getValue().equals("yes")); 486 } 487 } 488 } 489 } 490 491 @Override 492 public int loadOrder() { 493 return InstanceManager.getDefault(jmri.AudioManager.class).getXMLOrder(); 494 } 495 496 private static final Logger log = LoggerFactory.getLogger(AbstractAudioManagerConfigXML.class); 497 498}