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