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 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 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 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; } } }