package chatty.util; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.sound.sampled.*; /** * Static methods to play sounds. * * @author tduva */ public class Sound { private static final int MAX_VOLUME = 100; private static final int MIN_VOLUME = 0; private static final int MIN_GAIN = -40; private static final Logger LOGGER = Logger.getLogger(Sound.class.getName()); private static final String PATH = System.getProperty("user.dir") +File.separator+"sounds"+File.separator; private static final Map<String, Long> lastPlayed = new HashMap<>(); private static Mixer mixer; private static String mixerName; public static void play(String fileName, float volume, String id, int delay) { if (lastPlayed.containsKey(id)) { long timePassed = (System.currentTimeMillis() - lastPlayed.get(id)) / 1000; if (timePassed < delay) { //LOGGER.info("Not playing sound "+id+" ("+timePassed+"/"+delay+")"); return; } } if (delay >= 0) { lastPlayed.put(id, System.currentTimeMillis()); } try { // getAudioInputStream() also accepts a File or InputStream File file = new File(PATH+fileName); AudioInputStream ais = AudioSystem.getAudioInputStream(file); DataLine.Info info = new DataLine.Info(Clip.class, ais.getFormat()); final Clip clip; if (mixer != null) { clip = (Clip)mixer.getLine(info); } else { clip = (Clip)AudioSystem.getLine(info); } clip.open(ais); // Volume, use what is available String volumeInfo; FloatControl gain = getFirstAvailableControl(clip, FloatControl.Type.MASTER_GAIN, FloatControl.Type.VOLUME); if (gain != null) { gain.setValue(calculateGain(volume, gain.getMinimum(), gain.getMaximum())); volumeInfo = gain.toString(); } else { volumeInfo = "no volume control"; } clip.addLineListener(new LineListener() { @Override public void update(LineEvent event) { if (event.getType() == LineEvent.Type.STOP) { clip.close(); } } }); clip.start(); LOGGER.info("Playing sound "+id+"/"+fileName+" ("+volumeInfo+")"); } catch (Exception ex) { LOGGER.warning("Couldn't play sound ("+id+"/"+fileName+"): "+ex); } } private static FloatControl getFirstAvailableControl(Clip clip, FloatControl.Type... types) { for (FloatControl.Type type : types) { if (clip.isControlSupported(type)) { return (FloatControl)clip.getControl(type); } } return null; } private static float calculateGain(float volume, float min, float max) { // Restrict to min/max if (volume > MAX_VOLUME) { volume = MAX_VOLUME; } if (volume < MIN_VOLUME) { volume = MIN_VOLUME; } volume = (float)(MAX_VOLUME - MAX_VOLUME*Math.pow(1.25, -0.25*volume)); if (min < MIN_GAIN) { min = MIN_GAIN; } float range = max - min; float gain = ((range * volume / (MAX_VOLUME - MIN_VOLUME)) + min); return gain; } public static List<String> getDeviceNames() { List<String> result = new ArrayList<>(); try { for (Mixer.Info info : AudioSystem.getMixerInfo()) { for (Line.Info info2 : AudioSystem.getMixer(info).getSourceLineInfo()) { if (info2.getLineClass() == Clip.class) { result.add(info.getName()); } } } } catch (Exception ex) { LOGGER.warning("Error getting list of sound devices: "+ex); } return result; } public static void setDeviceName(String name) { if (mixerName != null && mixerName.equals(name)) { return; } mixerName = name; if (name == null || name.isEmpty()) { mixer = null; LOGGER.info("Set to default sound device"); return; } for (Mixer.Info info : AudioSystem.getMixerInfo()) { if (info.getName().equals(name)) { mixer = AudioSystem.getMixer(info); LOGGER.info("Set sound device to "+name); return; } } mixer = null; LOGGER.info("Could not find sound device "+name); } }