Update AudioEngine and add AudioDevice and AudioSample

pull/35/head
James Ball 2021-06-15 22:02:29 +01:00
rodzic b823a0fd69
commit 45289200a4
6 zmienionych plików z 105 dodań i 26 usunięć

Wyświetl plik

@ -0,0 +1,9 @@
package sh.ball.audio.engine;
public interface AudioDevice {
String name();
int sampleRate();
AudioSample sample();
}

Wyświetl plik

@ -2,11 +2,14 @@ package sh.ball.audio.engine;
import sh.ball.shapes.Vector2;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.ReentrantLock;
public interface AudioEngine {
void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock);
void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device);
void stop();
int sampleRate();
List<AudioDevice> devices();
}

Wyświetl plik

@ -0,0 +1,11 @@
package sh.ball.audio.engine;
public enum AudioSample {
UINT8,
INT8,
INT16,
INT24,
INT32,
FLOAT32,
FLOAT64
}

Wyświetl plik

@ -0,0 +1,29 @@
package sh.ball.audio.engine;
public class DefaultAudioDevice implements AudioDevice {
final String name;
final int sampleRate;
final AudioSample sample;
public DefaultAudioDevice(String name, int sampleRate, AudioSample sample) {
this.name = name;
this.sampleRate = sampleRate;
this.sample = sample;
}
@Override
public String name() {
return name;
}
@Override
public int sampleRate() {
return sampleRate;
}
@Override
public AudioSample sample() {
return sample;
}
}

Wyświetl plik

@ -3,33 +3,23 @@ package sh.ball.audio.engine;
import sh.ball.shapes.Vector2;
import xt.audio.*;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.ReentrantLock;
public class XtAudioEngine implements AudioEngine {
private static final int DEFAULT_SAMPLE_RATE = 192000;
// Stereo audio
private static final int NUM_OUTPUTS = 2;
private final int sampleRate;
private volatile boolean stopped = false;
private ReentrantLock renderLock;
private Callable<Vector2> channelGenerator;
public XtAudioEngine() {
try (XtPlatform platform = XtAudio.init(null, null)) {
XtService service = getService(platform);
String deviceId = getDeviceId(service);
try (XtDevice device = service.openDevice(deviceId)) {
// Gets the sample rate of the device. E.g. 192000 hz.
this.sampleRate = device.getMix().map(xtMix -> xtMix.rate).orElse(DEFAULT_SAMPLE_RATE);
}
}
}
public XtAudioEngine() {}
private int render(XtStream stream, Structs.XtBuffer buffer, Object user) throws Exception {
XtSafeBuffer safe = XtSafeBuffer.get(stream);
@ -52,25 +42,24 @@ public class XtAudioEngine implements AudioEngine {
}
@Override
public void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock) {
public void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device) {
this.channelGenerator = channelGenerator;
this.renderLock = renderLock;
try (XtPlatform platform = XtAudio.init(null, null)) {
XtService service = getService(platform);
String deviceId = getDeviceId(service);
try (XtDevice device = service.openDevice(deviceId)) {
try (XtDevice xtDevice = service.openDevice(device.name())) {
// TODO: Make this generic to the type of XtSample of the current device.
Structs.XtMix mix = new Structs.XtMix(sampleRate, Enums.XtSample.FLOAT32);
Structs.XtMix mix = new Structs.XtMix(xtDevice.getMix().orElseThrow().rate, Enums.XtSample.FLOAT32);
Structs.XtChannels channels = new Structs.XtChannels(0, 0, NUM_OUTPUTS, 0);
Structs.XtFormat format = new Structs.XtFormat(mix, channels);
if (device.supportsFormat(format)) {
Structs.XtBufferSize size = device.getBufferSize(format);
if (xtDevice.supportsFormat(format)) {
Structs.XtBufferSize size = xtDevice.getBufferSize(format);
Structs.XtStreamParams streamParams = new Structs.XtStreamParams(true, this::render, null, null);
Structs.XtDeviceStreamParams deviceParams = new Structs.XtDeviceStreamParams(streamParams, format, size.current);
try (XtStream stream = device.openStream(deviceParams, null);
try (XtStream stream = xtDevice.openStream(deviceParams, null);
XtSafeBuffer safe = XtSafeBuffer.register(stream, true)) {
stream.start();
while (!stopped) {
@ -91,10 +80,37 @@ public class XtAudioEngine implements AudioEngine {
}
@Override
public int sampleRate() {
return sampleRate;
public List<AudioDevice> devices() {
List<AudioDevice> devices = new ArrayList<>();
try (XtPlatform platform = XtAudio.init(null, null)) {
XtService service = getService(platform);
XtDeviceList xtDevices = service.openDeviceList(EnumSet.of(Enums.XtEnumFlags.OUTPUT));
for (int i = 0; i < xtDevices.getCount(); i++) {
String device = xtDevices.getId(i);
try (XtDevice xtDevice = service.openDevice(device)) {
Optional<Structs.XtMix> mix = xtDevice.getMix();
if (mix.isEmpty()) {
continue;
}
Structs.XtChannels channels = new Structs.XtChannels(0, 0, NUM_OUTPUTS, 0);
Structs.XtFormat format = new Structs.XtFormat(mix.get(), channels);
if (xtDevice.supportsFormat(format)) {
devices.add(new DefaultAudioDevice(device, mix.get().rate, XtSampleToAudioSample(mix.get().sample)));
}
}
}
}
return devices;
}
private XtService getService(XtPlatform platform) {
XtService service = platform.getService(platform.setupToSystem(Enums.XtSetup.SYSTEM_AUDIO));
if (service == null) {
@ -125,4 +141,14 @@ public class XtAudioEngine implements AudioEngine {
private String getFirstDevice(XtService service) {
return service.openDeviceList(EnumSet.of(Enums.XtEnumFlags.OUTPUT)).getId(0);
}
private AudioSample XtSampleToAudioSample(Enums.XtSample sample) {
return switch (sample) {
case UINT8 -> AudioSample.UINT8;
case INT16 -> AudioSample.INT16;
case INT24 -> AudioSample.INT24;
case INT32 -> AudioSample.INT32;
case FLOAT32 -> AudioSample.FLOAT32;
};
}
}

Wyświetl plik

@ -2,6 +2,7 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.TextField?>
@ -9,7 +10,6 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="498.0" prefWidth="837.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane id="control-pane" layoutX="422.0" layoutY="19.0" prefHeight="185.0" prefWidth="402.0">
<children>
@ -18,6 +18,7 @@
<Button fx:id="recordButton" layoutX="14.0" layoutY="54.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Record" />
<Label fx:id="recordLabel" layoutX="146.0" layoutY="59.0" maxWidth="270.0" prefHeight="18.0" prefWidth="245.0" />
<Label id="frequency" fx:id="frequencyLabel" layoutX="14.0" layoutY="113.0" prefHeight="58.0" prefWidth="376.0" text="L/R Frequency:&#10; &#10;" />
<ComboBox layoutX="251.0" layoutY="113.0" prefWidth="150.0" />
</children>
</AnchorPane>
<TitledPane animated="false" collapsible="false" layoutX="422.0" layoutY="213.0" prefHeight="272.0" prefWidth="402.0" text="Effects">