view overlays/openjdk/jdk/src/share/classes/com/sun/media/sound/SoftMixingDataLine.java @ 873:a1832ea486e9

Import Gervill fixes from CVS and add tests. 2008-05-23 Mark Wielaard <mark@klomp.org> * overlays/openjdk/jdk/src/share/classes/com/sun/media/sound: Import Gervill fixes from CVS. See CHANGES.txt. Added: Software Mixing Mixer. Fix: AudioFloatFormatConverter.getTargetFormats() * overlays/openjdk/jdk/test/com/sun/media/sound: Imported all tests.
author Mark Wielaard <mark@klomp.org>
date Sun, 25 May 2008 21:21:11 +0200
parents
children
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.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.
 * 
 * @version %I%, %E%
 * @author Karl Helgason
 */

public abstract class SoftMixingDataLine implements DataLine {

	public static final FloatControl.Type CHORUS_SEND = new FloatControl.Type(
			"Chorus Send") {
	};

	protected class AudioFloatInputStreamResampler extends
			AudioFloatInputStream {

		private AudioFloatInputStream ais;

		private AudioFormat targetFormat;

		private float[] skipbuffer;

		private SoftAbstractResampler resampler;

		private float[] pitch = new float[1];

		private float[] ibuffer2;

		private float[][] ibuffer;

		private float ibuffer_index = 0;

		private int ibuffer_len = 0;

		private int nrofchannels = 0;

		private float[][] cbuffer;

		private int buffer_len = 512;

		private int pad;

		private int pad2;

		private float[] ix = new float[1];

		private 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 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 class Mute extends BooleanControl {

		private Mute() {
			super(BooleanControl.Type.MUTE, false, "True", "False");
		}

		public void setValue(boolean newValue) {
			super.setValue(newValue);
			calcVolume();
		}
	}

	private class ApplyReverb extends BooleanControl {

		private ApplyReverb() {
			super(BooleanControl.Type.APPLY_REVERB, false, "True", "False");
		}

		public void setValue(boolean newValue) {
			super.setValue(newValue);
			calcVolume();
		}

	}

	private 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 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 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 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 Gain gain_control = new Gain();

	private Mute mute_control = new Mute();

	private Balance balance_control = new Balance();

	private Pan pan_control = new Pan();

	private ReverbSend reverbsend_control = new ReverbSend();

	private ChorusSend chorussend_control = new ChorusSend();

	private ApplyReverb apply_reverb = new ApplyReverb();

	private Control[] controls;

	protected float leftgain = 1;

	protected float rightgain = 1;

	protected float eff1gain = 0;

	protected float eff2gain = 0;

	protected List<LineListener> listeners = new ArrayList<LineListener>();

	protected Object control_mutex;

	protected SoftMixingMixer mixer;

	protected DataLine.Info info;

	protected abstract void processControlLogic();

	protected abstract void processAudioLogic(SoftAudioBuffer[] buffers);

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

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

	protected 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 void addLineListener(LineListener listener) {
		synchronized (control_mutex) {
			listeners.add(listener);
		}
	}

	public void removeLineListener(LineListener listener) {
		synchronized (control_mutex) {
			listeners.add(listener);
		}
	}

	public javax.sound.sampled.Line.Info getLineInfo() {
		return info;
	}

	public 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 Control[] getControls() {
		return controls;
	}

	public boolean isControlSupported(Type control) {
		if (control != null) {
			for (int i = 0; i < controls.length; i++) {
				if (controls[i].getType() == control) {
					return true;
				}
			}
		}
		return false;
	}

}