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