001package jmri.jmrit.vsdecoder; 002 003import jmri.AudioException; 004import jmri.AudioManager; 005import jmri.jmrit.audio.AudioBuffer; 006import jmri.jmrit.audio.AudioSource; 007import jmri.util.PhysicalLocation; 008 009/** 010 * VSD implementation of an audio sound. 011 * 012 * <hr> 013 * This file is part of JMRI. 014 * <p> 015 * JMRI is free software; you can redistribute it and/or modify it under 016 * the terms of version 2 of the GNU General Public License as published 017 * by the Free Software Foundation. See the "COPYING" file for a copy 018 * of this license. 019 * <p> 020 * JMRI is distributed in the hope that it will be useful, but WITHOUT 021 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 022 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 023 * for more details. 024 * 025 * @author Mark Underwood Copyright (C) 2011 026 * @author Klaus Killinger Copyright (C) 2025 027 */ 028class SoundBite extends VSDSound { 029 030 private static enum BufferMode { 031 032 BOUND_MODE, QUEUE_MODE 033 } 034 035 private String filename, system_name, user_name; 036 private AudioBuffer sound_buf; 037 private AudioSource sound_src; 038 private boolean initialized; 039 private float rd; 040 private long length; 041 private BufferMode bufferMode; 042 private VSDFile vsdfile; 043 044 // Constructor for QUEUE_MODE. 045 public SoundBite(String name) { 046 super(name); 047 system_name = name; 048 user_name = name; 049 bufferMode = BufferMode.QUEUE_MODE; 050 initialized = false; 051 } 052 053 // Constructor for BOUND_MODE. 054 public SoundBite(VSDFile vf, String filename, String sname, String uname) { 055 super(uname); 056 vsdfile = vf; 057 this.filename = filename; 058 system_name = sname; 059 user_name = uname; 060 bufferMode = BufferMode.BOUND_MODE; 061 initialized = false; 062 } 063 064 public String getFileName() { 065 return filename; 066 } 067 068 public String getSystemName() { 069 return system_name; 070 } 071 072 public String getUserName() { 073 return user_name; 074 } 075 076 final boolean isInitialized() { 077 AudioManager am = jmri.InstanceManager.getDefault(jmri.AudioManager.class); 078 if (!initialized) { 079 try { 080 sound_src = (AudioSource) am.provideAudio(SrcSysNamePrefix + system_name); 081 sound_src.setUserName(SrcUserNamePrefix + user_name); 082 setLooped(false); 083 if (bufferMode == BufferMode.BOUND_MODE) { 084 if (checkForFreeBuffer()) { 085 sound_buf = (AudioBuffer) am.provideAudio(BufSysNamePrefix + system_name); 086 sound_buf.setUserName(BufUserNamePrefix + user_name); 087 if (vsdfile == null) { 088 log.debug("No VSD File! Filename: {}", filename); 089 sound_buf.setURL(filename); // Path must be provided by caller. 090 } else { 091 java.io.InputStream ins = vsdfile.getInputStream(filename); 092 if (ins != null) { 093 sound_buf.setInputStream(ins); 094 } else { 095 return false; 096 } 097 } 098 sound_src.setAssignedBuffer(sound_buf); 099 setLength(); 100 } else { 101 return false; 102 } 103 } 104 } catch (AudioException | IllegalArgumentException ex) { 105 log.warn("Problem creating SoundBite", ex); 106 } 107 } 108 return true; 109 } 110 111 public void queueBuffer(AudioBuffer b) { 112 if (bufferMode == BufferMode.QUEUE_MODE) { 113 if (b == null) { 114 log.debug("queueAudioBuffer with null buffer input"); 115 return; 116 } 117 if (sound_src == null) { 118 log.debug("queueAudioBuffer with null sound_src"); 119 return; 120 } 121 log.debug("Queueing Buffer: {}", b.getSystemName()); 122 sound_src.queueBuffer(b); 123 } else { 124 log.warn("Attempted to Queue buffer to a Bound SoundBite."); 125 } 126 } 127 128 public void unqueueBuffers() { 129 if (bufferMode == BufferMode.QUEUE_MODE) { 130 sound_src.unqueueBuffers(); 131 } 132 } 133 134 public int numQueuedBuffers() { 135 if (bufferMode == BufferMode.QUEUE_MODE) { 136 return sound_src.numQueuedBuffers(); 137 } else { 138 return 0; 139 } 140 } 141 142 // Direct access to the underlying source. use with caution. 143 public AudioSource getSource() { 144 return sound_src; 145 } 146 147 // WARNING: This will go away when we go to shared buffers... or at least it will 148 // have to do the name lookup on behalf of the caller... 149 public AudioBuffer getBuffer() { 150 return sound_buf; 151 } 152 153 // These can(?) be used to get the underlying AudioSource and AudioBuffer objects 154 // from the DefaultAudioManager. 155 public String getSourceSystemName() { 156 return SrcSysNamePrefix + system_name; 157 } 158 159 public String getSourceUserName() { 160 return SrcUserNamePrefix + user_name; 161 } 162 163 public String getBufferSystemName() { 164 return BufSysNamePrefix + system_name; 165 } 166 167 public String getBufferUserName() { 168 return BufUserNamePrefix + user_name; 169 } 170 171 public void setLooped(boolean loop) { 172 sound_src.setLooped(loop); 173 } 174 175 public int getFadeInTime() { 176 return sound_src.getFadeIn(); 177 } 178 179 public int getFadeOutTime() { 180 return sound_src.getFadeOut(); 181 } 182 183 public void setFadeInTime(int t) { 184 sound_src.setFadeIn(t); 185 } 186 187 public void setFadeOutTime(int t) { 188 sound_src.setFadeOut(t); 189 } 190 191 public void setFadeTimes(int in, int out) { 192 sound_src.setFadeIn(in); 193 sound_src.setFadeOut(out); 194 } 195 196 public float getReferenceDistance() { 197 return sound_src.getReferenceDistance(); 198 } 199 200 public void setReferenceDistance(float r) { 201 this.rd = r; 202 sound_src.setReferenceDistance(rd); 203 } 204 205 @Override 206 public void shutdown() { 207 } 208 209 @Override 210 public void mute(boolean m) { 211 if (m) { 212 volume = sound_src.getGain(); 213 sound_src.setGain(0); 214 } else { 215 sound_src.setGain(volume); 216 } 217 } 218 219 @Override 220 public void setVolume(float v) { 221 volume = v * gain; 222 sound_src.setGain(volume); 223 } 224 225 @Override 226 public void play() { 227 sound_src.play(); 228 } 229 230 @Override 231 public void loop() { 232 sound_src.play(); 233 } 234 235 @Override 236 public void stop() { 237 sound_src.stop(); 238 } 239 240 public void pause() { 241 sound_src.pause(); 242 } 243 244 public void rewind() { 245 sound_src.rewind(); 246 } 247 248 @Override 249 public void fadeOut() { 250 // Skip the fade action if the fade out time is zero. 251 if (sound_src.getFadeOut() == 0) { 252 sound_src.stop(); 253 } else { 254 sound_src.fadeOut(); 255 } 256 } 257 258 @Override 259 public void fadeIn() { 260 // Skip the fade action if the fade in time is zero. 261 if (sound_src.getFadeIn() == 0) { 262 sound_src.play(); 263 } else { 264 sound_src.fadeIn(); 265 } 266 } 267 268 @Override 269 public void setPosition(PhysicalLocation v) { 270 super.setPosition(v); 271 sound_src.setPosition(v); 272 } 273 274 /** 275 * Method to attach a source to effects 276 * 277 * @return 0 when failed, 1 when successful 278 */ 279 @Override 280 int attachSourcesToEffects() { 281 return sound_src.attachSourcesToEffects(); 282 } 283 284 /** 285 * Method to detach a source to effects 286 * 287 * @return 0 when failed, 1 when successful 288 */ 289 @Override 290 int detachSourcesToEffects() { 291 return sound_src.detachSourcesToEffects(); 292 } 293 294 public void setURL(String filename) { 295 this.filename = filename; 296 sound_buf.setURL(filename); // Path must be provided by caller. 297 } 298 299 public long getLength() { 300 return length; 301 } 302 303 public int getLengthAsInt() { 304 // Note: this only works for positive lengths... 305 // Timer only takes an int... cap the length at MAXINT 306 if (length > Integer.MAX_VALUE) { 307 return Integer.MAX_VALUE; 308 } else { // small enough to safely cast. 309 return (int) length; 310 } 311 } 312 313 public void setLength(long p) { 314 length = p; 315 } 316 317 public void setLength() { 318 length = calcLength(this); 319 } 320 321 public static long calcLength(SoundBite s) { 322 return calcLength(s.getBuffer()); 323 } 324 325 public static long calcLength(AudioBuffer buf) { 326 // Assumes later getBuffer() will find the buffer from AudioManager instead 327 // of the current local reference... that's why I'm not directly using sound_buf here. 328 329 // Required buffer functions not yet implemented 330 long num_frames; 331 int frequency; 332 333 if (buf != null) { 334 num_frames = buf.getLength(); 335 frequency = buf.getFrequency(); 336 } else { 337 // No buffer attached! 338 num_frames = 0; 339 frequency = 0; 340 } 341 342 /* 343 long num_frames = 1; 344 long frequency = 125; 345 */ 346 if (frequency <= 0) { 347 // Protect against divide-by-zero errors 348 return 0L; 349 } else { 350 return (1000 * num_frames) / frequency; 351 } 352 } 353 354 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SoundBite.class); 355}