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}