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