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