package sound; import gui.forms.GUIMain; import lib.pircbot.User; import util.Permissions; import util.Response; import util.Timer; import util.Utils; import util.settings.Settings; import java.io.File; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Created by Nick on 12/20/13. */ public class SoundEngine { private static SoundEngine engine = null; private static SoundPlayer player = null; public static SoundEngine getEngine() { return engine; } private boolean soundToggle = true; private Timer soundTimer; private ConcurrentHashMap<String, Sound> soundMap; private Deque<Sound> subStack, donationStack; private Sound lastSubSound, lastDonationSound; public static void init() { engine = new SoundEngine(); } public SoundEngine() { soundMap = new ConcurrentHashMap<>(); player = new SoundPlayer(); subStack = new ArrayDeque<>(); donationStack = new ArrayDeque<>(); lastSubSound = null; lastDonationSound = null; soundTimer = new Timer(Settings.soundEngineDelay.getValue()); } public void setDelay(int newDelay) { Settings.soundEngineDelay.setValue(newDelay); soundTimer = new Timer(newDelay); } public ConcurrentHashMap<String, Sound> getSoundMap() { return soundMap; } public Deque<Sound> getSubStack() { return subStack; } public Deque<Sound> getDonationStack() { return donationStack; } public Timer getSoundTimer() { return soundTimer; } public void setShouldPlay(boolean newBool) { soundToggle = newBool; } public boolean shouldPlay() { return soundToggle; } public void setPermission(int perm) { Settings.soundEnginePermission.setValue(perm); } public int getPermission() { return Settings.soundEnginePermission.getValue(); } /** * Adds a sound to the sound set. * This respects the "one at a time" but * also can allow spam if the sound delay is 0 * * @param s The sound to add. */ public void playSound(Sound s) { if (!soundTimer.isRunning()) { if (soundTimer.period == 0) {//alowing for spam try { player.play(s.getFile(), SoundPlayer.PlayMode.Force); } catch (Exception ignored) { } } else { try { player.play(s.getFile(), SoundPlayer.PlayMode.Ignore); } catch (Exception ignored) { } soundTimer.reset(); } } } /** * Plays the new subscriber/donation sound, overrides current ruleset for engine. */ public void playSpecialSound(boolean isSub) { Sound s = getSpecialSound(isSub); try { player.play(s.getFile(), SoundPlayer.PlayMode.Force); } catch (Exception ignored) { } } private Sound getSpecialSound(boolean isSub) { if ((isSub ? subStack : donationStack).isEmpty()) {//refreshes and reshuffles if (isSub) Settings.loadSubSounds(); else Settings.loadDonationSounds(); } Sound sound = (isSub ? subStack : donationStack).pop(); if (isSub) lastSubSound = sound; else lastDonationSound = sound; return sound; } public Response getLastDonationSound() { Response toReturn = new Response(); if (lastDonationSound != null) { toReturn.setResponseText("The previous donation notification sound was taken from the song: " + Utils.removeExt(lastDonationSound.getFile().getName())); } else { toReturn.setResponseText("There is no previous donation sound!"); } return toReturn; } public Response getLastSubSound() { Response toReturn = new Response(); if (lastSubSound != null) { toReturn.setResponseText("The previous subscriber notification sound was taken from the song: " + Utils.removeExt(lastSubSound.getFile().getName())); } else { toReturn.setResponseText("There is no previous donation sound!"); } return toReturn; } /** * Gets the first playing sound in the queue. * * @return The first playing sound. */ public SoundEntry getCurrentPlayingSound() { Collection<SoundEntry> coll = player.getPlayingSounds(); if (!coll.isEmpty()) { for (SoundEntry s : coll) { if (s.getClip().isRunning()) { return s; } } } return null; } /** * Gets all of the current playing sounds. * * @return All of the currently playing sounds. */ public Collection<SoundEntry> getCurrentPlayingSounds() { return player.getPlayingSounds(); } public void close() { player.close(); } private String getSoundStateString() { int delay = (int) getSoundTimer().period / 1000; String onOrOff = (shouldPlay() ? "ON" : "OFF"); int numSound = getCurrentPlayingSounds().size(); int permission = getPermission(); String numSounds = (numSound > 0 ? (numSound == 1 ? "one sound" : (numSound + " sounds")) : "no sounds") + " currently playing"; String delayS = (delay < 2 ? (delay == 0 ? "no delay." : "a delay of 1 second.") : ("a delay of " + delay + " seconds.")); String perm = Utils.getPermissionString(permission) + " can play sounds."; return "Sound is currently turned " + onOrOff + " with " + numSounds + " with " + delayS + " " + perm; } /** * Base trigger for sounds. Checks if a dev sound is not playing, if the general delay is up, * if the channel is yours, and if the user can even play the sound if it exists. * * @param s Sound command trigger/name. * @param send The sender of the command. * @param channel Channel the command was in. * @return true to play the sound, else false */ public boolean soundTrigger(String s, String send, String channel) { if (SoundEngine.getEngine().shouldPlay()) {//sound not turned off if (Utils.isMainChannel(channel)) {//is in main channel if (soundCheck(s, send, channel)) {//let's check the existence/permission return true;//HIT THAT } } } return false; } /** * Checks the existence of a sound, and the permission of the requester. * * @param sound Sound trigger * @param sender Sender of the command trigger. * @return false if the sound is not allowed, else true if it is. */ private boolean soundCheck(String sound, String sender, String channel) { //set the permission User u = Settings.channelManager.getUser(sender, true); ArrayList<Permissions.Permission> permissions = Permissions.getUserPermissions(u, channel); Sound snd = soundMap.get(sound.toLowerCase()); if (snd != null && snd.isEnabled()) { int perm = snd.getPermission(); if (Permissions.hasAtLeast(permissions, perm) && Permissions.hasAtLeast(permissions, SoundEngine.getEngine().getPermission())) { return true; } } return false; } /** * Handles the adding/changing of a sound, its permission, and/or its files. * * @param s The string from the chat to manipulate. * @param change True for changing a sound, false for adding. */ public Response handleSound(String s, boolean change) { Response toReturn = new Response(); if (!Settings.defaultSoundDir.getValue().equals("null") && !Settings.defaultSoundDir.getValue().equals("")) { try { String[] split = s.split(" "); String name = split[1].toLowerCase();//both commands have this in common. int perm; if (split.length > 3) {//!add/changesound sound 0 sound(,maybe,more) try { perm = Integer.parseInt(split[2]); } catch (Exception e) { toReturn.setResponseText("Failed to handle sound, could not parse the permission!"); return toReturn; } String files = split[3]; if (perm < 0 || perm > 4) { toReturn.setResponseText("Failed to handle sound due to a bad permission!"); return toReturn; } if (!files.contains(",")) {//isn't multiple //this can be !addsound sound 0 sound or !changesound sound 0 newsound String filename = Settings.defaultSoundDir.getValue() + File.separator + Utils.setExtension(files, ".wav"); if (Utils.areFilesGood(filename)) { if (soundMap.containsKey(name)) {//they could technically change the permission here as well if (!change) {//!addsound soundMap.put(name, new Sound(perm,// add it tooo it maaan Utils.addStringsToArray(soundMap.get(name).getSounds().data, filename))); toReturn.setResponseText("Successfully added the sound \"" + filename + "\" to the command \"!" + name + "\" !"); toReturn.wasSuccessful(); } else {//!changesound soundMap.put(name, new Sound(perm, filename));//replace it toReturn.setResponseText("Successfully changed the sound \"!" + name + "\" !"); toReturn.wasSuccessful(); } } else { //*gasp* A NEW SOUND!? if (!change) {//can't have !changesound act like !addsound soundMap.put(name, new Sound(perm, filename)); toReturn.setResponseText("Successfully added the new sound \"!" + name + "\" !"); toReturn.wasSuccessful(); } else { toReturn.setResponseText("The sound \"!" + name + "\" does not exist; cannot change it!"); } } } else { toReturn.setResponseText("Failed to handle sound, the file \"" + filename + "\" does not exist!"); } } else {//is multiple //this can be !addsound sound 0 multi,sound or !changesound sound 0 multi,sound ArrayList<String> list = new ArrayList<>(); String[] filesSplit = files.split(","); for (String str : filesSplit) { list.add(Settings.defaultSoundDir.getValue() + File.separator + Utils.setExtension(str, ".wav")); } //calls the areFilesGood boolean in it (filters bad files already) filesSplit = Utils.checkFiles(list.toArray(new String[list.size()])); list.clear();//recycle time! if (!change) { //adding sounds if (soundMap.containsKey(name)) {//adding sounds, so get the old ones V Collections.addAll(list, soundMap.get(name).getSounds().data); } Utils.checkAndAdd(list, filesSplit);//checks for repetition, will add anyway if list is empty soundMap.put(name, new Sound(perm, list.toArray(new String[list.size()]))); toReturn.setResponseText("Successfully added multiple sounds!"); toReturn.wasSuccessful(); return toReturn; } else {//!changesound, so replace it if it's in there if (soundMap.containsKey(name)) { soundMap.put(name, new Sound(perm, filesSplit)); toReturn.setResponseText("Successfully changed the sound \"!" + name + "\" to have the new files!"); toReturn.wasSuccessful(); } else { toReturn.setResponseText("Failed to change sound, the sound \"" + name + "\" does not exist!"); } } } } else if (split.length == 3) {//add/changesound sound perm/newsound if (split[2].length() == 1) {//ASSUMING it's a permission change. try { perm = Integer.parseInt(split[2]);//I mean come on. What sound will have a 1 char name? if (perm != -1 && perm >= 0 && perm < 5) { if (change) {//because adding just a sound name and a permission is silly soundMap.put(name, new Sound(perm, soundMap.get(name).getSounds().data));//A pretty bad one... toReturn.setResponseText("Successfully changed the sound \"!" + name + "\" to have the new permission: " + perm); toReturn.wasSuccessful(); } else { toReturn.setResponseText("Failed to add sound, cannot add a permission as a sound!"); } } else { toReturn.setResponseText("Failed to change the permission, please give a permission from 0 to 4!"); } } catch (NumberFormatException e) {//maybe it really is a 1-char-named sound? String test = Settings.defaultSoundDir.getValue() + File.separator + Utils.setExtension(split[2], ".wav"); if (Utils.areFilesGood(test)) { //wow... if (change) { soundMap.put(name, new Sound(soundMap.get(name).getPermission(), test)); toReturn.setResponseText("Successfully changed the sound for \"!" + name + "\" !"); toReturn.wasSuccessful(); } else {//adding a 1 char sound that exists to the pool... soundMap.put(name, new Sound(soundMap.get(name).getPermission(), Utils.addStringsToArray(soundMap.get(name).getSounds().data, test))); toReturn.setResponseText("Successfully added the sound \"" + split[2] + "\" to the command \"!" + name + "\" !"); toReturn.wasSuccessful(); } } else { toReturn.setResponseText("Failed to handle sound, invalid permission!"); } } } else { //it's a/some new file(s) as replacement/to add! if (split[2].contains(",")) {//multiple String[] filesSplit = split[2].split(","); ArrayList<String> list = new ArrayList<>(); for (String str : filesSplit) { list.add(Settings.defaultSoundDir.getValue() + File.separator + Utils.setExtension(str, ".wav")); } //calls the areFilesGood boolean in it (filters bad files already) filesSplit = Utils.checkFiles(list.toArray(new String[list.size()])); if (!change) {//!addsound soundname more,sounds if (soundMap.containsKey(name)) { filesSplit = Utils.addStringsToArray(soundMap.get(name).getSounds().data, filesSplit); soundMap.put(name, new Sound(soundMap.get(name).getPermission(), filesSplit)); toReturn.wasSuccessful(); toReturn.setResponseText("Successfully added " + filesSplit.length + " new sounds to the sound \"!" + name + "\" !"); } else { //use default permission soundMap.put(name, new Sound(filesSplit)); toReturn.setResponseText("Successfully added " + filesSplit.length + " new sounds to the new command \"!" + name + "\" !"); toReturn.wasSuccessful(); } } else {//!changesound soundname new,sounds if (soundMap.containsKey(name)) {//!changesound isn't !addsound soundMap.put(name, new Sound(soundMap.get(name).getPermission(), filesSplit)); toReturn.setResponseText("Successfully changed the sound \"!" + name + "\" to have the " + filesSplit.length + " new files!"); toReturn.wasSuccessful(); } else { toReturn.setResponseText("Cannot change the sound, \"" + name + "\" doesn't exist!"); } } } else {//singular String test = Settings.defaultSoundDir.getValue() + File.separator + Utils.setExtension(split[2], ".wav"); if (Utils.areFilesGood(test)) { if (!change) {//!addsound sound newsound if (soundMap.containsKey(name)) {//getting the old permission/files soundMap.put(name, new Sound(soundMap.get(name).getPermission(), Utils.addStringsToArray(soundMap.get(name).getSounds().data, test))); toReturn.setResponseText("Successfully added the sound \"" + split[2] + "\" to the command \"!" + name + "\" !"); toReturn.wasSuccessful(); } else {//use default permission soundMap.put(name, new Sound(test)); toReturn.setResponseText("Successfully added new sound \"!" + name + "\" !"); toReturn.wasSuccessful(); } } else { //!changesound sound newsound if (soundMap.containsKey(name)) {//!changesound isn't !addsound soundMap.put(name, new Sound(soundMap.get(name).getPermission(), test)); toReturn.setResponseText("Successfully changed the sound \"" + name + "\" to have the new sound \"" + split[2] + "\" !"); toReturn.wasSuccessful(); } else { toReturn.setResponseText("Cannot change sound, \"" + name + "\" does not exist!"); } } } else { toReturn.setResponseText("Cannot handle sound, the file \"" + split[2] + "\" does not exist!"); } } } } } catch (Exception e) { toReturn.setResponseText("Failed to handle sound due to Exception: " + e.getMessage()); } } else { toReturn.setResponseText("Failed to handle sound, the default sound directory is null!"); } return toReturn; } public Response toggleSound(String name, boolean individualSound) { Response toReturn = new Response(); boolean newBool; if (individualSound) { if (soundMap.containsKey(name)) { Sound s = soundMap.get(name); newBool = !s.isEnabled(); s.setEnabled(newBool); toReturn.wasSuccessful(); toReturn.setResponseText("The sound " + name + " is now turned " + (newBool ? "ON" : "OFF")); } else { toReturn.setResponseText("Cannot toggle sound; the sound \"" + name + "\" does not exist!"); } } else { newBool = !shouldPlay(); setShouldPlay(newBool); GUIMain.instance.updateSoundToggle(newBool); toReturn.wasSuccessful(); toReturn.setResponseText("Sound is now turned " + (newBool ? "ON" : "OFF")); } return toReturn; } public Response removeSound(String name) { Response toReturn = new Response(); if (!"".equals(name)) { if (soundMap.containsKey(name)) { soundMap.remove(name); toReturn.wasSuccessful(); toReturn.setResponseText("Successfully removed sound \"!" + name + "\" !"); } else { toReturn.setResponseText("Failed to remove sound, the sound \"!" + name + "\" does not exist!"); } } else { toReturn.setResponseText("Failed to remove sound, no specified name!"); } return toReturn; } public Response setSoundDelay(String first) { Response toReturn = new Response(); if (!"".equals(first)) { int soundTime; soundTime = Utils.getTime(first); if (soundTime < 0) { toReturn.setResponseText("Failed to set sound delay; could not parse the given time!"); return toReturn; } soundTime = Utils.handleInt(soundTime); int delay = soundTime / 1000; toReturn.setResponseText("Sound delay " + (delay < 2 ? (delay == 0 ? "off." : "is now 1 second.") : ("is now " + delay + " seconds."))); setDelay(soundTime); toReturn.wasSuccessful(); } else { toReturn.setResponseText("Failed to set sound delay, usage: !setsound (time)"); } return toReturn; } public Response setSoundPermission(String first) { Response toReturn = new Response(); try { int perm = Integer.parseInt(first); if (perm > -1 && perm < 5) { setPermission(perm); toReturn.wasSuccessful(); toReturn.setResponseText("Sound permission successfully changed to: " + Utils.getPermissionString(perm)); } else { toReturn.setResponseText("Failed to set sound permission, the permission must be from 0 to 4!"); } } catch (Exception ignored) { toReturn.setResponseText("Failed to set sound permission, usage: !setsoundperm (permission)"); } return toReturn; } public Response stopSound(boolean all) { Response toReturn = new Response(); if (all) { Collection<SoundEntry> coll = getCurrentPlayingSounds(); if (!coll.isEmpty()) { coll.forEach(SoundEntry::close); toReturn.setResponseText("Successfully stopped all playing sounds!"); toReturn.wasSuccessful(); } else { toReturn.setResponseText("There are no sounds currently playing!"); } } else {//first sound it can find SoundEntry sound = getCurrentPlayingSound(); if (sound != null) { sound.close(); toReturn.wasSuccessful(); toReturn.setResponseText("Successfully stopped the first found playing sound!"); } else { toReturn.setResponseText("There are no sounds currently playing!"); } } return toReturn; } public Response getSoundState(String name) { Response toReturn = new Response(); if ("".equals(name)) { toReturn.setResponseText(getSoundStateString()); toReturn.wasSuccessful(); } else { if (soundMap.containsKey(name)) { Sound toCheck = soundMap.get(name); toReturn.setResponseText("The sound \"!" + name + "\" is currently turned " + (toCheck.isEnabled() ? "ON" : "OFF")); toReturn.wasSuccessful(); } else { toReturn.setResponseText("The sound \"!" + name + "\" does not exist!"); } } return toReturn; } }