2021-05-26 19:44:18 +00:00
|
|
|
package sh.ball.gui;
|
|
|
|
|
2021-07-15 20:18:59 +00:00
|
|
|
import javafx.animation.*;
|
2021-05-26 19:44:18 +00:00
|
|
|
import javafx.application.Platform;
|
2021-08-30 22:36:12 +00:00
|
|
|
import javafx.beans.property.DoubleProperty;
|
|
|
|
import javafx.beans.property.SimpleDoubleProperty;
|
2021-06-17 18:53:58 +00:00
|
|
|
import javafx.collections.FXCollections;
|
2021-05-26 19:44:18 +00:00
|
|
|
import javafx.scene.control.*;
|
2021-07-14 19:00:51 +00:00
|
|
|
import javafx.scene.paint.Color;
|
|
|
|
import javafx.scene.paint.Paint;
|
|
|
|
import javafx.scene.shape.SVGPath;
|
2021-07-10 17:38:48 +00:00
|
|
|
import javafx.stage.DirectoryChooser;
|
2021-05-26 19:44:18 +00:00
|
|
|
import javafx.util.Duration;
|
|
|
|
import sh.ball.audio.*;
|
|
|
|
import sh.ball.audio.effect.*;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.text.SimpleDateFormat;
|
2021-07-10 17:38:48 +00:00
|
|
|
import java.util.*;
|
2021-12-12 00:17:49 +00:00
|
|
|
import java.util.concurrent.*;
|
2021-05-26 19:44:18 +00:00
|
|
|
import java.util.function.Consumer;
|
|
|
|
|
|
|
|
import javafx.beans.InvalidationListener;
|
|
|
|
import javafx.fxml.FXML;
|
|
|
|
import javafx.fxml.Initializable;
|
|
|
|
import javafx.stage.FileChooser;
|
|
|
|
import javafx.stage.Stage;
|
|
|
|
|
2021-07-18 16:48:57 +00:00
|
|
|
import javax.sound.midi.ShortMessage;
|
2021-05-26 19:44:18 +00:00
|
|
|
import javax.sound.sampled.AudioFileFormat;
|
|
|
|
import javax.sound.sampled.AudioInputStream;
|
|
|
|
import javax.sound.sampled.AudioSystem;
|
|
|
|
import javax.xml.parsers.ParserConfigurationException;
|
|
|
|
|
|
|
|
import org.xml.sax.SAXException;
|
|
|
|
import sh.ball.audio.effect.Effect;
|
|
|
|
import sh.ball.audio.effect.EffectType;
|
2021-06-17 18:06:41 +00:00
|
|
|
import sh.ball.audio.engine.AudioDevice;
|
2021-08-30 22:36:12 +00:00
|
|
|
import sh.ball.audio.engine.ConglomerateAudioEngine;
|
2021-07-13 21:44:04 +00:00
|
|
|
import sh.ball.audio.midi.MidiCommunicator;
|
|
|
|
import sh.ball.audio.midi.MidiListener;
|
2021-08-31 21:09:15 +00:00
|
|
|
import sh.ball.audio.midi.MidiNote;
|
2021-05-26 19:44:18 +00:00
|
|
|
import sh.ball.engine.Vector3;
|
2021-12-12 00:58:02 +00:00
|
|
|
import sh.ball.parser.obj.ObjFrameSettings;
|
2021-05-26 19:44:18 +00:00
|
|
|
import sh.ball.parser.obj.ObjSettingsFactory;
|
|
|
|
import sh.ball.parser.obj.ObjParser;
|
|
|
|
import sh.ball.parser.ParserFactory;
|
|
|
|
import sh.ball.shapes.Shape;
|
|
|
|
import sh.ball.shapes.Vector2;
|
|
|
|
|
2021-08-31 21:00:50 +00:00
|
|
|
import static sh.ball.math.Math.round;
|
2021-08-31 21:09:15 +00:00
|
|
|
import static sh.ball.math.Math.tryParse;
|
2021-08-31 21:00:50 +00:00
|
|
|
|
2021-08-31 20:40:38 +00:00
|
|
|
public class Controller implements Initializable, FrequencyListener, MidiListener {
|
2021-05-26 19:44:18 +00:00
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// audio
|
2021-07-15 20:18:59 +00:00
|
|
|
private static final double MAX_FREQUENCY = 12000;
|
2021-08-30 22:36:12 +00:00
|
|
|
private final ShapeAudioPlayer audioPlayer;
|
2021-06-04 15:10:39 +00:00
|
|
|
private final RotateEffect rotateEffect;
|
|
|
|
private final TranslateEffect translateEffect;
|
2021-07-20 20:41:43 +00:00
|
|
|
private final WobbleEffect wobbleEffect;
|
2021-12-06 00:09:41 +00:00
|
|
|
private final SmoothEffect smoothEffect;
|
2021-08-30 22:36:12 +00:00
|
|
|
private final DoubleProperty frequency;
|
2021-09-01 20:37:48 +00:00
|
|
|
private final AudioDevice defaultDevice;
|
2021-06-17 20:36:06 +00:00
|
|
|
private int sampleRate;
|
|
|
|
private FrequencyAnalyser<List<Shape>> analyser;
|
2021-05-26 19:44:18 +00:00
|
|
|
private boolean recording = false;
|
2021-07-10 20:53:24 +00:00
|
|
|
private Timeline recordingTimeline;
|
2021-09-01 20:37:48 +00:00
|
|
|
|
|
|
|
// midi
|
|
|
|
private final Map<Integer, SVGPath> CCMap = new HashMap<>();
|
2021-07-14 19:00:51 +00:00
|
|
|
private Paint armedMidiPaint;
|
|
|
|
private SVGPath armedMidi;
|
2021-09-01 20:37:48 +00:00
|
|
|
private Map<SVGPath, Slider> midiButtonMap;
|
2021-05-26 19:44:18 +00:00
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// frames
|
|
|
|
private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj");
|
|
|
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
2021-12-12 00:17:49 +00:00
|
|
|
private final List<String> frameSourcePaths = new ArrayList<>();
|
|
|
|
private List<FrameSource<List<Shape>>> frameSources = new ArrayList<>();
|
2021-09-01 20:37:48 +00:00
|
|
|
private FrameProducer<List<Shape>> producer;
|
2021-12-12 00:17:49 +00:00
|
|
|
private int currentFrameSource;
|
2021-07-10 17:38:48 +00:00
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// javafx
|
|
|
|
private final FileChooser wavFileChooser = new FileChooser();
|
|
|
|
private final FileChooser renderFileChooser = new FileChooser();
|
|
|
|
private final DirectoryChooser folderChooser = new DirectoryChooser();
|
2021-05-26 19:44:18 +00:00
|
|
|
private Stage stage;
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private Label frequencyLabel;
|
|
|
|
@FXML
|
|
|
|
private Button chooseFileButton;
|
|
|
|
@FXML
|
2021-07-10 17:38:48 +00:00
|
|
|
private Button chooseFolderButton;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private Label fileLabel;
|
|
|
|
@FXML
|
2021-07-10 18:02:21 +00:00
|
|
|
private Label jkLabel;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private Button recordButton;
|
|
|
|
@FXML
|
|
|
|
private Label recordLabel;
|
|
|
|
@FXML
|
2021-07-10 20:53:24 +00:00
|
|
|
private TextField recordTextField;
|
|
|
|
@FXML
|
|
|
|
private CheckBox recordCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Label recordLengthLabel;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private TextField translationXTextField;
|
|
|
|
@FXML
|
|
|
|
private TextField translationYTextField;
|
|
|
|
@FXML
|
2021-07-15 18:38:53 +00:00
|
|
|
private Slider frequencySlider;
|
2021-05-26 19:44:18 +00:00
|
|
|
@FXML
|
2021-07-15 18:38:53 +00:00
|
|
|
private SVGPath frequencyMidi;
|
2021-07-14 19:00:51 +00:00
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private Slider rotateSpeedSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath rotateSpeedMidi;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private Slider translationSpeedSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath translationSpeedMidi;
|
|
|
|
@FXML
|
2021-08-31 20:30:14 +00:00
|
|
|
private Slider volumeSlider;
|
2021-05-26 19:44:18 +00:00
|
|
|
@FXML
|
2021-08-31 20:30:14 +00:00
|
|
|
private SVGPath volumeMidi;
|
2021-07-14 19:00:51 +00:00
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private TitledPane objTitledPane;
|
|
|
|
@FXML
|
|
|
|
private Slider focalLengthSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath focalLengthMidi;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private TextField cameraXTextField;
|
|
|
|
@FXML
|
|
|
|
private TextField cameraYTextField;
|
|
|
|
@FXML
|
|
|
|
private TextField cameraZTextField;
|
|
|
|
@FXML
|
|
|
|
private Slider objectRotateSpeedSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath objectRotateSpeedMidi;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private CheckBox rotateCheckBox;
|
|
|
|
@FXML
|
|
|
|
private CheckBox vectorCancellingCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider vectorCancellingSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath vectorCancellingMidi;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private CheckBox bitCrushCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider bitCrushSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath bitCrushMidi;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private CheckBox verticalDistortCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider verticalDistortSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath verticalDistortMidi;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private CheckBox horizontalDistortCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider horizontalDistortSlider;
|
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath horizontalDistortMidi;
|
|
|
|
@FXML
|
2021-05-26 19:44:18 +00:00
|
|
|
private CheckBox wobbleCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider wobbleSlider;
|
2021-06-17 18:53:58 +00:00
|
|
|
@FXML
|
2021-07-14 19:00:51 +00:00
|
|
|
private SVGPath wobbleMidi;
|
|
|
|
@FXML
|
2021-12-06 00:09:41 +00:00
|
|
|
private CheckBox smoothCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider smoothSlider;
|
|
|
|
@FXML
|
|
|
|
private SVGPath smoothMidi;
|
|
|
|
@FXML
|
2021-07-20 21:09:39 +00:00
|
|
|
private Slider octaveSlider;
|
|
|
|
@FXML
|
|
|
|
private SVGPath octaveMidi;
|
|
|
|
@FXML
|
2021-12-06 22:59:22 +00:00
|
|
|
private CheckBox traceCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider traceSlider;
|
|
|
|
@FXML
|
|
|
|
private SVGPath traceMidi;
|
|
|
|
@FXML
|
2021-07-23 20:42:58 +00:00
|
|
|
private Slider visibilitySlider;
|
|
|
|
@FXML
|
|
|
|
private SVGPath visibilityMidi;
|
|
|
|
@FXML
|
2021-06-17 18:53:58 +00:00
|
|
|
private ComboBox<AudioDevice> deviceComboBox;
|
2021-05-26 19:44:18 +00:00
|
|
|
|
2021-08-30 22:36:12 +00:00
|
|
|
public Controller() throws Exception {
|
|
|
|
MidiCommunicator midiCommunicator = new MidiCommunicator();
|
|
|
|
midiCommunicator.addListener(this);
|
|
|
|
new Thread(midiCommunicator).start();
|
|
|
|
|
|
|
|
this.audioPlayer = new ShapeAudioPlayer(ConglomerateAudioEngine::new, midiCommunicator);
|
2021-12-12 00:17:49 +00:00
|
|
|
FrameSource<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse();
|
|
|
|
frameSources.add(frames);
|
|
|
|
frameSourcePaths.add("cube.obj");
|
|
|
|
currentFrameSource = 0;
|
2021-06-17 16:56:48 +00:00
|
|
|
this.producer = new FrameProducer<>(audioPlayer, frames);
|
2021-06-17 18:53:58 +00:00
|
|
|
this.defaultDevice = audioPlayer.getDefaultDevice();
|
2021-06-18 17:49:23 +00:00
|
|
|
if (defaultDevice == null) {
|
|
|
|
throw new RuntimeException("No default audio device found!");
|
|
|
|
}
|
2021-06-17 18:53:58 +00:00
|
|
|
this.sampleRate = defaultDevice.sampleRate();
|
2021-06-04 15:10:39 +00:00
|
|
|
this.rotateEffect = new RotateEffect(sampleRate);
|
|
|
|
this.translateEffect = new TranslateEffect(sampleRate);
|
2021-07-20 20:41:43 +00:00
|
|
|
this.wobbleEffect = new WobbleEffect(sampleRate);
|
2021-12-06 00:09:41 +00:00
|
|
|
this.smoothEffect = new SmoothEffect(1);
|
2021-08-30 22:36:12 +00:00
|
|
|
this.frequency = new SimpleDoubleProperty(0);
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// initialises midiButtonMap by mapping MIDI logo SVGs to the slider that they
|
|
|
|
// control if they are selected.
|
2021-07-14 19:00:51 +00:00
|
|
|
private Map<SVGPath, Slider> initializeMidiButtonMap() {
|
|
|
|
Map<SVGPath, Slider> midiMap = new HashMap<>();
|
2021-07-15 18:38:53 +00:00
|
|
|
midiMap.put(frequencyMidi, frequencySlider);
|
2021-07-14 19:00:51 +00:00
|
|
|
midiMap.put(rotateSpeedMidi, rotateSpeedSlider);
|
|
|
|
midiMap.put(translationSpeedMidi, translationSpeedSlider);
|
2021-08-31 20:30:14 +00:00
|
|
|
midiMap.put(volumeMidi, volumeSlider);
|
2021-07-14 19:00:51 +00:00
|
|
|
midiMap.put(focalLengthMidi, focalLengthSlider);
|
|
|
|
midiMap.put(objectRotateSpeedMidi, objectRotateSpeedSlider);
|
|
|
|
midiMap.put(vectorCancellingMidi, vectorCancellingSlider);
|
|
|
|
midiMap.put(bitCrushMidi, bitCrushSlider);
|
|
|
|
midiMap.put(wobbleMidi, wobbleSlider);
|
2021-12-06 00:09:41 +00:00
|
|
|
midiMap.put(smoothMidi, smoothSlider);
|
2021-07-20 21:09:39 +00:00
|
|
|
midiMap.put(octaveMidi, octaveSlider);
|
2021-12-06 22:59:22 +00:00
|
|
|
midiMap.put(traceMidi, traceSlider);
|
2021-07-23 20:42:58 +00:00
|
|
|
midiMap.put(visibilityMidi, visibilitySlider);
|
2021-07-14 19:00:51 +00:00
|
|
|
midiMap.put(verticalDistortMidi, verticalDistortSlider);
|
|
|
|
midiMap.put(horizontalDistortMidi, horizontalDistortSlider);
|
|
|
|
return midiMap;
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// Maps sliders to the functions that they should call whenever their value
|
|
|
|
// changes.
|
2021-05-26 19:44:18 +00:00
|
|
|
private Map<Slider, Consumer<Double>> initializeSliderMap() {
|
|
|
|
return Map.of(
|
2021-07-14 19:00:51 +00:00
|
|
|
rotateSpeedSlider, rotateEffect::setSpeed,
|
|
|
|
translationSpeedSlider, translateEffect::setSpeed,
|
2021-09-01 20:37:48 +00:00
|
|
|
focalLengthSlider, this::updateFocalLength,
|
|
|
|
objectRotateSpeedSlider, this::updateObjectRotateSpeed,
|
2021-07-23 20:42:58 +00:00
|
|
|
visibilitySlider, audioPlayer::setMainFrequencyScale
|
2021-05-26 19:44:18 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Map<EffectType, Slider> effectTypes;
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// Maps EffectTypes to the slider that controls the effect so that they can be
|
|
|
|
// toggled when the appropriate checkbox is ticked.
|
2021-05-26 19:44:18 +00:00
|
|
|
private void initializeEffectTypes() {
|
|
|
|
effectTypes = Map.of(
|
|
|
|
EffectType.VECTOR_CANCELLING,
|
|
|
|
vectorCancellingSlider,
|
|
|
|
EffectType.BIT_CRUSH,
|
|
|
|
bitCrushSlider,
|
|
|
|
EffectType.VERTICAL_DISTORT,
|
|
|
|
verticalDistortSlider,
|
|
|
|
EffectType.HORIZONTAL_DISTORT,
|
|
|
|
horizontalDistortSlider,
|
|
|
|
EffectType.WOBBLE,
|
2021-12-06 00:09:41 +00:00
|
|
|
wobbleSlider,
|
|
|
|
EffectType.SMOOTH,
|
|
|
|
smoothSlider
|
2021-05-26 19:44:18 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
2021-09-01 20:37:48 +00:00
|
|
|
// converts the value of frequencySlider to the actual frequency that it represents so that it
|
|
|
|
// can increase at an exponential scale.
|
2021-08-30 22:36:12 +00:00
|
|
|
frequencySlider.valueProperty().addListener((o, old, f) -> frequency.set(Math.pow(MAX_FREQUENCY, f.doubleValue())));
|
|
|
|
frequency.addListener((o, old, f) -> frequencySlider.setValue(Math.log(f.doubleValue()) / Math.log(MAX_FREQUENCY)));
|
|
|
|
audioPlayer.setFrequency(frequency);
|
2021-09-01 20:37:48 +00:00
|
|
|
// default value is middle C
|
2021-08-31 21:09:15 +00:00
|
|
|
frequency.set(MidiNote.MIDDLE_C);
|
2021-08-31 20:30:14 +00:00
|
|
|
audioPlayer.setVolume(volumeSlider.valueProperty());
|
2021-08-30 22:36:12 +00:00
|
|
|
|
2021-07-14 19:00:51 +00:00
|
|
|
this.midiButtonMap = initializeMidiButtonMap();
|
|
|
|
|
|
|
|
midiButtonMap.keySet().forEach(midi -> midi.setOnMouseClicked(e -> {
|
|
|
|
if (armedMidi == midi) {
|
|
|
|
// we are already armed, so we should unarm
|
|
|
|
midi.setFill(armedMidiPaint);
|
|
|
|
armedMidiPaint = null;
|
|
|
|
armedMidi = null;
|
|
|
|
} else {
|
|
|
|
// not yet armed
|
|
|
|
if (armedMidi != null) {
|
|
|
|
armedMidi.setFill(armedMidiPaint);
|
|
|
|
}
|
|
|
|
armedMidiPaint = midi.getFill();
|
|
|
|
armedMidi = midi;
|
2021-08-30 22:36:12 +00:00
|
|
|
midi.setFill(Color.RED);
|
2021-07-14 19:00:51 +00:00
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2021-05-26 19:44:18 +00:00
|
|
|
Map<Slider, Consumer<Double>> sliders = initializeSliderMap();
|
|
|
|
initializeEffectTypes();
|
|
|
|
|
|
|
|
for (Slider slider : sliders.keySet()) {
|
|
|
|
slider.valueProperty().addListener((source, oldValue, newValue) ->
|
2021-12-12 00:58:02 +00:00
|
|
|
sliders.get(slider).accept(newValue.doubleValue())
|
2021-05-26 19:44:18 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
translationXTextField.textProperty().addListener(e -> updateTranslation());
|
|
|
|
translationYTextField.textProperty().addListener(e -> updateTranslation());
|
|
|
|
|
|
|
|
cameraXTextField.focusedProperty().addListener(e -> updateCameraPos());
|
|
|
|
cameraYTextField.focusedProperty().addListener(e -> updateCameraPos());
|
|
|
|
cameraZTextField.focusedProperty().addListener(e -> updateCameraPos());
|
|
|
|
|
|
|
|
InvalidationListener vectorCancellingListener = e ->
|
|
|
|
updateEffect(EffectType.VECTOR_CANCELLING, vectorCancellingCheckBox.isSelected(),
|
|
|
|
EffectFactory.vectorCancelling((int) vectorCancellingSlider.getValue()));
|
|
|
|
InvalidationListener bitCrushListener = e ->
|
|
|
|
updateEffect(EffectType.BIT_CRUSH, bitCrushCheckBox.isSelected(),
|
|
|
|
EffectFactory.bitCrush(bitCrushSlider.getValue()));
|
|
|
|
InvalidationListener verticalDistortListener = e ->
|
|
|
|
updateEffect(EffectType.VERTICAL_DISTORT, verticalDistortCheckBox.isSelected(),
|
|
|
|
EffectFactory.verticalDistort(verticalDistortSlider.getValue()));
|
|
|
|
InvalidationListener horizontalDistortListener = e ->
|
|
|
|
updateEffect(EffectType.HORIZONTAL_DISTORT, horizontalDistortCheckBox.isSelected(),
|
|
|
|
EffectFactory.horizontalDistort(horizontalDistortSlider.getValue()));
|
|
|
|
InvalidationListener wobbleListener = e -> {
|
|
|
|
wobbleEffect.setVolume(wobbleSlider.getValue());
|
|
|
|
updateEffect(EffectType.WOBBLE, wobbleCheckBox.isSelected(), wobbleEffect);
|
|
|
|
};
|
2021-12-06 00:09:41 +00:00
|
|
|
InvalidationListener smoothListener = e -> {
|
|
|
|
smoothEffect.setWindowSize((int) smoothSlider.getValue());
|
|
|
|
updateEffect(EffectType.SMOOTH, smoothCheckBox.isSelected(), smoothEffect);
|
|
|
|
};
|
2021-12-06 22:59:22 +00:00
|
|
|
InvalidationListener traceListener = e -> {
|
|
|
|
double trace = traceCheckBox.isSelected() ? traceSlider.valueProperty().getValue() : 1;
|
|
|
|
audioPlayer.setTrace(trace);
|
|
|
|
traceSlider.setDisable(!traceCheckBox.isSelected());
|
|
|
|
};
|
2021-05-26 19:44:18 +00:00
|
|
|
|
|
|
|
vectorCancellingSlider.valueProperty().addListener(vectorCancellingListener);
|
|
|
|
vectorCancellingCheckBox.selectedProperty().addListener(vectorCancellingListener);
|
|
|
|
|
|
|
|
bitCrushSlider.valueProperty().addListener(bitCrushListener);
|
|
|
|
bitCrushCheckBox.selectedProperty().addListener(bitCrushListener);
|
|
|
|
|
|
|
|
verticalDistortSlider.valueProperty().addListener(verticalDistortListener);
|
|
|
|
verticalDistortCheckBox.selectedProperty().addListener(verticalDistortListener);
|
|
|
|
|
|
|
|
horizontalDistortSlider.valueProperty().addListener(horizontalDistortListener);
|
|
|
|
horizontalDistortCheckBox.selectedProperty().addListener(horizontalDistortListener);
|
|
|
|
|
|
|
|
wobbleSlider.valueProperty().addListener(wobbleListener);
|
|
|
|
wobbleCheckBox.selectedProperty().addListener(wobbleListener);
|
|
|
|
wobbleCheckBox.selectedProperty().addListener(e -> wobbleEffect.update());
|
|
|
|
|
2021-12-06 00:09:41 +00:00
|
|
|
smoothSlider.valueProperty().addListener(smoothListener);
|
|
|
|
smoothCheckBox.selectedProperty().addListener(smoothListener);
|
|
|
|
|
2021-07-20 21:09:39 +00:00
|
|
|
octaveSlider.valueProperty().addListener((e, old, octave) -> audioPlayer.setOctave(octave.intValue()));
|
|
|
|
|
2021-12-06 22:59:22 +00:00
|
|
|
traceSlider.valueProperty().addListener(traceListener);
|
|
|
|
traceCheckBox.selectedProperty().addListener(traceListener);
|
|
|
|
|
2021-08-31 20:23:45 +00:00
|
|
|
wavFileChooser.setInitialFileName("out.wav");
|
|
|
|
wavFileChooser.getExtensionFilters().addAll(
|
2021-05-26 19:44:18 +00:00
|
|
|
new FileChooser.ExtensionFilter("WAV Files", "*.wav"),
|
2021-08-31 20:23:45 +00:00
|
|
|
new FileChooser.ExtensionFilter("All Files", "*.*")
|
|
|
|
);
|
2021-09-01 20:37:48 +00:00
|
|
|
// when opening new files, we support .obj, .svg, and .txt
|
2021-08-31 20:23:45 +00:00
|
|
|
renderFileChooser.getExtensionFilters().addAll(
|
|
|
|
new FileChooser.ExtensionFilter("All Files", "*.*"),
|
2021-05-26 19:44:18 +00:00
|
|
|
new FileChooser.ExtensionFilter("Wavefront OBJ Files", "*.obj"),
|
|
|
|
new FileChooser.ExtensionFilter("SVG Files", "*.svg"),
|
|
|
|
new FileChooser.ExtensionFilter("Text Files", "*.txt")
|
|
|
|
);
|
|
|
|
|
|
|
|
chooseFileButton.setOnAction(e -> {
|
2021-08-31 20:23:45 +00:00
|
|
|
File file = renderFileChooser.showOpenDialog(stage);
|
2021-05-26 19:44:18 +00:00
|
|
|
if (file != null) {
|
|
|
|
chooseFile(file);
|
2021-07-10 17:51:55 +00:00
|
|
|
updateLastVisitedDirectory(new File(file.getParent()));
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-07-10 17:38:48 +00:00
|
|
|
chooseFolderButton.setOnAction(e -> {
|
|
|
|
File file = folderChooser.showDialog(stage);
|
|
|
|
if (file != null) {
|
|
|
|
chooseFile(file);
|
2021-07-10 17:51:55 +00:00
|
|
|
updateLastVisitedDirectory(file);
|
2021-07-10 17:38:48 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-05-26 19:44:18 +00:00
|
|
|
recordButton.setOnAction(event -> toggleRecord());
|
|
|
|
|
2021-07-10 20:53:24 +00:00
|
|
|
recordCheckBox.selectedProperty().addListener((e, oldVal, newVal) -> {
|
|
|
|
recordLengthLabel.setDisable(!newVal);
|
|
|
|
recordTextField.setDisable(!newVal);
|
|
|
|
});
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
updateObjectRotateSpeed(rotateSpeedSlider.getValue());
|
2021-05-26 19:44:18 +00:00
|
|
|
|
2021-06-17 16:56:48 +00:00
|
|
|
audioPlayer.addEffect(EffectType.ROTATE, rotateEffect);
|
|
|
|
audioPlayer.addEffect(EffectType.TRANSLATE, translateEffect);
|
2021-05-26 19:44:18 +00:00
|
|
|
|
2021-06-17 18:53:58 +00:00
|
|
|
audioPlayer.setDevice(defaultDevice);
|
2021-07-18 15:53:42 +00:00
|
|
|
List<AudioDevice> devices = audioPlayer.devices();
|
|
|
|
deviceComboBox.setItems(FXCollections.observableList(devices));
|
2021-06-17 20:36:06 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// used when a file is chosen so that the same folder is reopened when a
|
|
|
|
// file chooser opens
|
2021-07-10 17:51:55 +00:00
|
|
|
private void updateLastVisitedDirectory(File file) {
|
2021-07-13 21:44:04 +00:00
|
|
|
String lastVisitedDirectory = file != null ? file.getAbsolutePath() : System.getProperty("user.home");
|
2021-07-10 17:51:55 +00:00
|
|
|
File dir = new File(lastVisitedDirectory);
|
2021-08-31 20:23:45 +00:00
|
|
|
wavFileChooser.setInitialDirectory(dir);
|
2021-09-01 20:37:48 +00:00
|
|
|
renderFileChooser.setInitialDirectory(dir);
|
2021-07-10 17:51:55 +00:00
|
|
|
folderChooser.setInitialDirectory(dir);
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// restarts audioPlayer and FrequencyAnalyser to support new device
|
2021-06-17 20:36:06 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// creates a new thread for the audioPlayer and starts it
|
2021-06-17 20:36:06 +00:00
|
|
|
private void startAudioPlayerThread() {
|
|
|
|
Thread audioPlayerThread = new Thread(audioPlayer);
|
|
|
|
audioPlayerThread.setUncaughtExceptionHandler((thread, throwable) -> throwable.printStackTrace());
|
|
|
|
audioPlayerThread.start();
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// creates a new thread for the frequency analyser and adds the wobble effect and the controller
|
|
|
|
// as listeners of it so that they can get updates as the frequency changes
|
2021-06-17 20:36:06 +00:00
|
|
|
private void startFrequencyAnalyser(FrequencyAnalyser<List<Shape>> analyser) {
|
2021-05-26 19:44:18 +00:00
|
|
|
analyser.addListener(this);
|
|
|
|
analyser.addListener(wobbleEffect);
|
|
|
|
new Thread(analyser).start();
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// alternates between recording and not recording when called.
|
|
|
|
// If it is a non-timed recording, it is saved when this is called and
|
|
|
|
// recording is stopped. If it is a time recording, this function will cancel
|
|
|
|
// the recording.
|
2021-05-26 19:44:18 +00:00
|
|
|
private void toggleRecord() {
|
|
|
|
recording = !recording;
|
2021-07-10 20:53:24 +00:00
|
|
|
boolean timedRecord = recordCheckBox.isSelected();
|
2021-05-26 19:44:18 +00:00
|
|
|
if (recording) {
|
2021-09-01 20:37:48 +00:00
|
|
|
// if it is a timed recording then a timeline is scheduled to start and
|
|
|
|
// stop recording at the predefined times.
|
2021-07-10 20:53:24 +00:00
|
|
|
if (timedRecord) {
|
|
|
|
double recordingLength;
|
|
|
|
try {
|
|
|
|
recordingLength = Double.parseDouble(recordTextField.getText());
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
recordLabel.setText("Please set a valid record length");
|
|
|
|
recording = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
recordButton.setText("Cancel");
|
|
|
|
KeyFrame kf1 = new KeyFrame(
|
|
|
|
Duration.seconds(0),
|
|
|
|
e -> audioPlayer.startRecord()
|
|
|
|
);
|
2021-09-01 20:37:48 +00:00
|
|
|
// save the recording after recordingLength seconds
|
2021-07-10 20:53:24 +00:00
|
|
|
KeyFrame kf2 = new KeyFrame(
|
|
|
|
Duration.seconds(recordingLength),
|
|
|
|
e -> {
|
|
|
|
saveRecording();
|
|
|
|
recording = false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
recordingTimeline = new Timeline(kf1, kf2);
|
|
|
|
Platform.runLater(recordingTimeline::play);
|
|
|
|
} else {
|
|
|
|
recordButton.setText("Stop Recording");
|
|
|
|
audioPlayer.startRecord();
|
|
|
|
}
|
2021-05-26 19:44:18 +00:00
|
|
|
recordLabel.setText("Recording...");
|
2021-07-10 20:53:24 +00:00
|
|
|
} else if (timedRecord) {
|
2021-09-01 20:37:48 +00:00
|
|
|
// cancel the recording
|
2021-07-10 20:53:24 +00:00
|
|
|
recordingTimeline.stop();
|
|
|
|
recordLabel.setText("");
|
|
|
|
recordButton.setText("Record");
|
|
|
|
audioPlayer.stopRecord();
|
2021-05-26 19:44:18 +00:00
|
|
|
} else {
|
2021-07-10 20:53:24 +00:00
|
|
|
saveRecording();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// Stops recording and opens a fileChooser so that the user can choose a
|
|
|
|
// location to save the recording to. If no location is chosen then it will
|
|
|
|
// be saved as the current date-time of the machine.
|
2021-07-10 20:53:24 +00:00
|
|
|
private void saveRecording() {
|
|
|
|
try {
|
2021-05-26 19:44:18 +00:00
|
|
|
recordButton.setText("Record");
|
2021-06-17 16:56:48 +00:00
|
|
|
AudioInputStream input = audioPlayer.stopRecord();
|
2021-08-31 20:23:45 +00:00
|
|
|
File file = wavFileChooser.showSaveDialog(stage);
|
2021-07-10 20:53:24 +00:00
|
|
|
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");
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
2021-07-10 20:53:24 +00:00
|
|
|
AudioSystem.write(input, AudioFileFormat.Type.WAVE, file);
|
|
|
|
input.close();
|
|
|
|
recordLabel.setText("Saved to " + file.getAbsolutePath());
|
|
|
|
} catch (IOException e) {
|
|
|
|
recordLabel.setText("Error saving file");
|
|
|
|
e.printStackTrace();
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// changes the focalLength of the FrameProducer
|
|
|
|
private void updateFocalLength(double focalLength) {
|
2021-05-26 19:44:18 +00:00
|
|
|
producer.setFrameSettings(ObjSettingsFactory.focalLength(focalLength));
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// changes the rotateSpeed of the FrameProducer
|
|
|
|
private void updateObjectRotateSpeed(double rotateSpeed) {
|
2021-05-26 19:44:18 +00:00
|
|
|
producer.setFrameSettings(
|
|
|
|
ObjSettingsFactory.rotateSpeed((Math.exp(3 * rotateSpeed) - 1) / 50)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// changes the sinusoidal translation of the image rendered
|
2021-05-26 19:44:18 +00:00
|
|
|
private void updateTranslation() {
|
|
|
|
translateEffect.setTranslation(new Vector2(
|
|
|
|
tryParse(translationXTextField.getText()),
|
|
|
|
tryParse(translationYTextField.getText())
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// updates the camera position of the FrameProducer, as well as rounding the
|
|
|
|
// text fields that control it
|
2021-05-26 19:44:18 +00:00
|
|
|
private void updateCameraPos() {
|
2021-08-31 20:40:38 +00:00
|
|
|
Vector3 vector = new Vector3(
|
2021-05-26 19:44:18 +00:00
|
|
|
tryParse(cameraXTextField.getText()),
|
|
|
|
tryParse(cameraYTextField.getText()),
|
|
|
|
tryParse(cameraZTextField.getText())
|
2021-08-31 20:40:38 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
producer.setFrameSettings(ObjSettingsFactory.cameraPosition(vector));
|
|
|
|
|
|
|
|
cameraXTextField.setText(String.valueOf(round(vector.getX(), 3)));
|
|
|
|
cameraYTextField.setText(String.valueOf(round(vector.getY(), 3)));
|
|
|
|
cameraZTextField.setText(String.valueOf(round(vector.getZ(), 3)));
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// selects or deselects the given audio effect
|
2021-05-26 19:44:18 +00:00
|
|
|
private void updateEffect(EffectType type, boolean checked, Effect effect) {
|
|
|
|
if (checked) {
|
2021-06-17 16:56:48 +00:00
|
|
|
audioPlayer.addEffect(type, effect);
|
2021-12-06 00:09:41 +00:00
|
|
|
if (effectTypes.containsKey(type)) {
|
|
|
|
effectTypes.get(type).setDisable(false);
|
|
|
|
}
|
2021-05-26 19:44:18 +00:00
|
|
|
} else {
|
2021-06-17 16:56:48 +00:00
|
|
|
audioPlayer.removeEffect(type);
|
2021-12-06 00:09:41 +00:00
|
|
|
if (effectTypes.containsKey(type)) {
|
|
|
|
effectTypes.get(type).setDisable(true);
|
|
|
|
}
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// changes the FrameProducer e.g. could be changing from a 3D object to an
|
|
|
|
// SVG. The old FrameProducer is stopped and a new one created and initialised
|
|
|
|
// with the same settings that the original had.
|
2021-12-12 00:58:02 +00:00
|
|
|
private void changeFrameSource(int index) {
|
2021-12-12 00:17:49 +00:00
|
|
|
index = Math.max(0, Math.min(index, frameSources.size() - 1));
|
|
|
|
currentFrameSource = index;
|
|
|
|
FrameSource<List<Shape>> frames = frameSources.get(index);
|
|
|
|
frameSources.forEach(FrameSource::disable);
|
|
|
|
frames.enable();
|
2021-12-12 00:58:02 +00:00
|
|
|
producer = new FrameProducer<>(audioPlayer, frames);
|
2021-12-12 00:17:49 +00:00
|
|
|
|
|
|
|
// Apply the same settings that the previous frameSource had
|
2021-12-12 00:58:02 +00:00
|
|
|
updateObjectRotateSpeed(objectRotateSpeedSlider.getValue());
|
2021-09-01 20:37:48 +00:00
|
|
|
updateFocalLength(focalLengthSlider.getValue());
|
2021-12-12 00:58:02 +00:00
|
|
|
executor.submit(producer);
|
2021-07-10 16:06:49 +00:00
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// apply the wobble effect after a second as the frequency of the audio takes a while to
|
|
|
|
// propagate and send to its listeners.
|
2021-07-10 16:06:49 +00:00
|
|
|
KeyFrame kf1 = new KeyFrame(Duration.seconds(0), e -> wobbleEffect.setVolume(0));
|
|
|
|
KeyFrame kf2 = new KeyFrame(Duration.seconds(1), e -> {
|
|
|
|
wobbleEffect.update();
|
|
|
|
wobbleEffect.setVolume(wobbleSlider.getValue());
|
|
|
|
});
|
|
|
|
Timeline timeline = new Timeline(kf1, kf2);
|
|
|
|
Platform.runLater(timeline::play);
|
2021-09-01 20:37:48 +00:00
|
|
|
|
2021-12-12 00:17:49 +00:00
|
|
|
fileLabel.setText(frameSourcePaths.get(index));
|
|
|
|
// enable the .obj file settings iff the new frameSource is for a 3D object.
|
|
|
|
objTitledPane.setDisable(!ObjParser.isObjFile(frameSourcePaths.get(index)));
|
2021-07-10 16:06:49 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// selects a new file or folder for files to be rendered from
|
2021-07-10 17:38:48 +00:00
|
|
|
private void chooseFile(File chosenFile) {
|
2021-05-26 19:44:18 +00:00
|
|
|
try {
|
2021-07-10 17:38:48 +00:00
|
|
|
if (chosenFile.exists()) {
|
2021-12-12 00:17:49 +00:00
|
|
|
List<FrameSource<List<Shape>>> oldFrameSources = frameSources;
|
|
|
|
frameSources = new ArrayList<>();
|
|
|
|
frameSourcePaths.clear();
|
2021-07-10 17:38:48 +00:00
|
|
|
|
|
|
|
if (chosenFile.isDirectory()) {
|
2021-07-10 18:02:21 +00:00
|
|
|
jkLabel.setVisible(true);
|
2021-08-31 21:09:15 +00:00
|
|
|
for (File file : Objects.requireNonNull(chosenFile.listFiles())) {
|
2021-07-10 17:38:48 +00:00
|
|
|
try {
|
2021-12-12 00:17:49 +00:00
|
|
|
frameSources.add(ParserFactory.getParser(file.getAbsolutePath()).parse());
|
|
|
|
frameSourcePaths.add(file.getName());
|
2021-07-10 20:53:24 +00:00
|
|
|
} catch (IOException ignored) {
|
|
|
|
}
|
2021-07-10 17:38:48 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-07-10 18:02:21 +00:00
|
|
|
jkLabel.setVisible(false);
|
2021-12-12 00:17:49 +00:00
|
|
|
frameSources.add(ParserFactory.getParser(chosenFile.getAbsolutePath()).parse());
|
|
|
|
frameSourcePaths.add(chosenFile.getName());
|
2021-07-10 17:38:48 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:17:49 +00:00
|
|
|
oldFrameSources.forEach(FrameSource::disable);
|
|
|
|
changeFrameSource(0);
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
|
|
|
} catch (IOException | ParserConfigurationException | SAXException ioException) {
|
|
|
|
ioException.printStackTrace();
|
2021-07-10 17:38:48 +00:00
|
|
|
|
|
|
|
// display error to user (for debugging purposes)
|
|
|
|
String oldPath = fileLabel.getText();
|
2021-09-01 20:37:48 +00:00
|
|
|
// shows the error message and later shows the old path as the file being rendered
|
|
|
|
// doesn't change
|
2021-07-10 17:38:48 +00:00
|
|
|
KeyFrame kf1 = new KeyFrame(Duration.seconds(0), e -> fileLabel.setText(ioException.getMessage()));
|
|
|
|
KeyFrame kf2 = new KeyFrame(Duration.seconds(5), e -> fileLabel.setText(oldPath));
|
|
|
|
Timeline timeline = new Timeline(kf1, kf2);
|
|
|
|
Platform.runLater(timeline::play);
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// used so that the Controller has access to the stage, allowing it to open
|
|
|
|
// file directories etc.
|
2021-05-26 19:44:18 +00:00
|
|
|
public void setStage(Stage stage) {
|
|
|
|
this.stage = stage;
|
|
|
|
}
|
|
|
|
|
2021-12-12 00:17:49 +00:00
|
|
|
// increments and changes the frameSource after pressing 'j'
|
2021-07-10 17:38:48 +00:00
|
|
|
public void nextFrameSet() {
|
2021-12-12 00:17:49 +00:00
|
|
|
int index = currentFrameSource + 1;
|
|
|
|
if (index >= frameSources.size()) {
|
|
|
|
index = 0;
|
2021-07-10 17:38:48 +00:00
|
|
|
}
|
2021-12-12 00:17:49 +00:00
|
|
|
changeFrameSource(index);
|
2021-07-10 17:38:48 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:17:49 +00:00
|
|
|
// decrements and changes the frameSource after pressing 'k'
|
|
|
|
public void previousFrameSource() {
|
|
|
|
int index = currentFrameSource - 1;
|
|
|
|
if (index < 0) {
|
|
|
|
index = frameSources.size() - 1;
|
2021-07-10 17:38:48 +00:00
|
|
|
}
|
2021-12-12 00:17:49 +00:00
|
|
|
changeFrameSource(index);
|
2021-07-10 17:38:48 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// determines whether the mouse is being used to rotate a 3D object
|
2021-05-26 19:44:18 +00:00
|
|
|
protected boolean mouseRotate() {
|
|
|
|
return rotateCheckBox.isSelected();
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// stops the mouse rotating the 3D object when ESC is pressed or checkbox is
|
|
|
|
// unchecked
|
2021-05-26 19:44:18 +00:00
|
|
|
protected void disableMouseRotate() {
|
|
|
|
rotateCheckBox.setSelected(false);
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// updates the 3D object rotation angle
|
2021-05-26 19:44:18 +00:00
|
|
|
protected void setObjRotate(Vector3 vector) {
|
|
|
|
producer.setFrameSettings(ObjSettingsFactory.rotation(vector));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void updateFrequency(double leftFrequency, double rightFrequency) {
|
|
|
|
Platform.runLater(() ->
|
|
|
|
frequencyLabel.setText(String.format("L/R Frequency:\n%d Hz / %d Hz", Math.round(leftFrequency), Math.round(rightFrequency)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// converts MIDI pressure value into a valid value for a slider
|
2021-07-15 20:18:59 +00:00
|
|
|
private double midiPressureToPressure(Slider slider, int midiPressure) {
|
|
|
|
double max = slider.getMax();
|
|
|
|
double min = slider.getMin();
|
|
|
|
double range = max - min;
|
2021-08-31 21:09:15 +00:00
|
|
|
return min + (midiPressure / MidiNote.MAX_PRESSURE) * range;
|
2021-07-15 20:18:59 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// handles newly received MIDI messages. For CC messages, this handles
|
|
|
|
// whether or not there is a slider that is associated with the CC channel,
|
|
|
|
// and the slider's value is updated if so. If there are channels that are
|
|
|
|
// looking to be armed, a new association will be created between the CC
|
|
|
|
// channel and the slider.
|
2021-07-13 21:44:04 +00:00
|
|
|
@Override
|
2021-07-18 16:48:57 +00:00
|
|
|
public void sendMidiMessage(ShortMessage message) {
|
2021-08-30 22:36:12 +00:00
|
|
|
int command = message.getCommand();
|
2021-07-18 16:48:57 +00:00
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// the audioPlayer handles all non-CC MIDI messages
|
2021-08-30 22:36:12 +00:00
|
|
|
if (command == ShortMessage.CONTROL_CHANGE) {
|
|
|
|
int id = message.getData1();
|
|
|
|
int value = message.getData2();
|
2021-07-18 16:48:57 +00:00
|
|
|
|
2021-09-01 20:37:48 +00:00
|
|
|
// if a user has selected a MIDI logo next to a slider, create a mapping
|
|
|
|
// between the MIDI channel and the SVG MIDI logo
|
2021-08-30 22:36:12 +00:00
|
|
|
if (armedMidi != null) {
|
|
|
|
if (CCMap.containsValue(armedMidi)) {
|
|
|
|
CCMap.values().remove(armedMidi);
|
2021-07-18 16:48:57 +00:00
|
|
|
}
|
|
|
|
if (CCMap.containsKey(id)) {
|
2021-08-30 22:36:12 +00:00
|
|
|
CCMap.get(id).setFill(Color.WHITE);
|
2021-07-15 20:18:59 +00:00
|
|
|
}
|
2021-08-30 22:36:12 +00:00
|
|
|
CCMap.put(id, armedMidi);
|
|
|
|
armedMidi.setFill(Color.LIME);
|
|
|
|
armedMidiPaint = null;
|
|
|
|
armedMidi = null;
|
2021-07-15 20:18:59 +00:00
|
|
|
}
|
2021-09-01 20:37:48 +00:00
|
|
|
// If there is a slider associated with the MIDI channel, update the value
|
|
|
|
// of it
|
2021-08-30 22:36:12 +00:00
|
|
|
if (CCMap.containsKey(id)) {
|
|
|
|
Slider slider = midiButtonMap.get(CCMap.get(id));
|
|
|
|
double sliderValue = midiPressureToPressure(slider, value);
|
2021-07-15 20:18:59 +00:00
|
|
|
|
2021-08-30 22:36:12 +00:00
|
|
|
if (slider.isSnapToTicks()) {
|
|
|
|
double increment = slider.getMajorTickUnit() / (slider.getMinorTickCount() + 1);
|
|
|
|
sliderValue = increment * (Math.round(sliderValue / increment));
|
|
|
|
}
|
|
|
|
slider.setValue(sliderValue);
|
|
|
|
}
|
2021-12-12 00:17:49 +00:00
|
|
|
} else if (command == ShortMessage.PROGRAM_CHANGE) {
|
|
|
|
// We want to change the file that is currently playing
|
|
|
|
Platform.runLater(() -> changeFrameSource(message.getMessage()[1]));
|
2021-08-30 22:36:12 +00:00
|
|
|
}
|
2021-07-15 20:18:59 +00:00
|
|
|
}
|
2021-05-26 19:44:18 +00:00
|
|
|
}
|