Reoganise UI and fully implement device selection

pull/35/head
James Ball 2021-06-17 21:36:06 +01:00
rodzic 4c584fb96c
commit 3f07b5f433
10 zmienionych plików z 113 dodań i 40 usunięć

Wyświetl plik

@ -8,8 +8,12 @@ import java.util.List;
public interface AudioPlayer<S> extends Runnable {
void reset() throws Exception;
void stop();
boolean isPlaying();
void setQuality(double quality);
void addFrame(S frame);

Wyświetl plik

@ -18,6 +18,8 @@ public class FrequencyAnalyser<S> implements Runnable {
private final int sampleRate;
private final int powerOfTwo;
private volatile boolean stopped;
public FrequencyAnalyser(AudioPlayer<S> audioPlayer, int frameSize, int sampleRate) {
this.audioPlayer = audioPlayer;
this.frameSize = frameSize;
@ -40,7 +42,7 @@ public class FrequencyAnalyser<S> implements Runnable {
public void run() {
byte[] buf = new byte[2 << powerOfTwo];
while (true) {
while (!stopped) {
try {
audioPlayer.read(buf);
} catch (InterruptedException e) {
@ -78,6 +80,10 @@ public class FrequencyAnalyser<S> implements Runnable {
}
}
public void stop() {
stopped = true;
}
private double[] decode(final byte[] buf, boolean decodeLeft) {
final double[] fbuf = new double[(buf.length / 2) / frameSize];
int byteNum = 0;

Wyświetl plik

@ -14,6 +14,7 @@ import sh.ball.shapes.Vector2;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;
@ -29,12 +30,13 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
// Stereo audio
private static final int NUM_OUTPUTS = 2;
private final AudioEngine audioEngine;
private final Callable<AudioEngine> audioEngineBuilder;
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
private final Map<Object, Effect> effects = new HashMap<>();
private final ReentrantLock renderLock = new ReentrantLock();
private final List<Listener> listeners = new ArrayList<>();
private AudioEngine audioEngine;
private ByteArrayOutputStream outputStream;
private boolean recording = false;
private int framesRecorded = 0;
@ -46,8 +48,9 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
private double weight = Shape.DEFAULT_WEIGHT;
private AudioDevice device;
public ShapeAudioPlayer(AudioEngine audioEngine) {
this.audioEngine = audioEngine;
public ShapeAudioPlayer(Callable<AudioEngine> audioEngineBuilder) throws Exception {
this.audioEngineBuilder = audioEngineBuilder;
this.audioEngine = audioEngineBuilder.call();
}
private Vector2 generateChannels() throws InterruptedException {
@ -155,11 +158,25 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
audioEngine.play(this::generateChannels, renderLock, device);
}
@Override
public void reset() throws Exception {
audioEngine.stop();
while (isPlaying()) {
Thread.onSpinWait();
}
audioEngine = audioEngineBuilder.call();
}
@Override
public void stop() {
audioEngine.stop();
}
@Override
public boolean isPlaying() {
return audioEngine.isPlaying();
}
@Override
public void addFrame(List<Shape> frame) {
try {

Wyświetl plik

@ -7,6 +7,8 @@ import java.util.concurrent.Callable;
import java.util.concurrent.locks.ReentrantLock;
public interface AudioEngine {
boolean isPlaying();
void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device);
void stop();

Wyświetl plik

@ -1,5 +1,7 @@
package sh.ball.audio.engine;
import java.util.Objects;
public class DefaultAudioDevice implements AudioDevice {
final String id;
@ -36,6 +38,21 @@ public class DefaultAudioDevice implements AudioDevice {
@Override
public String toString() {
return name + " @ " + sampleRate + "KHz";
String simplifiedName = name.replaceFirst(" \\(Shared\\)", "");
simplifiedName = simplifiedName.replaceFirst(" \\(NVIDIA High Definition Audio\\)", "");
return simplifiedName + " @ " + sampleRate + "KHz";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DefaultAudioDevice that = (DefaultAudioDevice) o;
return sampleRate == that.sampleRate && Objects.equals(id, that.id) && Objects.equals(name, that.name) && sample == that.sample;
}
@Override
public int hashCode() {
return Objects.hash(id, name, sampleRate, sample);
}
}

Wyświetl plik

@ -16,6 +16,8 @@ public class XtAudioEngine implements AudioEngine {
private static final int NUM_OUTPUTS = 2;
private volatile boolean stopped = false;
private boolean playing = false;
private ReentrantLock renderLock;
private Callable<Vector2> channelGenerator;
@ -41,8 +43,14 @@ public class XtAudioEngine implements AudioEngine {
return 0;
}
@Override
public boolean isPlaying() {
return playing;
}
@Override
public void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device) {
playing = true;
this.channelGenerator = channelGenerator;
this.renderLock = renderLock;
try (XtPlatform platform = XtAudio.init(null, null)) {
@ -72,6 +80,7 @@ public class XtAudioEngine implements AudioEngine {
}
}
}
playing = false;
}
@Override
@ -158,18 +167,6 @@ public class XtAudioEngine implements AudioEngine {
return service;
}
private String getDeviceId(XtService service) {
String deviceId = service.getDefaultDeviceId(true);
if (deviceId == null) {
return getFirstDevice(service);
}
return deviceId;
}
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;

Wyświetl plik

@ -54,14 +54,15 @@ public class Controller implements Initializable, FrequencyListener, Listener {
private final AudioPlayer<List<Shape>> audioPlayer;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final int sampleRate;
private final RotateEffect rotateEffect;
private final TranslateEffect translateEffect;
private final WobbleEffect wobbleEffect;
private final ScaleEffect scaleEffect;
private AudioDevice defaultDevice;
private int sampleRate;
private FrequencyAnalyser<List<Shape>> analyser;
private final AudioDevice defaultDevice;
private FrameProducer<List<Shape>> producer;
private boolean recording = false;
@ -248,15 +249,43 @@ public class Controller implements Initializable, FrequencyListener, Listener {
audioPlayer.addEffect(EffectType.ROTATE, rotateEffect);
audioPlayer.addEffect(EffectType.TRANSLATE, translateEffect);
executor.submit(producer);
audioPlayer.setDevice(defaultDevice);
System.out.println(audioPlayer.devices());
deviceComboBox.setItems(FXCollections.observableList(audioPlayer.devices()));
deviceComboBox.getSelectionModel().select(defaultDevice);
Thread renderThread = new Thread(audioPlayer);
renderThread.setUncaughtExceptionHandler((thread, throwable) -> throwable.printStackTrace());
renderThread.start();
FrequencyAnalyser<List<Shape>> analyser = new FrequencyAnalyser<>(audioPlayer, 2, sampleRate);
deviceComboBox.setValue(defaultDevice);
executor.submit(producer);
analyser = new FrequencyAnalyser<>(audioPlayer, 2, sampleRate);
startFrequencyAnalyser(analyser);
startAudioPlayerThread();
deviceComboBox.valueProperty().addListener((options, oldDevice, newDevice) -> {
if (newDevice != null) {
switchAudioDevice(newDevice);
}
});
}
private void switchAudioDevice(AudioDevice device) {
try {
audioPlayer.reset();
} catch (Exception e) {
e.printStackTrace();
}
audioPlayer.setDevice(device);
analyser.stop();
sampleRate = device.sampleRate();
analyser = new FrequencyAnalyser<>(audioPlayer, 2, sampleRate);
startFrequencyAnalyser(analyser);
startAudioPlayerThread();
}
private void startAudioPlayerThread() {
Thread audioPlayerThread = new Thread(audioPlayer);
audioPlayerThread.setUncaughtExceptionHandler((thread, throwable) -> throwable.printStackTrace());
audioPlayerThread.start();
}
private void startFrequencyAnalyser(FrequencyAnalyser<List<Shape>> analyser) {
analyser.addListener(this);
analyser.addListener(wobbleEffect);
new Thread(analyser).start();
@ -273,7 +302,7 @@ public class Controller implements Initializable, FrequencyListener, Listener {
AudioInputStream input = audioPlayer.stopRecord();
try {
File file = fileChooser.showSaveDialog(stage);
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
Date date = new Date(System.currentTimeMillis());
if (file == null) {
file = new File("out-" + formatter.format(date) + ".wav");

Wyświetl plik

@ -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(new XtAudioEngine()));
Controller controller = new Controller(new ShapeAudioPlayer(XtAudioEngine::new));
loader.setController(controller);
Parent root = loader.load();

Wyświetl plik

@ -113,6 +113,7 @@
-fx-background-radius: 0;
-fx-border-radius: 0;
-fx-border-width: 0;
-fx-cell-size: 35;
/* No alternate highlighting */
-fx-background-color: dark_color;
@ -137,7 +138,7 @@
.combo-box-base
{
-fx-background-color: very_dark;
-fx-background-color: white;
-fx-border-radius: 0;
-fx-border-width: 1 1 1 0;
-fx-border-color: white;
@ -148,16 +149,16 @@
.combo-box-base:hover
{
-fx-color: very_dark;
-fx-color: white;
}
.combo-box-base:showing
{
-fx-color: very_dark;
-fx-color: white;
}
.combo-box-base:focused {
-fx-background-color: very_dark;
-fx-background-color: white;
-fx-background-radius: 0;
-fx-background-insets: 0;
}

Wyświetl plik

@ -11,14 +11,14 @@
<?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">
<AnchorPane id="control-pane" layoutX="422.0" layoutY="10.0" prefHeight="195.0" prefWidth="402.0">
<children>
<Button fx:id="chooseFileButton" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Choose File" />
<Label fx:id="fileLabel" layoutX="146.0" layoutY="19.0" maxWidth="270.0" prefHeight="18.0" prefWidth="246.0" text="cube.obj" />
<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 fx:id="deviceComboBox" layoutX="169.0" layoutY="113.0" prefHeight="26.0" prefWidth="221.0" />
<Button fx:id="chooseFileButton" layoutX="14.0" layoutY="53.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Choose File" />
<Label fx:id="fileLabel" layoutX="146.0" layoutY="58.0" maxWidth="270.0" prefHeight="18.0" prefWidth="246.0" text="cube.obj" />
<Button fx:id="recordButton" layoutX="14.0" layoutY="93.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Record" />
<Label fx:id="recordLabel" layoutX="146.0" layoutY="98.0" maxWidth="270.0" prefHeight="18.0" prefWidth="245.0" />
<Label id="frequency" fx:id="frequencyLabel" layoutX="14.0" layoutY="131.0" prefHeight="58.0" prefWidth="376.0" text="L/R Frequency:&#10; &#10;" />
<ComboBox fx:id="deviceComboBox" layoutX="14.0" layoutY="11.0" prefHeight="26.0" prefWidth="376.0" />
</children>
</AnchorPane>
<TitledPane animated="false" collapsible="false" layoutX="422.0" layoutY="213.0" prefHeight="272.0" prefWidth="402.0" text="Effects">
@ -55,7 +55,7 @@
<CheckBox fx:id="rotateCheckBox" layoutX="90.0" layoutY="147.0" mnemonicParsing="false" text="Rotate with Mouse (Esc to disable)" />
</AnchorPane>
</TitledPane>
<TitledPane collapsible="false" layoutX="11.0" layoutY="18.0" prefHeight="248.0" prefWidth="402.0" text="Image settings">
<TitledPane collapsible="false" layoutX="11.0" layoutY="9.0" prefHeight="257.0" prefWidth="402.0" text="Image settings">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0">
<Slider fx:id="rotateSpeedSlider" blockIncrement="0.05" layoutX="116.0" layoutY="90.0" majorTickUnit="1.0" max="10.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" />