| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
package com.sun.media.sound;  | 
 | 
 | 
 | 
import java.io.IOException;  | 
 | 
import java.util.ArrayList;  | 
 | 
import java.util.Arrays;  | 
 | 
import java.util.List;  | 
 | 
 | 
 | 
import javax.sound.sampled.AudioFormat;  | 
 | 
import javax.sound.sampled.AudioSystem;  | 
 | 
import javax.sound.sampled.BooleanControl;  | 
 | 
import javax.sound.sampled.Control;  | 
 | 
import javax.sound.sampled.DataLine;  | 
 | 
import javax.sound.sampled.FloatControl;  | 
 | 
import javax.sound.sampled.LineEvent;  | 
 | 
import javax.sound.sampled.LineListener;  | 
 | 
import javax.sound.sampled.Control.Type;  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
public abstract class SoftMixingDataLine implements DataLine { | 
 | 
 | 
 | 
    public static final FloatControl.Type CHORUS_SEND = new FloatControl.Type(  | 
 | 
            "Chorus Send") { | 
 | 
    };  | 
 | 
 | 
 | 
    protected static final class AudioFloatInputStreamResampler extends  | 
 | 
            AudioFloatInputStream { | 
 | 
 | 
 | 
        private final AudioFloatInputStream ais;  | 
 | 
 | 
 | 
        private final AudioFormat targetFormat;  | 
 | 
 | 
 | 
        private float[] skipbuffer;  | 
 | 
 | 
 | 
        private SoftAbstractResampler resampler;  | 
 | 
 | 
 | 
        private final float[] pitch = new float[1];  | 
 | 
 | 
 | 
        private final float[] ibuffer2;  | 
 | 
 | 
 | 
        private final float[][] ibuffer;  | 
 | 
 | 
 | 
        private float ibuffer_index = 0;  | 
 | 
 | 
 | 
        private int ibuffer_len = 0;  | 
 | 
 | 
 | 
        private int nrofchannels = 0;  | 
 | 
 | 
 | 
        private float[][] cbuffer;  | 
 | 
 | 
 | 
        private final int buffer_len = 512;  | 
 | 
 | 
 | 
        private final int pad;  | 
 | 
 | 
 | 
        private final int pad2;  | 
 | 
 | 
 | 
        private final float[] ix = new float[1];  | 
 | 
 | 
 | 
        private final int[] ox = new int[1];  | 
 | 
 | 
 | 
        private float[][] mark_ibuffer = null;  | 
 | 
 | 
 | 
        private float mark_ibuffer_index = 0;  | 
 | 
 | 
 | 
        private int mark_ibuffer_len = 0;  | 
 | 
 | 
 | 
        public AudioFloatInputStreamResampler(AudioFloatInputStream ais,  | 
 | 
                AudioFormat format) { | 
 | 
            this.ais = ais;  | 
 | 
            AudioFormat sourceFormat = ais.getFormat();  | 
 | 
            targetFormat = new AudioFormat(sourceFormat.getEncoding(), format  | 
 | 
                    .getSampleRate(), sourceFormat.getSampleSizeInBits(),  | 
 | 
                    sourceFormat.getChannels(), sourceFormat.getFrameSize(),  | 
 | 
                    format.getSampleRate(), sourceFormat.isBigEndian());  | 
 | 
            nrofchannels = targetFormat.getChannels();  | 
 | 
            Object interpolation = format.getProperty("interpolation"); | 
 | 
            if (interpolation != null && (interpolation instanceof String)) { | 
 | 
                String resamplerType = (String) interpolation;  | 
 | 
                if (resamplerType.equalsIgnoreCase("point")) | 
 | 
                    this.resampler = new SoftPointResampler();  | 
 | 
                if (resamplerType.equalsIgnoreCase("linear")) | 
 | 
                    this.resampler = new SoftLinearResampler2();  | 
 | 
                if (resamplerType.equalsIgnoreCase("linear1")) | 
 | 
                    this.resampler = new SoftLinearResampler();  | 
 | 
                if (resamplerType.equalsIgnoreCase("linear2")) | 
 | 
                    this.resampler = new SoftLinearResampler2();  | 
 | 
                if (resamplerType.equalsIgnoreCase("cubic")) | 
 | 
                    this.resampler = new SoftCubicResampler();  | 
 | 
                if (resamplerType.equalsIgnoreCase("lanczos")) | 
 | 
                    this.resampler = new SoftLanczosResampler();  | 
 | 
                if (resamplerType.equalsIgnoreCase("sinc")) | 
 | 
                    this.resampler = new SoftSincResampler();  | 
 | 
            }  | 
 | 
            if (resampler == null)  | 
 | 
                resampler = new SoftLinearResampler2();   | 
 | 
              | 
 | 
            pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate();  | 
 | 
            pad = resampler.getPadding();  | 
 | 
            pad2 = pad * 2;  | 
 | 
            ibuffer = new float[nrofchannels][buffer_len + pad2];  | 
 | 
            ibuffer2 = new float[nrofchannels * buffer_len];  | 
 | 
            ibuffer_index = buffer_len + pad;  | 
 | 
            ibuffer_len = buffer_len;  | 
 | 
        }  | 
 | 
 | 
 | 
        public int available() throws IOException { | 
 | 
            return 0;  | 
 | 
        }  | 
 | 
 | 
 | 
        public void close() throws IOException { | 
 | 
            ais.close();  | 
 | 
        }  | 
 | 
 | 
 | 
        public AudioFormat getFormat() { | 
 | 
            return targetFormat;  | 
 | 
        }  | 
 | 
 | 
 | 
        public long getFrameLength() { | 
 | 
            return AudioSystem.NOT_SPECIFIED;   | 
 | 
        }  | 
 | 
 | 
 | 
        public void mark(int readlimit) { | 
 | 
            ais.mark((int) (readlimit * pitch[0]));  | 
 | 
            mark_ibuffer_index = ibuffer_index;  | 
 | 
            mark_ibuffer_len = ibuffer_len;  | 
 | 
            if (mark_ibuffer == null) { | 
 | 
                mark_ibuffer = new float[ibuffer.length][ibuffer[0].length];  | 
 | 
            }  | 
 | 
            for (int c = 0; c < ibuffer.length; c++) { | 
 | 
                float[] from = ibuffer[c];  | 
 | 
                float[] to = mark_ibuffer[c];  | 
 | 
                for (int i = 0; i < to.length; i++) { | 
 | 
                    to[i] = from[i];  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        public boolean markSupported() { | 
 | 
            return ais.markSupported();  | 
 | 
        }  | 
 | 
 | 
 | 
        private void readNextBuffer() throws IOException { | 
 | 
 | 
 | 
            if (ibuffer_len == -1)  | 
 | 
                return;  | 
 | 
 | 
 | 
            for (int c = 0; c < nrofchannels; c++) { | 
 | 
                float[] buff = ibuffer[c];  | 
 | 
                int buffer_len_pad = ibuffer_len + pad2;  | 
 | 
                for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) { | 
 | 
                    buff[ix] = buff[i];  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            ibuffer_index -= (ibuffer_len);  | 
 | 
 | 
 | 
            ibuffer_len = ais.read(ibuffer2);  | 
 | 
            if (ibuffer_len >= 0) { | 
 | 
                while (ibuffer_len < ibuffer2.length) { | 
 | 
                    int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length  | 
 | 
                            - ibuffer_len);  | 
 | 
                    if (ret == -1)  | 
 | 
                        break;  | 
 | 
                    ibuffer_len += ret;  | 
 | 
                }  | 
 | 
                Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0);  | 
 | 
                ibuffer_len /= nrofchannels;  | 
 | 
            } else { | 
 | 
                Arrays.fill(ibuffer2, 0, ibuffer2.length, 0);  | 
 | 
            }  | 
 | 
 | 
 | 
            int ibuffer2_len = ibuffer2.length;  | 
 | 
            for (int c = 0; c < nrofchannels; c++) { | 
 | 
                float[] buff = ibuffer[c];  | 
 | 
                for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) { | 
 | 
                    buff[ix] = ibuffer2[i];  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
        }  | 
 | 
 | 
 | 
        public int read(float[] b, int off, int len) throws IOException { | 
 | 
 | 
 | 
            if (cbuffer == null || cbuffer[0].length < len / nrofchannels) { | 
 | 
                cbuffer = new float[nrofchannels][len / nrofchannels];  | 
 | 
            }  | 
 | 
            if (ibuffer_len == -1)  | 
 | 
                return -1;  | 
 | 
            if (len < 0)  | 
 | 
                return 0;  | 
 | 
            int remain = len / nrofchannels;  | 
 | 
            int destPos = 0;  | 
 | 
            int in_end = ibuffer_len;  | 
 | 
            while (remain > 0) { | 
 | 
                if (ibuffer_len >= 0) { | 
 | 
                    if (ibuffer_index >= (ibuffer_len + pad))  | 
 | 
                        readNextBuffer();  | 
 | 
                    in_end = ibuffer_len + pad;  | 
 | 
                }  | 
 | 
 | 
 | 
                if (ibuffer_len < 0) { | 
 | 
                    in_end = pad2;  | 
 | 
                    if (ibuffer_index >= in_end)  | 
 | 
                        break;  | 
 | 
                }  | 
 | 
 | 
 | 
                if (ibuffer_index < 0)  | 
 | 
                    break;  | 
 | 
                int preDestPos = destPos;  | 
 | 
                for (int c = 0; c < nrofchannels; c++) { | 
 | 
                    ix[0] = ibuffer_index;  | 
 | 
                    ox[0] = destPos;  | 
 | 
                    float[] buff = ibuffer[c];  | 
 | 
                    resampler.interpolate(buff, ix, in_end, pitch, 0,  | 
 | 
                            cbuffer[c], ox, len / nrofchannels);  | 
 | 
                }  | 
 | 
                ibuffer_index = ix[0];  | 
 | 
                destPos = ox[0];  | 
 | 
                remain -= destPos - preDestPos;  | 
 | 
            }  | 
 | 
            for (int c = 0; c < nrofchannels; c++) { | 
 | 
                int ix = 0;  | 
 | 
                float[] buff = cbuffer[c];  | 
 | 
                for (int i = c; i < b.length; i += nrofchannels) { | 
 | 
                    b[i] = buff[ix++];  | 
 | 
                }  | 
 | 
            }  | 
 | 
            return len - remain * nrofchannels;  | 
 | 
        }  | 
 | 
 | 
 | 
        public void reset() throws IOException { | 
 | 
            ais.reset();  | 
 | 
            if (mark_ibuffer == null)  | 
 | 
                return;  | 
 | 
            ibuffer_index = mark_ibuffer_index;  | 
 | 
            ibuffer_len = mark_ibuffer_len;  | 
 | 
            for (int c = 0; c < ibuffer.length; c++) { | 
 | 
                float[] from = mark_ibuffer[c];  | 
 | 
                float[] to = ibuffer[c];  | 
 | 
                for (int i = 0; i < to.length; i++) { | 
 | 
                    to[i] = from[i];  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
        }  | 
 | 
 | 
 | 
        public long skip(long len) throws IOException { | 
 | 
            if (len > 0)  | 
 | 
                return 0;  | 
 | 
            if (skipbuffer == null)  | 
 | 
                skipbuffer = new float[1024 * targetFormat.getFrameSize()];  | 
 | 
            float[] l_skipbuffer = skipbuffer;  | 
 | 
            long remain = len;  | 
 | 
            while (remain > 0) { | 
 | 
                int ret = read(l_skipbuffer, 0, (int) Math.min(remain,  | 
 | 
                        skipbuffer.length));  | 
 | 
                if (ret < 0) { | 
 | 
                    if (remain == len)  | 
 | 
                        return ret;  | 
 | 
                    break;  | 
 | 
                }  | 
 | 
                remain -= ret;  | 
 | 
            }  | 
 | 
            return len - remain;  | 
 | 
 | 
 | 
        }  | 
 | 
 | 
 | 
    }  | 
 | 
 | 
 | 
    private final class Gain extends FloatControl { | 
 | 
 | 
 | 
        private Gain() { | 
 | 
 | 
 | 
            super(FloatControl.Type.MASTER_GAIN, -80f, 6.0206f, 80f / 128.0f,  | 
 | 
                    -1, 0.0f, "dB", "Minimum", "", "Maximum");  | 
 | 
        }  | 
 | 
 | 
 | 
        public void setValue(float newValue) { | 
 | 
            super.setValue(newValue);  | 
 | 
            calcVolume();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private final class Mute extends BooleanControl { | 
 | 
 | 
 | 
        private Mute() { | 
 | 
            super(BooleanControl.Type.MUTE, false, "True", "False");  | 
 | 
        }  | 
 | 
 | 
 | 
        public void setValue(boolean newValue) { | 
 | 
            super.setValue(newValue);  | 
 | 
            calcVolume();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private final class ApplyReverb extends BooleanControl { | 
 | 
 | 
 | 
        private ApplyReverb() { | 
 | 
            super(BooleanControl.Type.APPLY_REVERB, false, "True", "False");  | 
 | 
        }  | 
 | 
 | 
 | 
        public void setValue(boolean newValue) { | 
 | 
            super.setValue(newValue);  | 
 | 
            calcVolume();  | 
 | 
        }  | 
 | 
 | 
 | 
    }  | 
 | 
 | 
 | 
    private final class Balance extends FloatControl { | 
 | 
 | 
 | 
        private Balance() { | 
 | 
            super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1,  | 
 | 
                    0.0f, "", "Left", "Center", "Right");  | 
 | 
        }  | 
 | 
 | 
 | 
        public void setValue(float newValue) { | 
 | 
            super.setValue(newValue);  | 
 | 
            calcVolume();  | 
 | 
        }  | 
 | 
 | 
 | 
    }  | 
 | 
 | 
 | 
    private final class Pan extends FloatControl { | 
 | 
 | 
 | 
        private Pan() { | 
 | 
            super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1,  | 
 | 
                    0.0f, "", "Left", "Center", "Right");  | 
 | 
        }  | 
 | 
 | 
 | 
        public void setValue(float newValue) { | 
 | 
            super.setValue(newValue);  | 
 | 
            balance_control.setValue(newValue);  | 
 | 
        }  | 
 | 
 | 
 | 
        public float getValue() { | 
 | 
            return balance_control.getValue();  | 
 | 
        }  | 
 | 
 | 
 | 
    }  | 
 | 
 | 
 | 
    private final class ReverbSend extends FloatControl { | 
 | 
 | 
 | 
        private ReverbSend() { | 
 | 
            super(FloatControl.Type.REVERB_SEND, -80f, 6.0206f, 80f / 128.0f,  | 
 | 
                    -1, -80f, "dB", "Minimum", "", "Maximum");  | 
 | 
        }  | 
 | 
 | 
 | 
        public void setValue(float newValue) { | 
 | 
            super.setValue(newValue);  | 
 | 
            balance_control.setValue(newValue);  | 
 | 
        }  | 
 | 
 | 
 | 
    }  | 
 | 
 | 
 | 
    private final class ChorusSend extends FloatControl { | 
 | 
 | 
 | 
        private ChorusSend() { | 
 | 
            super(CHORUS_SEND, -80f, 6.0206f, 80f / 128.0f, -1, -80f, "dB",  | 
 | 
                    "Minimum", "", "Maximum");  | 
 | 
        }  | 
 | 
 | 
 | 
        public void setValue(float newValue) { | 
 | 
            super.setValue(newValue);  | 
 | 
            balance_control.setValue(newValue);  | 
 | 
        }  | 
 | 
 | 
 | 
    }  | 
 | 
 | 
 | 
    private final Gain gain_control = new Gain();  | 
 | 
 | 
 | 
    private final Mute mute_control = new Mute();  | 
 | 
 | 
 | 
    private final Balance balance_control = new Balance();  | 
 | 
 | 
 | 
    private final Pan pan_control = new Pan();  | 
 | 
 | 
 | 
    private final ReverbSend reverbsend_control = new ReverbSend();  | 
 | 
 | 
 | 
    private final ChorusSend chorussend_control = new ChorusSend();  | 
 | 
 | 
 | 
    private final ApplyReverb apply_reverb = new ApplyReverb();  | 
 | 
 | 
 | 
    private final Control[] controls;  | 
 | 
 | 
 | 
    float leftgain = 1;  | 
 | 
 | 
 | 
    float rightgain = 1;  | 
 | 
 | 
 | 
    float eff1gain = 0;  | 
 | 
 | 
 | 
    float eff2gain = 0;  | 
 | 
 | 
 | 
    List<LineListener> listeners = new ArrayList<LineListener>();  | 
 | 
 | 
 | 
    final Object control_mutex;  | 
 | 
 | 
 | 
    SoftMixingMixer mixer;  | 
 | 
 | 
 | 
    DataLine.Info info;  | 
 | 
 | 
 | 
    protected abstract void processControlLogic();  | 
 | 
 | 
 | 
    protected abstract void processAudioLogic(SoftAudioBuffer[] buffers);  | 
 | 
 | 
 | 
    SoftMixingDataLine(SoftMixingMixer mixer, DataLine.Info info) { | 
 | 
        this.mixer = mixer;  | 
 | 
        this.info = info;  | 
 | 
        this.control_mutex = mixer.control_mutex;  | 
 | 
 | 
 | 
        controls = new Control[] { gain_control, mute_control, balance_control, | 
 | 
                pan_control, reverbsend_control, chorussend_control,  | 
 | 
                apply_reverb };  | 
 | 
        calcVolume();  | 
 | 
    }  | 
 | 
 | 
 | 
    final void calcVolume() { | 
 | 
        synchronized (control_mutex) { | 
 | 
            double gain = Math.pow(10.0, gain_control.getValue() / 20.0);  | 
 | 
            if (mute_control.getValue())  | 
 | 
                gain = 0;  | 
 | 
            leftgain = (float) gain;  | 
 | 
            rightgain = (float) gain;  | 
 | 
            if (mixer.getFormat().getChannels() > 1) { | 
 | 
                  | 
 | 
                double balance = balance_control.getValue();  | 
 | 
                if (balance > 0)  | 
 | 
                    leftgain *= (1 - balance);  | 
 | 
                else  | 
 | 
                    rightgain *= (1 + balance);  | 
 | 
 | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        eff1gain = (float) Math.pow(10.0, reverbsend_control.getValue() / 20.0);  | 
 | 
        eff2gain = (float) Math.pow(10.0, chorussend_control.getValue() / 20.0);  | 
 | 
 | 
 | 
        if (!apply_reverb.getValue()) { | 
 | 
            eff1gain = 0;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    final void sendEvent(LineEvent event) { | 
 | 
        if (listeners.size() == 0)  | 
 | 
            return;  | 
 | 
        LineListener[] listener_array = listeners  | 
 | 
                .toArray(new LineListener[listeners.size()]);  | 
 | 
        for (LineListener listener : listener_array) { | 
 | 
            listener.update(event);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    public final void addLineListener(LineListener listener) { | 
 | 
        synchronized (control_mutex) { | 
 | 
            listeners.add(listener);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    public final void removeLineListener(LineListener listener) { | 
 | 
        synchronized (control_mutex) { | 
 | 
            listeners.add(listener);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    public final javax.sound.sampled.Line.Info getLineInfo() { | 
 | 
        return info;  | 
 | 
    }  | 
 | 
 | 
 | 
    public final Control getControl(Type control) { | 
 | 
        if (control != null) { | 
 | 
            for (int i = 0; i < controls.length; i++) { | 
 | 
                if (controls[i].getType() == control) { | 
 | 
                    return controls[i];  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
        throw new IllegalArgumentException("Unsupported control type : " | 
 | 
                + control);  | 
 | 
    }  | 
 | 
 | 
 | 
    public final Control[] getControls() { | 
 | 
        return Arrays.copyOf(controls, controls.length);  | 
 | 
    }  | 
 | 
 | 
 | 
    public final boolean isControlSupported(Type control) { | 
 | 
        if (control != null) { | 
 | 
            for (int i = 0; i < controls.length; i++) { | 
 | 
                if (controls[i].getType() == control) { | 
 | 
                    return true;  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return false;  | 
 | 
    }  | 
 | 
 | 
 | 
}  |