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 }