001package jmri.jmrit.audio;
002
003import javax.vecmath.Vector3f;
004import jmri.Audio;
005import jmri.InstanceManager;
006import jmri.implementation.AbstractAudio;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Base implementation of the AudioListener class.
012 * <p>
013 * Specific implementations will extend this base class.
014 * <br>
015 * <hr>
016 * This file is part of JMRI.
017 * <p>
018 * JMRI is free software; you can redistribute it and/or modify it under the
019 * terms of version 2 of the GNU General Public License as published by the Free
020 * Software Foundation. See the "COPYING" file for a copy of this license.
021 * <p>
022 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
024 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
025 *
026 * @author Matthew Harris copyright (c) 2009
027 */
028public abstract class AbstractAudioListener extends AbstractAudio implements AudioListener {
029
030    private Vector3f position = new Vector3f(0.0f, 0.0f, 0.0f);
031    private Vector3f currentPosition = new Vector3f(0.0f, 0.0f, 0.0f);
032    private Vector3f velocity = new Vector3f(0.0f, 0.0f, 0.0f);
033    private Vector3f orientationAt = new Vector3f(0.0f, 1.0f, 0.0f);
034    private Vector3f orientationUp = new Vector3f(0.0f, 0.0f, 1.0f);
035    private Vector3f currentOriAt = new Vector3f(0.0f, 1.0f, 0.0f);
036    private Vector3f currentOriUp = new Vector3f(0.0f, 0.0f, 1.0f);
037    private float gain = 1.0f;
038    private float metersPerUnit = 1.0f;
039    private long timeOfLastPositionCheck = 0;
040
041    private static final AudioFactory activeAudioFactory = InstanceManager.getDefault(jmri.AudioManager.class).getActiveAudioFactory();
042
043    /**
044     * Abstract constructor for new AudioListener with system name
045     *
046     * @param systemName AudioListener object system name (e.g. IAL)
047     */
048    public AbstractAudioListener(String systemName) {
049        super(systemName);
050        this.setState(STATE_POSITIONED);
051    }
052
053    /**
054     * Abstract constructor for new AudioListener with system name and user name
055     *
056     * @param systemName AudioListener object system name (e.g. IAL)
057     * @param userName   AudioListener object user name
058     */
059    public AbstractAudioListener(String systemName, String userName) {
060        super(systemName, userName);
061        this.setState(STATE_POSITIONED);
062    }
063
064    @Override
065    public char getSubType() {
066        return LISTENER;
067    }
068
069    @Override
070    public void setPosition(Vector3f pos) {
071        this.position = pos;
072        changePosition(pos);
073        if (log.isDebugEnabled()) {
074            log.debug("Set position of Listener {} to {}", this.getSystemName(), pos);
075        }
076    }
077
078    @Override
079    public void setPosition(float x, float y, float z) {
080        this.setPosition(new Vector3f(x, y, z));
081    }
082
083    @Override
084    public void setPosition(float x, float y) {
085        this.setPosition(new Vector3f(x, y, 0.0f));
086    }
087
088    @Override
089    public Vector3f getPosition() {
090        return this.position;
091    }
092
093    @Override
094    public Vector3f getCurrentPosition() {
095        return this.currentPosition;
096    }
097
098    @Override
099    public void setVelocity(Vector3f vel) {
100        this.velocity = vel;
101        this.setState(vel.length() != 0 ? STATE_MOVING : STATE_POSITIONED);
102        if (log.isDebugEnabled()) {
103            log.debug("Set velocity of Listener {} to {}", this.getSystemName(), vel);
104        }
105    }
106
107    @Override
108    public Vector3f getVelocity() {
109        return this.velocity;
110    }
111
112    /**
113     * Method to calculate current position based on velocity
114     */
115    protected void calculateCurrentPosition() {
116
117        // Calculate how long it's been since we lasted checked position
118        long currentTime = System.currentTimeMillis();
119        long timePassed = (currentTime - this.timeOfLastPositionCheck) / 1000;
120        this.timeOfLastPositionCheck = currentTime;
121
122        if (this.velocity.length() != 0) {
123            this.currentPosition.scaleAdd(timePassed * this.metersPerUnit,
124                    this.velocity,
125                    this.currentPosition);
126            this.currentOriAt.scaleAdd(timePassed * this.metersPerUnit,
127                    this.velocity,
128                    this.currentOriAt);
129            this.currentOriUp.scaleAdd(timePassed * this.metersPerUnit,
130                    this.velocity,
131                    this.currentOriUp);
132        }
133    }
134
135    @Override
136    public void resetCurrentPosition() {
137        activeAudioFactory.audioCommandQueue(new AudioCommand(this, Audio.CMD_RESET_POSITION));
138        activeAudioFactory.getCommandThread().interrupt();
139    }
140
141    /**
142     * Method to reset the current position
143     */
144    protected void doResetCurrentPosition() {
145        this.currentPosition = this.position;
146    }
147
148    /**
149     * Method to change the current position of this source
150     *
151     * @param pos new position
152     */
153    abstract protected void changePosition(Vector3f pos);
154
155    @Override
156    public void setOrientation(Vector3f at, Vector3f up) {
157        this.orientationAt = at;
158        this.orientationUp = up;
159        if (log.isDebugEnabled()) {
160            log.debug("Set orientation of Listener {} to (at) {} (up) {}", this.getSystemName(), at, up);
161        }
162    }
163
164    @Override
165    public Vector3f getOrientation(int which) {
166        Vector3f orientation = null;
167        switch (which) {
168            case AT: {
169                orientation = this.orientationAt;
170                break;
171            }
172            case UP: {
173                orientation = this.orientationUp;
174                break;
175            }
176            default:
177                throw new IllegalArgumentException();
178        }
179        return orientation;
180    }
181
182    @Override
183    public Vector3f getCurrentOrientation(int which) {
184        Vector3f orientation = null;
185        switch (which) {
186            case AT: {
187                orientation = this.currentOriAt;
188                break;
189            }
190            case UP: {
191                orientation = this.currentOriUp;
192                break;
193            }
194            default:
195                throw new IllegalArgumentException();
196        }
197        return orientation;
198    }
199
200    @Override
201    public void setGain(float gain) {
202        this.gain = gain;
203        if (log.isDebugEnabled()) {
204            log.debug("Set gain of Listener {} to {}", this.getSystemName(), gain);
205        }
206    }
207
208    @Override
209    public float getGain() {
210        return this.gain;
211    }
212
213    @Override
214    public void setMetersPerUnit(float metersPerUnit) {
215        this.metersPerUnit = metersPerUnit;
216        if (log.isDebugEnabled()) {
217            log.debug("Set Meters per unit of Listener {} to {}", this.getSystemName(), metersPerUnit);
218        }
219    }
220
221    @Override
222    public float getMetersPerUnit() {
223        return this.metersPerUnit;
224    }
225
226    @Override
227    public void stateChanged(int oldState) {
228        // Move along... nothing to see here...
229    }
230
231    private static final Logger log = LoggerFactory.getLogger(AbstractAudioListener.class);
232
233    /**
234     * An internal class used to create a new thread to monitor and maintain
235     * current listener position with respect to velocity.
236     */
237    protected static class AudioListenerMoveThread extends AbstractAudioThread {
238
239        /**
240         * Reference to the AudioListener object being monitored
241         */
242        private AbstractAudioListener audioListener;
243
244        /**
245         * Constructor that takes handle to AudioListener to monitor
246         *
247         * @param audioListener AudioListener to monitor
248         */
249        AudioListenerMoveThread(AbstractAudioListener audioListener) {
250            super();
251            this.setName("movelis-" + super.getName());
252            this.audioListener = audioListener;
253            if (log.isDebugEnabled()) {
254                log.debug("Created AudioListenerMoveThread for AudioListener {}", audioListener.getSystemName());
255            }
256        }
257
258        /**
259         * Main processing loop
260         */
261        @Override
262        public void run() {
263
264            while (!dying()) {
265
266                // Recalculate the position
267                audioListener.calculateCurrentPosition();
268
269                // Check state and die if not playing
270                if (audioListener.getState() != STATE_MOVING) {
271                    die();
272                }
273
274                // sleep for a while so as not to overload CPU
275                snooze(20);
276            }
277
278//            // Reset the current position
279//            audioListener.resetCurrentPosition();
280            // Finish up
281            if (log.isDebugEnabled()) {
282                log.debug("Clean up thread {}", this.getName());
283            }
284            cleanup();
285        }
286
287        /**
288         * Shuts this thread down and clears references to created objects
289         */
290        @Override
291        protected void cleanup() {
292            // Thread is to shutdown
293            die();
294
295            // Clear references to objects
296            this.audioListener = null;
297
298            // Finalise cleanup in super-class
299            super.cleanup();
300        }
301    }
302}