view jdk/src/share/classes/com/sun/media/sound/SoftSynthesizer.java @ 12:7c56bb8ffc4b jdk6-b11

Import b11
author Mark Wielaard <mark@klomp.org>
date Thu, 10 Jul 2008 00:00:00 +0200
parents a6a6e9c3d502
children c8bd11255f96
line wrap: on
line source

/*
 * Copyright 2008 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package com.sun.media.sound;

import java.io.File;
import java.io.IOException;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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 class SoftSynthesizer implements AudioSynthesizer,
        ReferenceCountingDevice {

    private static class Info extends MidiDevice.Info {
        public Info() {
            super(INFO_NAME, INFO_VENDOR, INFO_DESCRIPTION, INFO_VERSION);
        }
    }

    protected static final String INFO_NAME = "Gervill";
    protected static final String INFO_VENDOR = "OpenJDK";
    protected static final String INFO_DESCRIPTION = "Software MIDI Synthesizer";
    protected static final String INFO_VERSION = "1.0";
    protected static MidiDevice.Info info = new Info();

    private static Soundbank defaultSoundBank = null;

    protected Object control_mutex = this;

    protected int voiceIDCounter = 0;

    // 0: default
    // 1: DLS Voice Allocation
    protected int voice_allocation_mode = 0;

    protected boolean reverb_on = true;
    protected boolean chorus_on = true;
    protected boolean agc_on = true;

    protected SoftChannel[] channels;
    protected 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 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> availlist
            = new HashMap<String, ModelInstrument>();
    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;
            for (Instrument instrument : instruments) {
                String pat = patchToString(instrument.getPatch());
                availlist.remove(pat);
                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();
        else if (resamplerType.equalsIgnoreCase("linear"))
            this.resampler = new SoftLinearResampler2();
        else if (resamplerType.equalsIgnoreCase("linear1"))
            this.resampler = new SoftLinearResampler();
        else if (resamplerType.equalsIgnoreCase("linear2"))
            this.resampler = new SoftLinearResampler2();
        else if (resamplerType.equalsIgnoreCase("cubic"))
            this.resampler = new SoftCubicResampler();
        else if (resamplerType.equalsIgnoreCase("lanczos"))
            this.resampler = new SoftLanczosResampler();
        else if (resamplerType.equalsIgnoreCase("sinc"))
            this.resampler = new SoftSincResampler();

        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;
    }

    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;
    }

    protected 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();
    }

    protected SoftMainMixer getMainMixer() {
        if (!isOpen())
            return null;
        return mainmixer;
    }

    protected 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;
    }

    protected int getVoiceAllocationMode() {
        return voice_allocation_mode;
    }

    protected int getGeneralMidiMode() {
        return gmmode;
    }

    protected void setGeneralMidiMode(int gmmode) {
        this.gmmode = gmmode;
    }

    protected int getDeviceID() {
        return deviceid;
    }

    protected float getControlRate() {
        return controlrate;
    }

    protected SoftVoice[] getVoices() {
        return voices;
    }

    protected 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.toString());
        }
        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.toString());
        }
        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);
            availlist.remove(pat);
        }
    }

    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) && !availlist.containsValue(to))
                throw new IllegalArgumentException("Instrument to is not loaded.");
            unloadInstrument(from);
            ModelMappedInstrument mfrom = new ModelMappedInstrument(
                    (ModelInstrument)to, from.getPatch());
            return loadInstrument(mfrom);
        }
    }

    public synchronized Soundbank getDefaultSoundbank() {
        if (defaultSoundBank == null) {
            try {
                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 {
                            Soundbank sbk = MidiSystem.getSoundbank(foundfile);
                            defaultSoundBank = sbk;
                            return defaultSoundBank;
                        } catch (Exception e) {
                            //e.printStackTrace();
                        }
                    }
                }

                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 {
                            Soundbank sbk = MidiSystem.getSoundbank(gm_dls);
                            defaultSoundBank = sbk;
                            return defaultSoundBank;
                        } catch (Exception e) {
                            //e.printStackTrace();
                        }
                    }
                }
            } catch (AccessControlException e) {
            } catch (Exception e) {
                //e.printStackTrace();
            }

            try {
                defaultSoundBank = EmergencySoundbank.createSoundbank();
            } catch (Exception e) {
                //e.printStackTrace();
            }

        }
        return defaultSoundBank;
    }

    public Instrument[] getAvailableInstruments() {
        if (!isOpen()) {
            Soundbank defsbk = getDefaultSoundbank();
            if (defsbk == null)
                return new Instrument[0];
            return defsbk.getInstruments();
        }

        synchronized (control_mutex) {
            ModelInstrument[] inslist_array =
                    new ModelInstrument[availlist.values().size()];
            availlist.values().toArray(inslist_array);
            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 (!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 (!isOpen())
            return;

        for (Patch pat: patchList) {
            Instrument ins = soundbank.getInstrument(pat);
            if (ins instanceof ModelInstrument) {
                unloadInstrument(ins);
            }
        }
    }

    public MidiDevice.Info getDeviceInfo() {
        return info;
    }

    public AudioSynthesizerPropertyInfo[] getPropertyInfo(Map<String, Object> info) {
        List<AudioSynthesizerPropertyInfo> list =
                new ArrayList<AudioSynthesizerPropertyInfo>();

        AudioSynthesizerPropertyInfo item;

        item = new AudioSynthesizerPropertyInfo("interpolation", "linear");
        item.choices = new String[]{"linear", "linear1", "linear2", "cubic",
                                    "lanczos", "sinc", "point"};
        item.description = "Interpolation method";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("control rate", 147f);
        item.description = "Control rate";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("format",
                new AudioFormat(44100, 16, 2, true, false));
        item.description = "Default audio format";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("latency", 120000L);
        item.description = "Default latency";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("device id", 0);
        item.description = "Device ID for SysEx Messages";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("max polyphony", 64);
        item.description = "Maximum polyphony";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("reverb", true);
        item.description = "Turn reverb effect on or off";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("chorus", true);
        item.description = "Turn chorus effect on or off";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("auto gain control", true);
        item.description = "Turn auto gain control on or off";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("large mode", false);
        item.description = "Turn large mode on or off.";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("midi channels", 16);
        item.description = "Number of midi channels.";
        list.add(item);

        item = new AudioSynthesizerPropertyInfo("jitter correction", true);
        item.description = "Turn jitter correction on or off.";
        list.add(item);

        AudioSynthesizerPropertyInfo[] items;
        items = list.toArray(new AudioSynthesizerPropertyInfo[list.size()]);

        if (info != null)
            for (AudioSynthesizerPropertyInfo item2: items) {
                Object v = info.get(item2.name);
                Class c = (item2.valueClass);
                if (v != null)
                    if (c.isInstance(v))
                        item2.value = v;
            }

        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) {
            try {
                if (line != null)
                    setFormat(line.getFormat());

                AudioInputStream ais = openStream(getFormat(), info);

                if (line == null)
                    line = AudioSystem.getSourceDataLine(getFormat());

                double latency = this.latency;

                if (!line.isOpen()) {
                    int bufferSize = getFormat().getFrameSize()
                        * (int)(getFormat().getFrameRate() * (latency/1000000f));
                    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);
                }
                pusher = new SoftAudioPusher(line, ais, controlbuffersize);
                pusher_stream = ais;
                pusher.start();


            } catch (LineUnavailableException e) {
                if (isOpen())
                    close();
                // am: need MidiUnavailableException(Throwable) ctor!
                throw new MidiUnavailableException(e.toString());
            }

        }
    }

    public AudioInputStream openStream(AudioFormat targetFormat,
            Map<String, Object> info) throws MidiUnavailableException {

        if (isOpen())
            throw new MidiUnavailableException("Synthesizer is already open");

        synchronized (control_mutex) {
            open = true;
            implicitOpen = false;

            gmmode = 0;
            voice_allocation_mode = 0;

            processPropertyInfo(info);
            if (targetFormat != null)
                setFormat(targetFormat);

            Soundbank defbank = getDefaultSoundbank();
            if (defbank != null) {
                loadAllInstruments(defbank);
                availlist.putAll(loadedlist);
                loadedlist.clear();
            }

            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.drain();
                sourceDataLine.close();
                sourceDataLine = null;
            }

            inslist.clear();
            availlist.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");
    }
}