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