/** * $RCSfile: ,v $ * $Revision: $ * $Date: $ * * Copyright (C) 2004-2011 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.sparkplugin.ui.components; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.BooleanControl; import javax.sound.sampled.CompoundControl; import javax.sound.sampled.Control; import javax.sound.sampled.DataLine; import javax.sound.sampled.EnumControl; 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 javax.swing.AbstractButton; import javax.swing.BoundedRangeModel; import javax.swing.ButtonModel; import javax.swing.DefaultBoundedRangeModel; import javax.swing.DefaultButtonModel; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JTree; import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; /** * Pure Java Audio Mixer. Control Volume and Settings for any Sound device in the OS. * * @author Thiago Camargo */ public class JavaMixer { private static final Line.Info[] EMPTY_PORT_INFO_ARRAY = new Line.Info[0]; DefaultMutableTreeNode root = new DefaultMutableTreeNode("Sound Mixers", true); JTree tree = new JTree(root); public JavaMixer() { List<Mixer> portMixers = getPortMixers(); if (portMixers.size() == 0) System.err.println("No Mixers Found."); for (Mixer mixer : portMixers) { JavaMixer.MixerNode mixerNode = new JavaMixer.MixerNode(mixer); createMixerChildren(mixerNode); root.add(mixerNode); } } public JTree getTree() { return tree; } public Component getPrefferedMasterVolume() { TreePath path = findByName(new TreePath(root), new String[]{"SPEAKER", "Volume"}); if (path == null) { path = findByName(new TreePath(root), new String[]{"Master target", "Master", "Mute"}); } if (path != null) { if (path.getLastPathComponent() instanceof JavaMixer.ControlNode) return ((JavaMixer.ControlNode) path.getLastPathComponent()).getComponent(); } return null; } public Component getPrefferedInputVolume() { TreePath path = findByName(new TreePath(root), new String[]{"MICROPHONE", "Volume"}); if (path == null) { path = findByName(new TreePath(root), new String[]{"Capture source", "Capture", "Volume"}); } if (path != null) { if (path.getLastPathComponent() instanceof JavaMixer.ControlNode) return ((JavaMixer.ControlNode) path.getLastPathComponent()).getComponent(); } return null; } public void setMicrophoneInput() { TreePath path = findByName(new TreePath(root), new String[]{"MICROPHONE", "Select"}); if (path == null) { path = findByName(new TreePath(root), new String[]{"Capture source", "Capture", "Mute"}); } if (path != null) { if (path.getLastPathComponent() instanceof JavaMixer.ControlNode) { BooleanControl bControl = (BooleanControl) (((JavaMixer.ControlNode) path.getLastPathComponent()).getControl()); bControl.setValue(true); } } } public void setMuteForMicrophoneOutput() { TreePath path = findByName(new TreePath(root), new String[]{"SPEAKER", "Microfone", "Mute"}); if (path == null) { path = findByName(new TreePath(root), new String[]{"MIC target", "mic", "Mute"}); } if (path != null) { if (path.getLastPathComponent() instanceof JavaMixer.ControlNode) { BooleanControl bControl = (BooleanControl) (((JavaMixer.ControlNode) path.getLastPathComponent()).getControl()); bControl.setValue(true); } } } /** * Returns the Mixers that support Port lines. * * @return List<Mixer> Port Mixers */ private List<Mixer> getPortMixers() { List<Mixer> supportingMixers = new ArrayList<Mixer>(); Mixer.Info[] aMixerInfos = AudioSystem.getMixerInfo(); for (Mixer.Info aMixerInfo : aMixerInfos) { Mixer mixer = AudioSystem.getMixer(aMixerInfo); boolean bSupportsPorts = arePortsSupported(mixer); if (bSupportsPorts) { supportingMixers.add(mixer); } } return supportingMixers; } private boolean arePortsSupported(Mixer mixer) { Line.Info[] infos; infos = mixer.getSourceLineInfo(); for (Line.Info info : infos) { if (info instanceof Port.Info) { return true; } else if (info instanceof DataLine.Info) { return true; } } infos = mixer.getTargetLineInfo(); for (Line.Info info : infos) { if (info instanceof Port.Info) { return true; } else if (info instanceof DataLine.Info) { return true; } } return false; } private void createMixerChildren(JavaMixer.MixerNode mixerNode) { Mixer mixer = mixerNode.getMixer(); Line.Info[] infosToCheck = getPortInfo(mixer); for (Line.Info anInfosToCheck : infosToCheck) { if (mixer.isLineSupported(anInfosToCheck)) { Port port = null; DataLine dLine = null; int maxLines = mixer.getMaxLines(anInfosToCheck); // Workaround to prevent a JVM crash on Mac OS X (Intel) 1.5.0_07 JVM if (maxLines > 0) { try { if (anInfosToCheck instanceof Port.Info) { port = (Port) mixer.getLine(anInfosToCheck); port.open(); } else if (anInfosToCheck instanceof DataLine.Info) { dLine = (DataLine) mixer.getLine(anInfosToCheck); if (!dLine.isOpen()) { dLine.open(); } } } catch (LineUnavailableException e) { // Do Nothing } catch (Exception e) { // Do Nothing } } if (port != null) { JavaMixer.PortNode portNode = new JavaMixer.PortNode(port); createPortChildren(portNode); mixerNode.add(portNode); } else if (dLine != null) { JavaMixer.PortNode portNode = new JavaMixer.PortNode(dLine); createPortChildren(portNode); mixerNode.add(portNode); } } } } private Line.Info[] getPortInfo(Mixer mixer) { Line.Info[] infos; List<Line.Info> portInfoList = new ArrayList<Line.Info>(); infos = mixer.getSourceLineInfo(); for (Line.Info info : infos) { if (info instanceof Port.Info || info instanceof DataLine.Info) { portInfoList.add((Line.Info) info); } } infos = mixer.getTargetLineInfo(); for (Line.Info info1 : infos) { if (info1 instanceof Port.Info || info1 instanceof DataLine.Info) { portInfoList.add((Line.Info) info1); } } return portInfoList.toArray(EMPTY_PORT_INFO_ARRAY); } private void createPortChildren(JavaMixer.PortNode portNode) { Control[] aControls = portNode.getPort().getControls(); for (Control aControl : aControls) { JavaMixer.ControlNode controlNode = new JavaMixer.ControlNode(aControl); createControlChildren(controlNode); portNode.add(controlNode); } } private void createControlChildren(JavaMixer.ControlNode controlNode) { if (controlNode.getControl() instanceof CompoundControl) { CompoundControl control = (CompoundControl) controlNode.getControl(); Control[] aControls = control.getMemberControls(); for (Control con : aControls) { JavaMixer.ControlNode conNode = new JavaMixer.ControlNode(con); createControlChildren(conNode); controlNode.add(conNode); } } } /** * Returns whether the type of a FloatControl is BALANCE or PAN. * * @param control FloatControl control * @return boolean is Balance or Pan */ private static boolean isBalanceOrPan(FloatControl control) { Control.Type type = control.getType(); return type.equals(FloatControl.Type.PAN) || type.equals(FloatControl.Type.BALANCE); } public class MixerNode extends DefaultMutableTreeNode { private static final long serialVersionUID = 5514718793497318648L; Mixer mixer; public MixerNode(Mixer mixer) { super(mixer.getMixerInfo(), true); this.mixer = mixer; } public Mixer getMixer() { return mixer; } } public class PortNode extends DefaultMutableTreeNode { private static final long serialVersionUID = -4388437363589688167L; Line port; public PortNode(Line port) { super(port.getLineInfo(), true); this.port = port; } public Line getPort() { return port; } } public class ControlNode extends DefaultMutableTreeNode { private static final long serialVersionUID = 8840473247266403685L; Control control; Component component; public ControlNode(Control control) { super(control.getType(), true); this.control = control; if (control instanceof BooleanControl) { component = createControlComponent((BooleanControl) control); } else if (control instanceof EnumControl) { component = createControlComponent((EnumControl) control); } else if (control instanceof FloatControl) { component = createControlComponent((FloatControl) control); } else { component = null; } } public Control getControl() { return control; } public Component getComponent() { return component; } private JComponent createControlComponent(BooleanControl control) { AbstractButton button; String strControlName = control.getType().toString(); ButtonModel model = new JavaMixer.BooleanControlButtonModel(control); button = new JCheckBox(strControlName); button.setModel(model); return button; } private JComponent createControlComponent(EnumControl control) { JPanel component = new JPanel(); String strControlName = control.getType().toString(); component.setBorder(new TitledBorder(new EtchedBorder(), strControlName)); return component; } private JComponent createControlComponent(FloatControl control) { int orientation = isBalanceOrPan(control) ? JSlider.HORIZONTAL : JSlider.VERTICAL; BoundedRangeModel model = new JavaMixer.FloatControlBoundedRangeModel(control); JSlider slider = new JSlider(model); slider.setOrientation(orientation); slider.setPaintLabels(true); slider.setPaintTicks(true); slider.setSize(10, 50); return slider; } } public class BooleanControlButtonModel extends DefaultButtonModel { private static final long serialVersionUID = -8264153878420797906L; private BooleanControl control; public BooleanControlButtonModel(BooleanControl control) { this.control = control; this.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setSelected(!isSelected()); } }); } public void setSelected(boolean bSelected) { control.setValue(bSelected); } public boolean isSelected() { return control.getValue(); } } public class FloatControlBoundedRangeModel extends DefaultBoundedRangeModel { private static final long serialVersionUID = 7826447995854231838L; private FloatControl control; private float factor; public FloatControlBoundedRangeModel(FloatControl control) { this.control = control; float range = 100; float steps = range / 100; factor = range / steps; int min = (int) (control.getMinimum() * factor); int max = (int) (control.getMaximum() * factor); int value = (int) (control.getValue() * factor); setRangeProperties(value, 0, min, max, false); } private float getScaleFactor() { return factor; } public void setValue(int nValue) { super.setValue(nValue); control.setValue((float) nValue / getScaleFactor()); } public int getValue() { return (int) (control.getValue() * getScaleFactor()); } } public static void main(String[] args) { final JavaMixer sm = new JavaMixer(); final JFrame jf = new JFrame("Mixer Test"); final JPanel jp = new JPanel(); jf.add(jp); jp.add(sm.getTree()); jf.setSize(600, 500); jf.setVisible(true); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); sm.getTree().addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { TreePath path = e.getPath(); if (path.getLastPathComponent() instanceof JavaMixer.ControlNode) { JavaMixer.ControlNode controlNode = (JavaMixer.ControlNode) path.getLastPathComponent(); if (!(controlNode.getControl() instanceof CompoundControl)) { if (jp.getComponentCount() > 1) jp.remove(1); jp.add(controlNode.getComponent(), 1); jp.repaint(); } } } }); jp.add(sm.getPrefferedMasterVolume()); jp.add(sm.getPrefferedMasterVolume()); jp.add(sm.getPrefferedInputVolume()); jp.repaint(); sm.setMicrophoneInput(); sm.setMuteForMicrophoneOutput(); } public TreePath find(TreePath path, Object[] nodes) { return find2(path, nodes, 0, false); } public TreePath findByName(TreePath path, String[] names) { return find2(path, names, 0, true); } private TreePath find2(TreePath parent, Object[] nodes, int depth, boolean byName) { TreeNode node = (TreeNode) parent.getLastPathComponent(); if (depth > nodes.length - 1) { return parent; } if (node.getChildCount() >= 0) { for (Enumeration<TreeNode> e = node.children(); e.hasMoreElements();) { TreeNode n = e.nextElement(); TreePath path = parent.pathByAddingChild(n); boolean find; if (byName) { find = n.toString().toUpperCase().indexOf(nodes[depth].toString().toUpperCase()) > -1; } else { find = n.equals(nodes[depth]); } if (find) { TreePath result = find2(path, nodes, depth + 1, byName); if (result != null) { return result; } } else { TreePath result = find2(path, nodes, depth, byName); if (result != null) { return result; } } } } return null; } }