kopia lustrzana https://github.com/jameshball/osci-render
Add audio sample drop-down in Audio settings for recording to wav
rodzic
538db5f137
commit
0a661da836
|
@ -14,6 +14,7 @@ import sh.ball.audio.effect.SineEffect;
|
||||||
import sh.ball.audio.effect.SmoothEffect;
|
import sh.ball.audio.effect.SmoothEffect;
|
||||||
import sh.ball.audio.engine.AudioDevice;
|
import sh.ball.audio.engine.AudioDevice;
|
||||||
import sh.ball.audio.engine.AudioEngine;
|
import sh.ball.audio.engine.AudioEngine;
|
||||||
|
import sh.ball.audio.engine.AudioSample;
|
||||||
import sh.ball.audio.midi.MidiCommunicator;
|
import sh.ball.audio.midi.MidiCommunicator;
|
||||||
import sh.ball.audio.midi.MidiNote;
|
import sh.ball.audio.midi.MidiNote;
|
||||||
import sh.ball.shapes.Shape;
|
import sh.ball.shapes.Shape;
|
||||||
|
@ -85,7 +86,8 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
private int sampleRate;
|
private int sampleRate;
|
||||||
private boolean flipX = false;
|
private boolean flipX = false;
|
||||||
private boolean flipY = false;
|
private boolean flipY = false;
|
||||||
private boolean recordHQ = true;
|
private double brightness = 1.0;
|
||||||
|
private AudioSample audioSample = AudioSample.INT16;
|
||||||
|
|
||||||
private AudioDevice device;
|
private AudioDevice device;
|
||||||
|
|
||||||
|
@ -99,9 +101,8 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
communicator.addListener(this);
|
communicator.addListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean toggleHQRecording() {
|
public void setAudioSample(AudioSample sample) {
|
||||||
recordHQ = !recordHQ;
|
this.audioSample = sample;
|
||||||
return recordHQ;
|
|
||||||
}
|
}
|
||||||
public boolean midiPlaying() {
|
public boolean midiPlaying() {
|
||||||
return numKeysDown.get() > 0;
|
return numKeysDown.get() > 0;
|
||||||
|
@ -213,6 +214,20 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeChannels(float leftChannel, float rightChannel) {
|
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 left = (int) (leftChannel * Short.MAX_VALUE);
|
||||||
int right = (int) (rightChannel * 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 b2 = (byte) right;
|
||||||
byte b3 = (byte) (right >> 8);
|
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) {
|
for (Listener listener : listeners) {
|
||||||
listener.write(b0);
|
listener.write(b0);
|
||||||
listener.write(b1);
|
listener.write(b1);
|
||||||
|
@ -517,13 +506,14 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
byte[] input = outputStream.toByteArray();
|
byte[] input = outputStream.toByteArray();
|
||||||
outputStream = null;
|
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);
|
return new AudioInputStream(new ByteArrayInputStream(input), audioFormat, framesRecorded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBrightness(double brightness) {
|
public void setBrightness(double brightness) {
|
||||||
|
this.brightness = brightness;
|
||||||
audioEngine.setBrightness(brightness);
|
audioEngine.setBrightness(brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,84 @@
|
||||||
package sh.ball.audio.engine;
|
package sh.ball.audio.engine;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
// defines the different kinds of support audio samples for different audio
|
// defines the different kinds of support audio samples for different audio
|
||||||
// devices
|
// devices
|
||||||
public enum AudioSample {
|
public enum AudioSample {
|
||||||
UINT8,
|
UINT8(8, false),
|
||||||
INT8,
|
INT8(8, true),
|
||||||
INT16,
|
INT16(16, true),
|
||||||
INT24,
|
INT24(24, true),
|
||||||
INT32,
|
INT32(32, true),
|
||||||
FLOAT32,
|
FLOAT32(32, true),
|
||||||
FLOAT64
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,7 @@ import javax.xml.transform.TransformerFactory;
|
||||||
import javax.xml.transform.dom.DOMSource;
|
import javax.xml.transform.dom.DOMSource;
|
||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
|
|
||||||
import sh.ball.audio.engine.AudioDevice;
|
import sh.ball.audio.engine.*;
|
||||||
import sh.ball.audio.engine.AudioInput;
|
|
||||||
import sh.ball.audio.engine.AudioInputListener;
|
|
||||||
import sh.ball.audio.engine.JavaAudioInput;
|
|
||||||
import sh.ball.audio.midi.MidiListener;
|
import sh.ball.audio.midi.MidiListener;
|
||||||
import sh.ball.audio.midi.MidiNote;
|
import sh.ball.audio.midi.MidiNote;
|
||||||
import sh.ball.engine.ObjectServer;
|
import sh.ball.engine.ObjectServer;
|
||||||
|
@ -179,6 +176,8 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
private Spinner<Double> recordLengthSpinner;
|
private Spinner<Double> recordLengthSpinner;
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem softwareOscilloscopeMenuItem;
|
private MenuItem softwareOscilloscopeMenuItem;
|
||||||
|
@FXML
|
||||||
|
private ComboBox<AudioSample> audioSampleComboBox;
|
||||||
|
|
||||||
public MainController() throws Exception {
|
public MainController() throws Exception {
|
||||||
// Clone DEFAULT_OBJ InputStream using a ByteArrayOutputStream
|
// Clone DEFAULT_OBJ InputStream using a ByteArrayOutputStream
|
||||||
|
@ -240,6 +239,8 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
// the recording.
|
// the recording.
|
||||||
private void toggleRecord() {
|
private void toggleRecord() {
|
||||||
recording = !recording;
|
recording = !recording;
|
||||||
|
audioSampleComboBox.setDisable(recording);
|
||||||
|
deviceComboBox.setDisable(recording);
|
||||||
boolean timedRecord = recordCheckBox.isSelected();
|
boolean timedRecord = recordCheckBox.isSelected();
|
||||||
if (recording) {
|
if (recording) {
|
||||||
// if it is a timed recording then a timeline is scheduled to start and
|
// 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()));
|
brightnessSlider.valueProperty().addListener((e, old, brightness) -> audioPlayer.setBrightness(brightness.doubleValue()));
|
||||||
|
|
||||||
objController.updateObjectRotateSpeed();
|
objController.updateObjectRotateSpeed();
|
||||||
|
@ -1146,6 +1155,10 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
midiDecay.appendChild(document.createTextNode(decaySpinner.getValue().toString()));
|
midiDecay.appendChild(document.createTextNode(decaySpinner.getValue().toString()));
|
||||||
root.appendChild(midiDecay);
|
root.appendChild(midiDecay);
|
||||||
|
|
||||||
|
Element audioSample = document.createElement("audioSample");
|
||||||
|
audioSample.appendChild(document.createTextNode(audioSampleComboBox.getValue().name()));
|
||||||
|
root.appendChild(audioSample);
|
||||||
|
|
||||||
Element filesElement = document.createElement("files");
|
Element filesElement = document.createElement("files");
|
||||||
for (int i = 0; i < openFiles.size(); i++) {
|
for (int i = 0; i < openFiles.size(); i++) {
|
||||||
Element fileElement = document.createElement("file");
|
Element fileElement = document.createElement("file");
|
||||||
|
@ -1269,6 +1282,11 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
decaySpinner.getValueFactory().setValue(Double.parseDouble(midiDecay.getTextContent()));
|
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);
|
Element filesElement = (Element) root.getElementsByTagName("files").item(0);
|
||||||
List<byte[]> files = new ArrayList<>();
|
List<byte[]> files = new ArrayList<>();
|
||||||
List<String> fileNames = new ArrayList<>();
|
List<String> fileNames = new ArrayList<>();
|
||||||
|
|
|
@ -69,6 +69,16 @@
|
||||||
<KeyCodeCombination alt="UP" code="R" control="UP" meta="UP" shift="UP" shortcut="DOWN" />
|
<KeyCodeCombination alt="UP" code="R" control="UP" meta="UP" shift="UP" shortcut="DOWN" />
|
||||||
</accelerator>
|
</accelerator>
|
||||||
</MenuItem>
|
</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?">
|
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Timed recording?">
|
||||||
<content>
|
<content>
|
||||||
<CheckBox fx:id="recordCheckBox" text="Timed recording?" />
|
<CheckBox fx:id="recordCheckBox" text="Timed recording?" />
|
||||||
|
|
Ładowanie…
Reference in New Issue