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