kopia lustrzana https://github.com/jameshball/osci-render
Merge pull request #30 from jameshball/java-audio-engine
Add JavaAudioEngine for greater compatibilitypull/35/head
commit
923af64c40
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<groupId>sh.ball</groupId>
|
<groupId>sh.ball</groupId>
|
||||||
<artifactId>osci-render</artifactId>
|
<artifactId>osci-render</artifactId>
|
||||||
<version>1.7.3</version>
|
<version>1.8.0</version>
|
||||||
|
|
||||||
<name>osci-render</name>
|
<name>osci-render</name>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package sh.ball.audio.engine;
|
||||||
|
|
||||||
|
import sh.ball.shapes.Vector2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class ConglomerateAudioEngine implements AudioEngine {
|
||||||
|
|
||||||
|
private final XtAudioEngine xtEngine = new XtAudioEngine();
|
||||||
|
private final JavaAudioEngine javaEngine = new JavaAudioEngine();
|
||||||
|
|
||||||
|
// TODO: Try and make non-static
|
||||||
|
private static List<AudioDevice> xtDevices;
|
||||||
|
private static AudioDevice javaDevice;
|
||||||
|
|
||||||
|
private boolean playing = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play(Callable<Vector2> channelGenerator, AudioDevice device) throws Exception {
|
||||||
|
playing = true;
|
||||||
|
if (xtDevices.contains(device)) {
|
||||||
|
xtEngine.play(channelGenerator, device);
|
||||||
|
} else {
|
||||||
|
javaEngine.play(channelGenerator, javaDevice);
|
||||||
|
}
|
||||||
|
playing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
xtEngine.stop();
|
||||||
|
javaEngine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AudioDevice> devices() {
|
||||||
|
if (xtDevices == null) {
|
||||||
|
xtDevices = xtEngine.devices();
|
||||||
|
}
|
||||||
|
if (javaDevice == null) {
|
||||||
|
javaDevice = javaEngine.getDefaultDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AudioDevice> devices = new ArrayList<>(xtDevices);
|
||||||
|
devices.add(javaDevice);
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AudioDevice getDefaultDevice() {
|
||||||
|
return javaEngine.getDefaultDevice();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
private volatile boolean stopped = false;
|
||||||
|
|
||||||
|
private SourceDataLine source;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return source.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play(Callable<Vector2> channelGenerator, AudioDevice device) throws Exception {
|
||||||
|
this.stopped = false;
|
||||||
|
|
||||||
|
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 List.of(getDefaultDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AudioDevice getDefaultDevice() {
|
||||||
|
return new DefaultAudioDevice("default", "default", DEFAULT_SAMPLE_RATE, AudioSample.INT16);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,8 @@ import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import sh.ball.audio.ShapeAudioPlayer;
|
import sh.ball.audio.ShapeAudioPlayer;
|
||||||
import sh.ball.audio.engine.XtAudioEngine;
|
import sh.ball.audio.engine.ConglomerateAudioEngine;
|
||||||
|
import sh.ball.audio.engine.JavaAudioEngine;
|
||||||
import sh.ball.engine.Vector3;
|
import sh.ball.engine.Vector3;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -24,7 +25,7 @@ public class Gui extends Application {
|
||||||
System.setProperty("prism.lcdtext", "false");
|
System.setProperty("prism.lcdtext", "false");
|
||||||
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml"));
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml"));
|
||||||
Controller controller = new Controller(new ShapeAudioPlayer(XtAudioEngine::new));
|
Controller controller = new Controller(new ShapeAudioPlayer(ConglomerateAudioEngine::new));
|
||||||
loader.setController(controller);
|
loader.setController(controller);
|
||||||
Parent root = loader.load();
|
Parent root = loader.load();
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue