001package jmri.jmrit.audio;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.SortedSet;
005import java.util.TreeSet;
006import javax.sound.sampled.AudioSystem;
007import javax.sound.sampled.Mixer;
008import jmri.Audio;
009import jmri.AudioManager;
010import jmri.InstanceManager;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * This is the JavaSound audio system specific AudioFactory.
016 * <p>
017 * The JavaSound sound system supports, where available, 2-channel stereo.
018 * <p>
019 * The implemented Audio objects provide an approximation of a 3D positionable
020 * audio model through the use of calculated panning and gain based on the 3D
021 * position of the individual sound sources.
022 * <p>
023 * This factory initialises JavaSound, provides new JavaSound-specific Audio
024 * objects and deals with clean-up operations.
025 * <p>
026 * For more information about the JavaSound API, visit
027 * <a href="http://java.sun.com/products/java-media/sound/">http://java.sun.com/products/java-media/sound/</a>
028 *
029 * <hr>
030 * This file is part of JMRI.
031 * <p>
032 * JMRI is free software; you can redistribute it and/or modify it under the
033 * terms of version 2 of the GNU General Public License as published by the Free
034 * Software Foundation. See the "COPYING" file for a copy of this license.
035 * <p>
036 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
037 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
038 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
039 *
040 * @author Matthew Harris copyright (c) 2009
041 */
042public class JavaSoundAudioFactory extends AbstractAudioFactory {
043
044    private static boolean initialised = false;
045
046    private volatile static Mixer mixer;
047
048    private JavaSoundAudioListener activeAudioListener;
049
050    @Override
051    public boolean init() {
052        if (initialised) {
053            return true;
054        }
055
056        // Initialise JavaSound
057        if (JavaSoundAudioFactory.mixer == null) {
058            // Iterate through possible mixers until we find the one we require
059            for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
060                if (mixerInfo.getName().equals("Java Sound Audio Engine")) {
061                    JavaSoundAudioFactory.setMixer(AudioSystem.getMixer(mixerInfo));
062                    break;
063                }
064            }
065        }
066        // Check to see if a suitable mixer has been found
067        if (JavaSoundAudioFactory.mixer == null) {
068            log.debug("No JavaSound audio system found.");
069            return false;
070        } else {
071            if (log.isInfoEnabled()) {
072                log.info("Initialised JavaSound: vendor - {} version - {}", JavaSoundAudioFactory.mixer.getMixerInfo().getVendor(), JavaSoundAudioFactory.mixer.getMixerInfo().getVersion());
073            }
074        }
075
076        super.init();
077        setInit(true);
078        return true;
079    }
080
081    private synchronized static void setInit(boolean newVal) {
082        initialised = newVal;
083    }
084
085    private synchronized static void setMixer(Mixer newMixer) {
086        mixer = newMixer;
087    }
088
089    @Override
090    public String toString() {
091        return "JavaSoundAudioFactory:"
092                + " vendor - " + JavaSoundAudioFactory.mixer.getMixerInfo().getVendor()
093                + " version - " + JavaSoundAudioFactory.mixer.getMixerInfo().getVersion();
094    }
095
096    @Override
097    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
098            justification = "OK to write to static variable mixer as we are cleaning up")
099    public void cleanup() {
100        // Stop the command thread
101        super.cleanup();
102
103        // Get the active AudioManager
104        AudioManager am = InstanceManager.getDefault(jmri.AudioManager.class);
105
106        // Retrieve list of AudioSource objects and remove the sources
107        SortedSet<Audio> sources = new TreeSet<>(am.getNamedBeanSet(Audio.SOURCE));
108        for (Audio source: sources) {
109            if (log.isDebugEnabled()) {
110                log.debug("Removing JavaSoundAudioSource: {}", source.getSystemName());
111            }
112            // Includes cleanup
113            source.dispose();
114        }
115
116        // Now, retrieve list of AudioBuffer objects and remove the buffers
117        SortedSet<Audio> buffers = new TreeSet<>(am.getNamedBeanSet(Audio.BUFFER));
118        for (Audio buffer : buffers) {
119            if (log.isDebugEnabled()) {
120                log.debug("Removing JavaSoundAudioBuffer: {}", buffer.getSystemName());
121            }
122            // Includes cleanup
123            buffer.dispose();
124        }
125
126        // Lastly, retrieve list of AudioListener objects and remove listener.
127        SortedSet<Audio> listeners = new TreeSet<>(am.getNamedBeanSet(Audio.LISTENER));
128        for (Audio listener : listeners) {
129            if (log.isDebugEnabled()) {
130                log.debug("Removing JavaSoundAudioListener: {}", listener.getSystemName());
131            }
132            // Includes cleanup
133            listener.dispose();
134        }
135
136        // Finally, shutdown JavaSound and close the output device
137        log.debug("Shutting down JavaSound");
138        mixer = null;
139        initialised = false;
140    }
141
142    @Override
143    public boolean isInitialised() {
144        return initialised;
145    }
146
147    @Override
148    public AudioBuffer createNewBuffer(String systemName, String userName) {
149        return new JavaSoundAudioBuffer(systemName, userName);
150    }
151
152    @Override
153    public AudioListener createNewListener(String systemName, String userName) {
154        activeAudioListener = new JavaSoundAudioListener(systemName, userName);
155        return activeAudioListener;
156    }
157
158    @Override
159    public AudioListener getActiveAudioListener() {
160        return activeAudioListener;
161    }
162
163    @Override
164    public AudioSource createNewSource(String systemName, String userName) {
165        return new JavaSoundAudioSource(systemName, userName);
166    }
167
168    /**
169     * Return reference to the current JavaSound mixer object.
170     *
171     * @return current JavaSound mixer
172     */
173    public static synchronized Mixer getMixer() {
174        return mixer;
175    }
176
177    private static final Logger log = LoggerFactory.getLogger(JavaSoundAudioFactory.class);
178
179}