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 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(null, runnable, "Synthesizer",0,false).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 static final 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.isDirectory()) { 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("Linux")) { 673 674 File[] systemSoundFontsDir = new File[] { 675 /* Arch, Fedora, Mageia */ 676 new File("/usr/share/soundfonts/"), 677 new File("/usr/local/share/soundfonts/"), 678 /* Debian, Gentoo, OpenSUSE, Ubuntu */ 679 new File("/usr/share/sounds/sf2/"), 680 new File("/usr/local/share/sounds/sf2/"), 681 }; 682 683 /* 684 * Look for a default.sf2 685 */ 686 for (File systemSoundFontDir : systemSoundFontsDir) { 687 if (systemSoundFontDir.isDirectory()) { 688 File defaultSoundFont = new File(systemSoundFontDir, "default.sf2"); 689 if (defaultSoundFont.isFile()) { 690 try { 691 return new FileInputStream(defaultSoundFont); 692 } catch (IOException e) { 693 // continue with lookup 694 } 695 } 696 } 697 } 698 } 699 return null; 700 } 701 }); 702 703 actions.add(new PrivilegedAction<InputStream>() { 704 public InputStream run() { 705 if (System.getProperties().getProperty("os.name") 706 .startsWith("Windows")) { 707 File gm_dls = new File(System.getenv("SystemRoot") 708 + "\\system32\\drivers\\gm.dls"); 709 if (gm_dls.isFile()) { 710 try { 711 return new FileInputStream(gm_dls); 712 } catch (IOException e) { 713 } 714 } 715 } 716 return null; 717 } 718 }); 719 720 actions.add(new PrivilegedAction<InputStream>() { 721 public InputStream run() { 722 /* 723 * Try to load saved generated soundbank 724 */ 725 File userhome = new File(System.getProperty("user.home"), 726 ".gervill"); 727 File emg_soundbank_file = new File(userhome, 728 "soundbank-emg.sf2"); 729 if (emg_soundbank_file.isFile()) { 730 try { 731 return new FileInputStream(emg_soundbank_file); 732 } catch (IOException e) { 733 } 734 } 735 return null; 736 } 737 }); 738 739 for (PrivilegedAction<InputStream> action : actions) { 740 try { 741 InputStream is = AccessController.doPrivileged(action); 742 if(is == null) continue; 743 Soundbank sbk; 744 try { 745 sbk = MidiSystem.getSoundbank(new BufferedInputStream(is)); 746 } finally { 747 is.close(); 748 } 749 if (sbk != null) { 750 defaultSoundBank = sbk; 751 return defaultSoundBank; 752 } 753 } catch (Exception e) { 754 } 755 } 756 757 try { 758 /* 759 * Generate emergency soundbank 760 */ 761 defaultSoundBank = EmergencySoundbank.createSoundbank(); 762 } catch (Exception e) { 763 } 764 765 if (defaultSoundBank != null) { 766 /* 767 * Save generated soundbank to disk for faster future use. 768 */ 769 OutputStream out = AccessController 770 .doPrivileged((PrivilegedAction<OutputStream>) () -> { 771 try { 772 File userhome = new File(System 773 .getProperty("user.home"), ".gervill"); 774 if (!userhome.isDirectory()) { 775 if (!userhome.mkdirs()) { 776 return null; 777 } 778 } 779 File emg_soundbank_file = new File( 780 userhome, "soundbank-emg.sf2"); 781 if (emg_soundbank_file.isFile()) { 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 }