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}