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