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}