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