--- /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");
+ }
+}