package org.intellimate.izou.system.sound; import org.intellimate.izou.util.IzouModule; import org.intellimate.izou.addon.AddOnModel; import org.intellimate.izou.identification.Identification; import org.intellimate.izou.main.Main; import javax.sound.sampled.*; import java.util.concurrent.Future; import java.util.function.Consumer; /** * the base class for every IzouSoundLine, provides basic implementation of the Methods defined int IzouSoundLine and * delegates to Line, AutoCloseable etc. * @author LeanderK * @version 1.0 */ public class IzouSoundLineBaseClass extends IzouModule implements Line, AutoCloseable, IzouSoundLine { protected final Line line; private Future<?> closingThread; private boolean isPermanent; protected final SoundManager soundManager; private final AddOnModel addOnModel; protected final boolean isMutable; protected boolean isMutedFromSystem = false; private boolean isMutedFromUser = false; private Consumer<Void> closeCallback = null; private boolean muteIfNonPermanent = true; private Consumer<Void> muteCallback = null; private Identification responsibleID; public IzouSoundLineBaseClass(Line line, Main main, boolean isPermanent, AddOnModel addOnModel) { super(main, false); this.line = line; this.isPermanent = isPermanent; this.addOnModel = addOnModel; if (!isPermanent) { closingThread = getClosingThread(line, main, addOnModel); } else { closingThread = null; } soundManager = null; boolean mutable; try { line.getControl(BooleanControl.Type.MUTE); mutable = true; } catch (IllegalArgumentException e) { mutable = false; } isMutable = mutable; } private Future<?> getClosingThread(Line line, Main main, AddOnModel addOnModel) { return main.getThreadPoolManager().getIzouThreadPool().submit(() -> { try { Thread.sleep(600000); } catch (InterruptedException e) { error("interrupted while sleeping, canceling"); return; } if (line.isOpen()) { debug("closing line " + line + "for Addon " + addOnModel); line.close(); } }); } /** * Obtains the <code>Line.Info</code> object describing this * line. * @return description of the line */ @Override public Info getLineInfo() { return line.getLineInfo(); } /** * Opens the line, indicating that it should acquire any required * system resources and become operational. * If this operation * succeeds, the line is marked as open, and an <code>OPEN</code> event is dispatched * to the line's listeners. * <p> * Note that some lines, once closed, cannot be reopened. Attempts * to reopen such a line will always result in an <code>LineUnavailableException</code>. * <p> * Some types of lines have configurable properties that may affect * resource allocation. For example, a <code>DataLine</code> must * be opened with a particular format and buffer size. Such lines * should provide a mechanism for configuring these properties, such * as an additional <code>open</code> method or methods which allow * an application to specify the desired settings. * <p> * This method takes no arguments, and opens the line with the current * settings. For <code>SourceDataLine</code> and * <code>TargetDataLine</code> objects, this means that the line is * opened with default settings. For a <code>Clip</code>, however, * the buffer size is determined when data is loaded. Since this method does not * allow the application to specify any data to load, an IllegalArgumentException * is thrown. Therefore, you should instead use one of the <code>open</code> methods * provided in the <code>Clip</code> interface to load data into the <code>Clip</code>. * <p> * For <code>DataLine</code>'s, if the <code>DataLine.Info</code> * object which was used to retrieve the line, specifies at least * one fully qualified audio format, the last one will be used * as the default format. * * @throws IllegalArgumentException if this method is called on a Clip instance. * @throws LineUnavailableException if the line cannot be * opened due to resource restrictions. * @throws SecurityException if the line cannot be * opened due to security restrictions. * * @see #close * @see #isOpen */ @Override public void open() throws LineUnavailableException { opening(); line.open(); } protected void opening() { System.out.println("opening for " + addOnModel); if (!line.isOpen() && !isPermanent && muteCallback != null) muteCallback.accept(null); } /** * Closes the line, indicating that any system resources * in use by the line can be released. If this operation * succeeds, the line is marked closed and a <code>CLOSE</code> event is dispatched * to the line's listeners. * @throws SecurityException if the line cannot be * closed due to security restrictions. * * @see #open * @see #isOpen */ @Override public void close() { System.out.println("closing for " + addOnModel); closeCallback.accept(null); line.close(); } /** * Indicates whether the line is open, meaning that it has reserved * system resources and is operational, although it might not currently be * playing or capturing sound. * @return <code>true</code> if the line is open, otherwise <code>false</code> * * @see #open() * @see #close() */ @Override public boolean isOpen() { return line.isOpen(); } /** * Obtains the set of controls associated with this line. Some controls may only be available when the line is open. * If there are no controls, this method returns an array of length 0. * The mute-control operation may be overridden by the System. * @return the array of controls * @see #isMutedFromSystem() */ @Override public Control[] getControls() { Control[] controls = line.getControls(); for (int i = 0; i < controls.length; i++) { Control control = controls[i]; if (control.getType().toString().equals(BooleanControl.Type.MUTE.toString())) { controls[i] = new FakeMuteControl(); } } return controls; } /** * Indicates whether the line supports a control of the specified type. Some controls may only be available when the line is open. * @param control the type of the control for which support is queried * @return true if at least one control of the specified type is supported, otherwise false. */ @Override public boolean isControlSupported(Control.Type control) { return line.isControlSupported(control); } /** * Obtains a control of the specified type, if there is any. * Some controls may only be available when the line is open. * The mute-control operation may be overridden by the System. * @param control the type of the requested control * @return a control of the specified type * @throws IllegalArgumentException - if a control of the specified type is not supported * @see #isMutedFromSystem() */ @Override public Control getControl(Control.Type control) throws IllegalArgumentException { if (control.toString().equals(BooleanControl.Type.MUTE.toString())) { return new FakeMuteControl(); } else { return line.getControl(control); } } /** * follows no predictable behaviour, can be seen as not implemented. * @param listener the listener */ @Override public void addLineListener(LineListener listener) { line.addLineListener(listener); } /** * follows no predictable behaviour, can be seen as not implemented. * @param listener the listener */ @Override public void removeLineListener(LineListener listener) { line.removeLineListener(listener); } /** * returns whether the line is permanently-available. * If a line is not permanently available, it will close after max. 10 minutes * @return true if permanent. */ @Override public boolean isPermanent() { return isPermanent; } void setToPermanent() { if (isPermanent) return; closingThread.cancel(true); closingThread = null; isPermanent = true; } void setToNonPermanent() { if (!isPermanent) return; closingThread = getClosingThread(line, main, addOnModel); isPermanent = false; } /** * gets the associated AddonModel * @return the AddonModel */ @Override @SuppressWarnings("unused") public AddOnModel getAddOnModel() { return addOnModel; } /** * gets the ID responsible * * @return the the ID */ @Override public Identification getResponsibleID() { return responsibleID; } void setResponsibleID(Identification responsibleID) { this.responsibleID = responsibleID; } /** * returns whether the Line is muted * @return true if muted. */ @Override @SuppressWarnings("unused") public boolean isMutedFromSystem() { return isMutedFromSystem; } /** * sets whether other Addons audio-inputs should be muted while this line is open (only works for non-permanent lines). * The standard is true. * @param muteIfNonPermanent true if muted, false if not */ @Override @SuppressWarnings("unused") public void setMuteIfNonPermanent(boolean muteIfNonPermanent) { this.muteIfNonPermanent = muteIfNonPermanent; } /** * retruns whether other Addons audio-inputs should be muted while this line is open (only works for non-permanent lines). * @return true if muted, false if not */ @Override public boolean isMuteIfNonPermanent() { return muteIfNonPermanent; } void setMutedFromSystem(boolean isMuted) { if (isMutable) { BooleanControl bc = (BooleanControl) line.getControl(BooleanControl.Type.MUTE); if (bc != null) { if (isMuted) { bc.setValue(true); // true to mute the line, false to unmute } else { bc.setValue(isMutedFromUser); // true to mute the line, false to unmute } } } this.isMutedFromSystem = isMuted; } void registerCloseCallback(Consumer<Void> consumer) { this.closeCallback = consumer; } void registerMuteCallback(Consumer<Void> consumer) { this.muteCallback = consumer; } private class FakeMuteControl extends BooleanControl { private final BooleanControl control; /** * Constructs a new boolean control object with the given parameters. * The labels for the <code>true</code> and <code>false</code> states * default to "true" and "false." */ protected FakeMuteControl() { super(BooleanControl.Type.MUTE, isMutedFromUser); this.control = (BooleanControl) line.getControl(BooleanControl.Type.MUTE); } /** * Sets the current value for the control. The default * implementation simply sets the value as indicated. * Some controls require that their line be open before they can be affected * by setting a value. * * @param value desired new value. */ @Override public void setValue(boolean value) { isMutedFromUser = value; if (!isMutedFromSystem) { control.setValue(isMutedFromUser); } } /** * Obtains this control's current value. * * @return current value. */ @Override public boolean getValue() { return isMutedFromUser; } /** * Obtains the label for the specified state. * * @param state the state whose label will be returned * @return the label for the specified state, such as "true" or "on" * for <code>true</code>, or "false" or "off" for <code>false</code>. */ @Override public String getStateLabel(boolean state) { if (state) { return "true"; } else { return "false"; } } } }