Add audio sample drop-down in Audio settings for recording to wav

pull/85/head
James Ball 2022-06-13 21:01:09 +01:00 zatwierdzone przez James H Ball
rodzic 538db5f137
commit 0a661da836
4 zmienionych plików z 131 dodań i 42 usunięć

Wyświetl plik

@ -14,6 +14,7 @@ import sh.ball.audio.effect.SineEffect;
import sh.ball.audio.effect.SmoothEffect;
import sh.ball.audio.engine.AudioDevice;
import sh.ball.audio.engine.AudioEngine;
import sh.ball.audio.engine.AudioSample;
import sh.ball.audio.midi.MidiCommunicator;
import sh.ball.audio.midi.MidiNote;
import sh.ball.shapes.Shape;
@ -85,7 +86,8 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
private int sampleRate;
private boolean flipX = false;
private boolean flipY = false;
private boolean recordHQ = true;
private double brightness = 1.0;
private AudioSample audioSample = AudioSample.INT16;
private AudioDevice device;
@ -99,9 +101,8 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
communicator.addListener(this);
}
public boolean toggleHQRecording() {
recordHQ = !recordHQ;
return recordHQ;
public void setAudioSample(AudioSample sample) {
this.audioSample = sample;
}
public boolean midiPlaying() {
return numKeysDown.get() > 0;
@ -213,6 +214,20 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
}
private void writeChannels(float leftChannel, float rightChannel) {
double[] channels = new double[device.channels()];
Arrays.fill(channels, brightness);
if (channels.length > 0) {
channels[0] = leftChannel;
}
if (channels.length > 1) {
channels[1] = rightChannel;
}
if (recording) {
AudioSample.writeAudioSample(audioSample, channels, outputStream::write);
}
int left = (int) (leftChannel * Short.MAX_VALUE);
int right = (int) (rightChannel * Short.MAX_VALUE);
@ -221,32 +236,6 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
byte b2 = (byte) right;
byte b3 = (byte) (right >> 8);
if (recording) {
if (recordHQ) {
int leftR = (int) (leftChannel * 8388606);
int rightR = (int) (rightChannel * 8388606);
byte b0R = (byte) (leftR);
byte b1R = (byte) (leftR >> 8);
byte b2R = (byte) (leftR >> 16);
byte b3R = (byte) (rightR);
byte b4R = (byte) (rightR >> 8);
byte b5R = (byte) (rightR >> 16);
outputStream.write(b0R);
outputStream.write(b1R);
outputStream.write(b2R);
outputStream.write(b3R);
outputStream.write(b4R);
outputStream.write(b5R);
} else {
outputStream.write(b0);
outputStream.write(b1);
outputStream.write(b2);
outputStream.write(b3);
}
}
for (Listener listener : listeners) {
listener.write(b0);
listener.write(b1);
@ -517,13 +506,14 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
byte[] input = outputStream.toByteArray();
outputStream = null;
AudioFormat audioFormat = new AudioFormat(sampleRate, (recordHQ ? BITS_PER_SAMPLE_HQ : BITS_PER_SAMPLE), NUM_OUTPUTS, SIGNED, BIG_ENDIAN);
AudioFormat audioFormat = new AudioFormat(sampleRate, audioSample.size, device.channels(), audioSample.signed, BIG_ENDIAN);
return new AudioInputStream(new ByteArrayInputStream(input), audioFormat, framesRecorded);
}
@Override
public void setBrightness(double brightness) {
this.brightness = brightness;
audioEngine.setBrightness(brightness);
}

Wyświetl plik

@ -1,13 +1,84 @@
package sh.ball.audio.engine;
import java.util.function.Consumer;
// defines the different kinds of support audio samples for different audio
// devices
public enum AudioSample {
UINT8,
INT8,
INT16,
INT24,
INT32,
FLOAT32,
FLOAT64
UINT8(8, false),
INT8(8, true),
INT16(16, true),
INT24(24, true),
INT32(32, true),
FLOAT32(32, true),
FLOAT64(64, true);
public final int size;
public final boolean signed;
AudioSample(int size, boolean signed) {
this.size = size;
this.signed = signed;
}
public static void writeAudioSample(AudioSample sample, double[] channels, Consumer<Byte> writeByte) {
switch (sample) {
case UINT8 -> {
for (double channel : channels) {
writeByte.accept((byte) ((int) (127 * (channel + 1))));
}
}
case INT8 -> {
for (double channel : channels) {
writeByte.accept((byte) ((int) (127 * channel)));
}
}
case INT16 -> {
for (double channel : channels) {
short shortChannel = (short) (channel * Short.MAX_VALUE);
writeByte.accept((byte) shortChannel);
writeByte.accept((byte) (shortChannel >> 8));
}
}
case INT24 -> {
for (double channel : channels) {
int intChannel = (int) (channel * 8388606);
writeByte.accept((byte) intChannel);
writeByte.accept((byte) (intChannel >> 8));
writeByte.accept((byte) (intChannel >> 16));
}
}
case INT32 -> {
for (double channel : channels) {
int intChannel = (int) (channel * Integer.MAX_VALUE);
writeByte.accept((byte) intChannel);
writeByte.accept((byte) (intChannel >> 8));
writeByte.accept((byte) (intChannel >> 16));
writeByte.accept((byte) (intChannel >> 24));
}
}
case FLOAT32 -> {
for (double channel : channels) {
int intChannel = Float.floatToIntBits((float) channel);
writeByte.accept((byte) intChannel);
writeByte.accept((byte) (intChannel >> 8));
writeByte.accept((byte) (intChannel >> 16));
writeByte.accept((byte) (intChannel >> 24));
}
}
case FLOAT64 -> {
for (double channel : channels) {
long longChannel = Double.doubleToLongBits(channel);
writeByte.accept((byte) longChannel);
writeByte.accept((byte) (longChannel >> 8));
writeByte.accept((byte) (longChannel >> 16));
writeByte.accept((byte) (longChannel >> 24));
writeByte.accept((byte) (longChannel >> 32));
writeByte.accept((byte) (longChannel >> 40));
writeByte.accept((byte) (longChannel >> 48));
writeByte.accept((byte) (longChannel >> 56));
}
}
}
}
}

Wyświetl plik

@ -43,10 +43,7 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import sh.ball.audio.engine.AudioDevice;
import sh.ball.audio.engine.AudioInput;
import sh.ball.audio.engine.AudioInputListener;
import sh.ball.audio.engine.JavaAudioInput;
import sh.ball.audio.engine.*;
import sh.ball.audio.midi.MidiListener;
import sh.ball.audio.midi.MidiNote;
import sh.ball.engine.ObjectServer;
@ -179,6 +176,8 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
private Spinner<Double> recordLengthSpinner;
@FXML
private MenuItem softwareOscilloscopeMenuItem;
@FXML
private ComboBox<AudioSample> audioSampleComboBox;
public MainController() throws Exception {
// Clone DEFAULT_OBJ InputStream using a ByteArrayOutputStream
@ -240,6 +239,8 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
// the recording.
private void toggleRecord() {
recording = !recording;
audioSampleComboBox.setDisable(recording);
deviceComboBox.setDisable(recording);
boolean timedRecord = recordCheckBox.isSelected();
if (recording) {
// if it is a timed recording then a timeline is scheduled to start and
@ -486,6 +487,14 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
}
});
audioSampleComboBox.setItems(FXCollections.observableList(List.of(AudioSample.UINT8, AudioSample.INT8, AudioSample.INT16, AudioSample.INT24, AudioSample.INT32)));
audioSampleComboBox.setValue(AudioSample.INT16);
audioSampleComboBox.valueProperty().addListener((options, oldSample, sample) -> {
if (sample != null) {
audioPlayer.setAudioSample(sample);
}
});
brightnessSlider.valueProperty().addListener((e, old, brightness) -> audioPlayer.setBrightness(brightness.doubleValue()));
objController.updateObjectRotateSpeed();
@ -1146,6 +1155,10 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
midiDecay.appendChild(document.createTextNode(decaySpinner.getValue().toString()));
root.appendChild(midiDecay);
Element audioSample = document.createElement("audioSample");
audioSample.appendChild(document.createTextNode(audioSampleComboBox.getValue().name()));
root.appendChild(audioSample);
Element filesElement = document.createElement("files");
for (int i = 0; i < openFiles.size(); i++) {
Element fileElement = document.createElement("file");
@ -1269,6 +1282,11 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
decaySpinner.getValueFactory().setValue(Double.parseDouble(midiDecay.getTextContent()));
}
Element audioSample = (Element) root.getElementsByTagName("audioSample").item(0);
if (audioSample != null) {
audioSampleComboBox.setValue(AudioSample.valueOf(audioSample.getTextContent()));
}
Element filesElement = (Element) root.getElementsByTagName("files").item(0);
List<byte[]> files = new ArrayList<>();
List<String> fileNames = new ArrayList<>();

Wyświetl plik

@ -69,6 +69,16 @@
<KeyCodeCombination alt="UP" code="R" control="UP" meta="UP" shift="UP" shortcut="DOWN" />
</accelerator>
</MenuItem>
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Recording Bit Depth">
<content>
<AnchorPane>
<children>
<Label prefHeight="25.0" text="Recording Audio Sample" textFill="WHITE" />
<ComboBox fx:id="audioSampleComboBox" layoutY="25.0" prefHeight="26.0" prefWidth="376.0" />
</children>
</AnchorPane>
</content>
</CustomMenuItem>
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Timed recording?">
<content>
<CheckBox fx:id="recordCheckBox" text="Timed recording?" />