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