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