kopia lustrzana https://github.com/jameshball/osci-render
Reoganise UI and fully implement device selection
rodzic
4c584fb96c
commit
3f07b5f433
|
@ -8,8 +8,12 @@ import java.util.List;
|
||||||
|
|
||||||
public interface AudioPlayer<S> extends Runnable {
|
public interface AudioPlayer<S> extends Runnable {
|
||||||
|
|
||||||
|
void reset() throws Exception;
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
boolean isPlaying();
|
||||||
|
|
||||||
void setQuality(double quality);
|
void setQuality(double quality);
|
||||||
|
|
||||||
void addFrame(S frame);
|
void addFrame(S frame);
|
||||||
|
|
|
@ -18,6 +18,8 @@ public class FrequencyAnalyser<S> implements Runnable {
|
||||||
private final int sampleRate;
|
private final int sampleRate;
|
||||||
private final int powerOfTwo;
|
private final int powerOfTwo;
|
||||||
|
|
||||||
|
private volatile boolean stopped;
|
||||||
|
|
||||||
public FrequencyAnalyser(AudioPlayer<S> audioPlayer, int frameSize, int sampleRate) {
|
public FrequencyAnalyser(AudioPlayer<S> audioPlayer, int frameSize, int sampleRate) {
|
||||||
this.audioPlayer = audioPlayer;
|
this.audioPlayer = audioPlayer;
|
||||||
this.frameSize = frameSize;
|
this.frameSize = frameSize;
|
||||||
|
@ -40,7 +42,7 @@ public class FrequencyAnalyser<S> implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
byte[] buf = new byte[2 << powerOfTwo];
|
byte[] buf = new byte[2 << powerOfTwo];
|
||||||
|
|
||||||
while (true) {
|
while (!stopped) {
|
||||||
try {
|
try {
|
||||||
audioPlayer.read(buf);
|
audioPlayer.read(buf);
|
||||||
} catch (InterruptedException e) {
|
} 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) {
|
private double[] decode(final byte[] buf, boolean decodeLeft) {
|
||||||
final double[] fbuf = new double[(buf.length / 2) / frameSize];
|
final double[] fbuf = new double[(buf.length / 2) / frameSize];
|
||||||
int byteNum = 0;
|
int byteNum = 0;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import sh.ball.shapes.Vector2;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
import javax.sound.sampled.AudioInputStream;
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@ -29,12 +30,13 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
// Stereo audio
|
// Stereo audio
|
||||||
private static final int NUM_OUTPUTS = 2;
|
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 BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
|
||||||
private final Map<Object, Effect> effects = new HashMap<>();
|
private final Map<Object, Effect> effects = new HashMap<>();
|
||||||
private final ReentrantLock renderLock = new ReentrantLock();
|
private final ReentrantLock renderLock = new ReentrantLock();
|
||||||
private final List<Listener> listeners = new ArrayList<>();
|
private final List<Listener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private AudioEngine audioEngine;
|
||||||
private ByteArrayOutputStream outputStream;
|
private ByteArrayOutputStream outputStream;
|
||||||
private boolean recording = false;
|
private boolean recording = false;
|
||||||
private int framesRecorded = 0;
|
private int framesRecorded = 0;
|
||||||
|
@ -46,8 +48,9 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
private double weight = Shape.DEFAULT_WEIGHT;
|
private double weight = Shape.DEFAULT_WEIGHT;
|
||||||
private AudioDevice device;
|
private AudioDevice device;
|
||||||
|
|
||||||
public ShapeAudioPlayer(AudioEngine audioEngine) {
|
public ShapeAudioPlayer(Callable<AudioEngine> audioEngineBuilder) throws Exception {
|
||||||
this.audioEngine = audioEngine;
|
this.audioEngineBuilder = audioEngineBuilder;
|
||||||
|
this.audioEngine = audioEngineBuilder.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 generateChannels() throws InterruptedException {
|
private Vector2 generateChannels() throws InterruptedException {
|
||||||
|
@ -155,11 +158,25 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
audioEngine.play(this::generateChannels, renderLock, device);
|
audioEngine.play(this::generateChannels, renderLock, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws Exception {
|
||||||
|
audioEngine.stop();
|
||||||
|
while (isPlaying()) {
|
||||||
|
Thread.onSpinWait();
|
||||||
|
}
|
||||||
|
audioEngine = audioEngineBuilder.call();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
audioEngine.stop();
|
audioEngine.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return audioEngine.isPlaying();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFrame(List<Shape> frame) {
|
public void addFrame(List<Shape> frame) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -7,6 +7,8 @@ import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
public interface AudioEngine {
|
public interface AudioEngine {
|
||||||
|
boolean isPlaying();
|
||||||
|
|
||||||
void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device);
|
void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device);
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package sh.ball.audio.engine;
|
package sh.ball.audio.engine;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class DefaultAudioDevice implements AudioDevice {
|
public class DefaultAudioDevice implements AudioDevice {
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
|
@ -36,6 +38,21 @@ public class DefaultAudioDevice implements AudioDevice {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ public class XtAudioEngine implements AudioEngine {
|
||||||
private static final int NUM_OUTPUTS = 2;
|
private static final int NUM_OUTPUTS = 2;
|
||||||
|
|
||||||
private volatile boolean stopped = false;
|
private volatile boolean stopped = false;
|
||||||
|
|
||||||
|
private boolean playing = false;
|
||||||
private ReentrantLock renderLock;
|
private ReentrantLock renderLock;
|
||||||
private Callable<Vector2> channelGenerator;
|
private Callable<Vector2> channelGenerator;
|
||||||
|
|
||||||
|
@ -41,8 +43,14 @@ public class XtAudioEngine implements AudioEngine {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device) {
|
public void play(Callable<Vector2> channelGenerator, ReentrantLock renderLock, AudioDevice device) {
|
||||||
|
playing = true;
|
||||||
this.channelGenerator = channelGenerator;
|
this.channelGenerator = channelGenerator;
|
||||||
this.renderLock = renderLock;
|
this.renderLock = renderLock;
|
||||||
try (XtPlatform platform = XtAudio.init(null, null)) {
|
try (XtPlatform platform = XtAudio.init(null, null)) {
|
||||||
|
@ -72,6 +80,7 @@ public class XtAudioEngine implements AudioEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -158,18 +167,6 @@ public class XtAudioEngine implements AudioEngine {
|
||||||
return service;
|
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) {
|
private AudioSample XtSampleToAudioSample(Enums.XtSample sample) {
|
||||||
return switch (sample) {
|
return switch (sample) {
|
||||||
case UINT8 -> AudioSample.UINT8;
|
case UINT8 -> AudioSample.UINT8;
|
||||||
|
|
|
@ -54,14 +54,15 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
private final AudioPlayer<List<Shape>> audioPlayer;
|
private final AudioPlayer<List<Shape>> audioPlayer;
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
private final int sampleRate;
|
|
||||||
|
|
||||||
private final RotateEffect rotateEffect;
|
private final RotateEffect rotateEffect;
|
||||||
private final TranslateEffect translateEffect;
|
private final TranslateEffect translateEffect;
|
||||||
private final WobbleEffect wobbleEffect;
|
private final WobbleEffect wobbleEffect;
|
||||||
private final ScaleEffect scaleEffect;
|
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 FrameProducer<List<Shape>> producer;
|
||||||
private boolean recording = false;
|
private boolean recording = false;
|
||||||
|
|
||||||
|
@ -248,15 +249,43 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
audioPlayer.addEffect(EffectType.ROTATE, rotateEffect);
|
audioPlayer.addEffect(EffectType.ROTATE, rotateEffect);
|
||||||
audioPlayer.addEffect(EffectType.TRANSLATE, translateEffect);
|
audioPlayer.addEffect(EffectType.TRANSLATE, translateEffect);
|
||||||
|
|
||||||
executor.submit(producer);
|
|
||||||
audioPlayer.setDevice(defaultDevice);
|
audioPlayer.setDevice(defaultDevice);
|
||||||
System.out.println(audioPlayer.devices());
|
|
||||||
deviceComboBox.setItems(FXCollections.observableList(audioPlayer.devices()));
|
deviceComboBox.setItems(FXCollections.observableList(audioPlayer.devices()));
|
||||||
deviceComboBox.getSelectionModel().select(defaultDevice);
|
deviceComboBox.setValue(defaultDevice);
|
||||||
Thread renderThread = new Thread(audioPlayer);
|
|
||||||
renderThread.setUncaughtExceptionHandler((thread, throwable) -> throwable.printStackTrace());
|
executor.submit(producer);
|
||||||
renderThread.start();
|
analyser = new FrequencyAnalyser<>(audioPlayer, 2, sampleRate);
|
||||||
FrequencyAnalyser<List<Shape>> 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(this);
|
||||||
analyser.addListener(wobbleEffect);
|
analyser.addListener(wobbleEffect);
|
||||||
new Thread(analyser).start();
|
new Thread(analyser).start();
|
||||||
|
@ -273,7 +302,7 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
AudioInputStream input = audioPlayer.stopRecord();
|
AudioInputStream input = audioPlayer.stopRecord();
|
||||||
try {
|
try {
|
||||||
File file = fileChooser.showSaveDialog(stage);
|
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());
|
Date date = new Date(System.currentTimeMillis());
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
file = new File("out-" + formatter.format(date) + ".wav");
|
file = new File("out-" + formatter.format(date) + ".wav");
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class Gui extends Application {
|
||||||
System.setProperty("prism.lcdtext", "false");
|
System.setProperty("prism.lcdtext", "false");
|
||||||
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml"));
|
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);
|
loader.setController(controller);
|
||||||
Parent root = loader.load();
|
Parent root = loader.load();
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
-fx-background-radius: 0;
|
-fx-background-radius: 0;
|
||||||
-fx-border-radius: 0;
|
-fx-border-radius: 0;
|
||||||
-fx-border-width: 0;
|
-fx-border-width: 0;
|
||||||
|
-fx-cell-size: 35;
|
||||||
|
|
||||||
/* No alternate highlighting */
|
/* No alternate highlighting */
|
||||||
-fx-background-color: dark_color;
|
-fx-background-color: dark_color;
|
||||||
|
@ -137,7 +138,7 @@
|
||||||
|
|
||||||
.combo-box-base
|
.combo-box-base
|
||||||
{
|
{
|
||||||
-fx-background-color: very_dark;
|
-fx-background-color: white;
|
||||||
-fx-border-radius: 0;
|
-fx-border-radius: 0;
|
||||||
-fx-border-width: 1 1 1 0;
|
-fx-border-width: 1 1 1 0;
|
||||||
-fx-border-color: white;
|
-fx-border-color: white;
|
||||||
|
@ -148,16 +149,16 @@
|
||||||
|
|
||||||
.combo-box-base:hover
|
.combo-box-base:hover
|
||||||
{
|
{
|
||||||
-fx-color: very_dark;
|
-fx-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.combo-box-base:showing
|
.combo-box-base:showing
|
||||||
{
|
{
|
||||||
-fx-color: very_dark;
|
-fx-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.combo-box-base:focused {
|
.combo-box-base:focused {
|
||||||
-fx-background-color: very_dark;
|
-fx-background-color: white;
|
||||||
-fx-background-radius: 0;
|
-fx-background-radius: 0;
|
||||||
-fx-background-insets: 0;
|
-fx-background-insets: 0;
|
||||||
}
|
}
|
|
@ -11,14 +11,14 @@
|
||||||
<?import javafx.scene.text.Font?>
|
<?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 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>
|
<children>
|
||||||
<Button fx:id="chooseFileButton" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Choose File" />
|
<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="19.0" maxWidth="270.0" prefHeight="18.0" prefWidth="246.0" text="cube.obj" />
|
<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="54.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Record" />
|
<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="59.0" maxWidth="270.0" prefHeight="18.0" prefWidth="245.0" />
|
<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="113.0" prefHeight="58.0" prefWidth="376.0" text="L/R Frequency: " />
|
<Label id="frequency" fx:id="frequencyLabel" layoutX="14.0" layoutY="131.0" prefHeight="58.0" prefWidth="376.0" text="L/R Frequency: " />
|
||||||
<ComboBox fx:id="deviceComboBox" layoutX="169.0" layoutY="113.0" prefHeight="26.0" prefWidth="221.0" />
|
<ComboBox fx:id="deviceComboBox" layoutX="14.0" layoutY="11.0" prefHeight="26.0" prefWidth="376.0" />
|
||||||
</children>
|
</children>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
<TitledPane animated="false" collapsible="false" layoutX="422.0" layoutY="213.0" prefHeight="272.0" prefWidth="402.0" text="Effects">
|
<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)" />
|
<CheckBox fx:id="rotateCheckBox" layoutX="90.0" layoutY="147.0" mnemonicParsing="false" text="Rotate with Mouse (Esc to disable)" />
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
</TitledPane>
|
</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>
|
<content>
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0">
|
<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" />
|
<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" />
|
||||||
|
|
Ładowanie…
Reference in New Issue