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>
|
||||
<artifactId>osci-render</artifactId>
|
||||
<version>1.7.3</version>
|
||||
<version>1.8.0</version>
|
||||
|
||||
<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.stage.Stage;
|
||||
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 java.util.Objects;
|
||||
|
@ -24,7 +25,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(ConglomerateAudioEngine::new));
|
||||
loader.setController(controller);
|
||||
Parent root = loader.load();
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue