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
26package com.sun.media.sound;
27
28import java.io.BufferedInputStream;
29import java.io.File;
30import java.io.FileInputStream;
31import java.io.FileNotFoundException;
32import java.io.FileOutputStream;
33import java.io.IOException;
34import java.io.InputStream;
35import java.io.OutputStream;
36import java.lang.ref.WeakReference;
37import java.security.AccessController;
38import java.security.PrivilegedAction;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.HashMap;
42import java.util.List;
43import java.util.Map;
44import java.util.Properties;
45import java.util.StringTokenizer;
46import java.util.prefs.BackingStoreException;
47import java.util.prefs.Preferences;
48
49import javax.sound.midi.Instrument;
50import javax.sound.midi.MidiChannel;
51import javax.sound.midi.MidiDevice;
52import javax.sound.midi.MidiSystem;
53import javax.sound.midi.MidiUnavailableException;
54import javax.sound.midi.Patch;
55import javax.sound.midi.Receiver;
56import javax.sound.midi.Soundbank;
57import javax.sound.midi.Transmitter;
58import javax.sound.midi.VoiceStatus;
59import javax.sound.sampled.AudioFormat;
60import javax.sound.sampled.AudioInputStream;
61import javax.sound.sampled.AudioSystem;
62import javax.sound.sampled.LineUnavailableException;
63import javax.sound.sampled.SourceDataLine;
64
65/**
66 * The software synthesizer class.
67 *
68 * @author Karl Helgason
69 */
70public 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
86        public void setInputStream(AudioInputStream stream)
87        {
88            this.stream = stream;
89        }
90
91        @Override
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
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
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
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
160        public AudioInputStream getAudioInputStream()
161        {
162            return new AudioInputStream(this, stream.getFormat(), AudioSystem.NOT_SPECIFIED);
163        }
164
165        @Override
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 {
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
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
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
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
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
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
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
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
388    SoftMainMixer getMainMixer() {
389        if (!isOpen())
390            return null;
391        return mainmixer;
392    }
393
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
455    int getVoiceAllocationMode() {
456        return voice_allocation_mode;
457    }
458
459    int getGeneralMidiMode() {
460        return gmmode;
461    }
462
463    void setGeneralMidiMode(int gmmode) {
464        this.gmmode = gmmode;
465    }
466
467    int getDeviceID() {
468        return deviceid;
469    }
470
471    float getControlRate() {
472        return controlrate;
473    }
474
475    SoftVoice[] getVoices() {
476        return voices;
477    }
478
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
490    public long getLatency() {
491        synchronized (control_mutex) {
492            return latency;
493        }
494    }
495
496    @Override
497    public AudioFormat getFormat() {
498        synchronized (control_mutex) {
499            return format;
500        }
501    }
502
503    @Override
504    public int getMaxPolyphony() {
505        synchronized (control_mutex) {
506            return maxpoly;
507        }
508    }
509
510    @Override
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
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
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
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
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
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
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
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
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
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
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
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
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
898    public MidiDevice.Info getDeviceInfo() {
899        return info;
900    }
901
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
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
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
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
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
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
1330    public boolean isOpen() {
1331        synchronized (control_mutex) {
1332            return open;
1333        }
1334    }
1335
1336    @Override
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
1348    public int getMaxReceivers() {
1349        return -1;
1350    }
1351
1352    @Override
1353    public int getMaxTransmitters() {
1354        return 0;
1355    }
1356
1357    @Override
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
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
1379    public Transmitter getTransmitter() throws MidiUnavailableException {
1380
1381        throw new MidiUnavailableException("No transmitter available");
1382    }
1383
1384    @Override
1385    public List<Transmitter> getTransmitters() {
1386
1387        return new ArrayList<>();
1388    }
1389
1390    @Override
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
1405    public Transmitter getTransmitterReferenceCounting()
1406            throws MidiUnavailableException {
1407
1408        throw new MidiUnavailableException("No transmitter available");
1409    }
1410}
1411