001package jmri.jmrit.audio;
002
003import java.util.Collections;
004import java.util.SortedSet;
005import java.util.TreeSet;
006import javax.annotation.Nonnull;
007
008import jmri.Audio;
009import jmri.AudioException;
010import jmri.InstanceManager;
011import jmri.jmrix.internal.InternalSystemConnectionMemo;
012import jmri.managers.AbstractAudioManager;
013
014/**
015 * Provide the concrete implementation for the Internal Audio Manager.
016 *
017 * <hr>
018 * This file is part of JMRI.
019 * <p>
020 * JMRI is free software; you can redistribute it and/or modify it under the
021 * terms of version 2 of the GNU General Public License as published by the Free
022 * Software Foundation. See the "COPYING" file for a copy of this license.
023 * <p>
024 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
025 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
026 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
027 *
028 * @author Matthew Harris copyright (c) 2009
029 */
030public class DefaultAudioManager extends AbstractAudioManager {
031
032    private static int countListeners = 0;
033    private static int countSources = 0;
034    private static int countBuffers = 0;
035
036    public DefaultAudioManager(InternalSystemConnectionMemo memo) {
037        super(memo);
038    }
039
040    /**
041     * Reference to the currently active AudioFactory.
042     * Because of underlying (external to Java) implementation details,
043     * JMRI only ever has one AudioFactory, so we make this static.
044     */
045    private static AudioFactory activeAudioFactory = null;
046
047    private synchronized static void setActiveAudioFactory(AudioFactory factory){
048        activeAudioFactory = factory;
049    }
050
051    private static boolean initialised = false;
052
053    private final TreeSet<Audio> listeners = new TreeSet<>(new jmri.util.NamedBeanComparator<>());
054    private final TreeSet<Audio> buffers = new TreeSet<>(new jmri.util.NamedBeanComparator<>());
055    private final TreeSet<Audio> sources = new TreeSet<>(new jmri.util.NamedBeanComparator<>());
056
057    public final Runnable audioShutDownTask = this::cleanup;
058
059    @Override
060    public int getXMLOrder() {
061        return jmri.Manager.AUDIO;
062    }
063
064    @Override
065    protected synchronized Audio createNewAudio(@Nonnull String systemName, String userName) throws AudioException {
066
067        if (activeAudioFactory == null) {
068            log.debug("Initialise in createNewAudio");
069            init();
070        }
071
072        Audio a = null;
073
074        log.debug("sysName: {} userName: {}", systemName, userName);
075        if (userName != null && _tuser.containsKey(userName)) {
076            throw new AudioException("Duplicate name");
077        }
078
079        switch (systemName.charAt(2)) {
080
081            case Audio.BUFFER: {
082                if (countBuffers >= MAX_BUFFERS) {
083                    log.error("Maximum number of buffers reached ({}) " + MAX_BUFFERS, countBuffers);
084                    throw new AudioException("Maximum number of buffers reached (" + countBuffers + ") " + MAX_BUFFERS);
085                }
086                DefaultAudioManager.setCountBuffers(DefaultAudioManager.countBuffers+1);
087                a = activeAudioFactory.createNewBuffer(systemName, userName);
088                buffers.add(a);
089                break;
090            }
091            case Audio.LISTENER: {
092                if (countListeners >= MAX_LISTENERS) {
093                    log.error("Maximum number of Listeners reached ({}) " + MAX_LISTENERS, countListeners);
094                    throw new AudioException("Maximum number of Listeners reached (" + countListeners + ") " + MAX_LISTENERS);
095                }
096                DefaultAudioManager.setCountListeners(DefaultAudioManager.countListeners+1);
097                a = activeAudioFactory.createNewListener(systemName, userName);
098                listeners.add(a);
099                break;
100            }
101            case Audio.SOURCE: {
102                if (countSources >= MAX_SOURCES) {
103                    log.error("Maximum number of Sources reached ({}) " + MAX_SOURCES, countSources);
104                    throw new AudioException("Maximum number of Sources reached (" + countSources + ") " + MAX_SOURCES);
105                }
106                DefaultAudioManager.setCountSources(DefaultAudioManager.countSources+1);
107                a = activeAudioFactory.createNewSource(systemName, userName);
108                sources.add(a);
109                break;
110            }
111            default:
112                throw new IllegalArgumentException();
113        }
114
115        return a;
116    }
117
118    /** {@inheritDoc} */
119    @Override
120    @Nonnull
121    public SortedSet<Audio> getNamedBeanSet(char subType) {
122        switch (subType) {
123            case Audio.BUFFER: {
124                return Collections.unmodifiableSortedSet(buffers);
125            }
126            case Audio.LISTENER: {
127                return Collections.unmodifiableSortedSet(listeners);
128            }
129            case Audio.SOURCE: {
130                return Collections.unmodifiableSortedSet(sources);
131            }
132            default: {
133                throw new IllegalArgumentException();
134            }
135        }
136    }
137
138    /**
139     * Attempt to create and initialise an AudioFactory, working
140     * down a preference hierarchy. Result is in activeAudioFactory.
141     * Uses null implementation to always succeed
142     */
143    private void createFactory() {
144        // was a specific implementation requested?
145        // define as jmri.jmrit.audio.NullAudioFactory to get headless CI form in testing
146        String className = System.getProperty("jmri.jmrit.audio.DefaultAudioManager.implementation");
147        // if present, determines the active factory class
148        if (className != null) {
149            log.debug("Try to initialise {} from property", className);
150            try {
151                Class<?> c = Class.forName(className);
152                if (AudioFactory.class.isAssignableFrom(c)) {
153                    activeAudioFactory = (AudioFactory) c.getConstructor().newInstance();
154                    if (activeAudioFactory.init()) {
155                        // all OK
156                        return;
157                    } else {
158                        log.error("Specified jmri.jmrit.audio.DefaultAudioManager.implementation value {} did not initialize, continuing", className);
159                    }
160                } else {
161                    log.error("Specified jmri.jmrit.audio.DefaultAudioManager.implementation value {} is not a jmri.AudioFactory subclass, continuing", className);
162                }
163            } catch (
164                    ClassNotFoundException |
165                    InstantiationException |
166                    IllegalAccessException |
167                    java.lang.reflect.InvocationTargetException |
168                    NoSuchMethodException |
169                    SecurityException e) {
170                log.error("Unable to instantiate AudioFactory class {} with default constructor", className);
171                // and proceed to fallback choices
172            }
173        }
174
175//      // Try to initialise LWJGL
176//      log.debug("Try to initialise LWJGLAudioFactory");
177//      activeAudioFactory = new LWJGLAudioFactory();
178//      if (activeAudioFactory.init()) return;
179//
180        // Next try JOAL
181        log.debug("Try to initialise JoalAudioFactory");
182        DefaultAudioManager.setActiveAudioFactory( new JoalAudioFactory());
183        if (DefaultAudioManager.activeAudioFactory.init()) return;
184
185        // fall-back to JavaSound
186        log.debug("Try to initialise JavaSoundAudioFactory");
187        DefaultAudioManager.setActiveAudioFactory( new JavaSoundAudioFactory());
188        if (DefaultAudioManager.activeAudioFactory.init()) return;
189
190        // Finally, if JavaSound fails, fall-back to a Null sound system
191        log.debug("Try to initialise NullAudioFactory");
192        DefaultAudioManager.setActiveAudioFactory( new NullAudioFactory());
193        DefaultAudioManager.activeAudioFactory.init();
194        // assumed to succeed.
195    }
196
197    /**
198     * Initialise the manager and make connections.
199     */
200    @Override
201    public synchronized void init() {
202        if (!initialised) {
203
204            // create Factory of appropriate type
205            createFactory();
206
207            // Create default Listener and save in map
208            try {
209                Audio s = createNewAudio("IAL$", "Default Audio Listener");
210                register(s);
211            } catch (AudioException ex) {
212                log.error("Error creating Default Audio Listener", ex);
213            }
214
215            // Register a shutdown task to ensure clean exit
216            InstanceManager.getDefault(jmri.ShutDownManager.class).register(audioShutDownTask);
217
218            DefaultAudioManager.setInitialised(true);
219            log.debug("Initialised AudioFactory type: {}", activeAudioFactory.getClass().getSimpleName());
220
221        }
222    }
223
224    /**
225     * {@inheritDoc}
226     */
227    @Override
228    public boolean isInitialised() {
229        return initialised;
230    }
231
232    @Override
233    public synchronized void deregister(@Nonnull Audio s) {
234        // Decrement the relevant Audio object counter
235        switch (s.getSubType()) {
236            case (Audio.BUFFER): {
237                buffers.remove(s);
238                DefaultAudioManager.setCountBuffers(DefaultAudioManager.countBuffers-1);
239                log.debug("Remove buffer; count: {}", countBuffers);
240                break;
241            }
242            case (Audio.SOURCE): {
243                sources.remove(s);
244                DefaultAudioManager.setCountSources(DefaultAudioManager.countSources-1);
245                log.debug("Remove source; count: {}", countSources);
246                break;
247            }
248            case (Audio.LISTENER): {
249                listeners.remove(s);
250                DefaultAudioManager.setCountListeners(DefaultAudioManager.countListeners-1);
251                log.debug("Remove listener; count: {}", countListeners);
252                break;
253            }
254            default:
255                throw new IllegalArgumentException();
256        }
257        super.deregister(s);
258    }
259
260    @Override
261    public void cleanup() {
262        // Shutdown AudioFactory and close the output device
263        log.info("Shutting down active AudioFactory");
264        InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(audioShutDownTask);
265        if (activeAudioFactory != null) activeAudioFactory.cleanup();
266        // Reset counters
267        DefaultAudioManager.setCountBuffers(0);
268        DefaultAudioManager.setCountSources(0);
269        DefaultAudioManager.setCountListeners(0);
270        // Record that we're no longer initialised
271        DefaultAudioManager.setInitialised(false);
272    }
273
274    private static void setCountBuffers(int newCount){
275        countBuffers = newCount;
276    }
277
278    private static void setCountSources(int newCount){
279        countSources = newCount;
280    }
281
282    private static void setCountListeners(int newCount){
283        countListeners = newCount;
284    }
285
286    private static void setInitialised(boolean newValue) {
287        initialised = newValue;
288    }
289
290    @Override
291    public void dispose() {
292        buffers.clear();
293        sources.clear();
294        listeners.clear();
295        super.dispose();
296    }
297
298    @Override
299    public AudioFactory getActiveAudioFactory() {
300        return activeAudioFactory;
301    }
302
303    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultAudioManager.class);
304
305}