/*
* Copyright (c) 2007 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 javax.sound.sampled.BooleanControl;
import javax.sound.sampled.Control;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.Line;
import processing.core.PApplet;
/**
* <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(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
*
*/
public class Controller
{
/**
* The volume control type.
*/
public static FloatControl.Type VOLUME = FloatControl.Type.VOLUME;
/**
* The gain control type.
*/
public static FloatControl.Type GAIN = FloatControl.Type.MASTER_GAIN;
/**
* The balance control type.
*/
public static FloatControl.Type BALANCE = FloatControl.Type.BALANCE;
/**
* The pan control type.
*/
public static FloatControl.Type PAN = FloatControl.Type.PAN;
/**
* The sample rate control type.
*/
public static FloatControl.Type SAMPLE_RATE = FloatControl.Type.SAMPLE_RATE;
/**
* The mute control type.
*/
public static BooleanControl.Type MUTE = BooleanControl.Type.MUTE;
private Line line;
// 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 line
* the <code>Line</code> that will be controlled
*/
public Controller(Line line)
{
this.line = line;
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 = Minim.millis();
tend = tstart + t;
vstart = vs;
vend = ve;
}
public float value()
{
int millis = Minim.millis();
return PApplet.map(millis, tstart, tend, vstart, vend);
}
public boolean done()
{
return Minim.millis() > tend;
}
}
/**
* 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()
{
Control[] controls = line.getControls();
if (controls.length > 0)
{
PApplet.println("Available controls are:");
for (int i = 0; i < controls.length; i++)
{
Control.Type type = controls[i].getType();
PApplet.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";
}
PApplet.println(", which has a range of " + fc.getMaximum() + " to "
+ fc.getMinimum() + " and " + shiftSupported
+ " support shifting.");
}
else
{
PApplet.println("");
}
}
}
else
{
PApplet.println("There are no controls available for this line.");
}
}
/**
* 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
*/
public boolean hasControl(Control.Type type)
{
return line.isControlSupported(type);
}
/**
* 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
*/
public Control[] getControls()
{
return line.getControls();
}
/**
* 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(javax.sound.sampled.Control.Type)} before calling this
* method.
*
* @return the volume control
*/
public FloatControl volume()
{
return (FloatControl) line.getControl(VOLUME);
}
/**
* 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(javax.sound.sampled.Control.Type)} before calling this
* method.
*
* @return the gain control
*/
public FloatControl gain()
{
return (FloatControl) line.getControl(GAIN);
}
/**
* 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(javax.sound.sampled.Control.Type)} before calling this
* method.
*
* @return the balance control
*/
public FloatControl balance()
{
return (FloatControl) line.getControl(BALANCE);
}
/**
* 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(javax.sound.sampled.Control.Type)} before calling this
* method.
*
* @return the pan control
*/
public FloatControl pan()
{
return (FloatControl) line.getControl(BALANCE);
}
/**
* Mutes the line.
*
*/
public void mute()
{
setValue(MUTE, true);
}
/**
* Unmutes the line.
*
*/
public void unmute()
{
setValue(MUTE, false);
}
/**
* Returns true if the line is muted.
*
* @return the current mute state
*/
public boolean isMuted()
{
return getValue(MUTE);
}
private boolean getValue(BooleanControl.Type type)
{
boolean v = false;
if (line.isControlSupported(type))
{
BooleanControl c = (BooleanControl) line.getControl(type);
v = c.getValue();
}
else
{
Minim.error(type.toString() + " is not supported on this line.");
}
return v;
}
private void setValue(BooleanControl.Type type, boolean v)
{
if (line.isControlSupported(type))
{
BooleanControl c = (BooleanControl) line.getControl(type);
c.setValue(v);
}
else
{
if (type!=MUTE)
Minim.error(type.toString() + " is not supported on this line.");
}
}
private float getValue(FloatControl.Type type)
{
float v = 0;
if (line.isControlSupported(type))
{
FloatControl c = (FloatControl) line.getControl(type);
v = c.getValue();
}
else
{
Minim.error(type.toString() + " is not supported on this line.");
}
return v;
}
private void setValue(FloatControl.Type type, float v)
{
if (line.isControlSupported(type))
{
FloatControl c = (FloatControl) line.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 on this line.");
}
}
/**
* 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!
*
* @return the current volume or zero if a volume control is unavailable
*/
public float getVolume()
{
return getValue(VOLUME);
}
/**
* Sets the volume to <code>v</code>. If a volume control is not available,
* this does nothing.
*
* @param v
* the new value for the volume
*/
public void setVolume(float v)
{
setValue(VOLUME, v);
}
/**
* Shifts the value of the volume from <code>from</code> to <code>to</code>
* in the space of <code>millis</code> milliseconds.
*
* @param from
* the starting volume
* @param to
* the ending volume
* @param millis
* the length of the transition
*/
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!
*
* @return the current gain or zero if a gain control is unavailable
*/
public float getGain()
{
return getValue(GAIN);
}
/**
* Sets the gain to <code>v</code>. If a gain control is not available,
* this does nothing.
*
* @param v
* the new value for the gain
*/
public void setGain(float v)
{
setValue(GAIN, v);
}
/**
* Shifts the value of the gain from <code>from</code> to <code>to</code>
* in the space of <code>millis</code>
*
* @param from
* the starting volume
* @param to
* the ending volume
* @param millis
* the length of the transition
*/
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 of the line. This will be in the range [-1, 1].
* If a balance control is not available, this will do nothing.
*
* @return the current balance or zero if a balance control is unavailable
*/
public float getBalance()
{
return getValue(BALANCE);
}
/**
* Sets the balance of the line to <code>v</code>. The provided value
* should be in the range [-1, 1]. If a balance control is not available, this
* will do nothing.
*
* @param v
* the new value for the balance
*/
public void setBalance(float v)
{
setValue(BALANCE, v);
}
/**
* Shifts the value of the balance from <code>from</code> to <code>to</code>
* in the space of <code>millis</code> milliseconds.
*
* @param from
* the starting volume
* @param to
* the ending volume
* @param millis
* the length of the transition
*/
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 value. This will be in the range [-1, 1]. If the
* pan control is not available
*
* @return the current pan or zero if a pan control is unavailable
*/
public float getPan()
{
return getValue(PAN);
}
/**
* Sets the pan of the line to <code>v</code>. The provided value should be
* in the range [-1, 1].
*
* @param v
* the new value for the pan
*/
public void setPan(float v)
{
setValue(PAN, v);
}
/**
* Shifts the value of the pan from <code>from</code> to <code>to</code>
* in the space of <code>millis</code> milliseconds.
*
* @param from
* the starting volume
* @param to
* the ending volume
* @param millis
* the length of the transition
*/
public void shiftPan(float from, float to, int millis)
{
if ( hasControl(PAN) )
{
setPan(from);
pshifter = new ValueShifter(from, to, millis);
pshift = true;
}
}
public Line getLine() {
return line;
}
public void setLine(Line line) {
this.line = line;
}
}