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.isDirectory()) { 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.isDirectory()) { 690 File defaultSoundFont = new File(systemSoundFontDir, "default.sf2"); 691 if (defaultSoundFont.isFile()) { 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.isFile()) { 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.isFile()) { 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.isDirectory()) { 777 if (!userhome.mkdirs()) { 778 return null; 779 } 780 } 781 File emg_soundbank_file = new File( 782 userhome, "soundbank-emg.sf2"); 783 if (emg_soundbank_file.isFile()) { 784 return null; 785 } 786 return new FileOutputStream(emg_soundbank_file); 787 } catch (final FileNotFoundException ignored) { 788 } 789 return null; 790 }); 791 if (out != null) { 792 try { 793 ((SF2Soundbank) defaultSoundBank).save(out); 794 out.close(); 795 } catch (final IOException ignored) { 796 } 797 } 798 } 799 } 800 return defaultSoundBank; 801 } 802 803 public Instrument[] getAvailableInstruments() { 804 Soundbank defsbk = getDefaultSoundbank(); 805 if (defsbk == null) 806 return new Instrument[0]; 807 Instrument[] inslist_array = defsbk.getInstruments(); 808 Arrays.sort(inslist_array, new ModelInstrumentComparator()); 809 return inslist_array; 810 } 811 812 public Instrument[] getLoadedInstruments() { 813 if (!isOpen()) 814 return new Instrument[0]; 815 816 synchronized (control_mutex) { 817 ModelInstrument[] inslist_array = 818 new ModelInstrument[loadedlist.values().size()]; 819 loadedlist.values().toArray(inslist_array); 820 Arrays.sort(inslist_array, new ModelInstrumentComparator()); 821 return inslist_array; 822 } 823 } 824 825 public boolean loadAllInstruments(Soundbank soundbank) { 826 List<ModelInstrument> instruments = new ArrayList<ModelInstrument>(); 827 for (Instrument ins: soundbank.getInstruments()) { 828 if (ins == null || !(ins instanceof ModelInstrument)) { 829 throw new IllegalArgumentException( 830 "Unsupported instrument: " + ins); 831 } 832 instruments.add((ModelInstrument)ins); 833 } 834 return loadInstruments(instruments); 835 } 836 837 public void unloadAllInstruments(Soundbank soundbank) { 838 if (soundbank == null || !isSoundbankSupported(soundbank)) 839 throw new IllegalArgumentException("Unsupported soundbank: " + soundbank); 840 841 if (!isOpen()) 842 return; 843 844 for (Instrument ins: soundbank.getInstruments()) { 845 if (ins instanceof ModelInstrument) { 846 unloadInstrument(ins); 847 } 848 } 849 } 850 851 public boolean loadInstruments(Soundbank soundbank, Patch[] patchList) { 852 List<ModelInstrument> instruments = new ArrayList<ModelInstrument>(); 853 for (Patch patch: patchList) { 854 Instrument ins = soundbank.getInstrument(patch); 855 if (ins == null || !(ins instanceof ModelInstrument)) { 856 throw new IllegalArgumentException( 857 "Unsupported instrument: " + ins); 858 } 859 instruments.add((ModelInstrument)ins); 860 } 861 return loadInstruments(instruments); 862 } 863 864 public void unloadInstruments(Soundbank soundbank, Patch[] patchList) { 865 if (soundbank == null || !isSoundbankSupported(soundbank)) 866 throw new IllegalArgumentException("Unsupported soundbank: " + soundbank); 867 868 if (!isOpen()) 869 return; 870 871 for (Patch pat: patchList) { 872 Instrument ins = soundbank.getInstrument(pat); 873 if (ins instanceof ModelInstrument) { 874 unloadInstrument(ins); 875 } 876 } 877 } 878 879 public MidiDevice.Info getDeviceInfo() { 880 return info; 881 } 882 883 private Properties getStoredProperties() { 884 return AccessController 885 .doPrivileged((PrivilegedAction<Properties>) () -> { 886 Properties p = new Properties(); 887 String notePath = "/com/sun/media/sound/softsynthesizer"; 888 try { 889 Preferences prefroot = Preferences.userRoot(); 890 if (prefroot.nodeExists(notePath)) { 891 Preferences prefs = prefroot.node(notePath); 892 String[] prefs_keys = prefs.keys(); 893 for (String prefs_key : prefs_keys) { 894 String val = prefs.get(prefs_key, null); 895 if (val != null) { 896 p.setProperty(prefs_key, val); 897 } 898 } 899 } 900 } catch (final BackingStoreException ignored) { 901 } 902 return p; 903 }); 904 } 905 906 public AudioSynthesizerPropertyInfo[] getPropertyInfo(Map<String, Object> info) { 907 List<AudioSynthesizerPropertyInfo> list = 908 new ArrayList<AudioSynthesizerPropertyInfo>(); 909 910 AudioSynthesizerPropertyInfo item; 911 912 // If info != null or synthesizer is closed 913 // we return how the synthesizer will be set on next open 914 // If info == null and synthesizer is open 915 // we return current synthesizer properties. 916 boolean o = info == null && open; 917 918 item = new AudioSynthesizerPropertyInfo("interpolation", o?resamplerType:"linear"); 919 item.choices = new String[]{"linear", "linear1", "linear2", "cubic", 920 "lanczos", "sinc", "point"}; 921 item.description = "Interpolation method"; 922 list.add(item); 923 924 item = new AudioSynthesizerPropertyInfo("control rate", o?controlrate:147f); 925 item.description = "Control rate"; 926 list.add(item); 927 928 item = new AudioSynthesizerPropertyInfo("format", 929 o?format:new AudioFormat(44100, 16, 2, true, false)); 930 item.description = "Default audio format"; 931 list.add(item); 932 933 item = new AudioSynthesizerPropertyInfo("latency", o?latency:120000L); 934 item.description = "Default latency"; 935 list.add(item); 936 937 item = new AudioSynthesizerPropertyInfo("device id", o?deviceid:0); 938 item.description = "Device ID for SysEx Messages"; 939 list.add(item); 940 941 item = new AudioSynthesizerPropertyInfo("max polyphony", o?maxpoly:64); 942 item.description = "Maximum polyphony"; 943 list.add(item); 944 945 item = new AudioSynthesizerPropertyInfo("reverb", o?reverb_on:true); 946 item.description = "Turn reverb effect on or off"; 947 list.add(item); 948 949 item = new AudioSynthesizerPropertyInfo("chorus", o?chorus_on:true); 950 item.description = "Turn chorus effect on or off"; 951 list.add(item); 952 953 item = new AudioSynthesizerPropertyInfo("auto gain control", o?agc_on:true); 954 item.description = "Turn auto gain control on or off"; 955 list.add(item); 956 957 item = new AudioSynthesizerPropertyInfo("large mode", o?largemode:false); 958 item.description = "Turn large mode on or off."; 959 list.add(item); 960 961 item = new AudioSynthesizerPropertyInfo("midi channels", o?channels.length:16); 962 item.description = "Number of midi channels."; 963 list.add(item); 964 965 item = new AudioSynthesizerPropertyInfo("jitter correction", o?jitter_correction:true); 966 item.description = "Turn jitter correction on or off."; 967 list.add(item); 968 969 item = new AudioSynthesizerPropertyInfo("light reverb", o?reverb_light:true); 970 item.description = "Turn light reverb mode on or off"; 971 list.add(item); 972 973 item = new AudioSynthesizerPropertyInfo("load default soundbank", o?load_default_soundbank:true); 974 item.description = "Enabled/disable loading default soundbank"; 975 list.add(item); 976 977 AudioSynthesizerPropertyInfo[] items; 978 items = list.toArray(new AudioSynthesizerPropertyInfo[list.size()]); 979 980 Properties storedProperties = getStoredProperties(); 981 982 for (AudioSynthesizerPropertyInfo item2 : items) { 983 Object v = (info == null) ? null : info.get(item2.name); 984 v = (v != null) ? v : storedProperties.getProperty(item2.name); 985 if (v != null) { 986 Class<?> c = (item2.valueClass); 987 if (c.isInstance(v)) 988 item2.value = v; 989 else if (v instanceof String) { 990 String s = (String) v; 991 if (c == Boolean.class) { 992 if (s.equalsIgnoreCase("true")) 993 item2.value = Boolean.TRUE; 994 if (s.equalsIgnoreCase("false")) 995 item2.value = Boolean.FALSE; 996 } else if (c == AudioFormat.class) { 997 int channels = 2; 998 boolean signed = true; 999 boolean bigendian = false; 1000 int bits = 16; 1001 float sampleRate = 44100f; 1002 try { 1003 StringTokenizer st = new StringTokenizer(s, ", "); 1004 String prevToken = ""; 1005 while (st.hasMoreTokens()) { 1006 String token = st.nextToken().toLowerCase(); 1007 if (token.equals("mono")) 1008 channels = 1; 1009 if (token.startsWith("channel")) 1010 channels = Integer.parseInt(prevToken); 1011 if (token.contains("unsigned")) 1012 signed = false; 1013 if (token.equals("big-endian")) 1014 bigendian = true; 1015 if (token.equals("bit")) 1016 bits = Integer.parseInt(prevToken); 1017 if (token.equals("hz")) 1018 sampleRate = Float.parseFloat(prevToken); 1019 prevToken = token; 1020 } 1021 item2.value = new AudioFormat(sampleRate, bits, 1022 channels, signed, bigendian); 1023 } catch (NumberFormatException e) { 1024 } 1025 1026 } else 1027 try { 1028 if (c == Byte.class) 1029 item2.value = Byte.valueOf(s); 1030 else if (c == Short.class) 1031 item2.value = Short.valueOf(s); 1032 else if (c == Integer.class) 1033 item2.value = Integer.valueOf(s); 1034 else if (c == Long.class) 1035 item2.value = Long.valueOf(s); 1036 else if (c == Float.class) 1037 item2.value = Float.valueOf(s); 1038 else if (c == Double.class) 1039 item2.value = Double.valueOf(s); 1040 } catch (NumberFormatException e) { 1041 } 1042 } else if (v instanceof Number) { 1043 Number n = (Number) v; 1044 if (c == Byte.class) 1045 item2.value = Byte.valueOf(n.byteValue()); 1046 if (c == Short.class) 1047 item2.value = Short.valueOf(n.shortValue()); 1048 if (c == Integer.class) 1049 item2.value = Integer.valueOf(n.intValue()); 1050 if (c == Long.class) 1051 item2.value = Long.valueOf(n.longValue()); 1052 if (c == Float.class) 1053 item2.value = Float.valueOf(n.floatValue()); 1054 if (c == Double.class) 1055 item2.value = Double.valueOf(n.doubleValue()); 1056 } 1057 } 1058 } 1059 1060 return items; 1061 } 1062 1063 public void open() throws MidiUnavailableException { 1064 if (isOpen()) { 1065 synchronized (control_mutex) { 1066 implicitOpen = false; 1067 } 1068 return; 1069 } 1070 open(null, null); 1071 } 1072 1073 public void open(SourceDataLine line, Map<String, Object> info) throws MidiUnavailableException { 1074 if (isOpen()) { 1075 synchronized (control_mutex) { 1076 implicitOpen = false; 1077 } 1078 return; 1079 } 1080 synchronized (control_mutex) { 1081 try { 1082 if (line != null) { 1083 // can throw IllegalArgumentException 1084 setFormat(line.getFormat()); 1085 } 1086 1087 AudioInputStream ais = openStream(getFormat(), info); 1088 1089 weakstream = new WeakAudioStream(ais); 1090 ais = weakstream.getAudioInputStream(); 1091 1092 if (line == null) 1093 { 1094 if (testline != null) { 1095 line = testline; 1096 } else { 1097 // can throw LineUnavailableException, 1098 // IllegalArgumentException, SecurityException 1099 line = AudioSystem.getSourceDataLine(getFormat()); 1100 } 1101 } 1102 1103 double latency = this.latency; 1104 1105 if (!line.isOpen()) { 1106 int bufferSize = getFormat().getFrameSize() 1107 * (int)(getFormat().getFrameRate() * (latency/1000000f)); 1108 // can throw LineUnavailableException, 1109 // IllegalArgumentException, SecurityException 1110 line.open(getFormat(), bufferSize); 1111 1112 // Remember that we opened that line 1113 // so we can close again in SoftSynthesizer.close() 1114 sourceDataLine = line; 1115 } 1116 if (!line.isActive()) 1117 line.start(); 1118 1119 int controlbuffersize = 512; 1120 try { 1121 controlbuffersize = ais.available(); 1122 } catch (IOException e) { 1123 } 1124 1125 // Tell mixer not fill read buffers fully. 1126 // This lowers latency, and tells DataPusher 1127 // to read in smaller amounts. 1128 //mainmixer.readfully = false; 1129 //pusher = new DataPusher(line, ais); 1130 1131 int buffersize = line.getBufferSize(); 1132 buffersize -= buffersize % controlbuffersize; 1133 1134 if (buffersize < 3 * controlbuffersize) 1135 buffersize = 3 * controlbuffersize; 1136 1137 if (jitter_correction) { 1138 ais = new SoftJitterCorrector(ais, buffersize, 1139 controlbuffersize); 1140 if(weakstream != null) 1141 weakstream.jitter_stream = ais; 1142 } 1143 pusher = new SoftAudioPusher(line, ais, controlbuffersize); 1144 pusher_stream = ais; 1145 pusher.start(); 1146 1147 if(weakstream != null) 1148 { 1149 weakstream.pusher = pusher; 1150 weakstream.sourceDataLine = sourceDataLine; 1151 } 1152 1153 } catch (final LineUnavailableException | SecurityException 1154 | IllegalArgumentException e) { 1155 if (isOpen()) { 1156 close(); 1157 } 1158 // am: need MidiUnavailableException(Throwable) ctor! 1159 MidiUnavailableException ex = new MidiUnavailableException( 1160 "Can not open line"); 1161 ex.initCause(e); 1162 throw ex; 1163 } 1164 } 1165 } 1166 1167 public AudioInputStream openStream(AudioFormat targetFormat, 1168 Map<String, Object> info) throws MidiUnavailableException { 1169 1170 if (isOpen()) 1171 throw new MidiUnavailableException("Synthesizer is already open"); 1172 1173 synchronized (control_mutex) { 1174 1175 gmmode = 0; 1176 voice_allocation_mode = 0; 1177 1178 processPropertyInfo(info); 1179 1180 open = true; 1181 implicitOpen = false; 1182 1183 if (targetFormat != null) 1184 setFormat(targetFormat); 1185 1186 if (load_default_soundbank) 1187 { 1188 Soundbank defbank = getDefaultSoundbank(); 1189 if (defbank != null) { 1190 loadAllInstruments(defbank); 1191 } 1192 } 1193 1194 voices = new SoftVoice[maxpoly]; 1195 for (int i = 0; i < maxpoly; i++) 1196 voices[i] = new SoftVoice(this); 1197 1198 mainmixer = new SoftMainMixer(this); 1199 1200 channels = new SoftChannel[number_of_midi_channels]; 1201 for (int i = 0; i < channels.length; i++) 1202 channels[i] = new SoftChannel(this, i); 1203 1204 if (external_channels == null) { 1205 // Always create external_channels array 1206 // with 16 or more channels 1207 // so getChannels works correctly 1208 // when the synhtesizer is closed. 1209 if (channels.length < 16) 1210 external_channels = new SoftChannelProxy[16]; 1211 else 1212 external_channels = new SoftChannelProxy[channels.length]; 1213 for (int i = 0; i < external_channels.length; i++) 1214 external_channels[i] = new SoftChannelProxy(); 1215 } else { 1216 // We must resize external_channels array 1217 // but we must also copy the old SoftChannelProxy 1218 // into the new one 1219 if (channels.length > external_channels.length) { 1220 SoftChannelProxy[] new_external_channels 1221 = new SoftChannelProxy[channels.length]; 1222 for (int i = 0; i < external_channels.length; i++) 1223 new_external_channels[i] = external_channels[i]; 1224 for (int i = external_channels.length; 1225 i < new_external_channels.length; i++) { 1226 new_external_channels[i] = new SoftChannelProxy(); 1227 } 1228 } 1229 } 1230 1231 for (int i = 0; i < channels.length; i++) 1232 external_channels[i].setChannel(channels[i]); 1233 1234 for (SoftVoice voice: getVoices()) 1235 voice.resampler = resampler.openStreamer(); 1236 1237 for (Receiver recv: getReceivers()) { 1238 SoftReceiver srecv = ((SoftReceiver)recv); 1239 srecv.open = open; 1240 srecv.mainmixer = mainmixer; 1241 srecv.midimessages = mainmixer.midimessages; 1242 } 1243 1244 return mainmixer.getInputStream(); 1245 } 1246 } 1247 1248 public void close() { 1249 1250 if (!isOpen()) 1251 return; 1252 1253 SoftAudioPusher pusher_to_be_closed = null; 1254 AudioInputStream pusher_stream_to_be_closed = null; 1255 synchronized (control_mutex) { 1256 if (pusher != null) { 1257 pusher_to_be_closed = pusher; 1258 pusher_stream_to_be_closed = pusher_stream; 1259 pusher = null; 1260 pusher_stream = null; 1261 } 1262 } 1263 1264 if (pusher_to_be_closed != null) { 1265 // Pusher must not be closed synchronized against control_mutex, 1266 // this may result in synchronized conflict between pusher 1267 // and current thread. 1268 pusher_to_be_closed.stop(); 1269 1270 try { 1271 pusher_stream_to_be_closed.close(); 1272 } catch (IOException e) { 1273 //e.printStackTrace(); 1274 } 1275 } 1276 1277 synchronized (control_mutex) { 1278 1279 if (mainmixer != null) 1280 mainmixer.close(); 1281 open = false; 1282 implicitOpen = false; 1283 mainmixer = null; 1284 voices = null; 1285 channels = null; 1286 1287 if (external_channels != null) 1288 for (int i = 0; i < external_channels.length; i++) 1289 external_channels[i].setChannel(null); 1290 1291 if (sourceDataLine != null) { 1292 sourceDataLine.close(); 1293 sourceDataLine = null; 1294 } 1295 1296 inslist.clear(); 1297 loadedlist.clear(); 1298 tunings.clear(); 1299 1300 while (recvslist.size() != 0) 1301 recvslist.get(recvslist.size() - 1).close(); 1302 1303 } 1304 } 1305 1306 public boolean isOpen() { 1307 synchronized (control_mutex) { 1308 return open; 1309 } 1310 } 1311 1312 public long getMicrosecondPosition() { 1313 1314 if (!isOpen()) 1315 return 0; 1316 1317 synchronized (control_mutex) { 1318 return mainmixer.getMicrosecondPosition(); 1319 } 1320 } 1321 1322 public int getMaxReceivers() { 1323 return -1; 1324 } 1325 1326 public int getMaxTransmitters() { 1327 return 0; 1328 } 1329 1330 public Receiver getReceiver() throws MidiUnavailableException { 1331 1332 synchronized (control_mutex) { 1333 SoftReceiver receiver = new SoftReceiver(this); 1334 receiver.open = open; 1335 recvslist.add(receiver); 1336 return receiver; 1337 } 1338 } 1339 1340 public List<Receiver> getReceivers() { 1341 1342 synchronized (control_mutex) { 1343 ArrayList<Receiver> recvs = new ArrayList<Receiver>(); 1344 recvs.addAll(recvslist); 1345 return recvs; 1346 } 1347 } 1348 1349 public Transmitter getTransmitter() throws MidiUnavailableException { 1350 1351 throw new MidiUnavailableException("No transmitter available"); 1352 } 1353 1354 public List<Transmitter> getTransmitters() { 1355 1356 return new ArrayList<Transmitter>(); 1357 } 1358 1359 public Receiver getReceiverReferenceCounting() 1360 throws MidiUnavailableException { 1361 1362 if (!isOpen()) { 1363 open(); 1364 synchronized (control_mutex) { 1365 implicitOpen = true; 1366 } 1367 } 1368 1369 return getReceiver(); 1370 } 1371 1372 public Transmitter getTransmitterReferenceCounting() 1373 throws MidiUnavailableException { 1374 1375 throw new MidiUnavailableException("No transmitter available"); 1376 } 1377 }