Java tutorial
package jmri.jmrit.vsdecoder; /* * <hr> * This file is part of JMRI. * <P> * JMRI is free software; you can redistribute it and/or modify it under * the terms of version 2 of the GNU General Public License as published * by the Free Software Foundation. See the "COPYING" file for a copy * of this license. * <P> * JMRI is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * <P> * * @author Mark Underwood Copyright (C) 2011 * @version $Revision: 18481 $ */ import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.swing.Timer; import jmri.Audio; import jmri.AudioException; import jmri.AudioManager; import jmri.jmrit.audio.AudioBuffer; import jmri.util.PhysicalLocation; import org.jdom2.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // Usage: // EngineSound() : constructor // play() : plays short horn pop // loop() : starts extended sustain horn // stop() : ends extended sustain horn (plays end sound) // Suppressing "unused" warnings throughout. There are a dozen // methods, ctors and values that aren't used and don't have // outside access. @SuppressWarnings("unused") class Diesel3Sound extends EngineSound { // Engine Sounds HashMap<Integer, D3Notch> notch_sounds; SoundBite _sound; String _soundName; AudioBuffer start_buffer; AudioBuffer stop_buffer; AudioBuffer transition_buf; // Common variables Float throttle_setting; // used for handling speed changes EnginePane engine_pane; int current_notch = 1; boolean changing_speed = false; boolean is_looping = false; D3LoopThread _loopThread = null; Timer t; public Diesel3Sound(String name) { super(name); //_loopThread = new D3LoopThread(this); //_loopThread.start(); log.debug("New Diesel3Sound name(param) = " + name + " name(val) " + this.getName()); } private void startThread() { _loopThread = new D3LoopThread(this, notch_sounds.get(current_notch), _soundName, true); log.debug("Loop Thread Started. Sound name = " + _soundName); } // Note: Play and Loop do the same thing, since all of the notch sounds are set to loop. @Override public void play() { this.loop(); } // Note: Play and Loop do the same thing, since all of the notch sounds are set to loop. @Override public void loop() { if (notch_sounds.containsKey(current_notch) && (engine_started || auto_start_engine)) { // Really, nothing to do here. The engine should be started/stopped through // the startEngine() and stopEngine() calls. Maybe should clean that up or have // play() == loop() == startEngine() and stop == stopEngine() some time. if ((_loopThread != null) && (_loopThread.isRunning())) { _loopThread.setRunning(true); } } } @Override public void stop() { // Stop the loop thread, in case it's running if (_loopThread != null) { _loopThread.setRunning(false); } is_looping = false; } @Override public void handleSpeedChange(Float s, EnginePane e) { //log.debug("Handling SpeedSetting event. speed = " + s); throttle_setting = s; if (!changing_speed) { changeSpeed(s, e); } } // Responds to "CHANGE" trigger public void changeThrottle(float s) { // This is all we have to do. The loop thread will handle everything else. if (_loopThread != null) { _loopThread.setThrottle(s); } } protected void changeSpeed(Float s, EnginePane e) { engine_pane = e; // this should probably be cleaned up. It's here for the recursion. changeThrottle(s); } public D3Notch getNotch(int n) { return (notch_sounds.get(n)); } @Override public void startEngine() { log.debug("startEngine. ID = " + this.getName()); //_loopThread = new D3LoopThread(this, notch_sounds.get(current_notch), _soundName, true); _loopThread.startEngine(start_buffer); } @Override public void stopEngine() { log.debug("stopEngine. ID = " + this.getName()); if (_loopThread != null) { _loopThread.stopEngine(stop_buffer); } } @Override public void shutdown() { this.stop(); } @Override public void mute(boolean m) { if (_loopThread != null) { _loopThread.mute(m); } } @Override public void setVolume(float v) { if (_loopThread != null) { _loopThread.setVolume(v); } } @Override public void setPosition(PhysicalLocation p) { if (_loopThread != null) { _loopThread.setPosition(p); } } protected Timer newTimer(long time, boolean repeat, ActionListener al) { time = Math.max(1, time); // make sure the time is > zero t = new Timer((int) time, al); t.setInitialDelay((int) time); t.setRepeats(repeat); return (t); } @Override public Element getXml() { Element me = new Element("sound"); me.setAttribute("name", this.getName()); me.setAttribute("type", "engine"); // Do something, eventually... return (me); } @Override public void setXml(Element e, VSDFile vf) { Element el; String fn; D3Notch sb; // Handle the common stuff. super.setXml(e, vf); //log.debug("Diesel EngineSound: " + e.getAttribute("name").getValue()); _soundName = this.getName() + ":LoopSound"; log.debug("Diesel3: name: " + this.getName() + " soundName " + _soundName); notch_sounds = new HashMap<Integer, D3Notch>(); String in = e.getChildText("idle-notch"); Integer idle_notch = null; if (in != null) { idle_notch = Integer.parseInt(in); } else { // leave idle_notch null for now. We'll use it at the end to trigger a "grandfathering" action log.warn("No Idle Notch Specified!"); } // Get the notch sounds Iterator<Element> itr = (e.getChildren("notch-sound")).iterator(); int i = 0; while (itr.hasNext()) { el = itr.next(); sb = new D3Notch(); int nn = Integer.parseInt(el.getChildText("notch")); sb.setNotch(nn); if ((idle_notch != null) && (nn == idle_notch)) { sb.setIdleNotch(true); log.debug("This Notch (" + nn + ") is Idle."); } List<Element> elist = el.getChildren("file"); int j = 0; for (Element fe : elist) { fn = fe.getText(); //AudioBuffer b = D3Notch.getBuffer(vf, fn, "Engine_n" + i + "_" + j, "Engine_" + i + "_" + j); //log.debug("Buffer created: " + b + " name: " + b.getSystemName()); //sb.addLoopBuffer(b); List<AudioBuffer> l = D3Notch.getBufferList(vf, fn, "Engine_n" + i + "_" + j, "Engine_" + i + "_" + j); log.debug("Buffers Created: "); for (AudioBuffer b : l) { log.debug("\tSubBuffer: " + b.getSystemName()); } sb.addLoopBuffers(l); j++; } //log.debug("Notch: " + nn + " File: " + fn); // Gain is broken, for the moment. Buffers don't have gain. Sources do. //_sound.setGain(setXMLGain(el)); //_sound.setGain(default_gain); sb.setNextNotch(el.getChildText("next-notch")); sb.setPrevNotch(el.getChildText("prev-notch")); sb.setAccelLimit(el.getChildText("accel-limit")); sb.setDecelLimit(el.getChildText("decel-limit")); if (el.getChildText("accel-file") != null) { sb.setAccelBuffer( D3Notch.getBuffer(vf, el.getChildText("accel-file"), "Engine_na" + i, "Engine_na" + i)); } else { sb.setAccelBuffer(null); } if (el.getChildText("decel-file") != null) { sb.setDecelBuffer( D3Notch.getBuffer(vf, el.getChildText("decel-file"), "Engine_nd" + i, "Engine_nd" + i)); } else { sb.setDecelBuffer(null); } // Store in the list. notch_sounds.put(nn, sb); i++; } // Get the start and stop sounds el = e.getChild("start-sound"); if (el != null) { fn = el.getChild("file").getValue(); //log.debug("Start sound: " + fn); start_buffer = D3Notch.getBuffer(vf, fn, "Engine_start", "Engine_Start"); } el = e.getChild("shutdown-sound"); if (el != null) { fn = el.getChild("file").getValue(); //log.debug("Shutdown sound: " + fn); stop_buffer = D3Notch.getBuffer(vf, fn, "Engine_shutdown", "Engine_Shutdown"); } // Handle "grandfathering the idle notch indication // If the VSD designer did not explicitly designate an idle notch... // Find the Notch with the lowest notch number, and make it the idle notch. // If there's a tie, this will take the first value, but the notches /should/ // all have unique notch numbers. if (idle_notch == null) { D3Notch min_notch = null; // No, this is not a terribly efficient "min" operation. But that's OK. for (D3Notch n : notch_sounds.values()) { if ((min_notch == null) || (n.getNotch() < min_notch.getNotch())) { min_notch = n; } } log.debug("No Idle Notch Specified. Choosing Notch (" + (min_notch != null ? min_notch.getNotch() : "min_notch not set") + ") to be the Idle Notch."); if (min_notch != null) { min_notch.setIdleNotch(true); } else { log.warn("Could not set idle notch because min_notch was still null"); } } // Kick-start the loop thread. this.startThread(); } private static final Logger log = LoggerFactory.getLogger(Diesel3Sound.class.getName()); private static class D3Notch { private AudioBuffer accel_buf; private AudioBuffer decel_buf; private int my_notch, next_notch, prev_notch; private float accel_limit, decel_limit; private int loop_index; private List<AudioBuffer> loop_bufs = new ArrayList<AudioBuffer>(); private Boolean is_idle; public D3Notch() { this(1, 1, 1, null, null, null); } public D3Notch(int notch, int next, int prev) { this(notch, next, prev, null, null, null); } public D3Notch(int notch, int next, int prev, AudioBuffer accel, AudioBuffer decel, List<AudioBuffer> loop) { my_notch = notch; next_notch = next; prev_notch = prev; accel_buf = accel; decel_buf = decel; if (loop != null) { loop_bufs = loop; } loop_index = 0; } public int getNextNotch() { return (next_notch); } public int getPrevNotch() { return (prev_notch); } public int getNotch() { return (my_notch); } public AudioBuffer getAccelBuffer() { return (accel_buf); } public AudioBuffer getDecelBuffer() { return (decel_buf); } public float getAccelLimit() { return (accel_limit); } public float getDecelLimit() { return (decel_limit); } public Boolean isInLimits(float val) { return ((val >= decel_limit) && (val <= accel_limit)); } public List<AudioBuffer> getLoopBuffers() { return (loop_bufs); } public AudioBuffer getLoopBuffer(int idx) { return (loop_bufs.get(idx)); } public long getLoopBufferLength(int idx) { return (SoundBite.calcLength(loop_bufs.get(idx))); } public Boolean isIdleNotch() { return (is_idle); } public void setNextNotch(int n) { next_notch = n; } public void setNextNotch(String s) { next_notch = setIntegerFromString(s); } public void setPrevNotch(int p) { prev_notch = p; } public void setPrevNotch(String s) { prev_notch = setIntegerFromString(s); } public void setAccelLimit(float l) { accel_limit = l; } public void setAccelLimit(String s) { accel_limit = setFloatFromString(s); } public void setDecelLimit(float l) { decel_limit = l; } public void setDecelLimit(String s) { decel_limit = setFloatFromString(s); } public void setNotch(int n) { my_notch = n; } public void setAccelBuffer(AudioBuffer b) { accel_buf = b; } public void setDecelBuffer(AudioBuffer b) { decel_buf = b; } public void addLoopBuffer(AudioBuffer b) { loop_bufs.add(b); } public void addLoopBuffers(List<AudioBuffer> l) { loop_bufs.addAll(l); } public void setLoopBuffers(List<AudioBuffer> l) { loop_bufs = l; } public void clearLoopBuffers() { loop_bufs.clear(); } public AudioBuffer nextLoopBuffer() { return (loop_bufs.get(incLoopIndex())); } public void setIdleNotch(Boolean i) { is_idle = i; } public int loopIndex() { return (loop_index); } public int incLoopIndex() { // Increment loop_index++; // Correct for wrap. if (loop_index >= loop_bufs.size()) { loop_index = 0; } return (loop_index); } private int setIntegerFromString(String s) { if (s == null) { return (0); } try { int n = Integer.parseInt(s); return (n); } catch (NumberFormatException e) { log.debug("Invalid integer: " + s); return (0); } } private float setFloatFromString(String s) { if (s == null) { return (0.0f); } try { float f = Float.parseFloat(s) / 100.0f; return (f); } catch (NumberFormatException e) { log.debug("Invalid float: " + s); return (0.0f); } } static public List<AudioBuffer> getBufferList(VSDFile vf, String filename, String sname, String uname) { List<AudioBuffer> buflist = null; if (vf == null) { // Need to fix this. //buf.setURL(vsd_file_base + filename); log.debug("No VSD File"); return (null); } else { java.io.InputStream ins = vf.getInputStream(filename); if (ins != null) { //buflist = AudioUtil.getSplitInputStream(VSDSound.BufSysNamePrefix+filename, ins, 250, 100); buflist = AudioUtil.getAudioBufferList(VSDSound.BufSysNamePrefix + filename, ins, 250, 100); } else { log.debug("Input Stream failed"); return (null); } return (buflist); } } static public AudioBuffer getBuffer(VSDFile vf, String filename, String sname, String uname) { AudioBuffer buf = null; AudioManager am = jmri.InstanceManager.audioManagerInstance(); try { buf = (AudioBuffer) am.provideAudio(VSDSound.BufSysNamePrefix + filename); buf.setUserName(VSDSound.BufUserNamePrefix + uname); if (vf == null) { // Need to fix this. //buf.setURL(vsd_file_base + filename); log.debug("No VSD File"); return (null); } else { java.io.InputStream ins = vf.getInputStream(filename); if (ins != null) { buf.setInputStream(ins); } else { log.debug("Input Stream failed"); return (null); } } } catch (AudioException ex) { log.error("Problem creating SoundBite: " + ex); return (null); } log.debug("Buffer created: " + buf + " name: " + buf.getSystemName()); return (buf); } private static final Logger log = LoggerFactory.getLogger(D3Notch.class.getName()); } private static class D3LoopThread extends Thread { private boolean is_running = false; private boolean is_looping = false; private boolean is_dying = false; Diesel3Sound _parent; D3Notch _notch; SoundBite _sound; float _throttle; public static final int SLEEP_INTERVAL = 50; public D3LoopThread(Diesel3Sound p) { super(); is_running = false; is_looping = false; is_dying = false; _notch = null; _sound = null; _parent = p; _throttle = 0.0f; } public D3LoopThread(Diesel3Sound d, D3Notch n, String s, boolean r) { super(); is_running = r; is_looping = false; is_dying = false; _notch = n; _sound = new SoundBite(s, SoundBite.BufferMode.QUEUE_MODE); _sound.setGain(0.8f); _parent = d; _throttle = 0.0f; if (r) { this.start(); } } public void setNotch(D3Notch n) { _notch = n; } public D3Notch getNotch() { return (_notch); } public void setSound(SoundBite s) { _sound = s; } public void setRunning(boolean r) { is_running = r; } public boolean isRunning() { return (is_running); } public void setThrottle(float t) { _throttle = t; log.debug("Throttle set: " + _throttle); } public void startEngine(AudioBuffer start_buf) { _sound.unqueueBuffers(); // Adjust the current notch to match the throttle setting log.debug("Notch = " + _notch.getNotch() + " prev = " + _notch.getPrevNotch() + " next = " + _notch.getNextNotch()); if (!_notch.isInLimits(_throttle)) { // We're out of whack. Find the right notch for the current throttle setting. while (!_notch.isInLimits(_throttle)) { if (_throttle > _notch.getAccelLimit()) { _notch = _parent.getNotch(_notch.getNextNotch()); } else if (_throttle < _notch.getDecelLimit()) { _notch = _parent.getNotch(_notch.getPrevNotch()); } } } // Only queue the start buffer if we know we're in the idle notch. // This is indicated by prevNotch == self. if (_notch.isIdleNotch()) { _sound.queueBuffer(start_buf); } else { _sound.queueBuffer(_notch.nextLoopBuffer()); } // Follow up with another loop buffer. _sound.queueBuffer(_notch.nextLoopBuffer()); is_looping = true; if (!_sound.isPlaying()) { _sound.play(); } } public void stopEngine(AudioBuffer stop_buf) { is_looping = false; // stop the loop player is_dying = true; _sound.queueBuffer(stop_buf); if (!_sound.isPlaying()) { _sound.play(); } } public void run() { try { while (is_running) { if (is_looping) { if (_sound.getSource().numProcessedBuffers() > 0) { _sound.unqueueBuffers(); } //log.debug("D3Loop"+ _sound.getName() + "Run loop. Buffers: " + _sound.getSource().numQueuedBuffers()); if (!_notch.isInLimits(_throttle)) { //log.debug("Notch Change! throttle = " + _throttle); changeNotch(); } if (_sound.getSource().numQueuedBuffers() < 2) { //log.debug("D3Loop"+ _sound.getName() + "Buffer count low (" + _sound.getSource().numQueuedBuffers() + "). Adding buffer. Throttle = " + _throttle); AudioBuffer b = _notch.nextLoopBuffer(); //log.debug("D3Loop"+ _sound.getName() + "Loop: Adding buffer " + b.getSystemName()); _sound.queueBuffer(b); } if (!_sound.isPlaying()) { _sound.play(); } } else { // Quietly wait for the sound to get turned on again // Once we've stopped playing, kill the thread. if (_sound.getSource().numProcessedBuffers() > 0) { _sound.unqueueBuffers(); } if (is_dying && (_sound.getSource().getState() != Audio.STATE_PLAYING)) { _sound.stop(); // good reason to get rid of SoundBite.is_playing variable! //return; } } sleep(SLEEP_INTERVAL); } // Note: if (is_running == false) we'll exit the endless while and the Thread will die. return; } catch (InterruptedException ie) { //_notch = _parent.getCurrentNotch(); is_running = false; return; // probably should do something. Not sure what. } } private void changeNotch() { AudioBuffer transition_buf = null; int new_notch = _notch.getNotch(); log.debug("D3Thread Change Throttle: " + _throttle + " Accel Limit = " + _notch.getAccelLimit() + " Decel Limit = " + _notch.getDecelLimit()); if (_throttle < 0) { // DO something to shut down _sound.stop(); is_running = false; return; } if (_throttle > _notch.getAccelLimit()) { // Too fast. Need to go to next notch up. transition_buf = _notch.getAccelBuffer(); new_notch = _notch.getNextNotch(); //log.debug("Change up. notch=" + new_notch); } else if (_throttle < _notch.getDecelLimit()) { // Too slow. Need to go to next notch down. transition_buf = _notch.getDecelBuffer(); new_notch = _notch.getPrevNotch(); log.debug("Change down. notch=" + new_notch); } // Now, regardless of whether we're going up or down, set the timer, // fade the current sound, and move on. if (transition_buf == null) { // No transition sound to play. Skip the timer bit. // Recurse directly to try the next notch. _notch = _parent.getNotch(new_notch); log.debug("No transition sound defined."); return; } else { // Stop the loop if it's running //this.stopLoop(); // queue up the transition sound buffer. _notch = _parent.getNotch(new_notch); _sound.queueBuffer(transition_buf); try { sleep(SoundBite.calcLength(transition_buf) - 50); } catch (InterruptedException e) { } } return; } public void mute(boolean m) { _sound.mute(m); } public void setVolume(float v) { _sound.setVolume(v); } public void setPosition(PhysicalLocation p) { _sound.setPosition(p); } public void kill() { is_running = false; _notch = null; _sound = null; } private static final Logger log = LoggerFactory.getLogger(D3LoopThread.class.getName()); } }