/* * Copyright (c) 2007 - 2008 by Damien Di Fede <ddf@compartmental.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package ddf.minim; import ddf.minim.javax.sound.sampled.BooleanControl; import ddf.minim.javax.sound.sampled.Control; import ddf.minim.javax.sound.sampled.FloatControl; /** * <code>Controller</code> is the base class of all Minim classes that deal with * audio I/O. It provides control over the underlying <code>DataLine</code>, * which is a low-level JavaSound class that talks directly to the audio * hardware of the computer. This means that you can make changes to the audio * without having to manipulate the samples directly. The downside to this is * that when outputting sound to the system (such as with an * <code>AudioOutput</code>), these changes will not be present in the samples * made available to your program. * <p> * The {@link #volume()}, {@link #gain()}, {@link #pan()}, and * {@link #balance()} methods return objects of type <code>FloatControl</code>, * which is a class defined by the JavaSound API. A <code>FloatControl</code> * represents a control of a line that holds a <code>float</code> value. This * value has an associated maximum and minimum value (such as between -1 and 1 * for pan), and also a unit type (such as dB for gain). You should refer to the * <a href= * "http://java.sun.com/j2se/1.5.0/docs/api/javax/sound/sampled/FloatControl.html" * >FloatControl Javadoc</a> for the full description of the methods available. * <p> * Not all controls are available on all objects. Before calling the methods * mentioned above, you should call * {@link #hasControl(ddf.minim.javax.sound.sampled.Control.Type)} with the control type * you want to use. Alternatively, you can use the <code>get</code> and * <code>set</code> methods, which will simply do nothing if the control you are * trying to manipulate is not available. * * @author Damien Di Fede * @invisible * */ public class Controller { /** * @invisible The volume control type. */ @Deprecated public static FloatControl.Type VOLUME = FloatControl.Type.VOLUME; /** * @invisible The gain control type. */ @Deprecated public static FloatControl.Type GAIN = FloatControl.Type.MASTER_GAIN; /** * @invisible The balance control type. */ @Deprecated public static FloatControl.Type BALANCE = FloatControl.Type.BALANCE; /** * @invisible The pan control type. */ @Deprecated public static FloatControl.Type PAN = FloatControl.Type.PAN; /** * @invisible The sample rate control type. */ @Deprecated public static FloatControl.Type SAMPLE_RATE = FloatControl.Type.SAMPLE_RATE; /** * @invisible The mute control type. */ @Deprecated public static BooleanControl.Type MUTE = BooleanControl.Type.MUTE; private Control[] controls; // the starting value for shifting private ValueShifter vshifter, gshifter, bshifter, pshifter; private boolean vshift, gshift, bshift, pshift; /** * Constructs a <code>Controller</code> for the given <code>Line</code>. * * @param cntrls * an array of Controls that this Controller will manipulate * * @invisible */ public Controller(Control[] cntrls) { controls = cntrls; vshift = gshift = bshift = pshift = false; } // for line reading/writing classes to alert the controller // that a new buffer has been read/written void update() { if (vshift) { setVolume(vshifter.value()); if (vshifter.done()) vshift = false; } if (gshift) { setGain(gshifter.value()); if (gshifter.done()) gshift = false; } if (bshift) { setBalance(bshifter.value()); if (bshifter.done()) bshift = false; } if (pshift) { setPan(pshifter.value()); if (pshifter.done()) pshift = false; } } // a small class to interpolate a value over time class ValueShifter { private float tstart, tend, vstart, vend; public ValueShifter(float vs, float ve, int t) { tstart = (int) System.currentTimeMillis(); tend = tstart + t; vstart = vs; vend = ve; } public float value() { int millis = (int) System.currentTimeMillis(); float norm = (float) (millis - tstart) / (tend - tstart); float range = (float) (vend - vstart); return vstart + range * norm; } public boolean done() { return (int) System.currentTimeMillis() > tend; } } /** * @invisible * * Prints the available controls and their ranges to the console. * Not all lines have all of the controls available on them so * this is a way to find out what is available. * */ public void printControls() { if (controls.length > 0) { System.out.println("Available controls are:"); for (int i = 0; i < controls.length; i++) { Control.Type type = controls[i].getType(); System.out.print(" " + type.toString()); if (type == VOLUME || type == GAIN || type == BALANCE || type == PAN) { FloatControl fc = (FloatControl) controls[i]; String shiftSupported = "does"; if (fc.getUpdatePeriod() == -1) { shiftSupported = "doesn't"; } System.out.println(", which has a range of " + fc.getMaximum() + " to " + fc.getMinimum() + " and " + shiftSupported + " support shifting."); } else { System.out.println(""); } } } else { System.out .println("There are no controls available for this line."); } } /** * @invisible * * Returns whether or not the particular control type is * supported by the Line being controlled. * * @see #VOLUME * @see #GAIN * @see #BALANCE * @see #PAN * @see #SAMPLE_RATE * @see #MUTE * * @return true if the control is available */ @Deprecated public boolean hasControl(Control.Type type) { for (int i = 0; i < controls.length; i++) { if (controls[i].getType().equals(type)) { return true; } } return false; } /** * @invisible * * Returns an array of all the available <code>Control</code>s * for the <code>DataLine</code> being controlled. You can use * this if you want to access the controls directly, rather than * using the convenience methods provided by this class. * * @return an array of all available controls */ @Deprecated public Control[] getControls() { return controls; } @Deprecated public Control getControl(Control.Type type) { for (int i = 0; i < controls.length; i++) { if (controls[i].getType().equals(type)) { return controls[i]; } } return null; } /** * @invisible Gets the volume control for the <code>Line</code>, if it * exists. You should check for the availability of a volume * control by using * {@link #hasControl(ddf.minim.javax.sound.sampled.Control.Type)} before * calling this method. * * @return the volume control */ @Deprecated public FloatControl volume() { return (FloatControl) getControl(VOLUME); } /** * @invisible Gets the gain control for the <code>Line</code>, if it exists. * You should check for the availability of a gain control by * using {@link #hasControl(ddf.minim.javax.sound.sampled.Control.Type)} * before calling this method. * * @return the gain control */ @Deprecated public FloatControl gain() { return (FloatControl) getControl(GAIN); } /** * @invisible Gets the balance control for the <code>Line</code>, if it * exists. You should check for the availability of a balance * control by using * {@link #hasControl(ddf.minim.javax.sound.sampled.Control.Type)} before * calling this method. * * @return the balance control */ @Deprecated public FloatControl balance() { return (FloatControl) getControl(BALANCE); } /** * @invisible Gets the pan control for the <code>Line</code>, if it exists. * You should check for the availability of a pan control by * using {@link #hasControl(ddf.minim.javax.sound.sampled.Control.Type)} * before calling this method. * * @return the pan control */ @Deprecated public FloatControl pan() { return (FloatControl) getControl(PAN); } /** * Mutes the sound. * * @related unmute ( ) * @related isMuted ( ) */ public void mute() { setValue(MUTE, true); } /** * Unmutes the sound. * * @related mute ( ) * @related isMuted ( ) */ public void unmute() { setValue(MUTE, false); } /** * Returns true if the sound is muted. * * @return the current mute state * * @related mute ( ) * @related unmute ( ) */ public boolean isMuted() { return getValue(MUTE); } private boolean getValue(BooleanControl.Type type) { boolean v = false; if (hasControl(type)) { BooleanControl c = (BooleanControl) getControl(type); v = c.getValue(); } else { Minim.error(type.toString() + " is not supported."); } return v; } private void setValue(BooleanControl.Type type, boolean v) { if (hasControl(type)) { BooleanControl c = (BooleanControl) getControl(type); c.setValue(v); } else { Minim.error(type.toString() + " is not supported."); } } private float getValue(FloatControl.Type type) { float v = 0; if (hasControl(type)) { FloatControl c = (FloatControl) getControl(type); v = c.getValue(); } else { Minim.error(type.toString() + " is not supported."); } return v; } private void setValue(FloatControl.Type type, float v) { if (hasControl(type)) { FloatControl c = (FloatControl) getControl(type); if (v > c.getMaximum()) v = c.getMaximum(); else if (v < c.getMinimum()) v = c.getMinimum(); c.setValue(v); } else { Minim.error(type.toString() + " is not supported."); } } /** * Returns the current volume. If a volume control is not available, this * returns 0. Note that the volume is not the same thing as the * <code>level()</code> of an AudioBuffer! * * @shortdesc Returns the current volume. * * @return the current volume or zero if a volume control is unavailable * * @related setVolume ( ) * @related shiftVolume ( ) */ public float getVolume() { return getValue(VOLUME); } /** * Sets the volume. If a volume control is not available, this does nothing. * * @shortdesc Sets the volume. * * @param value * float: the new value for the volume, usually in the range * [0,1]. * * @related getVolume ( ) * @related shiftVolume ( ) */ public void setVolume(float value) { setValue(VOLUME, value); } /** * Transitions the volume from one value to another. * * @param from * float: the starting volume * @param to * float: the ending volume * @param millis * int: the length of the transition in milliseconds * * @related getVolume ( ) * @related setVolume ( ) */ public void shiftVolume(float from, float to, int millis) { if (hasControl(VOLUME)) { setVolume(from); vshifter = new ValueShifter(from, to, millis); vshift = true; } } /** * Returns the current gain. If a gain control is not available, this * returns 0. Note that the gain is not the same thing as the * <code>level()</code> of an AudioBuffer! Gain describes the current volume * of the sound in decibels, which is a logarithmic, rather than linear, * scale. A gain of 0dB means the sound is not being amplified or * attenuated. Negative gain values will reduce the volume of the sound, and * positive values will increase it. * <p> * See: <a * href="http://wikipedia.org/wiki/Decibel">http://wikipedia.org/wiki * /Decibel</a> * * @shortdesc Returns the current gain. * * @return float: the current gain or zero if a gain control is unavailable. * the gain is expressed in decibels. * * @related setGain ( ) * @related shiftGain ( ) */ public float getGain() { return getValue(GAIN); } /** * Sets the gain. If a gain control is not available, this does nothing. * * @shortdesc Sets the gain. * * @param value * float: the new value for the gain, expressed in decibels. * * @related getGain ( ) * @related shiftGain ( ) */ public void setGain(float value) { setValue(GAIN, value); } /** * Transitions the gain from one value to another. * * @param from * float: the starting gain * @param to * float: the ending gain * @param millis * int: the length of the transition in milliseconds * * @related getGain ( ) * @related setGain ( ) */ public void shiftGain(float from, float to, int millis) { if (hasControl(GAIN)) { setGain(from); gshifter = new ValueShifter(from, to, millis); gshift = true; } } /** * Returns the current balance. This will be in the range [-1, 1]. Usually * balance will only be available for stereo audio sources, because it * describes how much attenuation should be applied to the left and right * channels. If a balance control is not available, this will do nothing. * * @shortdesc Returns the current balance. * * @return float: the current balance or zero if a balance control is * unavailable * * @related setBalance ( ) * @related shiftBalance ( ) */ public float getBalance() { return getValue(BALANCE); } /** * Sets the balance. The value should be in the range [-1, 1]. If a balance * control is not available, this will do nothing. * * @shortdesc Sets the balance. * * @param value * float: the new value for the balance * * @related getBalance ( ) * @related shiftBalance ( ) */ public void setBalance(float value) { setValue(BALANCE, value); } /** * Transitions the balance from one value to another. * * @param from * float: the starting balance * @param to * float: the ending balance * @param millis * int: the length of the transition in milliseconds * * @related getBalance ( ) * @related setBalance ( ) */ public void shiftBalance(float from, float to, int millis) { if (hasControl(BALANCE)) { setBalance(from); bshifter = new ValueShifter(from, to, millis); bshift = true; } } /** * Returns the current pan. Usually pan will be only be available on mono * audio sources because it describes a mono signal's position in a stereo * field. This will be in the range [-1, 1], where -1 will place the sound * only in the left speaker and 1 will place the sound only in the right * speaker. * * @shortdesc Returns the current pan. * * @return float: the current pan or zero if a pan control is unavailable * * @related setPan ( ) * @related shiftPan ( ) */ public float getPan() { return getValue(PAN); } /** * Sets the pan. The provided value should be in the range [-1, 1]. If a pan * control is not present, this does nothing. * * @shortdesc Sets the pan. * * @param value * float: the new value for the pan * * @related getPan ( ) * @related shiftPan ( ) */ public void setPan(float value) { setValue(PAN, value); } /** * Transitions the pan from one value to another. * * @param from * float: the starting pan * @param to * float: the ending pan * @param millis * int: the length of the transition in milliseconds * * @related getPan ( ) * @related setPan ( ) */ public void shiftPan(float from, float to, int millis) { if (hasControl(PAN)) { setPan(from); pshifter = new ValueShifter(from, to, millis); pshift = true; } } }