package org.hamnett.adm.audio.impl;
import java.util.ArrayList;
import java.util.List;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.CompoundControl;
import javax.sound.sampled.Control;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Port;
import org.hamnett.adm.audio.Audio;
/**
* Default implementation of the SynchronousAudio interface using javax.sound.
*
* @author srt
* @version $Id: SynchronousAudioImpl.java,v 1.4 2005/07/16 11:48:33 srt Exp $
*/
class SynchronousAudioImpl implements SynchronousAudio
{
public static final long MILLISECONDS_PER_STEP = 100;
boolean cancel;
double fadeToVolume = -1;
//package restricted constructor
SynchronousAudioImpl()
{
}
public void cancelFade()
{
this.cancel = true;
}
public double getVolume()
{
VolumeControl volumeControl;
double currentVolume = 0;
if (fadeToVolume != -1)
{
return fadeToVolume;
}
volumeControl = openVolumeControl();
if (volumeControl == null)
{
return 0;
}
currentVolume = volumeControl.getVolume();
volumeControl.close();
return currentVolume;
}
public void fadeVolume(double percent, long velocity)
{
doFade(percent, true, velocity);
}
public void fadeVolumeTo(double newVolume, long velocity)
{
doFade(newVolume, false, velocity);
}
private void doFade(double value, boolean isRelative, long velocity)
{
VolumeControl volumeControl;
double oldVolume; // volume before fading starts
double currentVolume;
double newVolume; // new volume after fading has finished
long milliseconds; // duration of this fading
double volumeDiff;
double volumeDiffPerStep;
int steps;
cancel = false;
volumeControl = openVolumeControl();
if (volumeControl == null)
{
return;
}
oldVolume = volumeControl.getVolume();
currentVolume = oldVolume;
if (isRelative)
{
volumeDiff = oldVolume * (1 - value);
newVolume = oldVolume - volumeDiff;
}
else
{
newVolume = value;
volumeDiff = oldVolume - newVolume;
}
fadeToVolume = newVolume;
milliseconds = (long) (Math.abs(volumeDiff) * velocity);
steps = (int) (milliseconds / MILLISECONDS_PER_STEP);
volumeDiffPerStep = volumeDiff / steps;
System.out.println("Fading from " + oldVolume + " to " + newVolume + " in " + milliseconds + "ms");
for (int i = 0; i < steps && !cancel; i++)
{
currentVolume -= volumeDiffPerStep;
// System.out.println(currentVolume);
volumeControl.setVolume(currentVolume);
try
{
Thread.sleep(MILLISECONDS_PER_STEP);
}
catch (InterruptedException e)
{
// swallow
}
}
// System.out.println(newVolume);
if (cancel)
{
System.out.println("Fading was canceled.");
}
else
{
volumeControl.setVolume(newVolume);
}
fadeToVolume = -1;
volumeControl.close();
}
// TODO let the user choose which Port to use for volume adjustment
Port getMasterPort(Mixer mixer)
{
Line.Info[] lineInfos = mixer.getTargetLineInfo();
for (int i = 0; i < lineInfos.length; i++)
{
Line.Info lineInfo = lineInfos[i];
Line line;
try
{
line = mixer.getLine(lineInfo);
}
catch (LineUnavailableException e)
{
continue;
}
if (line instanceof Port
&& lineInfo.toString().startsWith("Master"))
{
return (Port) line;
}
}
return null;
}
VolumeControl openVolumeControl()
{
Mixer mixer;
Port port;
FloatControl volumeFloatControl;
mixer = getMixer();
if (mixer == null)
{
return null;
}
try
{
port = getMasterPort(mixer);
port.open();
volumeFloatControl = getVolumeFloatControl(port);
}
catch (LineUnavailableException e)
{
// hmm...
System.out.println("Port not available");
return null;
}
return new PortBasedVolumeControl(volumeFloatControl, port);
}
/**
* Returns the FloatControl to adjust volume for the given Port.
*
* @param port the Port that contains the Volume Control
* @return the Volume Control for the given Port or null if
* the Port doesn't support Volume adjustment.
*/
FloatControl getVolumeFloatControl(Port port)
{
FloatControl volumeControl = null;
boolean bClosePort = false;
if (!port.isOpen())
{
try
{
port.open();
}
catch (LineUnavailableException e)
{
throw new IllegalArgumentException("Unable to open port "
+ port.getLineInfo());
}
}
Control[] controls = port.getControls();
for (int j = 0; j < controls.length && volumeControl == null; j++)
{
Control control = controls[j];
if (isVolumeFloatControl(control))
{
volumeControl = (FloatControl) control;
}
// nested controls?
if (control instanceof CompoundControl)
{
Control[] memberControls;
memberControls = ((CompoundControl) control)
.getMemberControls();
for (int k = 0; k < memberControls.length; k++)
{
Control memberControl = memberControls[k];
if (isVolumeFloatControl(memberControl))
{
volumeControl = (FloatControl) memberControl;
break;
}
}
}
}
if (bClosePort)
{
port.close();
}
return volumeControl;
}
/**
* Checks if a Control is a Volume Control.
*
* @param control the Control to test
* @return true if the control is a volume control,
* false otherwise.
*/
private boolean isVolumeFloatControl(Control control)
{
return control instanceof FloatControl
&& control.getType().equals(FloatControl.Type.VOLUME);
}
Mixer getMixer()
{
// TODO add config
List mixers;
mixers = getPortMixers();
if (mixers.size() < 1)
{
System.out.println("No mixer available.");
return null;
}
return (Mixer) mixers.get(0);
}
/**
* Returns the Mixers that support Port lines.
*
* @return a List of the availble Mixers that support Port lines.
*/
private List getPortMixers()
{
List supportingMixers = new ArrayList();
Mixer.Info[] mixerInfos;
mixerInfos = AudioSystem.getMixerInfo();
for (int i = 0; i < mixerInfos.length; i++)
{
Mixer mixer = AudioSystem.getMixer(mixerInfos[i]);
boolean bSupportsPorts = arePortsSupported(mixer);
if (bSupportsPorts)
{
supportingMixers.add(mixer);
}
}
return supportingMixers;
}
/**
* Checks if a Mixer supports Port lines.
*
* @param mixer the Mixer to test
* @return true if the given Mixer supports Port lines,
* false otherwise.
*/
private boolean arePortsSupported(Mixer mixer)
{
Line.Info[] infos;
infos = mixer.getSourceLineInfo();
for (int i = 0; i < infos.length; i++)
{
if (infos[i] instanceof Port.Info)
{
return true;
}
}
infos = mixer.getTargetLineInfo();
for (int i = 0; i < infos.length; i++)
{
if (infos[i] instanceof Port.Info)
{
return true;
}
}
return false;
}
public static void main(String[] args)
{
Audio audio;
audio = new SynchronousAudioImpl();
audio.fadeVolume(0.2, 3000);
audio.fadeVolumeTo(1.0, 2000);
}
}