001package jmri.jmrit.audio;
002
003import com.jogamp.openal.AL;
004import java.util.Queue;
005import javax.vecmath.Vector3f;
006
007/**
008 * JOAL implementation of the Audio Source sub-class.
009 * <p>
010 * For now, no system-specific implementations are forseen - this will remain
011 * internal-only
012 * <br><br><hr><br><b>
013 * This software is based on or using the JOAL Library available from
014 * <a href="http://jogamp.org/joal/www/">http://jogamp.org/joal/www/</a>
015 * </b><br><br>
016 * JOAL is released under the BSD license. The full license terms follow:
017 * <br><i>
018 * Copyright (c) 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
019 * <br>
020 * Redistribution and use in source and binary forms, with or without
021 * modification, are permitted provided that the following conditions are
022 * met:
023 * <br>
024 * - Redistribution of source code must retain the above copyright
025 *   notice, this list of conditions and the following disclaimer.
026 * <br>
027 * - Redistribution in binary form must reproduce the above copyright
028 *   notice, this list of conditions and the following disclaimer in the
029 *   documentation and/or other materials provided with the distribution.
030 * <br>
031 * Neither the name of Sun Microsystems, Inc. or the names of
032 * contributors may be used to endorse or promote products derived from
033 * this software without specific prior written permission.
034 * <br>
035 * This software is provided "AS IS," without a warranty of any kind. ALL
036 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
037 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
038 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
039 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
040 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
041 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
042 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
043 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
044 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
045 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
046 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
047 * <br>
048 * You acknowledge that this software is not designed or intended for use
049 * in the design, construction, operation or maintenance of any nuclear
050 * facility.
051 * <br><br><br></i>
052 * <hr>
053 * This file is part of JMRI.
054 * <p>
055 * JMRI is free software; you can redistribute it and/or modify it under the
056 * terms of version 2 of the GNU General Public License as published by the Free
057 * Software Foundation. See the "COPYING" file for a copy of this license.
058 * <p>
059 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
060 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
061 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
062 *
063 * @author Matthew Harris copyright (c) 2009
064 */
065public class JoalAudioSource extends AbstractAudioSource {
066
067    private static AL al = JoalAudioFactory.getAL();
068
069    private boolean _initialised = false;
070
071    private int[] _source = new int[1];
072
073    private int[] _alState = new int[1];
074
075    /**
076     * Constructor for new JoalAudioSource with system name
077     *
078     * @param systemName AudioSource object system name (e.g. IAS1)
079     */
080    public JoalAudioSource(String systemName) {
081        super(systemName);
082        log.debug("New JoalAudioSource: {}", systemName);
083        _initialised = init();
084    }
085
086    /**
087     * Constructor for new JoalAudioSource with system name and user name
088     *
089     * @param systemName AudioSource object system name (e.g. IAS1)
090     * @param userName   AudioSource object user name
091     */
092    public JoalAudioSource(String systemName, String userName) {
093        super(systemName, userName);
094        if (log.isDebugEnabled()) {
095            log.debug("New JoalAudioSource: {} ({})", userName, systemName);
096        }
097        _initialised = init();
098    }
099
100    /**
101     * Initialise this AudioSource
102     *
103     * @return True if initialised
104     */
105    private boolean init() {
106        // Check that the JoalAudioFactory exists
107        if (al == null) {
108            log.warn("Al Factory not yet initialised");
109            return false;
110        }
111
112        // Now, check that the audio command thread exists
113        if (!isAudioAlive()) {
114            log.debug("Command thread not yet alive...");
115            return false;
116        } else {
117            log.debug("Command thread is alive - continue.");
118        }
119
120        // Generate the AudioSource
121        al.alGenSources(1, _source, 0);
122        if (JoalAudioFactory.checkALError()) {
123            log.warn("Error creating JoalSource ({})", this.getSystemName());
124            _source = null;
125            return false;
126        }
127        return true;
128    }
129
130    /**
131     * Queue a single AudioBuffer on this source.
132     *
133     * (called from DefaultAudioFactory command queue)
134     *
135     * @param audioBuffer AudioBuffer to queue
136     * @return True if successfully queued.
137     */
138    @Override
139    public boolean queueAudioBuffer(AudioBuffer audioBuffer) {
140        // First check we've been initialised
141        if (!_initialised || !isAudioAlive()) {
142            log.error("Source Not Initialized: {}", this.getSystemName());
143            return false;
144        }
145
146        if (audioBuffer instanceof JoalAudioBuffer) {
147            int[] bids = new int[1];
148            // Make an int[] of the buffer ids
149            bids[0] = ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0];
150            if (log.isDebugEnabled()) {
151                log.debug("Queueing Buffer: {} bid: {} Source: {}", audioBuffer.getSystemName(), ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0], this.getSystemName());
152            }
153
154            // Bind this AudioSource to the specified AudioBuffer
155            al.alSourceQueueBuffers(_source[0], 1, bids, 0);
156            if (JoalAudioFactory.checkALError()) {
157                log.warn("Error queueing JoalSource ({}) to AudioBuffers ({})", this.getSystemName(), audioBuffer.getDisplayName());
158                return false;
159            }
160
161            if (log.isDebugEnabled()) {
162                log.debug("Queue JoalAudioBuffer ({}) to JoalAudioSource ({})", audioBuffer.getSystemName(), this.getSystemName());
163            }
164            return true;
165        } else {
166            throw new IllegalArgumentException(audioBuffer.getSystemName() + " is not a JoalAudioBuffer");
167        }
168    }
169
170    /**
171     * Queue a list of AudioBuffers on this source.
172     *
173     * (called from DefaultAudioFactory command queue)
174     *
175     * @param audioBuffers AudioBuffers to queue
176     * @return True if successfully queued.
177     */
178    @Override
179    public boolean queueAudioBuffers(Queue<AudioBuffer> audioBuffers) {
180        // First check we've been initialised
181        if (!_initialised || !isAudioAlive()) {
182            return false;
183        }
184
185        // Make an int[] of the buffer ids
186        int[] bids = new int[1];
187        int i = 0;
188        // While the list isn't empty, pop elements and process.
189        AudioBuffer b;
190        while ((b = audioBuffers.poll()) != null) {
191            if (b instanceof JoalAudioBuffer) {
192                bids[0] = ((JoalAudioBuffer) b).getDataStorageBuffer()[0];
193            } else {
194                throw new IllegalArgumentException(b.getSystemName() + " is not a JoalAudioBuffer");
195            }
196            al.alSourceQueueBuffers(_source[0], 1, bids, 0);
197            if (log.isDebugEnabled()) {
198                log.debug("Queueing Buffer [{}] {}", i, b.getSystemName());
199            }
200            i++;
201            if (JoalAudioFactory.checkALError()) {
202                log.warn("Error queueing JoalSource ({}) to AudioBuffers ({}) etc.", this.getSystemName(), b.getDisplayName());
203                return false;
204            }
205        }
206
207        // Bind this AudioSource to the specified AudioBuffer
208        //al.alSourceQueueBuffers(_source[0], bids.length, bids, 0);
209        //al.alSourcei(_source[0], AL.AL_BUFFER, ((JoalAudioBuffer)audioBuffer).getDataStorageBuffer()[0]);
210        return true;
211    }
212
213    /**
214     * Remove all processed AudioBuffers from this Source.
215     *
216     * @return True if successful.
217     */
218    @Override
219    public boolean unqueueAudioBuffers() {
220        // First check we've been initialised
221        if (!_initialised || !isAudioAlive()) {
222            return false;
223        }
224
225        int[] num_processed = new int[1];
226
227        // How many processed buffers are there?
228        al.alGetSourcei(_source[0], AL.AL_BUFFERS_PROCESSED, num_processed, 0);
229        if (JoalAudioFactory.checkALError()) {
230            log.warn("Error getting # processed buffers from  JoalSource ({})", this.getSystemName());
231            return false;
232        }
233
234        // Try to unqueue them all.
235        if (num_processed[0] > 0) {
236            int[] bids = new int[num_processed[0]];
237            al.alSourceUnqueueBuffers(_source[0], num_processed[0], bids, 0);
238            if (JoalAudioFactory.checkALError()) {
239                log.warn("Error removing {} buffers from  JoalSource ({})", num_processed[0], this.getSystemName());
240                return false;
241            }
242        }
243        if (log.isDebugEnabled()) {
244            log.debug("Removed {} buffers from JoalAudioSource ({})", num_processed[0], this.getSystemName());
245        }
246        return (numQueuedBuffers() != 0);
247    }
248
249    @Override
250    public int numProcessedBuffers() {
251        if (!isAudioAlive()) {
252            return 0;
253        }
254
255        int[] num_processed = new int[1];
256
257        // How many processed buffers are there?
258        al.alGetSourcei(_source[0], AL.AL_BUFFERS_PROCESSED, num_processed, 0);
259        if (JoalAudioFactory.checkALError()) {
260            log.warn("Error getting # processed buffers from  JoalSource ({})", this.getSystemName());
261            return 0;
262        }
263        return (num_processed[0]);
264    }
265
266    /**
267     * Report the number of AudioBuffers queued to this source.
268     *
269     * @return number of queued buffers.
270     */
271    @Override
272    public int numQueuedBuffers() {
273        // First check we've been initialised
274        if (!_initialised || !isAudioAlive()) {
275            return 0;
276        }
277
278        int[] num_queued = new int[1];
279        // How many queued buffers are there?
280        al.alGetSourcei(_source[0], AL.AL_BUFFERS_QUEUED, num_queued, 0);
281        if (JoalAudioFactory.checkALError()) {
282            log.warn("Error getting # queued buffers from  JoalSource ({})", this.getSystemName());
283            return 0;
284        }
285
286        if (log.isDebugEnabled()) {
287            log.debug("Queued {} buffers on JoalAudioSource ({})", num_queued[0], this.getSystemName());
288        }
289        return (num_queued[0]);
290    }
291
292    @Override
293    boolean bindAudioBuffer(AudioBuffer audioBuffer) {
294        // First check we've been initialised
295        if (!_initialised || !isAudioAlive()) {
296            return false;
297        }
298
299        // Bind this AudioSource to the specified AudioBuffer
300        al.alSourcei(_source[0], AL.AL_BUFFER, ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0]);
301        if (JoalAudioFactory.checkALError()) {
302            log.warn("Error binding JoalSource ({}) to AudioBuffer ({})", this.getSystemName(), this.getAssignedBufferName());
303            return false;
304        }
305
306        if (log.isDebugEnabled()) {
307            log.debug("Bind JoalAudioSource ({}) to JoalAudioBuffer ({})", this.getSystemName(), audioBuffer.getSystemName());
308        }
309        return true;
310    }
311
312    @Override
313    protected void changePosition(Vector3f pos) {
314        if (_initialised && isAudioAlive()) {
315            if (_source != null) {
316                al.alSource3f(_source[0], AL.AL_POSITION, pos.x, pos.y, pos.z);
317                if (JoalAudioFactory.checkALError()) {
318                    log.warn("Error updating position of JoalAudioSource ({})", this.getSystemName());
319                }
320            }
321        }
322    }
323
324    @Override
325    public void setPositionRelative(boolean relative) {
326        super.setPositionRelative(relative);
327        if (_initialised && isAudioAlive()) {
328            al.alSourcei(_source[0], AL.AL_SOURCE_RELATIVE, relative ? AL.AL_TRUE : AL.AL_FALSE);
329            if (JoalAudioFactory.checkALError()) {
330                log.warn("Error updating relative position property of JoalAudioSource ({})", this.getSystemName());
331            }
332        }
333    }
334
335    @Override
336    public void setVelocity(Vector3f vel) {
337        super.setVelocity(vel);
338        if (_initialised && isAudioAlive()) {
339            al.alSource3f(_source[0], AL.AL_VELOCITY, vel.x, vel.y, vel.z);
340            if (JoalAudioFactory.checkALError()) {
341                log.warn("Error updating velocity of JoalAudioSource ({})", this.getSystemName());
342            }
343        }
344    }
345
346    @Override
347    public void setGain(float gain) {
348        super.setGain(gain);
349        if (_initialised && isAudioAlive()) {
350            calculateGain();
351        }
352    }
353
354    @Override
355    public void setPitch(float pitch) {
356        super.setPitch(pitch);
357        if (_initialised && isAudioAlive()) {
358            al.alSourcef(_source[0], AL.AL_PITCH, pitch);
359            if (JoalAudioFactory.checkALError()) {
360                log.warn("Error updating pitch of JoalAudioSource ({})", this.getSystemName());
361            }
362        }
363    }
364
365    @Override
366    public void setReferenceDistance(float referenceDistance) {
367        super.setReferenceDistance(referenceDistance);
368        if (_initialised && isAudioAlive()) {
369            al.alSourcef(_source[0], AL.AL_REFERENCE_DISTANCE, referenceDistance);
370            if (JoalAudioFactory.checkALError()) {
371                log.warn("Error updating reference distance of JoalAudioSource ({})", this.getSystemName());
372            }
373        }
374    }
375
376    @Override
377    public void setOffset(long offset) {
378        super.setOffset(offset);
379        if (_initialised && isAudioAlive()) {
380            al.alSourcei(_source[0], AL.AL_SAMPLE_OFFSET, (int) getOffset());
381            if (JoalAudioFactory.checkALError()) {
382                log.warn("Error updating Sample Offset of JoalAudioSource ({})", this.getSystemName());
383             }
384        }
385    }
386
387    @Override
388    public void setMaximumDistance(float maximumDistance) {
389        super.setMaximumDistance(maximumDistance);
390        if (_initialised && isAudioAlive()) {
391            al.alSourcef(_source[0], AL.AL_MAX_DISTANCE, maximumDistance);
392            if (JoalAudioFactory.checkALError()) {
393                log.warn("Error updating maximum distance of JoalAudioSource ({})", this.getSystemName());
394            }
395        }
396    }
397
398    @Override
399    public void setRollOffFactor(float rollOffFactor) {
400        super.setRollOffFactor(rollOffFactor);
401        if (_initialised && isAudioAlive()) {
402            al.alSourcef(_source[0], AL.AL_ROLLOFF_FACTOR, rollOffFactor);
403            if (JoalAudioFactory.checkALError()) {
404                log.warn("Error updating roll-off factor of JoalAudioSource ({})", this.getSystemName());
405            }
406        }
407    }
408
409    @Override
410    public int getState() {
411        if (_source != null && isAudioAlive()) {
412            int old = _alState[0];
413            al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, _alState, 0);
414            if (_alState[0] != old) {
415                if (_alState[0] == AL.AL_PLAYING) {
416                    this.setState(STATE_PLAYING);
417                } else {
418                    this.setState(STATE_STOPPED);
419                }
420            }
421            return super.getState();
422        } else {
423            return STATE_STOPPED;
424        }
425    }
426
427    @Override
428    public void stateChanged(int oldState) {
429        super.stateChanged(oldState);
430        if (_initialised && isAudioAlive()) {
431            if (_source != null) {
432                al.alSourcef(_source[0], AL.AL_PITCH, this.getPitch());
433                al.alSourcef(_source[0], AL.AL_GAIN, this.getGain());
434                al.alSource3f(_source[0], AL.AL_POSITION, this.getCurrentPosition().x, this.getCurrentPosition().y, this.getCurrentPosition().z);
435                al.alSource3f(_source[0], AL.AL_VELOCITY, this.getVelocity().x, this.getVelocity().y, this.getVelocity().z);
436                al.alSourcei(_source[0], AL.AL_LOOPING, this.isLooped() ? AL.AL_TRUE : AL.AL_FALSE);
437                if (JoalAudioFactory.checkALError()) {
438                    log.warn("Error updating JoalAudioSource ({})", this.getSystemName());
439                }
440            }
441        } else {
442            _initialised = init();
443        }
444    }
445
446    @Override
447    protected void doPlay() {
448        log.debug("Play JoalAudioSource ({})", this.getSystemName());
449        if (_initialised && isAudioAlive() && (isBound() || isQueued())) {
450            doRewind();
451            doResume();
452        }
453    }
454
455//    @SuppressWarnings("SleepWhileInLoop")
456    @Override
457    protected void doStop() {
458        log.debug("Stop JoalAudioSource ({})", this.getSystemName());
459        if (_source != null) {
460            if (_initialised && isAudioAlive() && (isBound() || isQueued())) {
461                al.alSourceStop(_source[0]);
462                doRewind();
463            }
464            int[] myState = new int[1];
465            al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, myState, 0);
466            boolean stopped = myState[0] != AL.AL_LOOPING;
467            while (!stopped) {
468                try {
469                    Thread.sleep(5);
470                } catch (InterruptedException ex) {
471                }
472                al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, myState, 0);
473                stopped = myState[0] != AL.AL_LOOPING;
474            }
475            this.setState(STATE_STOPPED);
476        }
477    }
478
479    @Override
480    protected void doPause() {
481        log.debug("Pause JoalAudioSource ({})", this.getSystemName());
482        if (_initialised && isAudioAlive() && (isBound() || isQueued())) {
483            al.alSourcePause(_source[0]);
484        }
485        this.setState(STATE_STOPPED);
486    }
487
488    @Override
489    protected void doResume() {
490        log.debug("Resume JoalAudioSource ({})", this.getSystemName());
491        if (_initialised && isAudioAlive() && (isBound() || isQueued())) {
492            calculateGain();
493            al.alSourcei(_source[0], AL.AL_LOOPING, this.isLooped() ? AL.AL_TRUE : AL.AL_FALSE);
494            al.alSourcePlay(_source[0]);
495            int numLoops = this.getNumLoops();
496            if (numLoops > 0) {
497                if (log.isDebugEnabled()) {
498                    log.debug("Create LoopThread for JoalAudioSource {}", this.getSystemName());
499                }
500                AudioSourceLoopThread aslt = new AudioSourceLoopThread(this, numLoops);
501                aslt.start();
502            }
503        }
504        this.setState(STATE_PLAYING);
505    }
506
507    @Override
508    protected void doRewind() {
509        log.debug("Rewind JoalAudioSource ({})", this.getSystemName());
510        if (_initialised && isAudioAlive() && (isBound() || isQueued())) {
511            al.alSourceRewind(_source[0]);
512        }
513    }
514
515    @Override
516    protected void doFadeIn() {
517        log.debug("Fade-in JoalAudioSource ({})", this.getSystemName());
518        if (_initialised && isAudioAlive() && (isBound() || isQueued())) {
519            doPlay();
520            AudioSourceFadeThread asft = new AudioSourceFadeThread(this);
521            asft.start();
522        }
523    }
524
525    @Override
526    protected void doFadeOut() {
527        log.debug("Fade-out JoalAudioSource ({})", this.getSystemName());
528        if (_initialised && isAudioAlive() && (isBound() || isQueued())) {
529            AudioSourceFadeThread asft = new AudioSourceFadeThread(this);
530            asft.start();
531        }
532    }
533
534    @Override
535    protected void cleanup() {
536        log.debug("Cleanup JoalAudioSource ({})", this.getSystemName());
537        int[] source_type = new int[1];
538        al.alGetSourcei(_source[0], AL.AL_SOURCE_TYPE, source_type, 0);
539        if (_initialised && (isBound() || isQueued() || source_type[0] == AL.AL_UNDETERMINED) || source_type[0] == AL.AL_STREAMING) {
540            al.alSourceStop(_source[0]);
541            al.alDeleteSources(1, _source, 0);
542            this._source = null;
543            log.debug("...done cleanup");
544        }
545    }
546
547    @Override
548    protected void calculateGain() {
549        // Adjust gain based on master gain for this source and any
550        // calculated fade gains
551        float currentGain = this.getGain() * this.getFadeGain();
552
553        // If playing, update the gain
554        if (_initialised && isAudioAlive()) {
555            al.alSourcef(_source[0], AL.AL_GAIN, currentGain);
556            if (JoalAudioFactory.checkALError()) {
557                log.warn("Error updating gain setting of JoalAudioSource ({})", this.getSystemName());
558            }
559            if (log.isDebugEnabled()) {
560                log.debug("Set current gain of JoalAudioSource {} to {}", this.getSystemName(), currentGain);
561            }
562        }
563    }
564
565    /**
566     * Internal method to return a reference to the OpenAL source buffer
567     *
568     * @return source buffer
569     */
570    private int[] getSourceBuffer() {
571        return this._source;
572    }
573
574    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JoalAudioSource.class);
575
576    /**
577     * An internal class used to create a new thread to monitor looping as,
578     * unlike JavaSound, OpenAL (and, therefore, JOAL) do not provide a
579     * convenient method to loop a sound a specific number of times.
580     */
581    private static class AudioSourceLoopThread extends AbstractAudioThread {
582
583        /**
584         * Number of times to loop this source
585         */
586        private int numLoops;
587
588        /**
589         * Reference to the OpenAL source buffer
590         */
591        private int[] sourceBuffer;
592
593        /**
594         * Constructor that takes handle to looping AudioSource to monitor
595         *
596         * @param audioSource looping AudioSource to monitor
597         * @param numLoops    number of loops for this AudioSource to make
598         */
599        AudioSourceLoopThread(JoalAudioSource audioSource, int numLoops) {
600            super();
601            this.setName("loopsrc-" + super.getName());
602            this.sourceBuffer = audioSource.getSourceBuffer();
603            this.numLoops = numLoops;
604            if (log.isDebugEnabled()) {
605                log.debug("Created AudioSourceLoopThread for AudioSource {} loopcount {}",
606                        audioSource.getSystemName(), this.numLoops);
607            }
608        }
609
610        /**
611         * Main processing loop
612         */
613        @Override
614        public void run() {
615
616            // Current loop count
617            int loopCount = 0;
618
619            // Previous position
620            float oldPos = 0;
621
622            // Current position
623            float[] newPos = new float[1];
624
625            // Turn on looping
626            JoalAudioSource.al.alSourcei(sourceBuffer[0], AL.AL_LOOPING, AL.AL_TRUE);
627
628            while (!dying()) {
629
630                // Determine current position
631                JoalAudioSource.al.alGetSourcef(sourceBuffer[0], AL.AL_SEC_OFFSET, newPos, 0);
632
633                // Check if it is smaller than the previous position
634                // If so, we've looped so increment the loop counter
635                if (oldPos > newPos[0]) {
636                    loopCount++;
637                    log.debug("Loop count {}", loopCount);
638                }
639                oldPos = newPos[0];
640
641                // Check if we've performed sufficient iterations
642                if (loopCount >= numLoops) {
643                    die();
644                }
645
646                // sleep for a while so as not to overload CPU
647                snooze(20);
648            }
649
650            // Turn off looping
651            JoalAudioSource.al.alSourcei(sourceBuffer[0], AL.AL_LOOPING, AL.AL_FALSE);
652
653            // Finish up
654            if (log.isDebugEnabled()) {
655                log.debug("Clean up thread {}", this.getName());
656            }
657            cleanup();
658        }
659
660        /**
661         * Shuts this thread down and clears references to created objects
662         */
663        @Override
664        protected void cleanup() {
665            // Thread is to shutdown
666            die();
667
668            // Clear references to objects
669            this.sourceBuffer = null;
670
671            // Finalise cleanup in super-class
672            super.cleanup();
673        }
674    }
675}