jdk/src/java.desktop/share/classes/com/sun/media/sound/SoftSynthesizer.java
changeset 25859 3317bb8137f4
parent 24548 9c007a986347
child 26037 508779ce6619
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/media/sound/SoftSynthesizer.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,1351 @@
+/*
+ * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.media.sound;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+import javax.sound.midi.Instrument;
+import javax.sound.midi.MidiChannel;
+import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiSystem;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.Patch;
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Soundbank;
+import javax.sound.midi.Transmitter;
+import javax.sound.midi.VoiceStatus;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.SourceDataLine;
+
+/**
+ * The software synthesizer class.
+ *
+ * @author Karl Helgason
+ */
+public final class SoftSynthesizer implements AudioSynthesizer,
+        ReferenceCountingDevice {
+
+    protected static final class WeakAudioStream extends InputStream
+    {
+        private volatile AudioInputStream stream;
+        public SoftAudioPusher pusher = null;
+        public AudioInputStream jitter_stream = null;
+        public SourceDataLine sourceDataLine = null;
+        public volatile long silent_samples = 0;
+        private int framesize = 0;
+        private WeakReference<AudioInputStream> weak_stream_link;
+        private AudioFloatConverter converter;
+        private float[] silentbuffer = null;
+        private int samplesize;
+
+        public void setInputStream(AudioInputStream stream)
+        {
+            this.stream = stream;
+        }
+
+        public int available() throws IOException {
+            AudioInputStream local_stream = stream;
+            if(local_stream != null)
+                return local_stream.available();
+            return 0;
+        }
+
+        public int read() throws IOException {
+             byte[] b = new byte[1];
+             if (read(b) == -1)
+                  return -1;
+             return b[0] & 0xFF;
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+             AudioInputStream local_stream = stream;
+             if(local_stream != null)
+                 return local_stream.read(b, off, len);
+             else
+             {
+                 int flen = len / samplesize;
+                 if(silentbuffer == null || silentbuffer.length < flen)
+                     silentbuffer = new float[flen];
+                 converter.toByteArray(silentbuffer, flen, b, off);
+
+                 silent_samples += (long)((len / framesize));
+
+                 if(pusher != null)
+                 if(weak_stream_link.get() == null)
+                 {
+                     Runnable runnable = new Runnable()
+                     {
+                         SoftAudioPusher _pusher = pusher;
+                         AudioInputStream _jitter_stream = jitter_stream;
+                         SourceDataLine _sourceDataLine = sourceDataLine;
+                         public void run()
+                         {
+                             _pusher.stop();
+                             if(_jitter_stream != null)
+                                try {
+                                    _jitter_stream.close();
+                                } catch (IOException e) {
+                                    e.printStackTrace();
+                                }
+                             if(_sourceDataLine != null)
+                                 _sourceDataLine.close();
+                         }
+                     };
+                     pusher = null;
+                     jitter_stream = null;
+                     sourceDataLine = null;
+                     new Thread(runnable).start();
+                 }
+                 return len;
+             }
+        }
+
+        public WeakAudioStream(AudioInputStream stream) {
+            this.stream = stream;
+            weak_stream_link = new WeakReference<AudioInputStream>(stream);
+            converter = AudioFloatConverter.getConverter(stream.getFormat());
+            samplesize = stream.getFormat().getFrameSize() / stream.getFormat().getChannels();
+            framesize = stream.getFormat().getFrameSize();
+        }
+
+        public AudioInputStream getAudioInputStream()
+        {
+            return new AudioInputStream(this, stream.getFormat(), AudioSystem.NOT_SPECIFIED);
+        }
+
+        public void close() throws IOException
+        {
+            AudioInputStream astream  = weak_stream_link.get();
+            if(astream != null)
+                astream.close();
+        }
+    }
+
+    private static class Info extends MidiDevice.Info {
+        Info() {
+            super(INFO_NAME, INFO_VENDOR, INFO_DESCRIPTION, INFO_VERSION);
+        }
+    }
+
+    static final String INFO_NAME = "Gervill";
+    static final String INFO_VENDOR = "OpenJDK";
+    static final String INFO_DESCRIPTION = "Software MIDI Synthesizer";
+    static final String INFO_VERSION = "1.0";
+    final static MidiDevice.Info info = new Info();
+
+    private static SourceDataLine testline = null;
+
+    private static Soundbank defaultSoundBank = null;
+
+    WeakAudioStream weakstream = null;
+
+    final Object control_mutex = this;
+
+    int voiceIDCounter = 0;
+
+    // 0: default
+    // 1: DLS Voice Allocation
+    int voice_allocation_mode = 0;
+
+    boolean load_default_soundbank = false;
+    boolean reverb_light = true;
+    boolean reverb_on = true;
+    boolean chorus_on = true;
+    boolean agc_on = true;
+
+    SoftChannel[] channels;
+    SoftChannelProxy[] external_channels = null;
+
+    private boolean largemode = false;
+
+    // 0: GM Mode off (default)
+    // 1: GM Level 1
+    // 2: GM Level 2
+    private int gmmode = 0;
+
+    private int deviceid = 0;
+
+    private AudioFormat format = new AudioFormat(44100, 16, 2, true, false);
+
+    private SourceDataLine sourceDataLine = null;
+
+    private SoftAudioPusher pusher = null;
+    private AudioInputStream pusher_stream = null;
+
+    private float controlrate = 147f;
+
+    private boolean open = false;
+    private boolean implicitOpen = false;
+
+    private String resamplerType = "linear";
+    private SoftResampler resampler = new SoftLinearResampler();
+
+    private int number_of_midi_channels = 16;
+    private int maxpoly = 64;
+    private long latency = 200000; // 200 msec
+    private boolean jitter_correction = false;
+
+    private SoftMainMixer mainmixer;
+    private SoftVoice[] voices;
+
+    private Map<String, SoftTuning> tunings
+            = new HashMap<String, SoftTuning>();
+    private Map<String, SoftInstrument> inslist
+            = new HashMap<String, SoftInstrument>();
+    private Map<String, ModelInstrument> loadedlist
+            = new HashMap<String, ModelInstrument>();
+
+    private ArrayList<Receiver> recvslist = new ArrayList<Receiver>();
+
+    private void getBuffers(ModelInstrument instrument,
+            List<ModelByteBuffer> buffers) {
+        for (ModelPerformer performer : instrument.getPerformers()) {
+            if (performer.getOscillators() != null) {
+                for (ModelOscillator osc : performer.getOscillators()) {
+                    if (osc instanceof ModelByteBufferWavetable) {
+                        ModelByteBufferWavetable w = (ModelByteBufferWavetable)osc;
+                        ModelByteBuffer buff = w.getBuffer();
+                        if (buff != null)
+                            buffers.add(buff);
+                        buff = w.get8BitExtensionBuffer();
+                        if (buff != null)
+                            buffers.add(buff);
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean loadSamples(List<ModelInstrument> instruments) {
+        if (largemode)
+            return true;
+        List<ModelByteBuffer> buffers = new ArrayList<ModelByteBuffer>();
+        for (ModelInstrument instrument : instruments)
+            getBuffers(instrument, buffers);
+        try {
+            ModelByteBuffer.loadAll(buffers);
+        } catch (IOException e) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean loadInstruments(List<ModelInstrument> instruments) {
+        if (!isOpen())
+            return false;
+        if (!loadSamples(instruments))
+            return false;
+
+        synchronized (control_mutex) {
+            if (channels != null)
+                for (SoftChannel c : channels)
+                {
+                    c.current_instrument = null;
+                    c.current_director = null;
+                }
+            for (Instrument instrument : instruments) {
+                String pat = patchToString(instrument.getPatch());
+                SoftInstrument softins
+                        = new SoftInstrument((ModelInstrument) instrument);
+                inslist.put(pat, softins);
+                loadedlist.put(pat, (ModelInstrument) instrument);
+            }
+        }
+
+        return true;
+    }
+
+    private void processPropertyInfo(Map<String, Object> info) {
+        AudioSynthesizerPropertyInfo[] items = getPropertyInfo(info);
+
+        String resamplerType = (String)items[0].value;
+        if (resamplerType.equalsIgnoreCase("point"))
+        {
+            this.resampler = new SoftPointResampler();
+            this.resamplerType = "point";
+        }
+        else if (resamplerType.equalsIgnoreCase("linear"))
+        {
+            this.resampler = new SoftLinearResampler2();
+            this.resamplerType = "linear";
+        }
+        else if (resamplerType.equalsIgnoreCase("linear1"))
+        {
+            this.resampler = new SoftLinearResampler();
+            this.resamplerType = "linear1";
+        }
+        else if (resamplerType.equalsIgnoreCase("linear2"))
+        {
+            this.resampler = new SoftLinearResampler2();
+            this.resamplerType = "linear2";
+        }
+        else if (resamplerType.equalsIgnoreCase("cubic"))
+        {
+            this.resampler = new SoftCubicResampler();
+            this.resamplerType = "cubic";
+        }
+        else if (resamplerType.equalsIgnoreCase("lanczos"))
+        {
+            this.resampler = new SoftLanczosResampler();
+            this.resamplerType = "lanczos";
+        }
+        else if (resamplerType.equalsIgnoreCase("sinc"))
+        {
+            this.resampler = new SoftSincResampler();
+            this.resamplerType = "sinc";
+        }
+
+        setFormat((AudioFormat)items[2].value);
+        controlrate = (Float)items[1].value;
+        latency = (Long)items[3].value;
+        deviceid = (Integer)items[4].value;
+        maxpoly = (Integer)items[5].value;
+        reverb_on = (Boolean)items[6].value;
+        chorus_on = (Boolean)items[7].value;
+        agc_on = (Boolean)items[8].value;
+        largemode = (Boolean)items[9].value;
+        number_of_midi_channels = (Integer)items[10].value;
+        jitter_correction = (Boolean)items[11].value;
+        reverb_light = (Boolean)items[12].value;
+        load_default_soundbank = (Boolean)items[13].value;
+    }
+
+    private String patchToString(Patch patch) {
+        if (patch instanceof ModelPatch && ((ModelPatch) patch).isPercussion())
+            return "p." + patch.getProgram() + "." + patch.getBank();
+        else
+            return patch.getProgram() + "." + patch.getBank();
+    }
+
+    private void setFormat(AudioFormat format) {
+        if (format.getChannels() > 2) {
+            throw new IllegalArgumentException(
+                    "Only mono and stereo audio supported.");
+        }
+        if (AudioFloatConverter.getConverter(format) == null)
+            throw new IllegalArgumentException("Audio format not supported.");
+        this.format = format;
+    }
+
+    void removeReceiver(Receiver recv) {
+        boolean perform_close = false;
+        synchronized (control_mutex) {
+            if (recvslist.remove(recv)) {
+                if (implicitOpen && recvslist.isEmpty())
+                    perform_close = true;
+            }
+        }
+        if (perform_close)
+            close();
+    }
+
+    SoftMainMixer getMainMixer() {
+        if (!isOpen())
+            return null;
+        return mainmixer;
+    }
+
+    SoftInstrument findInstrument(int program, int bank, int channel) {
+
+        // Add support for GM2 banks 0x78 and 0x79
+        // as specified in DLS 2.2 in Section 1.4.6
+        // which allows using percussion and melodic instruments
+        // on all channels
+        if (bank >> 7 == 0x78 || bank >> 7 == 0x79) {
+            SoftInstrument current_instrument
+                    = inslist.get(program + "." + bank);
+            if (current_instrument != null)
+                return current_instrument;
+
+            String p_plaf;
+            if (bank >> 7 == 0x78)
+                p_plaf = "p.";
+            else
+                p_plaf = "";
+
+            // Instrument not found fallback to MSB:bank, LSB:0
+            current_instrument = inslist.get(p_plaf + program + "."
+                    + ((bank & 128) << 7));
+            if (current_instrument != null)
+                return current_instrument;
+            // Instrument not found fallback to MSB:0, LSB:bank
+            current_instrument = inslist.get(p_plaf + program + "."
+                    + (bank & 128));
+            if (current_instrument != null)
+                return current_instrument;
+            // Instrument not found fallback to MSB:0, LSB:0
+            current_instrument = inslist.get(p_plaf + program + ".0");
+            if (current_instrument != null)
+                return current_instrument;
+            // Instrument not found fallback to MSB:0, LSB:0, program=0
+            current_instrument = inslist.get(p_plaf + program + "0.0");
+            if (current_instrument != null)
+                return current_instrument;
+            return null;
+        }
+
+        // Channel 10 uses percussion instruments
+        String p_plaf;
+        if (channel == 9)
+            p_plaf = "p.";
+        else
+            p_plaf = "";
+
+        SoftInstrument current_instrument
+                = inslist.get(p_plaf + program + "." + bank);
+        if (current_instrument != null)
+            return current_instrument;
+        // Instrument not found fallback to MSB:0, LSB:0
+        current_instrument = inslist.get(p_plaf + program + ".0");
+        if (current_instrument != null)
+            return current_instrument;
+        // Instrument not found fallback to MSB:0, LSB:0, program=0
+        current_instrument = inslist.get(p_plaf + "0.0");
+        if (current_instrument != null)
+            return current_instrument;
+        return null;
+    }
+
+    int getVoiceAllocationMode() {
+        return voice_allocation_mode;
+    }
+
+    int getGeneralMidiMode() {
+        return gmmode;
+    }
+
+    void setGeneralMidiMode(int gmmode) {
+        this.gmmode = gmmode;
+    }
+
+    int getDeviceID() {
+        return deviceid;
+    }
+
+    float getControlRate() {
+        return controlrate;
+    }
+
+    SoftVoice[] getVoices() {
+        return voices;
+    }
+
+    SoftTuning getTuning(Patch patch) {
+        String t_id = patchToString(patch);
+        SoftTuning tuning = tunings.get(t_id);
+        if (tuning == null) {
+            tuning = new SoftTuning(patch);
+            tunings.put(t_id, tuning);
+        }
+        return tuning;
+    }
+
+    public long getLatency() {
+        synchronized (control_mutex) {
+            return latency;
+        }
+    }
+
+    public AudioFormat getFormat() {
+        synchronized (control_mutex) {
+            return format;
+        }
+    }
+
+    public int getMaxPolyphony() {
+        synchronized (control_mutex) {
+            return maxpoly;
+        }
+    }
+
+    public MidiChannel[] getChannels() {
+
+        synchronized (control_mutex) {
+            // if (external_channels == null) => the synthesizer is not open,
+            // create 16 proxy channels
+            // otherwise external_channels has the same length as channels array
+            if (external_channels == null) {
+                external_channels = new SoftChannelProxy[16];
+                for (int i = 0; i < external_channels.length; i++)
+                    external_channels[i] = new SoftChannelProxy();
+            }
+            MidiChannel[] ret;
+            if (isOpen())
+                ret = new MidiChannel[channels.length];
+            else
+                ret = new MidiChannel[16];
+            for (int i = 0; i < ret.length; i++)
+                ret[i] = external_channels[i];
+            return ret;
+        }
+    }
+
+    public VoiceStatus[] getVoiceStatus() {
+        if (!isOpen()) {
+            VoiceStatus[] tempVoiceStatusArray
+                    = new VoiceStatus[getMaxPolyphony()];
+            for (int i = 0; i < tempVoiceStatusArray.length; i++) {
+                VoiceStatus b = new VoiceStatus();
+                b.active = false;
+                b.bank = 0;
+                b.channel = 0;
+                b.note = 0;
+                b.program = 0;
+                b.volume = 0;
+                tempVoiceStatusArray[i] = b;
+            }
+            return tempVoiceStatusArray;
+        }
+
+        synchronized (control_mutex) {
+            VoiceStatus[] tempVoiceStatusArray = new VoiceStatus[voices.length];
+            for (int i = 0; i < voices.length; i++) {
+                VoiceStatus a = voices[i];
+                VoiceStatus b = new VoiceStatus();
+                b.active = a.active;
+                b.bank = a.bank;
+                b.channel = a.channel;
+                b.note = a.note;
+                b.program = a.program;
+                b.volume = a.volume;
+                tempVoiceStatusArray[i] = b;
+            }
+            return tempVoiceStatusArray;
+        }
+    }
+
+    public boolean isSoundbankSupported(Soundbank soundbank) {
+        for (Instrument ins: soundbank.getInstruments())
+            if (!(ins instanceof ModelInstrument))
+                return false;
+        return true;
+    }
+
+    public boolean loadInstrument(Instrument instrument) {
+        if (instrument == null || (!(instrument instanceof ModelInstrument))) {
+            throw new IllegalArgumentException("Unsupported instrument: " +
+                    instrument);
+        }
+        List<ModelInstrument> instruments = new ArrayList<ModelInstrument>();
+        instruments.add((ModelInstrument)instrument);
+        return loadInstruments(instruments);
+    }
+
+    public void unloadInstrument(Instrument instrument) {
+        if (instrument == null || (!(instrument instanceof ModelInstrument))) {
+            throw new IllegalArgumentException("Unsupported instrument: " +
+                    instrument);
+        }
+        if (!isOpen())
+            return;
+
+        String pat = patchToString(instrument.getPatch());
+        synchronized (control_mutex) {
+            for (SoftChannel c: channels)
+                c.current_instrument = null;
+            inslist.remove(pat);
+            loadedlist.remove(pat);
+            for (int i = 0; i < channels.length; i++) {
+                channels[i].allSoundOff();
+            }
+        }
+    }
+
+    public boolean remapInstrument(Instrument from, Instrument to) {
+
+        if (from == null)
+            throw new NullPointerException();
+        if (to == null)
+            throw new NullPointerException();
+        if (!(from instanceof ModelInstrument)) {
+            throw new IllegalArgumentException("Unsupported instrument: " +
+                    from.toString());
+        }
+        if (!(to instanceof ModelInstrument)) {
+            throw new IllegalArgumentException("Unsupported instrument: " +
+                    to.toString());
+        }
+        if (!isOpen())
+            return false;
+
+        synchronized (control_mutex) {
+            if (!loadedlist.containsValue(to))
+                throw new IllegalArgumentException("Instrument to is not loaded.");
+            unloadInstrument(from);
+            ModelMappedInstrument mfrom = new ModelMappedInstrument(
+                    (ModelInstrument)to, from.getPatch());
+            return loadInstrument(mfrom);
+        }
+    }
+
+    public Soundbank getDefaultSoundbank() {
+        synchronized (SoftSynthesizer.class) {
+            if (defaultSoundBank != null)
+                return defaultSoundBank;
+
+            List<PrivilegedAction<InputStream>> actions =
+                new ArrayList<PrivilegedAction<InputStream>>();
+
+            actions.add(new PrivilegedAction<InputStream>() {
+                public InputStream run() {
+                    File javahome = new File(System.getProperties()
+                            .getProperty("java.home"));
+                    File libaudio = new File(new File(javahome, "lib"), "audio");
+                    if (libaudio.exists()) {
+                        File foundfile = null;
+                        File[] files = libaudio.listFiles();
+                        if (files != null) {
+                            for (int i = 0; i < files.length; i++) {
+                                File file = files[i];
+                                if (file.isFile()) {
+                                    String lname = file.getName().toLowerCase();
+                                    if (lname.endsWith(".sf2")
+                                            || lname.endsWith(".dls")) {
+                                        if (foundfile == null
+                                                || (file.length() > foundfile
+                                                        .length())) {
+                                            foundfile = file;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        if (foundfile != null) {
+                            try {
+                                return new FileInputStream(foundfile);
+                            } catch (IOException e) {
+                            }
+                        }
+                    }
+                    return null;
+                }
+            });
+
+            actions.add(new PrivilegedAction<InputStream>() {
+                public InputStream run() {
+                    if (System.getProperties().getProperty("os.name")
+                            .startsWith("Windows")) {
+                        File gm_dls = new File(System.getenv("SystemRoot")
+                                + "\\system32\\drivers\\gm.dls");
+                        if (gm_dls.exists()) {
+                            try {
+                                return new FileInputStream(gm_dls);
+                            } catch (IOException e) {
+                            }
+                        }
+                    }
+                    return null;
+                }
+            });
+
+            actions.add(new PrivilegedAction<InputStream>() {
+                public InputStream run() {
+                    /*
+                     * Try to load saved generated soundbank
+                     */
+                    File userhome = new File(System.getProperty("user.home"),
+                            ".gervill");
+                    File emg_soundbank_file = new File(userhome,
+                            "soundbank-emg.sf2");
+                    if (emg_soundbank_file.exists()) {
+                        try {
+                            return new FileInputStream(emg_soundbank_file);
+                        } catch (IOException e) {
+                        }
+                    }
+                    return null;
+                }
+            });
+
+            for (PrivilegedAction<InputStream> action : actions) {
+                try {
+                    InputStream is = AccessController.doPrivileged(action);
+                    if(is == null) continue;
+                    Soundbank sbk;
+                    try {
+                        sbk = MidiSystem.getSoundbank(new BufferedInputStream(is));
+                    } finally {
+                        is.close();
+                    }
+                    if (sbk != null) {
+                        defaultSoundBank = sbk;
+                        return defaultSoundBank;
+                    }
+                } catch (Exception e) {
+                }
+            }
+
+            try {
+                /*
+                 * Generate emergency soundbank
+                 */
+                defaultSoundBank = EmergencySoundbank.createSoundbank();
+            } catch (Exception e) {
+            }
+
+            if (defaultSoundBank != null) {
+                /*
+                 * Save generated soundbank to disk for faster future use.
+                 */
+                OutputStream out = AccessController
+                        .doPrivileged(new PrivilegedAction<OutputStream>() {
+                            public OutputStream run() {
+                                try {
+                                    File userhome = new File(System
+                                            .getProperty("user.home"),
+                                            ".gervill");
+                                    if (!userhome.exists())
+                                        userhome.mkdirs();
+                                    File emg_soundbank_file = new File(
+                                            userhome, "soundbank-emg.sf2");
+                                    if (emg_soundbank_file.exists())
+                                        return null;
+                                    return new FileOutputStream(
+                                            emg_soundbank_file);
+                                } catch (IOException e) {
+                                } catch (SecurityException e) {
+                                }
+                                return null;
+                            }
+                        });
+                if (out != null) {
+                    try {
+                        ((SF2Soundbank) defaultSoundBank).save(out);
+                        out.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        return defaultSoundBank;
+    }
+
+    public Instrument[] getAvailableInstruments() {
+        Soundbank defsbk = getDefaultSoundbank();
+        if (defsbk == null)
+            return new Instrument[0];
+        Instrument[] inslist_array = defsbk.getInstruments();
+        Arrays.sort(inslist_array, new ModelInstrumentComparator());
+        return inslist_array;
+    }
+
+    public Instrument[] getLoadedInstruments() {
+        if (!isOpen())
+            return new Instrument[0];
+
+        synchronized (control_mutex) {
+            ModelInstrument[] inslist_array =
+                    new ModelInstrument[loadedlist.values().size()];
+            loadedlist.values().toArray(inslist_array);
+            Arrays.sort(inslist_array, new ModelInstrumentComparator());
+            return inslist_array;
+        }
+    }
+
+    public boolean loadAllInstruments(Soundbank soundbank) {
+        List<ModelInstrument> instruments = new ArrayList<ModelInstrument>();
+        for (Instrument ins: soundbank.getInstruments()) {
+            if (ins == null || !(ins instanceof ModelInstrument)) {
+                throw new IllegalArgumentException(
+                        "Unsupported instrument: " + ins);
+            }
+            instruments.add((ModelInstrument)ins);
+        }
+        return loadInstruments(instruments);
+    }
+
+    public void unloadAllInstruments(Soundbank soundbank) {
+        if (soundbank == null || !isSoundbankSupported(soundbank))
+            throw new IllegalArgumentException("Unsupported soundbank: " + soundbank);
+
+        if (!isOpen())
+            return;
+
+        for (Instrument ins: soundbank.getInstruments()) {
+            if (ins instanceof ModelInstrument) {
+                unloadInstrument(ins);
+            }
+        }
+    }
+
+    public boolean loadInstruments(Soundbank soundbank, Patch[] patchList) {
+        List<ModelInstrument> instruments = new ArrayList<ModelInstrument>();
+        for (Patch patch: patchList) {
+            Instrument ins = soundbank.getInstrument(patch);
+            if (ins == null || !(ins instanceof ModelInstrument)) {
+                throw new IllegalArgumentException(
+                        "Unsupported instrument: " + ins);
+            }
+            instruments.add((ModelInstrument)ins);
+        }
+        return loadInstruments(instruments);
+    }
+
+    public void unloadInstruments(Soundbank soundbank, Patch[] patchList) {
+        if (soundbank == null || !isSoundbankSupported(soundbank))
+            throw new IllegalArgumentException("Unsupported soundbank: " + soundbank);
+
+        if (!isOpen())
+            return;
+
+        for (Patch pat: patchList) {
+            Instrument ins = soundbank.getInstrument(pat);
+            if (ins instanceof ModelInstrument) {
+                unloadInstrument(ins);
+            }
+        }
+    }
+
+    public MidiDevice.Info getDeviceInfo() {
+        return info;
+    }
+
+    private Properties getStoredProperties() {
+        return AccessController
+                .doPrivileged(new PrivilegedAction<Properties>() {
+                    public Properties run() {
+                        Properties p = new Properties();
+                        String notePath = "/com/sun/media/sound/softsynthesizer";
+                        try {
+                            Preferences prefroot = Preferences.userRoot();
+                            if (prefroot.nodeExists(notePath)) {
+                                Preferences prefs = prefroot.node(notePath);
+                                String[] prefs_keys = prefs.keys();
+                                for (String prefs_key : prefs_keys) {
+                                    String val = prefs.get(prefs_key, null);
+                                    if (val != null)
+                                        p.setProperty(prefs_key, val);
+                                }
+                            }
+                        } catch (BackingStoreException e) {
+                        } catch (SecurityException e) {
+                        }
+                        return p;
+                    }
+                });
+    }
+
+    public AudioSynthesizerPropertyInfo[] getPropertyInfo(Map<String, Object> info) {
+        List<AudioSynthesizerPropertyInfo> list =
+                new ArrayList<AudioSynthesizerPropertyInfo>();
+
+        AudioSynthesizerPropertyInfo item;
+
+        // If info != null or synthesizer is closed
+        //   we return how the synthesizer will be set on next open
+        // If info == null and synthesizer is open
+        //   we return current synthesizer properties.
+        boolean o = info == null && open;
+
+        item = new AudioSynthesizerPropertyInfo("interpolation", o?resamplerType:"linear");
+        item.choices = new String[]{"linear", "linear1", "linear2", "cubic",
+                                    "lanczos", "sinc", "point"};
+        item.description = "Interpolation method";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("control rate", o?controlrate:147f);
+        item.description = "Control rate";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("format",
+                o?format:new AudioFormat(44100, 16, 2, true, false));
+        item.description = "Default audio format";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("latency", o?latency:120000L);
+        item.description = "Default latency";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("device id", o?deviceid:0);
+        item.description = "Device ID for SysEx Messages";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("max polyphony", o?maxpoly:64);
+        item.description = "Maximum polyphony";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("reverb", o?reverb_on:true);
+        item.description = "Turn reverb effect on or off";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("chorus", o?chorus_on:true);
+        item.description = "Turn chorus effect on or off";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("auto gain control", o?agc_on:true);
+        item.description = "Turn auto gain control on or off";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("large mode", o?largemode:false);
+        item.description = "Turn large mode on or off.";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("midi channels", o?channels.length:16);
+        item.description = "Number of midi channels.";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("jitter correction", o?jitter_correction:true);
+        item.description = "Turn jitter correction on or off.";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("light reverb", o?reverb_light:true);
+        item.description = "Turn light reverb mode on or off";
+        list.add(item);
+
+        item = new AudioSynthesizerPropertyInfo("load default soundbank", o?load_default_soundbank:true);
+        item.description = "Enabled/disable loading default soundbank";
+        list.add(item);
+
+        AudioSynthesizerPropertyInfo[] items;
+        items = list.toArray(new AudioSynthesizerPropertyInfo[list.size()]);
+
+        Properties storedProperties = getStoredProperties();
+
+        for (AudioSynthesizerPropertyInfo item2 : items) {
+            Object v = (info == null) ? null : info.get(item2.name);
+            v = (v != null) ? v : storedProperties.getProperty(item2.name);
+            if (v != null) {
+                Class<?> c = (item2.valueClass);
+                if (c.isInstance(v))
+                    item2.value = v;
+                else if (v instanceof String) {
+                    String s = (String) v;
+                    if (c == Boolean.class) {
+                        if (s.equalsIgnoreCase("true"))
+                            item2.value = Boolean.TRUE;
+                        if (s.equalsIgnoreCase("false"))
+                            item2.value = Boolean.FALSE;
+                    } else if (c == AudioFormat.class) {
+                        int channels = 2;
+                        boolean signed = true;
+                        boolean bigendian = false;
+                        int bits = 16;
+                        float sampleRate = 44100f;
+                        try {
+                            StringTokenizer st = new StringTokenizer(s, ", ");
+                            String prevToken = "";
+                            while (st.hasMoreTokens()) {
+                                String token = st.nextToken().toLowerCase();
+                                if (token.equals("mono"))
+                                    channels = 1;
+                                if (token.startsWith("channel"))
+                                    channels = Integer.parseInt(prevToken);
+                                if (token.contains("unsigned"))
+                                    signed = false;
+                                if (token.equals("big-endian"))
+                                    bigendian = true;
+                                if (token.equals("bit"))
+                                    bits = Integer.parseInt(prevToken);
+                                if (token.equals("hz"))
+                                    sampleRate = Float.parseFloat(prevToken);
+                                prevToken = token;
+                            }
+                            item2.value = new AudioFormat(sampleRate, bits,
+                                    channels, signed, bigendian);
+                        } catch (NumberFormatException e) {
+                        }
+
+                    } else
+                        try {
+                            if (c == Byte.class)
+                                item2.value = Byte.valueOf(s);
+                            else if (c == Short.class)
+                                item2.value = Short.valueOf(s);
+                            else if (c == Integer.class)
+                                item2.value = Integer.valueOf(s);
+                            else if (c == Long.class)
+                                item2.value = Long.valueOf(s);
+                            else if (c == Float.class)
+                                item2.value = Float.valueOf(s);
+                            else if (c == Double.class)
+                                item2.value = Double.valueOf(s);
+                        } catch (NumberFormatException e) {
+                        }
+                } else if (v instanceof Number) {
+                    Number n = (Number) v;
+                    if (c == Byte.class)
+                        item2.value = Byte.valueOf(n.byteValue());
+                    if (c == Short.class)
+                        item2.value = Short.valueOf(n.shortValue());
+                    if (c == Integer.class)
+                        item2.value = Integer.valueOf(n.intValue());
+                    if (c == Long.class)
+                        item2.value = Long.valueOf(n.longValue());
+                    if (c == Float.class)
+                        item2.value = Float.valueOf(n.floatValue());
+                    if (c == Double.class)
+                        item2.value = Double.valueOf(n.doubleValue());
+                }
+            }
+        }
+
+        return items;
+    }
+
+    public void open() throws MidiUnavailableException {
+        if (isOpen()) {
+            synchronized (control_mutex) {
+                implicitOpen = false;
+            }
+            return;
+        }
+        open(null, null);
+    }
+
+    public void open(SourceDataLine line, Map<String, Object> info) throws MidiUnavailableException {
+        if (isOpen()) {
+            synchronized (control_mutex) {
+                implicitOpen = false;
+            }
+            return;
+        }
+        synchronized (control_mutex) {
+            Throwable causeException = null;
+            try {
+                if (line != null) {
+                    // can throw IllegalArgumentException
+                    setFormat(line.getFormat());
+                }
+
+                AudioInputStream ais = openStream(getFormat(), info);
+
+                weakstream = new WeakAudioStream(ais);
+                ais = weakstream.getAudioInputStream();
+
+                if (line == null)
+                {
+                    if (testline != null) {
+                        line = testline;
+                    } else {
+                        // can throw LineUnavailableException,
+                        // IllegalArgumentException, SecurityException
+                        line = AudioSystem.getSourceDataLine(getFormat());
+                    }
+                }
+
+                double latency = this.latency;
+
+                if (!line.isOpen()) {
+                    int bufferSize = getFormat().getFrameSize()
+                        * (int)(getFormat().getFrameRate() * (latency/1000000f));
+                    // can throw LineUnavailableException,
+                    // IllegalArgumentException, SecurityException
+                    line.open(getFormat(), bufferSize);
+
+                    // Remember that we opened that line
+                    // so we can close again in SoftSynthesizer.close()
+                    sourceDataLine = line;
+                }
+                if (!line.isActive())
+                    line.start();
+
+                int controlbuffersize = 512;
+                try {
+                    controlbuffersize = ais.available();
+                } catch (IOException e) {
+                }
+
+                // Tell mixer not fill read buffers fully.
+                // This lowers latency, and tells DataPusher
+                // to read in smaller amounts.
+                //mainmixer.readfully = false;
+                //pusher = new DataPusher(line, ais);
+
+                int buffersize = line.getBufferSize();
+                buffersize -= buffersize % controlbuffersize;
+
+                if (buffersize < 3 * controlbuffersize)
+                    buffersize = 3 * controlbuffersize;
+
+                if (jitter_correction) {
+                    ais = new SoftJitterCorrector(ais, buffersize,
+                            controlbuffersize);
+                    if(weakstream != null)
+                        weakstream.jitter_stream = ais;
+                }
+                pusher = new SoftAudioPusher(line, ais, controlbuffersize);
+                pusher_stream = ais;
+                pusher.start();
+
+                if(weakstream != null)
+                {
+                    weakstream.pusher = pusher;
+                    weakstream.sourceDataLine = sourceDataLine;
+                }
+
+            } catch (LineUnavailableException e) {
+                causeException = e;
+            } catch (IllegalArgumentException e) {
+                causeException = e;
+            } catch (SecurityException e) {
+                causeException = e;
+            }
+
+            if (causeException != null) {
+                if (isOpen())
+                    close();
+                // am: need MidiUnavailableException(Throwable) ctor!
+                MidiUnavailableException ex = new MidiUnavailableException(
+                        "Can not open line");
+                ex.initCause(causeException);
+                throw ex;
+            }
+
+        }
+    }
+
+    public AudioInputStream openStream(AudioFormat targetFormat,
+            Map<String, Object> info) throws MidiUnavailableException {
+
+        if (isOpen())
+            throw new MidiUnavailableException("Synthesizer is already open");
+
+        synchronized (control_mutex) {
+
+            gmmode = 0;
+            voice_allocation_mode = 0;
+
+            processPropertyInfo(info);
+
+            open = true;
+            implicitOpen = false;
+
+            if (targetFormat != null)
+                setFormat(targetFormat);
+
+            if (load_default_soundbank)
+            {
+                Soundbank defbank = getDefaultSoundbank();
+                if (defbank != null) {
+                    loadAllInstruments(defbank);
+                }
+            }
+
+            voices = new SoftVoice[maxpoly];
+            for (int i = 0; i < maxpoly; i++)
+                voices[i] = new SoftVoice(this);
+
+            mainmixer = new SoftMainMixer(this);
+
+            channels = new SoftChannel[number_of_midi_channels];
+            for (int i = 0; i < channels.length; i++)
+                channels[i] = new SoftChannel(this, i);
+
+            if (external_channels == null) {
+                // Always create external_channels array
+                // with 16 or more channels
+                // so getChannels works correctly
+                // when the synhtesizer is closed.
+                if (channels.length < 16)
+                    external_channels = new SoftChannelProxy[16];
+                else
+                    external_channels = new SoftChannelProxy[channels.length];
+                for (int i = 0; i < external_channels.length; i++)
+                    external_channels[i] = new SoftChannelProxy();
+            } else {
+                // We must resize external_channels array
+                // but we must also copy the old SoftChannelProxy
+                // into the new one
+                if (channels.length > external_channels.length) {
+                    SoftChannelProxy[] new_external_channels
+                            = new SoftChannelProxy[channels.length];
+                    for (int i = 0; i < external_channels.length; i++)
+                        new_external_channels[i] = external_channels[i];
+                    for (int i = external_channels.length;
+                            i < new_external_channels.length; i++) {
+                        new_external_channels[i] = new SoftChannelProxy();
+                    }
+                }
+            }
+
+            for (int i = 0; i < channels.length; i++)
+                external_channels[i].setChannel(channels[i]);
+
+            for (SoftVoice voice: getVoices())
+                voice.resampler = resampler.openStreamer();
+
+            for (Receiver recv: getReceivers()) {
+                SoftReceiver srecv = ((SoftReceiver)recv);
+                srecv.open = open;
+                srecv.mainmixer = mainmixer;
+                srecv.midimessages = mainmixer.midimessages;
+            }
+
+            return mainmixer.getInputStream();
+        }
+    }
+
+    public void close() {
+
+        if (!isOpen())
+            return;
+
+        SoftAudioPusher pusher_to_be_closed = null;
+        AudioInputStream pusher_stream_to_be_closed = null;
+        synchronized (control_mutex) {
+            if (pusher != null) {
+                pusher_to_be_closed = pusher;
+                pusher_stream_to_be_closed = pusher_stream;
+                pusher = null;
+                pusher_stream = null;
+            }
+        }
+
+        if (pusher_to_be_closed != null) {
+            // Pusher must not be closed synchronized against control_mutex,
+            // this may result in synchronized conflict between pusher
+            // and current thread.
+            pusher_to_be_closed.stop();
+
+            try {
+                pusher_stream_to_be_closed.close();
+            } catch (IOException e) {
+                //e.printStackTrace();
+            }
+        }
+
+        synchronized (control_mutex) {
+
+            if (mainmixer != null)
+                mainmixer.close();
+            open = false;
+            implicitOpen = false;
+            mainmixer = null;
+            voices = null;
+            channels = null;
+
+            if (external_channels != null)
+                for (int i = 0; i < external_channels.length; i++)
+                    external_channels[i].setChannel(null);
+
+            if (sourceDataLine != null) {
+                sourceDataLine.close();
+                sourceDataLine = null;
+            }
+
+            inslist.clear();
+            loadedlist.clear();
+            tunings.clear();
+
+            while (recvslist.size() != 0)
+                recvslist.get(recvslist.size() - 1).close();
+
+        }
+    }
+
+    public boolean isOpen() {
+        synchronized (control_mutex) {
+            return open;
+        }
+    }
+
+    public long getMicrosecondPosition() {
+
+        if (!isOpen())
+            return 0;
+
+        synchronized (control_mutex) {
+            return mainmixer.getMicrosecondPosition();
+        }
+    }
+
+    public int getMaxReceivers() {
+        return -1;
+    }
+
+    public int getMaxTransmitters() {
+        return 0;
+    }
+
+    public Receiver getReceiver() throws MidiUnavailableException {
+
+        synchronized (control_mutex) {
+            SoftReceiver receiver = new SoftReceiver(this);
+            receiver.open = open;
+            recvslist.add(receiver);
+            return receiver;
+        }
+    }
+
+    public List<Receiver> getReceivers() {
+
+        synchronized (control_mutex) {
+            ArrayList<Receiver> recvs = new ArrayList<Receiver>();
+            recvs.addAll(recvslist);
+            return recvs;
+        }
+    }
+
+    public Transmitter getTransmitter() throws MidiUnavailableException {
+
+        throw new MidiUnavailableException("No transmitter available");
+    }
+
+    public List<Transmitter> getTransmitters() {
+
+        return new ArrayList<Transmitter>();
+    }
+
+    public Receiver getReceiverReferenceCounting()
+            throws MidiUnavailableException {
+
+        if (!isOpen()) {
+            open();
+            synchronized (control_mutex) {
+                implicitOpen = true;
+            }
+        }
+
+        return getReceiver();
+    }
+
+    public Transmitter getTransmitterReferenceCounting()
+            throws MidiUnavailableException {
+
+        throw new MidiUnavailableException("No transmitter available");
+    }
+}