001package jmri.jmrit.vsdecoder;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.Iterator;
006import java.util.List;
007import java.nio.ByteBuffer;
008import jmri.Audio;
009import jmri.AudioException;
010import jmri.jmrit.audio.AudioBuffer;
011import jmri.util.PhysicalLocation;
012import org.jdom2.Element;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Diesel Sound version 3.
018 *
019 * <hr>
020 * This file is part of JMRI.
021 * <p>
022 * JMRI is free software; you can redistribute it and/or modify it under 
023 * the terms of version 2 of the GNU General Public License as published 
024 * by the Free Software Foundation. See the "COPYING" file for a copy
025 * of this license.
026 * <p>
027 * JMRI is distributed in the hope that it will be useful, but WITHOUT 
028 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
029 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
030 * for more details.
031 *
032 * @author Mark Underwood Copyright (C) 2011
033 * @author Klaus Killinger Copyright (C) 2018-2021
034 */
035class Diesel3Sound extends EngineSound {
036
037    // Engine Sounds
038    private HashMap<Integer, D3Notch> notch_sounds;
039    private String _soundName;
040
041    private AudioBuffer start_buffer;
042    private AudioBuffer stop_buffer;
043    private Integer idle_notch;
044    private int first_notch;
045    int top_speed;
046    final int number_helper_buffers = 5;
047
048    // Common variables
049    private int current_notch = 1;
050    private D3LoopThread _loopThread = null;
051
052    public Diesel3Sound(String name) {
053        super(name);
054        log.debug("New Diesel3Sound name(param): {}, name(val): {}", name, this.getName());
055    }
056
057    private void startThread() {
058        _loopThread = new D3LoopThread(this, notch_sounds.get(current_notch), _soundName, true);
059        log.debug("Loop Thread Started.  Sound name: {}", _soundName);
060    }
061
062    // Responds to "CHANGE" trigger
063    @Override
064    public void changeThrottle(float s) {
065        // This is all we have to do.  The loop thread will handle everything else.
066        if (_loopThread != null) {
067            _loopThread.setThrottle(s);
068        }
069    }
070
071    // Responds to throttle loco direction key (see EngineSound.java and EngineSoundEvent.java)
072    @Override
073    public void changeLocoDirection(int dirfn) {
074        log.debug("loco IsForward is {}", dirfn);
075        if (_loopThread != null) {
076            _loopThread.getLocoDirection(dirfn);
077        }
078    }
079
080    private D3Notch getNotch(int n) {
081        return notch_sounds.get(n);
082    }
083
084    @Override
085    public void startEngine() {
086        log.debug("startEngine.  ID: {}", this.getName());
087        if (_loopThread != null) {
088            _loopThread.startEngine(start_buffer);
089        }
090    }
091
092    @Override
093    public void stopEngine() {
094        log.debug("stopEngine.  ID: {}", this.getName());
095        if (_loopThread != null) {
096            _loopThread.stopEngine(stop_buffer);
097        }
098    }
099
100    @Override
101    public void shutdown() {
102        // Stop the loop thread, in case it's running
103        if (_loopThread != null) {
104            _loopThread.setRunning(false);
105        }
106    }
107
108    @Override
109    public void mute(boolean m) {
110        if (_loopThread != null) {
111            _loopThread.mute(m);
112        }
113    }
114
115    @Override
116    public void setVolume(float v) {
117        if (_loopThread != null) {
118            _loopThread.setVolume(v);
119        }
120    }
121
122    @Override
123    public void setPosition(PhysicalLocation p) {
124        if (_loopThread != null) {
125            _loopThread.setPosition(p);
126        }
127    }
128
129    @Override
130    public Element getXml() {
131        Element me = new Element("sound");
132        me.setAttribute("name", this.getName());
133        me.setAttribute("type", "engine");
134        // Do something, eventually...
135        return me;
136    }
137
138    @Override
139    public void setXml(Element e, VSDFile vf) {
140        Element el;
141        String fn;
142        String in;
143        D3Notch sb;
144        int frame_size = 0;
145        int freq = 0;
146
147        // Handle the common stuff.
148        super.setXml(e, vf);
149
150        _soundName = this.getName() + ":LoopSound";
151        log.debug("get name: {}, soundName: {}, name: {}", this.getName(), _soundName, name);
152
153        // Optional value
154        in = e.getChildText("top-speed");
155        if (in != null) {
156            top_speed = Integer.parseInt(in);
157        } else {
158            top_speed = 0; // default
159        }
160        log.debug("top speed forward: {} MPH", top_speed);
161
162        notch_sounds = new HashMap<>();
163        in = e.getChildText("idle-notch");
164        idle_notch = null;
165        if (in != null) {
166            idle_notch = Integer.parseInt(in);
167            log.debug("Notch {} is Idle.", idle_notch);
168        } else {
169            // leave idle_notch null for now. We'll use it at the end to trigger a "grandfathering" action
170            log.warn("No Idle Notch Specified!");
171        }
172
173        is_auto_start = setXMLAutoStart(e);
174        log.debug("config auto-start: {}", is_auto_start);
175
176        // Optional value
177        // Allows to adjust OpenAL attenuation
178        // Sounds with distance to listener position lower than reference-distance will not have attenuation
179        engine_rd = setXMLEngineReferenceDistance(e); // Handle engine reference distance
180        log.debug("engine-sound referenceDistance: {}", engine_rd);
181
182        // Optional value
183        // Allows to adjust the engine gain
184        in = e.getChildText("engine-gain");
185        if ((in != null) && (!in.isEmpty())) {
186            engine_gain = Float.parseFloat(in);
187        } else {
188            engine_gain = default_gain;
189        }
190        log.debug("engine gain: {}", engine_gain);
191
192        sleep_interval = setXMLSleepInterval(e); // Optional value
193        log.debug("sleep interval: {}", sleep_interval);
194
195        // Get the notch sounds
196        Iterator<Element> itr = (e.getChildren("notch-sound")).iterator();
197        int i = 0;
198        while (itr.hasNext()) {
199            el = itr.next();
200            int nn = Integer.parseInt(el.getChildText("notch"));
201            sb = new D3Notch(nn);
202            sb.setIdleNotch(false);
203            if ((idle_notch != null) && (nn == idle_notch)) {
204                sb.setIdleNotch(true);
205                log.debug("Notch {} set to Idle.", nn);
206            }
207
208            List<Element> elist = el.getChildren("file");
209            for (Element fe : elist) {
210                fn = fe.getText();
211                if (i == 0) {
212                    // Take the first notch-file to determine the audio formats (format, frequence and framesize)
213                    // All files of notch_sounds must have the same audio formats
214                    first_notch = nn;
215                    int[] formats;
216                    formats = AudioUtil.getWavFormats(D3Notch.getWavStream(vf, fn));
217                    frame_size = formats[2];
218                    freq = formats[1];
219                    sb.setBufferFmt(formats[0]);
220                    sb.setBufferFreq(formats[1]);
221                    sb.setBufferFrameSize(formats[2]);
222                    log.debug("formats: {}", formats);
223
224                    // Add some helper Buffers to the first notch
225                    for (int j = 0; j < number_helper_buffers; j++) {
226                        AudioBuffer bh = D3Notch.getBufferHelper(name + "_BUFFERHELPER_" + j, name + "_BUFFERHELPER_" + j);
227                        if (bh != null) {
228                            log.debug("helper buffer created: {}, name: {}", bh, bh.getSystemName());
229                            sb.addHelper(bh);
230                        }
231                    }
232                }
233
234                // Generate data slices from each notch sound file
235                List<ByteBuffer> l = D3Notch.getDataList(vf, fn, name + "_n" + i);
236                log.debug("{} internal sub buffers created from file {}:", l.size(), fn);
237                for (ByteBuffer b : l) {
238                    log.debug(" length: {} ms", (1000 * b.limit() / frame_size) / freq);
239                }
240                sb.addLoopData(l);
241            }
242
243            sb.setNextNotch(el.getChildText("next-notch"));
244            sb.setPrevNotch(el.getChildText("prev-notch"));
245            sb.setAccelLimit(el.getChildText("accel-limit"));
246            sb.setDecelLimit(el.getChildText("decel-limit"));
247
248            if (el.getChildText("accel-file") != null) {
249                sb.setAccelBuffer(D3Notch.getBuffer(vf, el.getChildText("accel-file"), name + "_na" + i, name + "_na" + i));
250            } else {
251                sb.setAccelBuffer(null);
252            }
253            if (el.getChildText("decel-file") != null) {
254                sb.setDecelBuffer(D3Notch.getBuffer(vf, el.getChildText("decel-file"), name + "_nd" + i, name + "_nd" + i));
255            } else {
256                sb.setDecelBuffer(null);
257            }
258            // Store in the list.
259            notch_sounds.put(nn, sb);
260            i++;
261        }
262
263        // Get the start and stop sounds (both sounds are optional)
264        el = e.getChild("start-sound");
265        if (el != null) {
266            fn = el.getChild("file").getValue();
267            start_buffer = D3Notch.getBuffer(vf, fn, name + "_start", name + "_Start");
268            log.debug("Start sound: {}, buffer {} created, length: {}", fn, start_buffer, SoundBite.calcLength(start_buffer));
269        }
270        el = e.getChild("shutdown-sound");
271        if (el != null) {
272            fn = el.getChild("file").getValue();
273            stop_buffer = D3Notch.getBuffer(vf, fn, name + "_shutdown", name + "_Shutdown");
274            log.debug("Shutdown sound: {}, buffer {} created, length: {}", fn, stop_buffer, SoundBite.calcLength(stop_buffer));
275        }
276
277        // Handle "grandfathering" the idle notch indication
278        // If the VSD designer did not explicitly designate an idle notch...
279        // Find the Notch with the lowest notch number, and make it the idle notch.
280        // If there's a tie, this will take the first value, but the notches /should/
281        // all have unique notch numbers.
282        if (idle_notch == null) {
283            D3Notch min_notch = null;
284            // No, this is not a terribly efficient "min" operation.  But that's OK.
285            for (D3Notch n : notch_sounds.values()) {
286                if ((min_notch == null) || (n.getNotch() < min_notch.getNotch())) {
287                    min_notch = n;
288                }
289            }
290            log.info("No Idle Notch Specified.  Choosing Notch {} to be the Idle Notch.", (min_notch != null ? min_notch.getNotch() : "min_notch not set"));
291            if (min_notch != null) {
292                min_notch.setIdleNotch(true);
293                idle_notch = min_notch.getNotch();
294            } else {
295                log.warn("Could not set idle notch because min_notch was still null");
296            }
297        }
298
299        // Kick-start the loop thread.
300        this.startThread();
301
302        // Check auto-start setting
303        autoStartCheck();
304    }
305
306    private static final Logger log = LoggerFactory.getLogger(Diesel3Sound.class);
307
308    private static class D3Notch {
309
310        private int my_notch;
311        private int next_notch;
312        private int prev_notch;
313        private int buffer_fmt;
314        private int buffer_freq;
315        private int buffer_frame_size;
316        private int loop_index;
317        private AudioBuffer accel_buf;
318        private AudioBuffer decel_buf;
319        private float accel_limit, decel_limit;
320        private List<AudioBuffer> bufs_helper = new ArrayList<>();
321        private List<ByteBuffer> loop_data = new ArrayList<>();
322        private boolean is_idle;
323
324        private D3Notch(int notch) {
325            my_notch = notch;
326            loop_index = 0;
327        }
328
329        private int getNotch() {
330            return my_notch;
331        }
332
333        private int getNextNotch() {
334            return next_notch;
335        }
336
337        private int getPrevNotch() {
338            return prev_notch;
339        }
340
341        private AudioBuffer getAccelBuffer() {
342            return accel_buf;
343        }
344
345        private AudioBuffer getDecelBuffer() {
346            return decel_buf;
347        }
348
349        private float getAccelLimit() {
350            return accel_limit;
351        }
352
353        private float getDecelLimit() {
354            return decel_limit;
355        }
356
357        private Boolean isInLimits(float val) {
358            return (val >= decel_limit) && (val <= accel_limit);
359        }
360
361        private void setBufferFmt(int fmt) {
362            buffer_fmt = fmt;
363        }
364
365        private int getBufferFmt() {
366            return buffer_fmt;
367        }
368
369        private void setBufferFreq(int freq) {
370            buffer_freq = freq;
371        }
372
373        private int getBufferFreq() {
374            return buffer_freq;
375        }
376
377        private void setBufferFrameSize(int framesize) {
378            buffer_frame_size = framesize;
379        }
380
381        private int getBufferFrameSize() {
382            return buffer_frame_size;
383        }
384
385        private Boolean isIdleNotch() {
386            return is_idle;
387        }
388
389        private void setNextNotch(String s) {
390            next_notch = setIntegerFromString(s);
391        }
392
393        private void setPrevNotch(String s) {
394            prev_notch = setIntegerFromString(s);
395        }
396
397        private void setAccelLimit(String s) {
398            accel_limit = setFloatFromString(s);
399        }
400
401        private void setDecelLimit(String s) {
402            decel_limit = setFloatFromString(s);
403        }
404
405        private void setAccelBuffer(AudioBuffer b) {
406            accel_buf = b;
407        }
408
409        private void setDecelBuffer(AudioBuffer b) {
410            decel_buf = b;
411        }
412
413        private void addLoopData(List<ByteBuffer> l) {
414            loop_data.addAll(l);
415        }
416
417        private ByteBuffer nextLoopData() {
418            return loop_data.get(incLoopIndex());
419        }
420
421        private void setIdleNotch(Boolean i) {
422            is_idle = i;
423        }
424
425        private void addHelper(AudioBuffer b) {
426            bufs_helper.add(b);
427        }
428
429        private int incLoopIndex() {
430            // Increment
431            loop_index++;
432            // Correct for wrap.
433            if (loop_index >= loop_data.size()) {
434                loop_index = 0;
435            }
436            return loop_index;
437        }
438
439        private int setIntegerFromString(String s) {
440            if (s == null) {
441                return 0;
442            }
443            try {
444                int n = Integer.parseInt(s);
445                return n;
446            } catch (NumberFormatException e) {
447                log.debug("Invalid integer: {}", s);
448                return 0;
449            }
450        }
451
452        private float setFloatFromString(String s) {
453            if (s == null) {
454                return 0.0f;
455            }
456            try {
457                float f = Float.parseFloat(s) / 100.0f;
458                return f;
459            } catch (NumberFormatException e) {
460                log.debug("Invalid float: {}", s);
461                return 0.0f;
462            }
463        }
464
465        private static List<ByteBuffer> getDataList(VSDFile vf, String filename, String sname) {
466            List<ByteBuffer> datalist = null;
467            java.io.InputStream ins = vf.getInputStream(filename);
468            if (ins != null) {
469                datalist = AudioUtil.getByteBufferList(ins, 250, 150);
470            } else {
471                log.debug("Input Stream failed");
472                return null;
473            }
474            return datalist;
475        }
476
477        private static AudioBuffer getBuffer(VSDFile vf, String filename, String sname, String uname) {
478            AudioBuffer buf = null;
479            jmri.AudioManager am = jmri.InstanceManager.getDefault(jmri.AudioManager.class);
480            try {
481                buf = (AudioBuffer) am.provideAudio(VSDSound.BufSysNamePrefix + sname);
482                buf.setUserName(VSDSound.BufUserNamePrefix + uname);
483                java.io.InputStream ins = vf.getInputStream(filename);
484                if (ins != null) {
485                    buf.setInputStream(ins);
486                } else {
487                    log.debug("Input Stream failed");
488                    return null;
489                }
490            } catch (AudioException | IllegalArgumentException ex) {
491                log.error("Problem creating SoundBite", ex);
492                return null;
493            }
494            log.debug("Buffer created: {}, name: {}", buf, buf.getSystemName());
495            return buf;
496        }
497
498        private static AudioBuffer getBufferHelper(String sname, String uname) {
499            AudioBuffer bf = null;
500            jmri.AudioManager am = jmri.InstanceManager.getDefault(jmri.AudioManager.class);
501            try {
502                bf = (AudioBuffer) am.provideAudio(VSDSound.BufSysNamePrefix + sname);
503                bf.setUserName(VSDSound.BufUserNamePrefix + uname);
504            } catch (AudioException | IllegalArgumentException ex) {
505                log.warn("problem creating SoundBite", ex);
506                return null;
507            }
508            log.debug("empty buffer created: {}, name: {}", bf, bf.getSystemName());
509            return bf;
510        }
511
512        private static java.io.InputStream getWavStream(VSDFile vf, String filename) {
513            java.io.InputStream ins = vf.getInputStream(filename);
514            if (ins != null) {
515                return ins;
516            } else {
517                log.warn("input Stream failed for {}", filename);
518                return null;
519            }
520        }
521
522        private static final Logger log = LoggerFactory.getLogger(D3Notch.class);
523
524    }
525
526    private static class D3LoopThread extends Thread {
527
528        private boolean is_running;
529        private boolean is_looping;
530        private boolean is_in_rampup_mode;
531        private Diesel3Sound _parent;
532        private D3Notch _notch;
533        private D3Notch notch1;
534        private SoundBite _sound;
535        private float _throttle;
536        private float rpm_dirfn;
537        private int helper_index;
538
539        private D3LoopThread(Diesel3Sound d, D3Notch n, String s, boolean r) {
540            super();
541            is_running = r;
542            is_looping = false;
543            is_in_rampup_mode = false;
544            _parent = d;
545            _notch = n;
546            _sound = new SoundBite(s);
547            _sound.setGain(_parent.engine_gain);
548            _throttle = 0.0f;
549            rpm_dirfn = 0.0f;
550            if (r) {
551                this.start();
552            }
553        }
554
555        private void setRunning(boolean r) {
556            is_running = r;
557        }
558
559        private void setThrottle(float t) {
560            if (_parent.isEngineStarted()) {
561                if (t < 0.0f) {
562                    t = 0.0f;
563                    is_in_rampup_mode = false; // interrupt ramp-up
564                }
565                _throttle = t;
566            }
567            log.debug("Throttle set: {}", _throttle);
568        }
569
570        private void getLocoDirection(int d) {
571            log.debug("loco direction: {}", d);
572
573            // React to a change in direction to slow down,
574            // then change direction, then ramp-up to the old speed
575            if (_throttle > 0.0f && _parent.isEngineStarted() && !is_in_rampup_mode) {
576                rpm_dirfn = _throttle; // save rpm for ramp-up
577                log.debug("speed {} saved", rpm_dirfn);
578                is_in_rampup_mode = true; // set a flag for the ramp-up
579                _throttle = 0.0f;
580                changeNotch(); // will slow down to 0
581            }
582        }
583
584        public void startEngine(AudioBuffer start_buf) {
585            _sound.unqueueBuffers();
586            log.debug("thread: start engine ...");
587
588            helper_index = -1; // Prepare helper buffer start; index will be incremented before first use
589            notch1 = _parent.getNotch(_parent.first_notch);
590
591            _sound.setReferenceDistance(_parent.engine_rd);
592            log.debug("set reference distance to {} for engine sound", _sound.getReferenceDistance());
593
594            _notch = _parent.getNotch(_parent.first_notch);
595            log.debug("Notch: {}, prev: {}, next: {}", _notch.getNotch(), _notch.getPrevNotch(), _notch.getNextNotch());
596
597            if (_parent.engine_pane != null) {
598                _parent.engine_pane.setThrottle(_notch.getNotch()); // Set EnginePane (DieselPane) notch
599            }
600
601            // Only queue the start buffer if we know we're in the idle notch.
602            // This is indicated by prevNotch == self.
603            if (_notch.isIdleNotch()) {
604                _sound.queueBuffer(start_buf);
605                if (_parent.engine_pane != null) {
606                    _parent.engine_pane.setButtonDelay(SoundBite.calcLength(start_buf));
607                }
608            } else {
609                setSound(_notch.nextLoopData());
610            }
611
612            // Follow up with another loop buffer.
613            setSound(_notch.nextLoopData());
614            is_looping = true;
615            if (_sound.getSource().getState() != Audio.STATE_PLAYING) {
616                _sound.play();
617            }
618        }
619
620        public void stopEngine(AudioBuffer stop_buf) {
621            log.debug("thread: stop engine ...");
622            is_looping = false; // stop the loop player
623            _throttle = 0.0f; // Clear it, just in case the engine was stopped at speed > 0
624            if (_parent.engine_pane != null) {
625                _parent.engine_pane.setButtonDelay(SoundBite.calcLength(stop_buf));
626            }
627            _sound.queueBuffer(stop_buf);
628            if (_sound.getSource().getState() != Audio.STATE_PLAYING) {
629                _sound.play();
630            }
631        }
632
633        // loop-player
634        @Override
635        public void run() {
636            try {
637                while (is_running) {
638                    if (is_looping && AudioUtil.isAudioRunning()) {
639                        if (_sound.getSource().numProcessedBuffers() > 0) {
640                            _sound.unqueueBuffers();
641                        }
642                        //log.debug("D3Loop {} Run loop. Buffers: {}", _sound.getName(), _sound.getSource().numQueuedBuffers());
643                        if (!_notch.isInLimits(_throttle)) {
644                            //log.debug("Notch Change! throttle: {}", _throttle);
645                            changeNotch();
646                        }
647                        if (_sound.getSource().numQueuedBuffers() < 2) {
648                            //log.debug("D3Loop {} Buffer count low ({}).  Adding buffer. Throttle: {}", _sound.getName(), _sound.getSource().numQueuedBuffers(), _throttle);
649                            setSound(_notch.nextLoopData());
650                        }
651                        checkAudioState();
652                    } else {
653                        if (_sound.getSource().numProcessedBuffers() > 0) {
654                            _sound.unqueueBuffers();
655                        }
656                    }
657                    sleep(_parent.sleep_interval);
658                    checkState();
659                }
660                _sound.stop();
661            } catch (InterruptedException ie) {
662                log.error("execption", ie);
663            }
664        }
665
666        private void checkAudioState() {
667            if (_sound.getSource().getState() != Audio.STATE_PLAYING) {
668                _sound.play();
669                log.info("loop sound re-started");
670            }
671        }
672
673        private void changeNotch() {
674            AudioBuffer transition_buf = null;
675            int new_notch = _notch.getNotch();
676
677            log.debug("D3Thread Change Throttle: {}, Accel Limit: {}, Decel Limit: {}", _throttle, _notch.getAccelLimit(), _notch.getDecelLimit());
678            if (_throttle > _notch.getAccelLimit()) {
679                // Too fast. Need to go to next notch up.
680                if (_notch.getNotch() < _notch.getNextNotch()) {
681                    // prepare for next notch up
682                    transition_buf = _notch.getAccelBuffer();
683                    new_notch = _notch.getNextNotch();
684                    //log.debug("Change up. notch: {}", new_notch);
685                }
686            } else if (_throttle < _notch.getDecelLimit()) {
687                // Too slow.  Need to go to next notch down.
688                transition_buf = _notch.getDecelBuffer();
689                new_notch = _notch.getPrevNotch();
690                log.debug("Change down. notch: {}", new_notch);
691            }
692            _parent.engine_pane.setThrottle(new_notch); // Update EnginePane (DieselPane) notch
693            // Now, regardless of whether we're going up or down, set the timer,
694            // fade the current sound, and move on.
695            if (transition_buf == null) {
696                // No transition sound to play.  Skip the timer bit
697                // Recurse directly to try the next notch
698                _notch = _parent.getNotch(new_notch);
699                log.debug("No transition sound defined.");
700            } else {
701                // queue up the transition sound buffer
702                _notch = _parent.getNotch(new_notch);
703                _sound.queueBuffer(transition_buf);
704                if (SoundBite.calcLength(transition_buf) > 50) {
705                    try {
706                        sleep(SoundBite.calcLength(transition_buf) - 50);
707                    } catch (InterruptedException e) {
708                    }
709                }
710            }
711        }
712
713        private void setSound(ByteBuffer data) {
714            AudioBuffer buf = notch1.bufs_helper.get(incHelperIndex()); // buffer for the queue
715            int sbl = 0; // sound bite length
716            int bbufcount; // number of bytes for the sound clip
717            ByteBuffer bbuf;
718            byte[] bbytes;
719
720            if (notch1.getBufferFreq() > 0) {
721                sbl = (1000 * data.limit() / notch1.getBufferFrameSize()) / notch1.getBufferFreq(); // calculate the length of the clip in milliseconds
722                // Prepare the sound and transfer it to the target ByteBuffer bbuf
723                bbufcount = notch1.getBufferFrameSize() * (sbl * notch1.getBufferFreq() / 1000);
724                bbuf = ByteBuffer.allocateDirect(bbufcount); // Target
725                bbytes = new byte[bbufcount];
726                data.get(bbytes); // Same as: data.get(bbytes, 0, bbufcount2);
727                data.rewind();
728                bbuf.order(data.order()); // Set new buffer's byte order to match source buffer.
729                bbuf.put(bbytes);
730                bbuf.rewind();
731                buf.loadBuffer(bbuf, notch1.getBufferFmt(), notch1.getBufferFreq());
732                _sound.queueBuffer(buf);
733            }
734        }
735
736        private void checkState() {
737            // Handle a throttle forward or reverse change
738            if (is_in_rampup_mode && _throttle == 0.0f && _notch.getNotch() == _parent.idle_notch) {
739                log.debug("now ramp-up to speed {}", rpm_dirfn);
740                is_in_rampup_mode = false;
741                _throttle = rpm_dirfn;
742            }
743        }
744
745        private int incHelperIndex() {
746            helper_index++;
747            // Correct for wrap.
748            if (helper_index >= _parent.number_helper_buffers) {
749                helper_index = 0;
750            }
751            return helper_index;
752        }
753
754        private void mute(boolean m) {
755            _sound.mute(m);
756        }
757
758        private void setVolume(float v) {
759            _sound.setVolume(v);
760        }
761
762        private void setPosition(PhysicalLocation p) {
763            _sound.setPosition(p);
764        }
765
766        private static final Logger log = LoggerFactory.getLogger(D3LoopThread.class);
767
768    }
769}