package magic.ui; import java.io.IOException; import java.net.URL; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.FloatControl; import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineListener; import javax.sound.sampled.LineUnavailableException; import magic.data.GeneralConfig; import magic.utility.MagicResources; public enum MagicSound { // uiSounds - keep sorted and verify enumset range if changed. ADD_CARD("cardSlide3.wav"), ALERT("bong.wav"), BEEP("noAction.wav"), BOOM("boom.wav"), REMOVE_CARD("cardTakeOutPackage1.wav"), CLICK("click.wav"), // gameSounds - keep sorted and verify enumset range if changed. COMBAT("combat.au"), LOSE("lose.au"), NEW_TURN("turn.au"), RESOLVE("resolve.au"), WIN("win.au"); private static final Set<MagicSound> uiSounds = EnumSet.range(ADD_CARD, CLICK); private static final Set<MagicSound> gameSounds = EnumSet.range(COMBAT, WIN); private static final GeneralConfig config = GeneralConfig.getInstance(); private static final ExecutorService executor = Executors.newSingleThreadExecutor(); private static volatile Clip clip; private static final LineListener closer = (event) -> { if (event.getType() == LineEvent.Type.STOP) { event.getLine().close(); } }; private final URL soundUrl; private MagicSound(final String aFilename) { this.soundUrl = MagicResources.getSoundUrl(aFilename); } private boolean isUISound() { return uiSounds.contains(this); } private boolean isGameSound() { return gameSounds.contains(this); } private boolean canPlay() { return getVolume() > 0; } private int getVolume() { if (isUISound()) { return config.getUiVolume(); } else if (isGameSound()) { return config.getGameVolume(); } else { return 100; } } /** * Plays sound at given volume. * * @param volPercent : volume of sound clip between 0 and 100 percent. */ public void play(int volPercent) { if (volPercent > 0 && volPercent <= 100) { executor.submit(() -> { playSound(soundUrl, volPercent); }); } } /** * Plays sound at volume specified in settings. */ public void play() { if (canPlay()) { play(getVolume()); } } public static void shutdown() { executor.shutdown(); } private static void setVolume(final Clip aClip, int volPercent) { FloatControl gainControl = (FloatControl) aClip.getControl(FloatControl.Type.MASTER_GAIN); double gain = volPercent / 100D; // number between 0 and 1 (loudest) float dB = (float) (Math.log(gain) / Math.log(10.0) * 20.0); gainControl.setValue(dB); } private static void playClip(AudioInputStream ins, int volPercent) throws IOException, LineUnavailableException { if (clip != null && (clip.isRunning() || clip.isActive())) { clip.loop(0); } clip = AudioSystem.getClip(); clip.addLineListener(closer); clip.open(ins); setVolume(clip, volPercent); clip.start(); } private static void playSound(URL url, int volPercent) { try (final AudioInputStream ins = AudioSystem.getAudioInputStream(url)) { playClip(ins, volPercent); } catch (Exception ex) { System.err.println("WARNING. Unable to play clip " + url.toExternalForm() + ", " + ex.getMessage()); // turn off all sound permanently. config.setGameVolume(0); config.setUiVolume(0); config.save(); } } }