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