forked from Apple-2-Tools/jace
265 lines
8.7 KiB
Java
265 lines
8.7 KiB
Java
/*
|
|
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
package jace.core;
|
|
|
|
import jace.config.ConfigurableField;
|
|
import jace.config.DynamicSelection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import javax.sound.sampled.AudioFormat;
|
|
import javax.sound.sampled.AudioSystem;
|
|
import javax.sound.sampled.DataLine;
|
|
import javax.sound.sampled.Line;
|
|
import javax.sound.sampled.LineUnavailableException;
|
|
import javax.sound.sampled.Mixer;
|
|
import javax.sound.sampled.Mixer.Info;
|
|
import javax.sound.sampled.SourceDataLine;
|
|
|
|
/**
|
|
* Manages sound resources used by various audio devices (such as speaker and
|
|
* mockingboard cards.) The plumbing is managed in this class so that the
|
|
* consumers do not have to do a lot of work to manage mixer lines or deal with
|
|
* how to reuse active lines if needed. It is possible that this class might be
|
|
* used to manage volume in the future, but that remains to be seen.
|
|
*
|
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
|
*/
|
|
public class SoundMixer extends Device {
|
|
|
|
private final Set<SourceDataLine> availableLines = Collections.synchronizedSet(new HashSet<SourceDataLine>());
|
|
private final Map<Object, SourceDataLine> activeLines = Collections.synchronizedMap(new HashMap<Object, SourceDataLine>());
|
|
/**
|
|
* Bits per sample
|
|
*/
|
|
@ConfigurableField(name = "Bits per sample", shortName = "bits")
|
|
public static int BITS = 16;
|
|
/**
|
|
* Sample playback rate
|
|
*/
|
|
@ConfigurableField(name = "Playback Rate", shortName = "freq")
|
|
public static int RATE = 48000;
|
|
/**
|
|
* Sound format used for playback
|
|
*/
|
|
private AudioFormat af;
|
|
/**
|
|
* Is sound line available for playback at all?
|
|
*/
|
|
public boolean lineAvailable;
|
|
@ConfigurableField(name = "Audio device", description = "Audio output device")
|
|
public static DynamicSelection<String> preferredMixer = new DynamicSelection<String>(null) {
|
|
@Override
|
|
public boolean allowNull() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public LinkedHashMap<? extends String, String> getSelections() {
|
|
Info[] mixerInfo = AudioSystem.getMixerInfo();
|
|
LinkedHashMap<String, String> out = new LinkedHashMap<>();
|
|
for (Info i : mixerInfo) {
|
|
out.put(i.getName(), i.getName());
|
|
}
|
|
return out;
|
|
}
|
|
};
|
|
private Mixer theMixer;
|
|
|
|
@Override
|
|
public String getDeviceName() {
|
|
return "Sound Output";
|
|
}
|
|
|
|
@Override
|
|
public String getShortName() {
|
|
return "mixer";
|
|
}
|
|
|
|
@Override
|
|
public synchronized void reconfigure() {
|
|
detach();
|
|
try {
|
|
initMixer();
|
|
if (lineAvailable) {
|
|
initAudio();
|
|
System.out.println("Started sound");
|
|
} else {
|
|
System.out.println("Sound not stared: Line not available");
|
|
}
|
|
} catch (LineUnavailableException ex) {
|
|
System.out.println("Unable to start sound");
|
|
Logger.getLogger(SoundMixer.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
attach();
|
|
}
|
|
|
|
/**
|
|
* Obtain sound playback line if available
|
|
*
|
|
* @throws javax.sound.sampled.LineUnavailableException If there is no line
|
|
* available
|
|
*/
|
|
private void initAudio() throws LineUnavailableException {
|
|
af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, RATE, BITS, 2, BITS / 4, RATE, true);
|
|
// af = new AudioFormat(RATE, BITS, 2, true, true);
|
|
DataLine.Info dli = new DataLine.Info(SourceDataLine.class, af);
|
|
lineAvailable = AudioSystem.isLineSupported(dli);
|
|
}
|
|
|
|
public synchronized SourceDataLine getLine(Object requester) throws LineUnavailableException {
|
|
if (activeLines.containsKey(requester)) {
|
|
return activeLines.get(requester);
|
|
}
|
|
SourceDataLine sdl = null;
|
|
if (availableLines.isEmpty()) {
|
|
sdl = getNewLine();
|
|
} else {
|
|
sdl = availableLines.iterator().next();
|
|
availableLines.remove(sdl);
|
|
}
|
|
activeLines.put(requester, sdl);
|
|
sdl.start();
|
|
return sdl;
|
|
}
|
|
|
|
public void returnLine(Object requester) {
|
|
if (activeLines.containsKey(requester)) {
|
|
SourceDataLine sdl = activeLines.remove(requester);
|
|
// Calling drain on pulse driver can cause it to freeze up (?)
|
|
// sdl.drain();
|
|
sdl.stop();
|
|
sdl.flush();
|
|
availableLines.add(sdl);
|
|
}
|
|
}
|
|
|
|
private SourceDataLine getNewLine() throws LineUnavailableException {
|
|
SourceDataLine l = null;
|
|
// Line.Info[] info = theMixer.getSourceLineInfo();
|
|
DataLine.Info dli = new DataLine.Info(SourceDataLine.class, af);
|
|
System.out.println("Maximum output lines: " + theMixer.getMaxLines(dli));
|
|
System.out.println("Allocated output lines: " + theMixer.getSourceLines().length);
|
|
System.out.println("Getting source line from " + theMixer.getMixerInfo().toString() + ": " + af.toString());
|
|
try {
|
|
l = (SourceDataLine) theMixer.getLine(dli);
|
|
} catch (IllegalArgumentException e) {
|
|
lineAvailable = false;
|
|
throw new LineUnavailableException(e.getMessage());
|
|
} catch (LineUnavailableException e) {
|
|
lineAvailable = false;
|
|
throw e;
|
|
}
|
|
if (!(l instanceof SourceDataLine)) {
|
|
lineAvailable = false;
|
|
throw new LineUnavailableException("Line is not an output line!");
|
|
}
|
|
final SourceDataLine sdl = (SourceDataLine) l;
|
|
// sdl.open(af);
|
|
// if (false) {
|
|
// return sdl;
|
|
// }
|
|
sdl.open();
|
|
sdl.start();
|
|
// new Thread(new Runnable() {
|
|
// @Override
|
|
// public void run() {
|
|
// System.out.println("Going into an infinite loop!!!");
|
|
// try {
|
|
// while (true) {
|
|
// sdl.write(new byte[]{randomByte(),randomByte(),randomByte(),randomByte()}, 0, 4);
|
|
// }
|
|
// } catch (Throwable t) {
|
|
// t.printStackTrace();
|
|
// }
|
|
// System.out.println("Thread dying...");
|
|
// }
|
|
// }).start();
|
|
return sdl;
|
|
}
|
|
|
|
public byte randomByte() {
|
|
return (byte) (Math.random() * 256);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
}
|
|
|
|
@Override
|
|
public void attach() {
|
|
// if (Motherboard.enableSpeaker)
|
|
// Motherboard.speaker.attach();
|
|
}
|
|
|
|
@Override
|
|
public void detach() {
|
|
availableLines.stream().forEach((line) -> {
|
|
line.close();
|
|
});
|
|
Set requesters = new HashSet(activeLines.keySet());
|
|
requesters.stream().map((o) -> {
|
|
if (o instanceof Device) {
|
|
((Device) o).detach();
|
|
}
|
|
return o;
|
|
}).filter((o) -> (o instanceof Card)).forEach((o) -> {
|
|
((Card) o).reconfigure();
|
|
});
|
|
if (theMixer != null) {
|
|
for (Line l : theMixer.getSourceLines()) {
|
|
l.close();
|
|
}
|
|
}
|
|
availableLines.clear();
|
|
activeLines.clear();
|
|
}
|
|
|
|
private void initMixer() {
|
|
Info selected = null;
|
|
Info[] mixerInfo = AudioSystem.getMixerInfo();
|
|
|
|
if (mixerInfo == null || mixerInfo.length == 0) {
|
|
theMixer = null;
|
|
lineAvailable = false;
|
|
System.out.println("No sound mixer is available!");
|
|
return;
|
|
}
|
|
|
|
String mixer = preferredMixer.getValue();
|
|
selected = mixerInfo[0];
|
|
for (Info i : mixerInfo) {
|
|
if (i.getName().equalsIgnoreCase(mixer)) {
|
|
selected = i;
|
|
break;
|
|
}
|
|
}
|
|
theMixer = AudioSystem.getMixer(selected);
|
|
for (Line l : theMixer.getSourceLines()) {
|
|
l.close();
|
|
}
|
|
lineAvailable = true;
|
|
}
|
|
}
|