diff --git a/.gitattributes b/.gitattributes index 5fdb105bc45..53567bd8159 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14324,6 +14324,7 @@ src/main/java/forge/quest/io/QuestDataIO.java svneol=native#text/plain src/main/java/forge/quest/io/ReadPriceList.java svneol=native#text/plain src/main/java/forge/quest/io/package-info.java svneol=native#text/plain src/main/java/forge/quest/package-info.java svneol=native#text/plain +src/main/java/forge/sound/AsyncSoundPlayer.java -text src/main/java/forge/sound/AudioClip.java -text src/main/java/forge/sound/EventVisualizer.java -text src/main/java/forge/sound/IAudioClip.java -text diff --git a/src/main/java/forge/gui/home/settings/CSubmenuPreferences.java b/src/main/java/forge/gui/home/settings/CSubmenuPreferences.java index de8bf2422f6..7e11437bd3c 100644 --- a/src/main/java/forge/gui/home/settings/CSubmenuPreferences.java +++ b/src/main/java/forge/gui/home/settings/CSubmenuPreferences.java @@ -186,6 +186,14 @@ public enum CSubmenuPreferences implements ICDoc { } }); + view.getCbAltSoundSystem().addItemListener(new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent arg0) { + prefs.setPref(FPref.UI_ALT_SOUND_SYSTEM, view.getCbAltSoundSystem().isSelected()); + prefs.save(); + } + }); + view.getBtnReset().setCommand(new Command() { @Override public void execute() { @@ -222,6 +230,7 @@ public enum CSubmenuPreferences implements ICDoc { view.getCbScaleLarger().setSelected(prefs.getPrefBoolean(FPref.UI_SCALE_LARGER)); view.getCbTextMana().setSelected(prefs.getPrefBoolean(FPref.UI_CARD_OVERLAY)); view.getCbEnableSounds().setSelected(prefs.getPrefBoolean(FPref.UI_ENABLE_SOUNDS)); + view.getCbAltSoundSystem().setSelected(prefs.getPrefBoolean(FPref.UI_ALT_SOUND_SYSTEM)); view.reloadShortcuts(); SwingUtilities.invokeLater(new Runnable() { diff --git a/src/main/java/forge/gui/home/settings/VSubmenuPreferences.java b/src/main/java/forge/gui/home/settings/VSubmenuPreferences.java index deeba7be321..95179f874a4 100644 --- a/src/main/java/forge/gui/home/settings/VSubmenuPreferences.java +++ b/src/main/java/forge/gui/home/settings/VSubmenuPreferences.java @@ -96,6 +96,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final JCheckBox cbRandomFoil = new OptionsCheckBox("Random Foil"); private final JCheckBox cbRandomizeArt = new OptionsCheckBox("Randomize Card Art"); private final JCheckBox cbEnableSounds = new OptionsCheckBox("Enable Sounds"); + private final JCheckBox cbAltSoundSystem = new OptionsCheckBox("Use Alternative Sound System"); private final Map shortcutFields = new HashMap(); @@ -178,6 +179,9 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbEnableSounds, regularConstraints); pnlPrefs.add(new NoteLabel("Enable sound effects during the game."), regularConstraints); + pnlPrefs.add(cbAltSoundSystem, regularConstraints); + pnlPrefs.add(new NoteLabel("Use an alternative sound system (in case your have issues with sound on Linux)"), regularConstraints); + // Keyboard shortcuts final JLabel lblShortcuts = new SectionLabel("Keyboard Shortcuts"); pnlPrefs.add(lblShortcuts, sectionConstraints); @@ -464,6 +468,11 @@ public enum VSubmenuPreferences implements IVSubmenu { return cbEnableSounds; } + /** @return {@link javax.swing.JCheckBox} */ + public JCheckBox getCbAltSoundSystem() { + return cbAltSoundSystem; + } + /** @return {@link forge.gui.toolbox.FLabel} */ public FLabel getBtnReset() { return btnReset; diff --git a/src/main/java/forge/properties/ForgePreferences.java b/src/main/java/forge/properties/ForgePreferences.java index cdcc6d75ccd..7cd1048b6be 100644 --- a/src/main/java/forge/properties/ForgePreferences.java +++ b/src/main/java/forge/properties/ForgePreferences.java @@ -50,6 +50,7 @@ public class ForgePreferences extends PreferencesStore { UI_PREFERRED_AVATARS_ONLY ("false"), UI_TARGETING_OVERLAY ("false"), UI_ENABLE_SOUNDS ("true"), + UI_ALT_SOUND_SYSTEM ("false"), UI_RANDOM_CARD_ART ("false"), UI_CURRENT_AI_PROFILE (AiProfileUtil.AI_PROFILE_RANDOM_MATCH), UI_CLONE_MODE_SOURCE ("false"), /** */ diff --git a/src/main/java/forge/sound/AsyncSoundPlayer.java b/src/main/java/forge/sound/AsyncSoundPlayer.java new file mode 100755 index 00000000000..2ff3238311c --- /dev/null +++ b/src/main/java/forge/sound/AsyncSoundPlayer.java @@ -0,0 +1,121 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package forge.sound; + +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 javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * + * @author agetian + */ +class AsyncSoundRegistry { + static Map soundsPlayed = new HashMap(); + + public static void registerSound(String soundName) { + if (soundsPlayed.containsKey(soundName)) { + soundsPlayed.put(soundName, soundsPlayed.get(soundName) + 1); + } else { + soundsPlayed.put(soundName, 1); + } + } + + public static void unregisterSound(String soundName) { + if (soundsPlayed.containsKey(soundName) && soundsPlayed.get(soundName) != 1) { + soundsPlayed.put(soundName, soundsPlayed.get(soundName) - 1); + } else { + soundsPlayed.remove(soundName); + } + } + + public static boolean isRegistered(String soundName) { + return soundsPlayed.containsKey(soundName); + } + + public static int getNumIterations(String soundName) { + return soundsPlayed.containsKey(soundName) ? soundsPlayed.get(soundName) : 0; + } +} + +public class AsyncSoundPlayer extends Thread { + + private String filename; + private boolean isSync; + + private final int EXTERNAL_BUFFER_SIZE = 524288; + private final int MAX_SOUND_ITERATIONS = 5; + + public AsyncSoundPlayer(String wavfile, boolean synced) { + filename = wavfile; + isSync = synced; + } + + public void run() { + if (isSync && AsyncSoundRegistry.isRegistered(filename)) { + return; + } + if (AsyncSoundRegistry.getNumIterations(filename) >= MAX_SOUND_ITERATIONS) { + return; + } + + File soundFile = new File(filename); + if (!soundFile.exists()) { + return; + } + + AudioInputStream audioInputStream = null; + try { + audioInputStream = AudioSystem.getAudioInputStream(soundFile); + } catch (UnsupportedAudioFileException e) { + e.printStackTrace(); + return; + } catch (IOException e) { + e.printStackTrace(); + return; + } + + AudioFormat format = audioInputStream.getFormat(); + SourceDataLine audioLine = null; + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); + + try { + audioLine = (SourceDataLine) AudioSystem.getLine(info); + audioLine.open(format); + } catch (Exception e) { + return; + } + + audioLine.start(); + AsyncSoundRegistry.registerSound(filename); + + int nBytesRead = 0; + byte[] audioBufData = new byte[EXTERNAL_BUFFER_SIZE]; + + try { + while (nBytesRead != -1) { + nBytesRead = audioInputStream.read(audioBufData, 0, audioBufData.length); + if (nBytesRead >= 0) + audioLine.write(audioBufData, 0, nBytesRead); + } + } catch (IOException e) { + e.printStackTrace(); + return; + } finally { + audioLine.drain(); + audioLine.close(); + AsyncSoundRegistry.unregisterSound(filename); + } + } +} diff --git a/src/main/java/forge/sound/AudioClip.java b/src/main/java/forge/sound/AudioClip.java index 621d6f37bad..d07e5a24178 100644 --- a/src/main/java/forge/sound/AudioClip.java +++ b/src/main/java/forge/sound/AudioClip.java @@ -43,8 +43,6 @@ public class AudioClip implements IAudioClip { private Clip clip; private final int SOUND_SYSTEM_DELAY = 30; - private static final String PathToSound = "res/sound"; - public static boolean fileExists(String fileName) { File fSound = new File(PathToSound, fileName); return fSound.exists(); diff --git a/src/main/java/forge/sound/IAudioClip.java b/src/main/java/forge/sound/IAudioClip.java index 050f354082b..1654917fd53 100644 --- a/src/main/java/forge/sound/IAudioClip.java +++ b/src/main/java/forge/sound/IAudioClip.java @@ -1,6 +1,8 @@ package forge.sound; public interface IAudioClip { + public static final String PathToSound = "res/sound"; + public void play(); public boolean isDone(); public void stop(); diff --git a/src/main/java/forge/sound/SoundSystem.java b/src/main/java/forge/sound/SoundSystem.java index 92a6cd4a29b..62877d6d5b6 100644 --- a/src/main/java/forge/sound/SoundSystem.java +++ b/src/main/java/forge/sound/SoundSystem.java @@ -22,6 +22,10 @@ public class SoundSystem { private final EventVisualizer visualizer = new EventVisualizer(); + private boolean isUsingAltSystem() { + return Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_ALT_SOUND_SYSTEM); + } + /** * Fetch a resource based on the sound effect type from the SoundEffectType enumeration. * @@ -67,38 +71,54 @@ public class SoundSystem { * Play the sound associated with the Sounds enumeration element. */ public void play(SoundEffectType type) { - fetchResource(type).play(); + if (isUsingAltSystem()) { + new AsyncSoundPlayer(String.format("%s/%s", IAudioClip.PathToSound, type.getResourceFileName()), false).start(); + } else { + fetchResource(type).play(); + } } /** * Play the sound associated with a specific resource file. */ public void play(String resourceFileName) { - fetchResource(resourceFileName).play(); + if (isUsingAltSystem()) { + new AsyncSoundPlayer(String.format("%s/%s", IAudioClip.PathToSound, resourceFileName), false).start(); + } else { + fetchResource(resourceFileName).play(); + } } /** - * Play the sound associated with the resource specified by the file name - * (synchronized with other sounds of the same kind, so only one can play - * at the same time). + * Play the sound associated with the resource specified by the file name + * (synchronized with other sounds of the same kind, so only one can play at + * the same time). */ public void playSync(String resourceFileName) { - IAudioClip snd = fetchResource(resourceFileName); - if (snd.isDone()) { - snd.play(); - } + if (isUsingAltSystem()) { + new AsyncSoundPlayer(String.format("%s/%s", IAudioClip.PathToSound, resourceFileName), false).start(); + } else { + IAudioClip snd = fetchResource(resourceFileName); + if (snd.isDone()) { + snd.play(); + } + } } /** * Play the sound associated with the Sounds enumeration element - * (synchronized with other sounds of the same kind, so only one can play - * at the same time). + * (synchronized with other sounds of the same kind, so only one can play at + * the same time). */ public void playSync(SoundEffectType type) { - IAudioClip snd = fetchResource(type); - if (snd.isDone()) { - snd.play(); - } + if (isUsingAltSystem()) { + new AsyncSoundPlayer(String.format("%s/%s", IAudioClip.PathToSound, type.getResourceFileName()), false).start(); + } else { + IAudioClip snd = fetchResource(type); + if (snd.isDone()) { + snd.play(); + } + } } /**