001package org.intellimate.izou.system.sound; 002 003import org.intellimate.izou.util.IzouModule; 004import org.intellimate.izou.addon.AddOnModel; 005import org.intellimate.izou.identification.Identification; 006import org.intellimate.izou.main.Main; 007 008import javax.sound.sampled.*; 009import java.util.concurrent.Future; 010import java.util.function.Consumer; 011 012/** 013 * the base class for every IzouSoundLine, provides basic implementation of the Methods defined int IzouSoundLine and 014 * delegates to Line, AutoCloseable etc. 015 * @author LeanderK 016 * @version 1.0 017 */ 018public class IzouSoundLineBaseClass extends IzouModule implements Line, AutoCloseable, IzouSoundLine { 019 protected final Line line; 020 private Future<?> closingThread; 021 private boolean isPermanent; 022 protected final SoundManager soundManager; 023 private final AddOnModel addOnModel; 024 protected final boolean isMutable; 025 protected boolean isMutedFromSystem = false; 026 private boolean isMutedFromUser = false; 027 private Consumer<Void> closeCallback = null; 028 private boolean muteIfNonPermanent = true; 029 private Consumer<Void> muteCallback = null; 030 private Identification responsibleID; 031 032 public IzouSoundLineBaseClass(Line line, Main main, boolean isPermanent, AddOnModel addOnModel) { 033 super(main, false); 034 this.line = line; 035 this.isPermanent = isPermanent; 036 this.addOnModel = addOnModel; 037 if (!isPermanent) { 038 closingThread = getClosingThread(line, main, addOnModel); 039 } else { 040 closingThread = null; 041 } 042 soundManager = null; 043 boolean mutable; 044 try { 045 line.getControl(BooleanControl.Type.MUTE); 046 mutable = true; 047 } catch (IllegalArgumentException e) { 048 mutable = false; 049 } 050 isMutable = mutable; 051 } 052 053 private Future<?> getClosingThread(Line line, Main main, AddOnModel addOnModel) { 054 return main.getThreadPoolManager().getIzouThreadPool().submit(() -> { 055 try { 056 Thread.sleep(600000); 057 } catch (InterruptedException e) { 058 error("interrupted while sleeping, canceling"); 059 return; 060 } 061 if (line.isOpen()) { 062 debug("closing line " + line + "for Addon " + addOnModel); 063 line.close(); 064 } 065 }); 066 } 067 068 /** 069 * Obtains the <code>Line.Info</code> object describing this 070 * line. 071 * @return description of the line 072 */ 073 @Override 074 public Info getLineInfo() { 075 return line.getLineInfo(); 076 } 077 078 /** 079 * Opens the line, indicating that it should acquire any required 080 * system resources and become operational. 081 * If this operation 082 * succeeds, the line is marked as open, and an <code>OPEN</code> event is dispatched 083 * to the line's listeners. 084 * <p> 085 * Note that some lines, once closed, cannot be reopened. Attempts 086 * to reopen such a line will always result in an <code>LineUnavailableException</code>. 087 * <p> 088 * Some types of lines have configurable properties that may affect 089 * resource allocation. For example, a <code>DataLine</code> must 090 * be opened with a particular format and buffer size. Such lines 091 * should provide a mechanism for configuring these properties, such 092 * as an additional <code>open</code> method or methods which allow 093 * an application to specify the desired settings. 094 * <p> 095 * This method takes no arguments, and opens the line with the current 096 * settings. For <code>SourceDataLine</code> and 097 * <code>TargetDataLine</code> objects, this means that the line is 098 * opened with default settings. For a <code>Clip</code>, however, 099 * the buffer size is determined when data is loaded. Since this method does not 100 * allow the application to specify any data to load, an IllegalArgumentException 101 * is thrown. Therefore, you should instead use one of the <code>open</code> methods 102 * provided in the <code>Clip</code> interface to load data into the <code>Clip</code>. 103 * <p> 104 * For <code>DataLine</code>'s, if the <code>DataLine.Info</code> 105 * object which was used to retrieve the line, specifies at least 106 * one fully qualified audio format, the last one will be used 107 * as the default format. 108 * 109 * @throws IllegalArgumentException if this method is called on a Clip instance. 110 * @throws LineUnavailableException if the line cannot be 111 * opened due to resource restrictions. 112 * @throws SecurityException if the line cannot be 113 * opened due to security restrictions. 114 * 115 * @see #close 116 * @see #isOpen 117 */ 118 @Override 119 public void open() throws LineUnavailableException { 120 opening(); 121 line.open(); 122 } 123 124 protected void opening() { 125 System.out.println("opening for " + addOnModel); 126 if (!line.isOpen() && !isPermanent && muteCallback != null) 127 muteCallback.accept(null); 128 } 129 130 /** 131 * Closes the line, indicating that any system resources 132 * in use by the line can be released. If this operation 133 * succeeds, the line is marked closed and a <code>CLOSE</code> event is dispatched 134 * to the line's listeners. 135 * @throws SecurityException if the line cannot be 136 * closed due to security restrictions. 137 * 138 * @see #open 139 * @see #isOpen 140 */ 141 @Override 142 public void close() { 143 System.out.println("closing for " + addOnModel); 144 closeCallback.accept(null); 145 line.close(); 146 } 147 148 /** 149 * Indicates whether the line is open, meaning that it has reserved 150 * system resources and is operational, although it might not currently be 151 * playing or capturing sound. 152 * @return <code>true</code> if the line is open, otherwise <code>false</code> 153 * 154 * @see #open() 155 * @see #close() 156 */ 157 @Override 158 public boolean isOpen() { 159 return line.isOpen(); 160 } 161 162 /** 163 * Obtains the set of controls associated with this line. Some controls may only be available when the line is open. 164 * If there are no controls, this method returns an array of length 0. 165 * The mute-control operation may be overridden by the System. 166 * @return the array of controls 167 * @see #isMutedFromSystem() 168 */ 169 @Override 170 public Control[] getControls() { 171 Control[] controls = line.getControls(); 172 for (int i = 0; i < controls.length; i++) { 173 Control control = controls[i]; 174 if (control.getType().toString().equals(BooleanControl.Type.MUTE.toString())) { 175 controls[i] = new FakeMuteControl(); 176 } 177 } 178 return controls; 179 } 180 181 /** 182 * Indicates whether the line supports a control of the specified type. Some controls may only be available when the line is open. 183 * @param control the type of the control for which support is queried 184 * @return true if at least one control of the specified type is supported, otherwise false. 185 */ 186 @Override 187 public boolean isControlSupported(Control.Type control) { 188 return line.isControlSupported(control); 189 } 190 191 /** 192 * Obtains a control of the specified type, if there is any. 193 * Some controls may only be available when the line is open. 194 * The mute-control operation may be overridden by the System. 195 * @param control the type of the requested control 196 * @return a control of the specified type 197 * @throws IllegalArgumentException - if a control of the specified type is not supported 198 * @see #isMutedFromSystem() 199 */ 200 @Override 201 public Control getControl(Control.Type control) throws IllegalArgumentException { 202 if (control.toString().equals(BooleanControl.Type.MUTE.toString())) { 203 return new FakeMuteControl(); 204 } else { 205 return line.getControl(control); 206 } 207 } 208 209 /** 210 * follows no predictable behaviour, can be seen as not implemented. 211 * @param listener the listener 212 */ 213 @Override 214 public void addLineListener(LineListener listener) { 215 line.addLineListener(listener); 216 } 217 218 /** 219 * follows no predictable behaviour, can be seen as not implemented. 220 * @param listener the listener 221 */ 222 @Override 223 public void removeLineListener(LineListener listener) { 224 line.removeLineListener(listener); 225 } 226 227 /** 228 * returns whether the line is permanently-available. 229 * If a line is not permanently available, it will close after max. 10 minutes 230 * @return true if permanent. 231 */ 232 @Override 233 public boolean isPermanent() { 234 return isPermanent; 235 } 236 237 void setToPermanent() { 238 if (isPermanent) 239 return; 240 closingThread.cancel(true); 241 closingThread = null; 242 isPermanent = true; 243 } 244 245 void setToNonPermanent() { 246 if (!isPermanent) 247 return; 248 closingThread = getClosingThread(line, main, addOnModel); 249 isPermanent = false; 250 } 251 252 /** 253 * gets the associated AddonModel 254 * @return the AddonModel 255 */ 256 @Override 257 @SuppressWarnings("unused") 258 public AddOnModel getAddOnModel() { 259 return addOnModel; 260 } 261 262 /** 263 * gets the ID responsible 264 * 265 * @return the the ID 266 */ 267 @Override 268 public Identification getResponsibleID() { 269 return responsibleID; 270 } 271 272 void setResponsibleID(Identification responsibleID) { 273 this.responsibleID = responsibleID; 274 } 275 276 /** 277 * returns whether the Line is muted 278 * @return true if muted. 279 */ 280 @Override 281 @SuppressWarnings("unused") 282 public boolean isMutedFromSystem() { 283 return isMutedFromSystem; 284 } 285 286 /** 287 * sets whether other Addons audio-inputs should be muted while this line is open (only works for non-permanent lines). 288 * The standard is true. 289 * @param muteIfNonPermanent true if muted, false if not 290 */ 291 @Override 292 @SuppressWarnings("unused") 293 public void setMuteIfNonPermanent(boolean muteIfNonPermanent) { 294 this.muteIfNonPermanent = muteIfNonPermanent; 295 } 296 297 /** 298 * retruns whether other Addons audio-inputs should be muted while this line is open (only works for non-permanent lines). 299 * @return true if muted, false if not 300 */ 301 @Override 302 public boolean isMuteIfNonPermanent() { 303 return muteIfNonPermanent; 304 } 305 306 void setMutedFromSystem(boolean isMuted) { 307 if (isMutable) { 308 BooleanControl bc = (BooleanControl) line.getControl(BooleanControl.Type.MUTE); 309 if (bc != null) { 310 if (isMuted) { 311 bc.setValue(true); // true to mute the line, false to unmute 312 } else { 313 bc.setValue(isMutedFromUser); // true to mute the line, false to unmute 314 } 315 } 316 } 317 this.isMutedFromSystem = isMuted; 318 } 319 320 void registerCloseCallback(Consumer<Void> consumer) { 321 this.closeCallback = consumer; 322 } 323 324 void registerMuteCallback(Consumer<Void> consumer) { 325 this.muteCallback = consumer; 326 } 327 328 private class FakeMuteControl extends BooleanControl { 329 private final BooleanControl control; 330 331 /** 332 * Constructs a new boolean control object with the given parameters. 333 * The labels for the <code>true</code> and <code>false</code> states 334 * default to "true" and "false." 335 */ 336 protected FakeMuteControl() { 337 super(BooleanControl.Type.MUTE, isMutedFromUser); 338 this.control = (BooleanControl) line.getControl(BooleanControl.Type.MUTE); 339 } 340 341 /** 342 * Sets the current value for the control. The default 343 * implementation simply sets the value as indicated. 344 * Some controls require that their line be open before they can be affected 345 * by setting a value. 346 * 347 * @param value desired new value. 348 */ 349 @Override 350 public void setValue(boolean value) { 351 isMutedFromUser = value; 352 if (!isMutedFromSystem) { 353 control.setValue(isMutedFromUser); 354 } 355 } 356 357 /** 358 * Obtains this control's current value. 359 * 360 * @return current value. 361 */ 362 @Override 363 public boolean getValue() { 364 return isMutedFromUser; 365 } 366 367 /** 368 * Obtains the label for the specified state. 369 * 370 * @param state the state whose label will be returned 371 * @return the label for the specified state, such as "true" or "on" 372 * for <code>true</code>, or "false" or "off" for <code>false</code>. 373 */ 374 @Override 375 public String getStateLabel(boolean state) { 376 if (state) { 377 return "true"; 378 } else { 379 return "false"; 380 } 381 } 382 } 383}