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}