1 /* 2 * Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.io.BufferedInputStream; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileNotFoundException; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.OutputStream; 36 import java.lang.ref.WeakReference; 37 import java.security.AccessController; 38 import java.security.PrivilegedAction; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Properties; 45 import java.util.StringTokenizer; 46 import java.util.prefs.BackingStoreException; 47 import java.util.prefs.Preferences; 48 49 import javax.sound.midi.Instrument; 50 import javax.sound.midi.MidiChannel; 51 import javax.sound.midi.MidiDevice; 52 import javax.sound.midi.MidiSystem; 53 import javax.sound.midi.MidiUnavailableException; 54 import javax.sound.midi.Patch; 55 import javax.sound.midi.Receiver; 56 import javax.sound.midi.Soundbank; 57 import javax.sound.midi.Transmitter; 58 import javax.sound.midi.VoiceStatus; 59 import javax.sound.sampled.AudioFormat; 60 import javax.sound.sampled.AudioInputStream; 61 import javax.sound.sampled.AudioSystem; 62 import javax.sound.sampled.LineUnavailableException; 63 import javax.sound.sampled.SourceDataLine; 64 65 /** 66 * The software synthesizer class. 67 * 68 * @author Karl Helgason 69 */ 70 public final class SoftSynthesizer implements AudioSynthesizer, 71 ReferenceCountingDevice { 72 73 protected static final class WeakAudioStream extends InputStream 74 { 75 private volatile AudioInputStream stream; 76 public SoftAudioPusher pusher = null; 77 public AudioInputStream jitter_stream = null; 78 public SourceDataLine sourceDataLine = null; 79 public volatile long silent_samples = 0; 80 private int framesize = 0; 81 private WeakReference<AudioInputStream> weak_stream_link; 82 private AudioFloatConverter converter; 83 private float[] silentbuffer = null; 84 private int samplesize; 85 86 public void setInputStream(AudioInputStream stream) 87 { 88 this.stream = stream; 89 } 90 91 public int available() throws IOException { 92 AudioInputStream local_stream = stream; 93 if(local_stream != null) 94 return local_stream.available(); 95 return 0; 96 } 97 98 public int read() throws IOException { 99 byte[] b = new byte[1]; 100 if (read(b) == -1) 101 return -1; 102 return b[0] & 0xFF; 103 } 104 105 public int read(byte[] b, int off, int len) throws IOException { 106 AudioInputStream local_stream = stream; 107 if(local_stream != null) 108 return local_stream.read(b, off, len); 109 else 110 { 111 int flen = len / samplesize; 112 if(silentbuffer == null || silentbuffer.length < flen) 113 silentbuffer = new float[flen]; 114 converter.toByteArray(silentbuffer, flen, b, off); 115 116 silent_samples += (long)((len / framesize)); 117 118 if(pusher != null) 119 if(weak_stream_link.get() == null) 120 { 121 Runnable runnable = new Runnable() 122 { 123 SoftAudioPusher _pusher = pusher; 124 AudioInputStream _jitter_stream = jitter_stream; 125 SourceDataLine _sourceDataLine = sourceDataLine; 126 public void run() 127 { 128 _pusher.stop(); 129 if(_jitter_stream != null) 130 try { 131 _jitter_stream.close(); 132 } catch (IOException e) { 133 e.printStackTrace(); 134 } 135 if(_sourceDataLine != null) 136 _sourceDataLine.close(); 137 } 138 }; 139 pusher = null; 140 jitter_stream = null; 141 sourceDataLine = null; 142 new Thread(runnable).start(); 143 } 144 return len; 145 } 146 } 147 148 public WeakAudioStream(AudioInputStream stream) { 149 this.stream = stream; 150 weak_stream_link = new WeakReference<AudioInputStream>(stream); 151 converter = AudioFloatConverter.getConverter(stream.getFormat()); 152 samplesize = stream.getFormat().getFrameSize() / stream.getFormat().getChannels(); 153 framesize = stream.getFormat().getFrameSize(); 154 } 155 156 public AudioInputStream getAudioInputStream() 157 { 158 return new AudioInputStream(this, stream.getFormat(), AudioSystem.NOT_SPECIFIED); 159 } 160 161 public void close() throws IOException 162 { 163 AudioInputStream astream = weak_stream_link.get(); 164 if(astream != null) 165 astream.close(); 166 } 167 } 168 169 private static class Info extends MidiDevice.Info { 170 Info() { 171 super(INFO_NAME, INFO_VENDOR, INFO_DESCRIPTION, INFO_VERSION); 172 } 173 } 174 175 static final String INFO_NAME = "Gervill"; 176 static final String INFO_VENDOR = "OpenJDK"; 177 static final String INFO_DESCRIPTION = "Software MIDI Synthesizer"; 178 static final String INFO_VERSION = "1.0"; 179 final static MidiDevice.Info info = new Info(); 180 181 private static SourceDataLine testline = null; 182 183 private static Soundbank defaultSoundBank = null; 184 185 WeakAudioStream weakstream = null; 186 187 final Object control_mutex = this; 188 189 int voiceIDCounter = 0; 190 191 // 0: default 192 // 1: DLS Voice Allocation 193 int voice_allocation_mode = 0; 194 195 boolean load_default_soundbank = false; 196 boolean reverb_light = true; 197 boolean reverb_on = true; 198 boolean chorus_on = true; 199 boolean agc_on = true; 200 201 SoftChannel[] channels; 202 SoftChannelProxy[] external_channels = null; 203 204 private boolean largemode = false; 205 206 // 0: GM Mode off (default) 207 // 1: GM Level 1 208 // 2: GM Level 2 209 private int gmmode = 0; 210 211 private int deviceid = 0; 212 213 private AudioFormat format = new AudioFormat(44100, 16, 2, true, false); 214 215 private SourceDataLine sourceDataLine = null; 216 217 private SoftAudioPusher pusher = null; 218 private AudioInputStream pusher_stream = null; 219 220 private float controlrate = 147f; 221 222 private boolean open = false; 223 private boolean implicitOpen = false; 224 225 private String resamplerType = "linear"; 226 private SoftResampler resampler = new SoftLinearResampler(); 227 228 private int number_of_midi_channels = 16; 229 private int maxpoly = 64; 230 private long latency = 200000; // 200 msec 231 private boolean jitter_correction = false; 232 233 private SoftMainMixer mainmixer; 234 private SoftVoice[] voices; 235 236 private Map<String, SoftTuning> tunings 237 = new HashMap<String, SoftTuning>(); 238 private Map<String, SoftInstrument> inslist 239 = new HashMap<String, SoftInstrument>(); 240 private Map<String, ModelInstrument> loadedlist 241 = new HashMap<String, ModelInstrument>(); 242 243 private ArrayList<Receiver> recvslist = new ArrayList<Receiver>(); 244 245 private void getBuffers(ModelInstrument instrument, 246 List<ModelByteBuffer> buffers) { 247 for (ModelPerformer performer : instrument.getPerformers()) { 248 if (performer.getOscillators() != null) { 249 for (ModelOscillator osc : performer.getOscillators()) { 250 if (osc instanceof ModelByteBufferWavetable) { 251 ModelByteBufferWavetable w = (ModelByteBufferWavetable)osc; 252 ModelByteBuffer buff = w.getBuffer(); 253 if (buff != null) 254 buffers.add(buff); 255 buff = w.get8BitExtensionBuffer(); 256 if (buff != null) 257 buffers.add(buff); 258 } 259 } 260 } 261 } 262 } 263 264 private boolean loadSamples(List<ModelInstrument> instruments) { 265 if (largemode) 266 return true; 267 List<ModelByteBuffer> buffers = new ArrayList<ModelByteBuffer>(); 268 for (ModelInstrument instrument : instruments) 269 getBuffers(instrument, buffers); 270 try { 271 ModelByteBuffer.loadAll(buffers); 272 } catch (IOException e) { 273 return false; 274 } 275 return true; 276 } 277 278 private boolean loadInstruments(List<ModelInstrument> instruments) { 279 if (!isOpen()) 280 return false; 281 if (!loadSamples(instruments)) 282 return false; 283 284 synchronized (control_mutex) { 285 if (channels != null) 286 for (SoftChannel c : channels) 287 { 288 c.current_instrument = null; 289 c.current_director = null; 290 } 291 for (Instrument instrument : instruments) { 292 String pat = patchToString(instrument.getPatch()); 293 SoftInstrument softins 294 = new SoftInstrument((ModelInstrument) instrument); 295 inslist.put(pat, softins); 296 loadedlist.put(pat, (ModelInstrument) instrument); 297 } 298 } 299 300 return true; 301 } 302 303 private void processPropertyInfo(Map<String, Object> info) { 304 AudioSynthesizerPropertyInfo[] items = getPropertyInfo(info); 305 306 String resamplerType = (String)items[0].value; 307 if (resamplerType.equalsIgnoreCase("point")) 308 { 309 this.resampler = new SoftPointResampler(); 310 this.resamplerType = "point"; 311 } 312 else if (resamplerType.equalsIgnoreCase("linear")) 313 { 314 this.resampler = new SoftLinearResampler2(); 315 this.resamplerType = "linear"; 316 } 317 else if (resamplerType.equalsIgnoreCase("linear1")) 318 { 319 this.resampler = new SoftLinearResampler(); 320 this.resamplerType = "linear1"; 321 } 322 else if (resamplerType.equalsIgnoreCase("linear2")) 323 { 324 this.resampler = new SoftLinearResampler2(); 325 this.resamplerType = "linear2"; 326 } 327 else if (resamplerType.equalsIgnoreCase("cubic")) 328 { 329 this.resampler = new SoftCubicResampler(); 330 this.resamplerType = "cubic"; 331 } 332 else if (resamplerType.equalsIgnoreCase("lanczos")) 333 { 334 this.resampler = new SoftLanczosResampler(); 335 this.resamplerType = "lanczos"; 336 } 337 else if (resamplerType.equalsIgnoreCase("sinc")) 338 { 339 this.resampler = new SoftSincResampler(); 340 this.resamplerType = "sinc"; 341 } 342 343 setFormat((AudioFormat)items[2].value); 344 controlrate = (Float)items[1].value; 345 latency = (Long)items[3].value; 346 deviceid = (Integer)items[4].value; 347 maxpoly = (Integer)items[5].value; 348 reverb_on = (Boolean)items[6].value; 349 chorus_on = (Boolean)items[7].value; 350 agc_on = (Boolean)items[8].value; 351 largemode = (Boolean)items[9].value; 352 number_of_midi_channels = (Integer)items[10].value; 353 jitter_correction = (Boolean)items[11].value; 354 reverb_light = (Boolean)items[12].value; 355 load_default_soundbank = (Boolean)items[13].value; 356 } 357 358 private String patchToString(Patch patch) { 359 if (patch instanceof ModelPatch && ((ModelPatch) patch).isPercussion()) 360 return "p." + patch.getProgram() + "." + patch.getBank(); 361 else 362 return patch.getProgram() + "." + patch.getBank(); 363 } 364 365 private void setFormat(AudioFormat format) { 366 if (format.getChannels() > 2) { 367 throw new IllegalArgumentException( 368 "Only mono and stereo audio supported."); 369 } 370 if (AudioFloatConverter.getConverter(format) == null) 371 throw new IllegalArgumentException("Audio format not supported."); 372 this.format = format; 373 } 374 375 void removeReceiver(Receiver recv) { 376 boolean perform_close = false; 377 synchronized (control_mutex) { 378 if (recvslist.remove(recv)) { 379 if (implicitOpen && recvslist.isEmpty()) 380 perform_close = true; 381 } 382 } 383 if (perform_close) 384 close(); 385 } 386 387 SoftMainMixer getMainMixer() { 388 if (!isOpen()) 389 return null; 390 return mainmixer; 391 } 392 393 SoftInstrument findInstrument(int program, int bank, int channel) { 394 395 // Add support for GM2 banks 0x78 and 0x79 396 // as specified in DLS 2.2 in Section 1.4.6 397 // which allows using percussion and melodic instruments 398 // on all channels 399 if (bank >> 7 == 0x78 || bank >> 7 == 0x79) { 400 SoftInstrument current_instrument 401 = inslist.get(program + "." + bank); 402 if (current_instrument != null) 403 return current_instrument; 404 405 String p_plaf; 406 if (bank >> 7 == 0x78) 407 p_plaf = "p."; 408 else 409 p_plaf = ""; 410 411 // Instrument not found fallback to MSB:bank, LSB:0 412 current_instrument = inslist.get(p_plaf + program + "." 413 + ((bank & 128) << 7)); 414 if (current_instrument != null) 415 return current_instrument; 416 // Instrument not found fallback to MSB:0, LSB:bank 417 current_instrument = inslist.get(p_plaf + program + "." 418 + (bank & 128)); 419 if (current_instrument != null) 420 return current_instrument; 421 // Instrument not found fallback to MSB:0, LSB:0 422 current_instrument = inslist.get(p_plaf + program + ".0"); 423 if (current_instrument != null) 424 return current_instrument; 425 // Instrument not found fallback to MSB:0, LSB:0, program=0 426 current_instrument = inslist.get(p_plaf + program + "0.0"); 427 if (current_instrument != null) 428 return current_instrument; 429 return null; 430 } 431 432 // Channel 10 uses percussion instruments 433 String p_plaf; 434 if (channel == 9) 435 p_plaf = "p."; 436 else 437 p_plaf = ""; 438 439 SoftInstrument current_instrument 440 = inslist.get(p_plaf + program + "." + bank); 441 if (current_instrument != null) 442 return current_instrument; 443 // Instrument not found fallback to MSB:0, LSB:0 444 current_instrument = inslist.get(p_plaf + program + ".0"); 445 if (current_instrument != null) 446 return current_instrument; 447 // Instrument not found fallback to MSB:0, LSB:0, program=0 448 current_instrument = inslist.get(p_plaf + "0.0"); 449 if (current_instrument != null) 450 return current_instrument; 451 return null; 452 } 453 454 int getVoiceAllocationMode() { 455 return voice_allocation_mode; 456 } 457 458 int getGeneralMidiMode() { 459 return gmmode; 460 } 461 462 void setGeneralMidiMode(int gmmode) { 463 this.gmmode = gmmode; 464 } 465 466 int getDeviceID() { 467 return deviceid; 468 } 469 470 float getControlRate() { 471 return controlrate; 472 } 473 474 SoftVoice[] getVoices() { 475 return voices; 476 } 477 478 SoftTuning getTuning(Patch patch) { 479 String t_id = patchToString(patch); 480 SoftTuning tuning = tunings.get(t_id); 481 if (tuning == null) { 482 tuning = new SoftTuning(patch); 483 tunings.put(t_id, tuning); 484 } 485 return tuning; 486 } 487 488 public long getLatency() { 489 synchronized (control_mutex) { 490 return latency; 491 } 492 } 493 494 public AudioFormat getFormat() { 495 synchronized (control_mutex) { 496 return format; 497 } 498 } 499 500 public int getMaxPolyphony() { 501 synchronized (control_mutex) { 502 return maxpoly; 503 } 504 } 505 506 public MidiChannel[] getChannels() { 507 508 synchronized (control_mutex) { 509 // if (external_channels == null) => the synthesizer is not open, 510 // create 16 proxy channels 511 // otherwise external_channels has the same length as channels array 512 if (external_channels == null) { 513 external_channels = new SoftChannelProxy[16]; 514 for (int i = 0; i < external_channels.length; i++) 515 external_channels[i] = new SoftChannelProxy(); 516 } 517 MidiChannel[] ret; 518 if (isOpen()) 519 ret = new MidiChannel[channels.length]; 520 else 521 ret = new MidiChannel[16]; 522 for (int i = 0; i < ret.length; i++) 523 ret[i] = external_channels[i]; 524 return ret; 525 } 526 } 527 528 public VoiceStatus[] getVoiceStatus() { 529 if (!isOpen()) { 530 VoiceStatus[] tempVoiceStatusArray 531 = new VoiceStatus[getMaxPolyphony()]; 532 for (int i = 0; i < tempVoiceStatusArray.length; i++) { 533 VoiceStatus b = new VoiceStatus(); 534 b.active = false; 535 b.bank = 0; 536 b.channel = 0; 537 b.note = 0; 538 b.program = 0; 539 b.volume = 0; 540 tempVoiceStatusArray[i] = b; 541 } 542 return tempVoiceStatusArray; 543 } 544 545 synchronized (control_mutex) { 546 VoiceStatus[] tempVoiceStatusArray = new VoiceStatus[voices.length]; 547 for (int i = 0; i < voices.length; i++) { 548 VoiceStatus a = voices[i]; 549 VoiceStatus b = new VoiceStatus(); 550 b.active = a.active; 551 b.bank = a.bank; 552 b.channel = a.channel; 553 b.note = a.note; 554 b.program = a.program; 555 b.volume = a.volume; 556 tempVoiceStatusArray[i] = b; 557 } 558 return tempVoiceStatusArray; 559 } 560 } 561 562 public boolean isSoundbankSupported(Soundbank soundbank) { 563 for (Instrument ins: soundbank.getInstruments()) 564 if (!(ins instanceof ModelInstrument)) 565 return false; 566 return true; 567 } 568 569 public boolean loadInstrument(Instrument instrument) { 570 if (instrument == null || (!(instrument instanceof ModelInstrument))) { 571 throw new IllegalArgumentException("Unsupported instrument: " + 572 instrument); 573 } 574 List<ModelInstrument> instruments = new ArrayList<ModelInstrument>(); 575 instruments.add((ModelInstrument)instrument); 576 return loadInstruments(instruments); 577 } 578 579 public void unloadInstrument(Instrument instrument) { 580 if (instrument == null || (!(instrument instanceof ModelInstrument))) { 581 throw new IllegalArgumentException("Unsupported instrument: " + 582 instrument); 583 } 584 if (!isOpen()) 585 return; 586 587 String pat = patchToString(instrument.getPatch()); 588 synchronized (control_mutex) { 589 for (SoftChannel c: channels) 590 c.current_instrument = null; 591 inslist.remove(pat); 592 loadedlist.remove(pat); 593 for (int i = 0; i < channels.length; i++) { 594 channels[i].allSoundOff(); 595 } 596 } 597 } 598 599 public boolean remapInstrument(Instrument from, Instrument to) { 600 601 if (from == null) 602 throw new NullPointerException(); 603 if (to == null) 604 throw new NullPointerException(); 605 if (!(from instanceof ModelInstrument)) { 606 throw new IllegalArgumentException("Unsupported instrument: " + 607 from.toString()); 608 } 609 if (!(to instanceof ModelInstrument)) { 610 throw new IllegalArgumentException("Unsupported instrument: " + 611 to.toString()); 612 } 613 if (!isOpen()) 614 return false; 615 616 synchronized (control_mutex) { 617 if (!loadedlist.containsValue(to)) 618 throw new IllegalArgumentException("Instrument to is not loaded."); 619 unloadInstrument(from); 620 ModelMappedInstrument mfrom = new ModelMappedInstrument( 621 (ModelInstrument)to, from.getPatch()); 622 return loadInstrument(mfrom); 623 } 624 } 625 626 public Soundbank getDefaultSoundbank() { 627 synchronized (SoftSynthesizer.class) { 628 if (defaultSoundBank != null) 629 return defaultSoundBank; 630 631 List<PrivilegedAction<InputStream>> actions = 632 new ArrayList<PrivilegedAction<InputStream>>(); 633 634 actions.add(new PrivilegedAction<InputStream>() { 635 public InputStream run() { 636 File javahome = new File(System.getProperties() 637 .getProperty("java.home")); 638 File libaudio = new File(new File(javahome, "lib"), "audio"); 639 if (libaudio.exists()) { 640 File foundfile = null; 641 File[] files = libaudio.listFiles(); 642 if (files != null) { 643 for (int i = 0; i < files.length; i++) { 644 File file = files[i]; 645 if (file.isFile()) { 646 String lname = file.getName().toLowerCase(); 647 if (lname.endsWith(".sf2") 648 || lname.endsWith(".dls")) { 649 if (foundfile == null 650 || (file.length() > foundfile 651 .length())) { 652 foundfile = file; 653 } 654 } 655 } 656 } 657 } 658 if (foundfile != null) { 659 try { 660 return new FileInputStream(foundfile); 661 } catch (IOException e) { 662 } 663 } 664 } 665 return null; 666 } 667 }); 668 669 actions.add(new PrivilegedAction<InputStream>() { 670 public InputStream run() { 671 if (System.getProperties().getProperty("os.name") 672 .startsWith("Windows")) { 673 File gm_dls = new File(System.getenv("SystemRoot") 674 + "\\system32\\drivers\\gm.dls"); 675 if (gm_dls.exists()) { 676 try { 677 return new FileInputStream(gm_dls); 678 } catch (IOException e) { 679 } 680 } 681 } 682 return null; 683 } 684 }); 685 686 actions.add(new PrivilegedAction<InputStream>() { 687 public InputStream run() { 688 /* 689 * Try to load saved generated soundbank 690 */ 691 File userhome = new File(System.getProperty("user.home"), 692 ".gervill"); 693 File emg_soundbank_file = new File(userhome, 694 "soundbank-emg.sf2"); 695 if (emg_soundbank_file.exists()) { 696 try { 697 return new FileInputStream(emg_soundbank_file); 698 } catch (IOException e) { 699 } 700 } 701 return null; 702 } 703 }); 704 705 for (PrivilegedAction<InputStream> action : actions) { 706 try { 707 InputStream is = AccessController.doPrivileged(action); 708 if(is == null) continue; 709 Soundbank sbk; 710 try { 711 sbk = MidiSystem.getSoundbank(new BufferedInputStream(is)); 712 } finally { 713 is.close(); 714 } 715 if (sbk != null) { 716 defaultSoundBank = sbk; 717 return defaultSoundBank; 718 } 719 } catch (Exception e) { 720 } 721 } 722 723 try { 724 /* 725 * Generate emergency soundbank 726 */ 727 defaultSoundBank = EmergencySoundbank.createSoundbank(); 728 } catch (Exception e) { 729 } 730 731 if (defaultSoundBank != null) { 732 /* 733 * Save generated soundbank to disk for faster future use. 734 */ 735 OutputStream out = AccessController 736 .doPrivileged((PrivilegedAction<OutputStream>) () -> { 737 try { 738 File userhome = new File(System 739 .getProperty("user.home"), ".gervill"); 740 if (!userhome.exists()) { 741 userhome.mkdirs(); 742 } 743 File emg_soundbank_file = new File( 744 userhome, "soundbank-emg.sf2"); 745 if (emg_soundbank_file.exists()) { 746 return null; 747 } 748 return new FileOutputStream(emg_soundbank_file); 749 } catch (final FileNotFoundException ignored) { 750 } 751 return null; 752 }); 753 if (out != null) { 754 try { 755 ((SF2Soundbank) defaultSoundBank).save(out); 756 out.close(); 757 } catch (final IOException ignored) { 758 } 759 } 760 } 761 } 762 return defaultSoundBank; 763 } 764 765 public Instrument[] getAvailableInstruments() { 766 Soundbank defsbk = getDefaultSoundbank(); 767 if (defsbk == null) 768 return new Instrument[0]; 769 Instrument[] inslist_array = defsbk.getInstruments(); 770 Arrays.sort(inslist_array, new ModelInstrumentComparator()); 771 return inslist_array; 772 } 773 774 public Instrument[] getLoadedInstruments() { 775 if (!isOpen()) 776 return new Instrument[0]; 777 778 synchronized (control_mutex) { 779 ModelInstrument[] inslist_array = 780 new ModelInstrument[loadedlist.values().size()]; 781 loadedlist.values().toArray(inslist_array); 782 Arrays.sort(inslist_array, new ModelInstrumentComparator()); 783 return inslist_array; 784 } 785 } 786 787 public boolean loadAllInstruments(Soundbank soundbank) { 788 List<ModelInstrument> instruments = new ArrayList<ModelInstrument>(); 789 for (Instrument ins: soundbank.getInstruments()) { 790 if (ins == null || !(ins instanceof ModelInstrument)) { 791 throw new IllegalArgumentException( 792 "Unsupported instrument: " + ins); 793 } 794 instruments.add((ModelInstrument)ins); 795 } 796 return loadInstruments(instruments); 797 } 798 799 public void unloadAllInstruments(Soundbank soundbank) { 800 if (soundbank == null || !isSoundbankSupported(soundbank)) 801 throw new IllegalArgumentException("Unsupported soundbank: " + soundbank); 802 803 if (!isOpen()) 804 return; 805 806 for (Instrument ins: soundbank.getInstruments()) { 807 if (ins instanceof ModelInstrument) { 808 unloadInstrument(ins); 809 } 810 } 811 } 812 813 public boolean loadInstruments(Soundbank soundbank, Patch[] patchList) { 814 List<ModelInstrument> instruments = new ArrayList<ModelInstrument>(); 815 for (Patch patch: patchList) { 816 Instrument ins = soundbank.getInstrument(patch); 817 if (ins == null || !(ins instanceof ModelInstrument)) { 818 throw new IllegalArgumentException( 819 "Unsupported instrument: " + ins); 820 } 821 instruments.add((ModelInstrument)ins); 822 } 823 return loadInstruments(instruments); 824 } 825 826 public void unloadInstruments(Soundbank soundbank, Patch[] patchList) { 827 if (soundbank == null || !isSoundbankSupported(soundbank)) 828 throw new IllegalArgumentException("Unsupported soundbank: " + soundbank); 829 830 if (!isOpen()) 831 return; 832 833 for (Patch pat: patchList) { 834 Instrument ins = soundbank.getInstrument(pat); 835 if (ins instanceof ModelInstrument) { 836 unloadInstrument(ins); 837 } 838 } 839 } 840 841 public MidiDevice.Info getDeviceInfo() { 842 return info; 843 } 844 845 private Properties getStoredProperties() { 846 return AccessController 847 .doPrivileged((PrivilegedAction<Properties>) () -> { 848 Properties p = new Properties(); 849 String notePath = "/com/sun/media/sound/softsynthesizer"; 850 try { 851 Preferences prefroot = Preferences.userRoot(); 852 if (prefroot.nodeExists(notePath)) { 853 Preferences prefs = prefroot.node(notePath); 854 String[] prefs_keys = prefs.keys(); 855 for (String prefs_key : prefs_keys) { 856 String val = prefs.get(prefs_key, null); 857 if (val != null) { 858 p.setProperty(prefs_key, val); 859 } 860 } 861 } 862 } catch (final BackingStoreException ignored) { 863 } 864 return p; 865 }); 866 } 867 868 public AudioSynthesizerPropertyInfo[] getPropertyInfo(Map<String, Object> info) { 869 List<AudioSynthesizerPropertyInfo> list = 870 new ArrayList<AudioSynthesizerPropertyInfo>(); 871 872 AudioSynthesizerPropertyInfo item; 873 874 // If info != null or synthesizer is closed 875 // we return how the synthesizer will be set on next open 876 // If info == null and synthesizer is open 877 // we return current synthesizer properties. 878 boolean o = info == null && open; 879 880 item = new AudioSynthesizerPropertyInfo("interpolation", o?resamplerType:"linear"); 881 item.choices = new String[]{"linear", "linear1", "linear2", "cubic", 882 "lanczos", "sinc", "point"}; 883 item.description = "Interpolation method"; 884 list.add(item); 885 886 item = new AudioSynthesizerPropertyInfo("control rate", o?controlrate:147f); 887 item.description = "Control rate"; 888 list.add(item); 889 890 item = new AudioSynthesizerPropertyInfo("format", 891 o?format:new AudioFormat(44100, 16, 2, true, false)); 892 item.description = "Default audio format"; 893 list.add(item); 894 895 item = new AudioSynthesizerPropertyInfo("latency", o?latency:120000L); 896 item.description = "Default latency"; 897 list.add(item); 898 899 item = new AudioSynthesizerPropertyInfo("device id", o?deviceid:0); 900 item.description = "Device ID for SysEx Messages"; 901 list.add(item); 902 903 item = new AudioSynthesizerPropertyInfo("max polyphony", o?maxpoly:64); 904 item.description = "Maximum polyphony"; 905 list.add(item); 906 907 item = new AudioSynthesizerPropertyInfo("reverb", o?reverb_on:true); 908 item.description = "Turn reverb effect on or off"; 909 list.add(item); 910 911 item = new AudioSynthesizerPropertyInfo("chorus", o?chorus_on:true); 912 item.description = "Turn chorus effect on or off"; 913 list.add(item); 914 915 item = new AudioSynthesizerPropertyInfo("auto gain control", o?agc_on:true); 916 item.description = "Turn auto gain control on or off"; 917 list.add(item); 918 919 item = new AudioSynthesizerPropertyInfo("large mode", o?largemode:false); 920 item.description = "Turn large mode on or off."; 921 list.add(item); 922 923 item = new AudioSynthesizerPropertyInfo("midi channels", o?channels.length:16); 924 item.description = "Number of midi channels."; 925 list.add(item); 926 927 item = new AudioSynthesizerPropertyInfo("jitter correction", o?jitter_correction:true); 928 item.description = "Turn jitter correction on or off."; 929 list.add(item); 930 931 item = new AudioSynthesizerPropertyInfo("light reverb", o?reverb_light:true); 932 item.description = "Turn light reverb mode on or off"; 933 list.add(item); 934 935 item = new AudioSynthesizerPropertyInfo("load default soundbank", o?load_default_soundbank:true); 936 item.description = "Enabled/disable loading default soundbank"; 937 list.add(item); 938 939 AudioSynthesizerPropertyInfo[] items; 940 items = list.toArray(new AudioSynthesizerPropertyInfo[list.size()]); 941 942 Properties storedProperties = getStoredProperties(); 943 944 for (AudioSynthesizerPropertyInfo item2 : items) { 945 Object v = (info == null) ? null : info.get(item2.name); 946 v = (v != null) ? v : storedProperties.getProperty(item2.name); 947 if (v != null) { 948 Class c = (item2.valueClass); 949 if (c.isInstance(v)) 950 item2.value = v; 951 else if (v instanceof String) { 952 String s = (String) v; 953 if (c == Boolean.class) { 954 if (s.equalsIgnoreCase("true")) 955 item2.value = Boolean.TRUE; 956 if (s.equalsIgnoreCase("false")) 957 item2.value = Boolean.FALSE; 958 } else if (c == AudioFormat.class) { 959 int channels = 2; 960 boolean signed = true; 961 boolean bigendian = false; 962 int bits = 16; 963 float sampleRate = 44100f; 964 try { 965 StringTokenizer st = new StringTokenizer(s, ", "); 966 String prevToken = ""; 967 while (st.hasMoreTokens()) { 968 String token = st.nextToken().toLowerCase(); 969 if (token.equals("mono")) 970 channels = 1; 971 if (token.startsWith("channel")) 972 channels = Integer.parseInt(prevToken); 973 if (token.contains("unsigned")) 974 signed = false; 975 if (token.equals("big-endian")) 976 bigendian = true; 977 if (token.equals("bit")) 978 bits = Integer.parseInt(prevToken); 979 if (token.equals("hz")) 980 sampleRate = Float.parseFloat(prevToken); 981 prevToken = token; 982 } 983 item2.value = new AudioFormat(sampleRate, bits, 984 channels, signed, bigendian); 985 } catch (NumberFormatException e) { 986 } 987 988 } else 989 try { 990 if (c == Byte.class) 991 item2.value = Byte.valueOf(s); 992 else if (c == Short.class) 993 item2.value = Short.valueOf(s); 994 else if (c == Integer.class) 995 item2.value = Integer.valueOf(s); 996 else if (c == Long.class) 997 item2.value = Long.valueOf(s); 998 else if (c == Float.class) 999 item2.value = Float.valueOf(s); 1000 else if (c == Double.class) 1001 item2.value = Double.valueOf(s); 1002 } catch (NumberFormatException e) { 1003 } 1004 } else if (v instanceof Number) { 1005 Number n = (Number) v; 1006 if (c == Byte.class) 1007 item2.value = Byte.valueOf(n.byteValue()); 1008 if (c == Short.class) 1009 item2.value = Short.valueOf(n.shortValue()); 1010 if (c == Integer.class) 1011 item2.value = Integer.valueOf(n.intValue()); 1012 if (c == Long.class) 1013 item2.value = Long.valueOf(n.longValue()); 1014 if (c == Float.class) 1015 item2.value = Float.valueOf(n.floatValue()); 1016 if (c == Double.class) 1017 item2.value = Double.valueOf(n.doubleValue()); 1018 } 1019 } 1020 } 1021 1022 return items; 1023 } 1024 1025 public void open() throws MidiUnavailableException { 1026 if (isOpen()) { 1027 synchronized (control_mutex) { 1028 implicitOpen = false; 1029 } 1030 return; 1031 } 1032 open(null, null); 1033 } 1034 1035 public void open(SourceDataLine line, Map<String, Object> info) throws MidiUnavailableException { 1036 if (isOpen()) { 1037 synchronized (control_mutex) { 1038 implicitOpen = false; 1039 } 1040 return; 1041 } 1042 synchronized (control_mutex) { 1043 try { 1044 if (line != null) { 1045 // can throw IllegalArgumentException 1046 setFormat(line.getFormat()); 1047 } 1048 1049 AudioInputStream ais = openStream(getFormat(), info); 1050 1051 weakstream = new WeakAudioStream(ais); 1052 ais = weakstream.getAudioInputStream(); 1053 1054 if (line == null) 1055 { 1056 if (testline != null) { 1057 line = testline; 1058 } else { 1059 // can throw LineUnavailableException, 1060 // IllegalArgumentException, SecurityException 1061 line = AudioSystem.getSourceDataLine(getFormat()); 1062 } 1063 } 1064 1065 double latency = this.latency; 1066 1067 if (!line.isOpen()) { 1068 int bufferSize = getFormat().getFrameSize() 1069 * (int)(getFormat().getFrameRate() * (latency/1000000f)); 1070 // can throw LineUnavailableException, 1071 // IllegalArgumentException, SecurityException 1072 line.open(getFormat(), bufferSize); 1073 1074 // Remember that we opened that line 1075 // so we can close again in SoftSynthesizer.close() 1076 sourceDataLine = line; 1077 } 1078 if (!line.isActive()) 1079 line.start(); 1080 1081 int controlbuffersize = 512; 1082 try { 1083 controlbuffersize = ais.available(); 1084 } catch (IOException e) { 1085 } 1086 1087 // Tell mixer not fill read buffers fully. 1088 // This lowers latency, and tells DataPusher 1089 // to read in smaller amounts. 1090 //mainmixer.readfully = false; 1091 //pusher = new DataPusher(line, ais); 1092 1093 int buffersize = line.getBufferSize(); 1094 buffersize -= buffersize % controlbuffersize; 1095 1096 if (buffersize < 3 * controlbuffersize) 1097 buffersize = 3 * controlbuffersize; 1098 1099 if (jitter_correction) { 1100 ais = new SoftJitterCorrector(ais, buffersize, 1101 controlbuffersize); 1102 if(weakstream != null) 1103 weakstream.jitter_stream = ais; 1104 } 1105 pusher = new SoftAudioPusher(line, ais, controlbuffersize); 1106 pusher_stream = ais; 1107 pusher.start(); 1108 1109 if(weakstream != null) 1110 { 1111 weakstream.pusher = pusher; 1112 weakstream.sourceDataLine = sourceDataLine; 1113 } 1114 1115 } catch (final LineUnavailableException | SecurityException 1116 | IllegalArgumentException e) { 1117 if (isOpen()) { 1118 close(); 1119 } 1120 // am: need MidiUnavailableException(Throwable) ctor! 1121 MidiUnavailableException ex = new MidiUnavailableException( 1122 "Can not open line"); 1123 ex.initCause(e); 1124 throw ex; 1125 } 1126 } 1127 } 1128 1129 public AudioInputStream openStream(AudioFormat targetFormat, 1130 Map<String, Object> info) throws MidiUnavailableException { 1131 1132 if (isOpen()) 1133 throw new MidiUnavailableException("Synthesizer is already open"); 1134 1135 synchronized (control_mutex) { 1136 1137 gmmode = 0; 1138 voice_allocation_mode = 0; 1139 1140 processPropertyInfo(info); 1141 1142 open = true; 1143 implicitOpen = false; 1144 1145 if (targetFormat != null) 1146 setFormat(targetFormat); 1147 1148 if (load_default_soundbank) 1149 { 1150 Soundbank defbank = getDefaultSoundbank(); 1151 if (defbank != null) { 1152 loadAllInstruments(defbank); 1153 } 1154 } 1155 1156 voices = new SoftVoice[maxpoly]; 1157 for (int i = 0; i < maxpoly; i++) 1158 voices[i] = new SoftVoice(this); 1159 1160 mainmixer = new SoftMainMixer(this); 1161 1162 channels = new SoftChannel[number_of_midi_channels]; 1163 for (int i = 0; i < channels.length; i++) 1164 channels[i] = new SoftChannel(this, i); 1165 1166 if (external_channels == null) { 1167 // Always create external_channels array 1168 // with 16 or more channels 1169 // so getChannels works correctly 1170 // when the synhtesizer is closed. 1171 if (channels.length < 16) 1172 external_channels = new SoftChannelProxy[16]; 1173 else 1174 external_channels = new SoftChannelProxy[channels.length]; 1175 for (int i = 0; i < external_channels.length; i++) 1176 external_channels[i] = new SoftChannelProxy(); 1177 } else { 1178 // We must resize external_channels array 1179 // but we must also copy the old SoftChannelProxy 1180 // into the new one 1181 if (channels.length > external_channels.length) { 1182 SoftChannelProxy[] new_external_channels 1183 = new SoftChannelProxy[channels.length]; 1184 for (int i = 0; i < external_channels.length; i++) 1185 new_external_channels[i] = external_channels[i]; 1186 for (int i = external_channels.length; 1187 i < new_external_channels.length; i++) { 1188 new_external_channels[i] = new SoftChannelProxy(); 1189 } 1190 } 1191 } 1192 1193 for (int i = 0; i < channels.length; i++) 1194 external_channels[i].setChannel(channels[i]); 1195 1196 for (SoftVoice voice: getVoices()) 1197 voice.resampler = resampler.openStreamer(); 1198 1199 for (Receiver recv: getReceivers()) { 1200 SoftReceiver srecv = ((SoftReceiver)recv); 1201 srecv.open = open; 1202 srecv.mainmixer = mainmixer; 1203 srecv.midimessages = mainmixer.midimessages; 1204 } 1205 1206 return mainmixer.getInputStream(); 1207 } 1208 } 1209 1210 public void close() { 1211 1212 if (!isOpen()) 1213 return; 1214 1215 SoftAudioPusher pusher_to_be_closed = null; 1216 AudioInputStream pusher_stream_to_be_closed = null; 1217 synchronized (control_mutex) { 1218 if (pusher != null) { 1219 pusher_to_be_closed = pusher; 1220 pusher_stream_to_be_closed = pusher_stream; 1221 pusher = null; 1222 pusher_stream = null; 1223 } 1224 } 1225 1226 if (pusher_to_be_closed != null) { 1227 // Pusher must not be closed synchronized against control_mutex, 1228 // this may result in synchronized conflict between pusher 1229 // and current thread. 1230 pusher_to_be_closed.stop(); 1231 1232 try { 1233 pusher_stream_to_be_closed.close(); 1234 } catch (IOException e) { 1235 //e.printStackTrace(); 1236 } 1237 } 1238 1239 synchronized (control_mutex) { 1240 1241 if (mainmixer != null) 1242 mainmixer.close(); 1243 open = false; 1244 implicitOpen = false; 1245 mainmixer = null; 1246 voices = null; 1247 channels = null; 1248 1249 if (external_channels != null) 1250 for (int i = 0; i < external_channels.length; i++) 1251 external_channels[i].setChannel(null); 1252 1253 if (sourceDataLine != null) { 1254 sourceDataLine.close(); 1255 sourceDataLine = null; 1256 } 1257 1258 inslist.clear(); 1259 loadedlist.clear(); 1260 tunings.clear(); 1261 1262 while (recvslist.size() != 0) 1263 recvslist.get(recvslist.size() - 1).close(); 1264 1265 } 1266 } 1267 1268 public boolean isOpen() { 1269 synchronized (control_mutex) { 1270 return open; 1271 } 1272 } 1273 1274 public long getMicrosecondPosition() { 1275 1276 if (!isOpen()) 1277 return 0; 1278 1279 synchronized (control_mutex) { 1280 return mainmixer.getMicrosecondPosition(); 1281 } 1282 } 1283 1284 public int getMaxReceivers() { 1285 return -1; 1286 } 1287 1288 public int getMaxTransmitters() { 1289 return 0; 1290 } 1291 1292 public Receiver getReceiver() throws MidiUnavailableException { 1293 1294 synchronized (control_mutex) { 1295 SoftReceiver receiver = new SoftReceiver(this); 1296 receiver.open = open; 1297 recvslist.add(receiver); 1298 return receiver; 1299 } 1300 } 1301 1302 public List<Receiver> getReceivers() { 1303 1304 synchronized (control_mutex) { 1305 ArrayList<Receiver> recvs = new ArrayList<Receiver>(); 1306 recvs.addAll(recvslist); 1307 return recvs; 1308 } 1309 } 1310 1311 public Transmitter getTransmitter() throws MidiUnavailableException { 1312 1313 throw new MidiUnavailableException("No transmitter available"); 1314 } 1315 1316 public List<Transmitter> getTransmitters() { 1317 1318 return new ArrayList<Transmitter>(); 1319 } 1320 1321 public Receiver getReceiverReferenceCounting() 1322 throws MidiUnavailableException { 1323 1324 if (!isOpen()) { 1325 open(); 1326 synchronized (control_mutex) { 1327 implicitOpen = true; 1328 } 1329 } 1330 1331 return getReceiver(); 1332 } 1333 1334 public Transmitter getTransmitterReferenceCounting() 1335 throws MidiUnavailableException { 1336 1337 throw new MidiUnavailableException("No transmitter available"); 1338 } 1339 }