|  | /* | 
|  | * 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.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; | 
|  |  | 
|  | /** | 
|  | * General software mixing line. | 
|  | * | 
|  | * @author Karl Helgason | 
|  | */ | 
|  | 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(); // 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; // ais.getFrameLength(); | 
|  | } | 
|  |  | 
|  | 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) { | 
|  | // -1 = Left, 0 Center, 1 = Right | 
|  | 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; | 
|  | } | 
|  |  | 
|  | } |