170 lines
5.3 KiB
Java
170 lines
5.3 KiB
Java
package de.zuim.ledcontrol.effects;
|
|
|
|
import com.tagtraum.jipes.math.FFTFactory;
|
|
import com.tagtraum.jipes.math.Transform;
|
|
import de.zuim.ledcontrol.LEDEffect;
|
|
|
|
import javax.sound.sampled.*;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public abstract class AudioEffect implements LEDEffect {
|
|
|
|
private final List<AudioData> cachedAudioData = new ArrayList<>();
|
|
private final static int BUFFERSIZE = 4096;
|
|
private Thread audioThread = null;
|
|
|
|
@Override
|
|
public void load() {
|
|
Runnable audioRunnable = () -> {
|
|
|
|
Mixer.Info[] mixers = AudioSystem.getMixerInfo();
|
|
List<Line.Info> availableTargetLines = new ArrayList<>();
|
|
Mixer mixer = null;
|
|
for (Mixer.Info mixerInfo : mixers) {
|
|
|
|
Mixer m = AudioSystem.getMixer(mixerInfo);
|
|
|
|
Line.Info[] lines = m.getTargetLineInfo();
|
|
System.out.println(mixerInfo);
|
|
|
|
for (Line.Info li : lines) {
|
|
try {
|
|
if (li instanceof DataLine.Info && mixerInfo.toString().contains("mix")) {
|
|
m.open();
|
|
mixer = m;
|
|
System.out.println("(" + availableTargetLines.size() + ") Found target line: " + li + " " + mixerInfo + "(" + li.getClass() + ")");
|
|
availableTargetLines.add(li);
|
|
m.close();
|
|
}
|
|
} catch (LineUnavailableException e) {
|
|
System.out.println("Line unavailable.");
|
|
}
|
|
}
|
|
}
|
|
|
|
DataLine.Info targetLineInfo = (DataLine.Info) availableTargetLines.get(0);
|
|
|
|
System.out.println("SUPPORTED TARGET FORMATS: ");
|
|
AudioFormat[] formats = (targetLineInfo).getFormats();
|
|
for (int i = 0; i < formats.length; i++) {
|
|
System.out.println("(" + i + ")" + formats[i]);
|
|
}
|
|
AudioFormat format = formats[formats.length > 10 ? 64 : 2];
|
|
|
|
|
|
System.out.println("SELECTED: " + format);
|
|
|
|
|
|
final DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
|
|
final AudioInputStream audioStream;
|
|
|
|
//verbesserung: https://stackoverflow.com/a/51240462
|
|
try {
|
|
//TargetDataLine targetLine = (TargetDataLine) AudioSystem.getLine(info);
|
|
TargetDataLine targetLine = (TargetDataLine) mixer.getLine(info);
|
|
targetLine.open();
|
|
targetLine.start();
|
|
audioStream = new AudioInputStream(targetLine);
|
|
|
|
final byte[] buf = new byte[BUFFERSIZE];
|
|
|
|
final int numberOfSamples = buf.length / format.getFrameSize();
|
|
final Transform fft = FFTFactory.getInstance().create(numberOfSamples);
|
|
while (audioThread != null) {
|
|
int read = audioStream.read(buf);
|
|
|
|
float[] fBuf = decode(buf, format);
|
|
|
|
final float[][] transformed = fft.transform(fBuf);
|
|
final float[] realPart = transformed[0];
|
|
final float[] imaginaryPart = transformed[1];
|
|
double[] magnitudes = toMagnitudes(realPart, imaginaryPart);
|
|
cachedAudioData.add(new AudioData(magnitudes, targetLine.getLevel()));
|
|
|
|
/*System.out.println("M"+ Arrays.toString(magnitudes));
|
|
float max = 0, avg = 0, min = 11111111;
|
|
for (float m : fBuf) {
|
|
avg += m;
|
|
if (m > max)
|
|
max = (int) m;
|
|
if (m < min)
|
|
min = (int) m;
|
|
}
|
|
System.out.println("M"+ max+" "+min+" "+avg/fBuf.length+ " "+read+" "+fBuf.length);*/
|
|
|
|
}
|
|
|
|
audioStream.close();
|
|
targetLine.close();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
};
|
|
|
|
audioThread = new Thread(audioRunnable);
|
|
audioThread.start();
|
|
}
|
|
|
|
@Override
|
|
public void unload() {
|
|
if (audioThread != null && audioThread.isAlive())
|
|
audioThread = null;
|
|
}
|
|
|
|
public List<AudioData> getData() {
|
|
return cachedAudioData;
|
|
}
|
|
|
|
public int getMagnitudeLength() {
|
|
return cachedAudioData.isEmpty() ? 0 : cachedAudioData.get(0).magnitudes.length;
|
|
}
|
|
|
|
private static float[] decode(final byte[] buf, final AudioFormat format) {
|
|
final float[] fbuf = new float[buf.length / format.getFrameSize()];
|
|
for (int pos = 0; pos < buf.length; pos += format.getFrameSize()) {
|
|
final int sample = format.isBigEndian()
|
|
? byteToIntBigEndian(buf, pos, format.getFrameSize())
|
|
: byteToIntLittleEndian(buf, pos, format.getFrameSize());
|
|
// normalize to [0,1]
|
|
fbuf[pos / format.getFrameSize()] = sample / (Short.MAX_VALUE + 1.0f);
|
|
}
|
|
return fbuf;
|
|
}
|
|
|
|
private static double[] toMagnitudes(final float[] realPart, final float[] imaginaryPart) {
|
|
final double[] powers = new double[realPart.length / 2];
|
|
for (int i = 0; i < powers.length; i++) {
|
|
powers[i] = Math.sqrt(realPart[i] * realPart[i] + imaginaryPart[i] * imaginaryPart[i]);
|
|
}
|
|
return powers;
|
|
}
|
|
|
|
private static int byteToIntLittleEndian(final byte[] buf, final int offset, final int bytesPerSample) {
|
|
int sample = 0;
|
|
for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
|
|
final int aByte = buf[offset + byteIndex] & 0xff;
|
|
sample += aByte << 8 * (byteIndex);
|
|
}
|
|
return sample;
|
|
}
|
|
|
|
private static int byteToIntBigEndian(final byte[] buf, final int offset, final int bytesPerSample) {
|
|
int sample = 0;
|
|
for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
|
|
final int aByte = buf[offset + byteIndex] & 0xff;
|
|
sample += aByte << (8 * (bytesPerSample - byteIndex - 1));
|
|
}
|
|
return sample;
|
|
}
|
|
|
|
static class AudioData {
|
|
public double[] magnitudes;
|
|
public float level;
|
|
|
|
public AudioData(double[] magnitudes, float level) {
|
|
this.magnitudes = magnitudes;
|
|
this.level = level;
|
|
}
|
|
}
|
|
}
|