Fully implement JavaAudioEngine

pull/35/head
James Ball 2021-07-04 18:27:52 +01:00
rodzic 3eed4b26ce
commit 60cb593a38
2 zmienionych plików z 64 dodań i 6 usunięć

Wyświetl plik

@ -2,33 +2,91 @@ package sh.ball.audio.engine;
import sh.ball.shapes.Vector2;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.SourceDataLine;
import java.util.List;
import java.util.concurrent.Callable;
public class JavaAudioEngine implements AudioEngine {
private static final int DEFAULT_SAMPLE_RATE = 192000;
private static final int NUM_CHANNELS = 2;
private static final int LATENCY_MS = 10;
private static final int MAX_FRAME_LATENCY = 512;
private static final int BIT_DEPTH = 16;
private static final int FRAME_SIZE = NUM_CHANNELS * BIT_DEPTH / 8;
private static final boolean BIG_ENDIAN = false;
private static final boolean SIGNED_SAMPLE = true;
public static final double EPSILON = 0.1;
private volatile boolean stopped = false;
private Callable<Vector2> channelGenerator;
private SourceDataLine source;
@Override
public boolean isPlaying() {
return false;
return source.isRunning();
}
@Override
public void play(Callable<Vector2> channelGenerator, AudioDevice device) throws Exception {
this.channelGenerator = channelGenerator;
AudioFormat format = new AudioFormat((float) device.sampleRate(), BIT_DEPTH, NUM_CHANNELS, SIGNED_SAMPLE, BIG_ENDIAN);
this.source = AudioSystem.getSourceDataLine(format);
source.open(format);
int frameLatency = Math.max((int) (device.sampleRate() * LATENCY_MS * 0.0005), MAX_FRAME_LATENCY);
int bufferSize = frameLatency * FRAME_SIZE;
int remainingBufferSpace = source.getBufferSize() - bufferSize;
byte[] buffer = new byte[bufferSize * 2];
source.start();
while (!stopped) {
int delta = source.available() - remainingBufferSpace;
if (delta > 0) {
int requiredSamples = (delta + bufferSize) / FRAME_SIZE;
if (requiredSamples * NUM_CHANNELS > buffer.length / 2) {
buffer = new byte[requiredSamples * NUM_CHANNELS * 2];
}
for (int i = 0; i < requiredSamples; i++) {
try {
Vector2 channels = channelGenerator.call();
short left = (short) (channels.getX() * Short.MAX_VALUE);
short right = (short) (channels.getY() * Short.MAX_VALUE);
buffer[i * 4] = (byte) left;
buffer[i * 4 + 1] = (byte) (left >> 8);
buffer[i * 4 + 2] = (byte) right;
buffer[i * 4 + 3] = (byte) (right >> 8);
} catch (Exception e) {
e.printStackTrace();
}
}
source.write(buffer, 0, requiredSamples * FRAME_SIZE);
}
}
source.stop();
}
@Override
public void stop() {
stopped = true;
}
@Override
public List<AudioDevice> devices() {
return null;
return List.of(getDefaultDevice());
}
@Override
public AudioDevice getDefaultDevice() {
return null;
return new DefaultAudioDevice("default", "default", DEFAULT_SAMPLE_RATE, AudioSample.INT16);
}
}

Wyświetl plik

@ -11,7 +11,7 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import sh.ball.audio.ShapeAudioPlayer;
import sh.ball.audio.engine.XtAudioEngine;
import sh.ball.audio.engine.JavaAudioEngine;
import sh.ball.engine.Vector3;
import java.util.Objects;
@ -24,7 +24,7 @@ public class Gui extends Application {
System.setProperty("prism.lcdtext", "false");
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml"));
Controller controller = new Controller(new ShapeAudioPlayer(XtAudioEngine::new));
Controller controller = new Controller(new ShapeAudioPlayer(JavaAudioEngine::new));
loader.setController(controller);
Parent root = loader.load();