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