001package jmri.jmrit.audio;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.LinkedList;
005import java.util.List;
006import jmri.Audio;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Abstract implementation of the AudioFactory class.
012 * <p>
013 * All code shared amongst the concrete AudioFactory classes is defined here.
014 * <hr>
015 * This file is part of JMRI.
016 * <p>
017 * JMRI is free software; you can redistribute it and/or modify it under the
018 * terms of version 2 of the GNU General Public License as published by the Free
019 * Software Foundation. See the "COPYING" file for a copy of this license.
020 * <p>
021 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
022 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
023 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
024 *
025 * @author Matthew Harris copyright (c) 2009
026 */
027public abstract class AbstractAudioFactory implements AudioFactory {
028
029    /**
030     * List of queued audio commands to process
031     */
032    private static List<AudioCommand> commandQueue = null;
033
034    /**
035     * Boolean used to determine if this AudioFactory has been initialised
036     */
037    private static boolean initialised = false;
038
039    /**
040     * Boolean used to determine if this AudioFactory should attenuate sources
041     * based on their distance from the Listener
042     */
043    private static boolean distanceAttenuated = true;
044
045    /**
046     * Reference to the separate thread used to process all AudioCommands
047     */
048    private static AbstractAudioThread audioCommandThread = null;
049
050    @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
051    @Override
052    public boolean init() {
053        if (initialised) {
054            log.debug("Already initialised");
055            return true;
056        }
057
058        // Create the command queue
059        commandQueue = new LinkedList<>();
060
061        // Create and start the command thread
062        audioCommandThread = new AudioCommandThread(this);
063        audioCommandThread.start();
064
065        initialised = true;
066        return true;
067    }
068
069    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
070            justification = "OK to write to static variables to record static library status")
071    @Override
072    public void cleanup() {
073
074        boolean dieException = false;
075
076        // End the command thread
077        try {
078            audioCommandThread.die();       // send the die signal to the thread
079            audioCommandThread.interrupt(); // interrupt the thread to process die signal
080        } catch (Exception e) {
081            dieException = true;
082        }
083
084        if (!dieException) {
085            // wait for up to 5 seconds for thread to end
086            for (int i = 0; i < 50; i++) {
087                if (!audioCommandThread.alive()) {
088                    break;
089                }
090                AbstractAudioThread.snooze(100);
091            }
092        }
093        initialised = false;
094    }
095
096    @Override
097    public synchronized boolean audioCommandQueue(AudioCommand queueAudioCommand) {
098        if (queueAudioCommand == null) {
099            log.debug("Processing command queue");
100            // Process command queue
101            AudioCommand audioCommand;
102            while (commandQueue != null && commandQueue.size() > 0) {
103                audioCommand = commandQueue.remove(0);
104                if (audioCommand != null) {
105                    if (log.isDebugEnabled()) {
106                        log.debug("Process command: {} ({} remaining)", audioCommand.toString(), commandQueue.size());
107                    }
108                    Audio audio = audioCommand.getAudio();
109
110                    // Process AudioSource commands
111                    if (audio instanceof AudioSource) {
112                        AbstractAudioSource audioSource = (AbstractAudioSource) audio;
113                        switch (audioCommand.getCommand()) {
114                            case Audio.CMD_BIND_BUFFER:
115                                audioSource.setBound(audioSource.bindAudioBuffer(audioSource.getAssignedBuffer()));
116                                break;
117                            case Audio.CMD_QUEUE_BUFFERS:
118                                audioSource.setQueued(audioSource.queueAudioBuffers(audioSource.getQueuedBuffers()));
119                                break;
120                            case Audio.CMD_UNQUEUE_BUFFERS:
121                                audioSource.setQueued(audioSource.unqueueAudioBuffers());
122                                break;
123                            case Audio.CMD_PLAY:
124                                audioSource.doPlay();
125                                break;
126                            case Audio.CMD_STOP:
127                                audioSource.doStop();
128                                break;
129                            case Audio.CMD_PLAY_TOGGLE:
130                                audioSource.doTogglePlay();
131                                break;
132                            case Audio.CMD_PAUSE:
133                                audioSource.doPause();
134                                break;
135                            case Audio.CMD_RESUME:
136                                audioSource.doResume();
137                                break;
138                            case Audio.CMD_PAUSE_TOGGLE:
139                                audioSource.doTogglePause();
140                                break;
141                            case Audio.CMD_REWIND:
142                                audioSource.doRewind();
143                                break;
144                            case Audio.CMD_FADE_IN:
145                                audioSource.doFadeIn();
146                                break;
147                            case Audio.CMD_FADE_OUT:
148                                audioSource.doFadeOut();
149                                break;
150                            case Audio.CMD_RESET_POSITION:
151                                audioSource.doResetCurrentPosition();
152                                break;
153                            default:
154                                log.warn("Command {} not suitable for AudioSource ({})", audioCommand.toString(), audioSource.getSystemName());
155                        }
156                    } // Process AudioBuffer commands
157                    else if (audio instanceof AudioBuffer) {
158                        AbstractAudioBuffer audioBuffer = (AbstractAudioBuffer) audio;
159                        switch (audioCommand.getCommand()) {
160                            case Audio.CMD_LOAD_SOUND:
161                                audioBuffer.loadBuffer();
162                                break;
163                            default:
164                                log.warn("Command {} not suitable for AudioBuffer ({})", audioCommand.toString(), audioBuffer.getSystemName());
165                        }
166                    } // Process AudioListener commands
167                    else if (audio instanceof AudioListener) {
168                        AbstractAudioListener audioListener = (AbstractAudioListener) audio;
169                        switch (audioCommand.getCommand()) {
170                            case Audio.CMD_RESET_POSITION:
171                                audioListener.doResetCurrentPosition();
172                                break;
173                            default:
174                                log.warn("Command {} not suitable for AudioListener ({})", audioCommand.toString(), audioListener.getSystemName());
175                        }
176                    }
177                }
178            }
179            return (commandQueue != null && commandQueue.size() > 0);
180        } else {
181            if (commandQueue == null) {
182                log.warn("Audio commandQueue not initialised");
183                return false;
184            }
185            commandQueue.add(queueAudioCommand);
186            if (log.isDebugEnabled()) {
187                log.debug("New audio command: {}", queueAudioCommand.toString());
188            }
189            return true;
190        }
191    }
192
193    @Override
194    public Thread getCommandThread() {
195        return audioCommandThread;
196    }
197
198    @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
199    @Override
200    public void setDistanceAttenuated(boolean attenuated) {
201        distanceAttenuated = attenuated;
202    }
203
204    @Override
205    public boolean isDistanceAttenuated() {
206        return distanceAttenuated;
207    }
208
209    private static final Logger log = LoggerFactory.getLogger(AbstractAudioFactory.class);
210
211}