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