2020-12-04 20:30:46 +00:00
|
|
|
package sh.ball.gui;
|
2020-11-22 14:39:38 +00:00
|
|
|
|
2021-05-08 21:49:43 +00:00
|
|
|
import javafx.scene.control.*;
|
2021-05-22 17:42:30 +00:00
|
|
|
import sh.ball.audio.FrequencyAnalyser;
|
|
|
|
import sh.ball.audio.FrequencyListener;
|
2021-05-15 17:35:53 +00:00
|
|
|
import sh.ball.audio.Renderer;
|
2021-05-15 17:53:46 +00:00
|
|
|
import sh.ball.audio.effect.Effect;
|
|
|
|
import sh.ball.audio.effect.EffectType;
|
|
|
|
import sh.ball.audio.effect.RotateEffect;
|
|
|
|
import sh.ball.audio.effect.ScaleEffect;
|
|
|
|
import sh.ball.audio.effect.EffectFactory;
|
2020-12-04 20:30:46 +00:00
|
|
|
import sh.ball.audio.FrameProducer;
|
2021-04-16 23:03:43 +00:00
|
|
|
|
2020-11-22 21:07:48 +00:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
2021-05-02 21:38:11 +00:00
|
|
|
import java.io.InputStream;
|
2020-11-22 18:03:20 +00:00
|
|
|
import java.net.URL;
|
2021-05-19 19:23:49 +00:00
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
import java.util.Date;
|
2020-11-22 21:07:48 +00:00
|
|
|
import java.util.List;
|
2020-11-23 22:56:50 +00:00
|
|
|
import java.util.Map;
|
2020-11-22 18:03:20 +00:00
|
|
|
import java.util.ResourceBundle;
|
2021-04-16 23:03:43 +00:00
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
import java.util.concurrent.Executors;
|
2021-05-18 19:57:24 +00:00
|
|
|
import java.util.function.Consumer;
|
2021-04-16 23:03:43 +00:00
|
|
|
|
2020-11-24 20:49:30 +00:00
|
|
|
import javafx.beans.InvalidationListener;
|
2020-11-22 18:03:20 +00:00
|
|
|
import javafx.fxml.FXML;
|
|
|
|
import javafx.fxml.Initializable;
|
2020-11-22 21:07:48 +00:00
|
|
|
import javafx.stage.FileChooser;
|
|
|
|
import javafx.stage.Stage;
|
2021-04-16 23:03:43 +00:00
|
|
|
|
2021-05-19 17:08:41 +00:00
|
|
|
import javax.sound.sampled.AudioFileFormat;
|
|
|
|
import javax.sound.sampled.AudioInputStream;
|
|
|
|
import javax.sound.sampled.AudioSystem;
|
2020-11-22 21:07:48 +00:00
|
|
|
import javax.xml.parsers.ParserConfigurationException;
|
2021-04-16 23:03:43 +00:00
|
|
|
|
2020-11-22 21:07:48 +00:00
|
|
|
import org.xml.sax.SAXException;
|
2021-05-15 17:53:46 +00:00
|
|
|
import sh.ball.audio.effect.TranslateEffect;
|
2021-04-17 11:14:49 +00:00
|
|
|
import sh.ball.engine.Vector3;
|
2021-05-18 19:01:29 +00:00
|
|
|
import sh.ball.parser.obj.ObjSettingsFactory;
|
2021-04-16 23:03:43 +00:00
|
|
|
import sh.ball.parser.obj.ObjParser;
|
|
|
|
import sh.ball.parser.ParserFactory;
|
2020-12-04 20:30:46 +00:00
|
|
|
import sh.ball.shapes.Shape;
|
|
|
|
import sh.ball.shapes.Vector2;
|
2020-11-22 18:03:20 +00:00
|
|
|
|
2021-05-22 17:42:30 +00:00
|
|
|
public class Controller implements Initializable, FrequencyListener {
|
2020-11-22 18:03:20 +00:00
|
|
|
|
2021-05-15 17:35:53 +00:00
|
|
|
private static final int SAMPLE_RATE = 192000;
|
2021-05-02 21:38:11 +00:00
|
|
|
private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj");
|
2020-11-22 21:07:48 +00:00
|
|
|
|
|
|
|
private final FileChooser fileChooser = new FileChooser();
|
2021-05-19 17:08:41 +00:00
|
|
|
private final Renderer<List<Shape>, AudioInputStream> renderer;
|
2021-04-16 23:03:43 +00:00
|
|
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
|
|
|
2021-05-15 17:35:53 +00:00
|
|
|
private final RotateEffect rotateEffect = new RotateEffect(SAMPLE_RATE);
|
|
|
|
private final TranslateEffect translateEffect = new TranslateEffect(SAMPLE_RATE);
|
|
|
|
private final ScaleEffect scaleEffect = new ScaleEffect();
|
|
|
|
|
2021-05-19 17:08:41 +00:00
|
|
|
private FrameProducer<List<Shape>, AudioInputStream> producer;
|
|
|
|
private boolean recording = false;
|
2020-11-23 22:56:50 +00:00
|
|
|
|
2020-11-22 21:07:48 +00:00
|
|
|
private Stage stage;
|
|
|
|
|
|
|
|
@FXML
|
2020-11-23 22:56:50 +00:00
|
|
|
private Button chooseFileButton;
|
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private Label fileLabel;
|
2020-11-23 22:56:50 +00:00
|
|
|
@FXML
|
2021-05-19 17:08:41 +00:00
|
|
|
private Button recordButton;
|
|
|
|
@FXML
|
|
|
|
private Label recordLabel;
|
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private TextField translationXTextField;
|
2020-11-23 22:56:50 +00:00
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private TextField translationYTextField;
|
2020-11-23 22:56:50 +00:00
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private Slider weightSlider;
|
2020-11-24 20:49:30 +00:00
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private Slider rotateSpeedSlider;
|
2020-11-23 22:56:50 +00:00
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private Slider translationSpeedSlider;
|
2020-11-22 18:03:20 +00:00
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private Slider scaleSlider;
|
2020-11-23 22:56:50 +00:00
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private TitledPane objTitledPane;
|
2020-11-25 21:03:06 +00:00
|
|
|
@FXML
|
2020-11-23 22:56:50 +00:00
|
|
|
private Slider focalLengthSlider;
|
|
|
|
@FXML
|
2020-11-25 21:28:37 +00:00
|
|
|
private TextField cameraXTextField;
|
|
|
|
@FXML
|
|
|
|
private TextField cameraYTextField;
|
|
|
|
@FXML
|
|
|
|
private TextField cameraZTextField;
|
2021-05-08 21:49:43 +00:00
|
|
|
@FXML
|
2021-05-17 18:54:27 +00:00
|
|
|
private Slider objectRotateSpeedSlider;
|
|
|
|
@FXML
|
2021-05-20 17:31:23 +00:00
|
|
|
private CheckBox rotateCheckBox;
|
2021-05-18 19:01:29 +00:00
|
|
|
@FXML
|
2021-05-08 21:49:43 +00:00
|
|
|
private CheckBox vectorCancellingCheckBox;
|
2021-05-09 19:00:46 +00:00
|
|
|
@FXML
|
|
|
|
private Slider vectorCancellingSlider;
|
2021-05-09 19:58:40 +00:00
|
|
|
@FXML
|
|
|
|
private CheckBox bitCrushCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider bitCrushSlider;
|
2021-05-19 21:27:18 +00:00
|
|
|
@FXML
|
|
|
|
private CheckBox verticalDistortCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider verticalDistortSlider;
|
|
|
|
@FXML
|
|
|
|
private CheckBox horizontalDistortCheckBox;
|
|
|
|
@FXML
|
|
|
|
private Slider horizontalDistortSlider;
|
2020-11-22 18:03:20 +00:00
|
|
|
|
2021-05-19 17:08:41 +00:00
|
|
|
public Controller(Renderer<List<Shape>, AudioInputStream> renderer) throws IOException {
|
2021-05-15 17:35:53 +00:00
|
|
|
this.renderer = renderer;
|
|
|
|
this.producer = new FrameProducer<>(
|
|
|
|
renderer,
|
|
|
|
new ObjParser(DEFAULT_OBJ).parse()
|
|
|
|
);
|
2021-04-16 23:03:43 +00:00
|
|
|
}
|
|
|
|
|
2021-05-18 19:57:24 +00:00
|
|
|
private Map<Slider, Consumer<Double>> initializeSliderMap() {
|
2020-11-23 22:56:50 +00:00
|
|
|
return Map.of(
|
2021-04-16 23:03:43 +00:00
|
|
|
weightSlider,
|
2021-05-18 19:57:24 +00:00
|
|
|
renderer::setQuality,
|
2021-04-16 23:03:43 +00:00
|
|
|
rotateSpeedSlider,
|
2021-05-18 19:57:24 +00:00
|
|
|
rotateEffect::setSpeed,
|
2021-04-16 23:03:43 +00:00
|
|
|
translationSpeedSlider,
|
2021-05-18 19:57:24 +00:00
|
|
|
translateEffect::setSpeed,
|
2021-04-16 23:03:43 +00:00
|
|
|
scaleSlider,
|
2021-05-18 19:57:24 +00:00
|
|
|
scaleEffect::setScale,
|
2021-04-17 11:14:49 +00:00
|
|
|
focalLengthSlider,
|
2021-05-19 20:25:38 +00:00
|
|
|
d -> updateFocalLength(),
|
2021-05-17 18:54:27 +00:00
|
|
|
objectRotateSpeedSlider,
|
2021-05-19 20:25:38 +00:00
|
|
|
d -> updateObjectRotateSpeed()
|
2020-11-23 22:56:50 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-09 19:00:46 +00:00
|
|
|
private Map<EffectType, Slider> effectTypes;
|
|
|
|
|
|
|
|
private void initializeEffectTypes() {
|
|
|
|
effectTypes = Map.of(
|
|
|
|
EffectType.VECTOR_CANCELLING,
|
2021-05-09 19:58:40 +00:00
|
|
|
vectorCancellingSlider,
|
|
|
|
EffectType.BIT_CRUSH,
|
2021-05-19 21:27:18 +00:00
|
|
|
bitCrushSlider,
|
|
|
|
EffectType.VERTICAL_DISTORT,
|
|
|
|
verticalDistortSlider,
|
|
|
|
EffectType.HORIZONTAL_DISTORT,
|
|
|
|
horizontalDistortSlider
|
2021-05-09 19:00:46 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-11-22 18:03:20 +00:00
|
|
|
@Override
|
|
|
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
2021-05-18 19:57:24 +00:00
|
|
|
Map<Slider, Consumer<Double>> sliders = initializeSliderMap();
|
2021-05-09 19:00:46 +00:00
|
|
|
initializeEffectTypes();
|
2020-11-23 22:56:50 +00:00
|
|
|
|
|
|
|
for (Slider slider : sliders.keySet()) {
|
|
|
|
slider.valueProperty().addListener((source, oldValue, newValue) ->
|
2021-05-18 19:57:24 +00:00
|
|
|
sliders.get(slider).accept(slider.getValue())
|
2020-11-23 22:56:50 +00:00
|
|
|
);
|
|
|
|
}
|
2020-11-22 21:07:48 +00:00
|
|
|
|
2021-05-19 20:25:38 +00:00
|
|
|
translationXTextField.textProperty().addListener(e -> updateTranslation());
|
|
|
|
translationYTextField.textProperty().addListener(e -> updateTranslation());
|
2020-11-24 20:49:30 +00:00
|
|
|
|
2021-05-19 20:25:38 +00:00
|
|
|
cameraXTextField.textProperty().addListener(e -> updateCameraPos());
|
|
|
|
cameraYTextField.textProperty().addListener(e -> updateCameraPos());
|
|
|
|
cameraZTextField.textProperty().addListener(e -> updateCameraPos());
|
2020-11-24 20:49:30 +00:00
|
|
|
|
2021-05-09 19:58:40 +00:00
|
|
|
InvalidationListener vectorCancellingListener = e ->
|
|
|
|
updateEffect(EffectType.VECTOR_CANCELLING, vectorCancellingCheckBox.isSelected(),
|
2021-05-15 17:53:46 +00:00
|
|
|
EffectFactory.vectorCancelling((int) vectorCancellingSlider.getValue()));
|
2021-05-09 19:58:40 +00:00
|
|
|
InvalidationListener bitCrushListener = e ->
|
|
|
|
updateEffect(EffectType.BIT_CRUSH, bitCrushCheckBox.isSelected(),
|
2021-05-15 17:53:46 +00:00
|
|
|
EffectFactory.bitCrush(bitCrushSlider.getValue()));
|
2021-05-19 21:27:18 +00:00
|
|
|
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()));
|
2021-05-09 19:58:40 +00:00
|
|
|
|
|
|
|
vectorCancellingSlider.valueProperty().addListener(vectorCancellingListener);
|
|
|
|
vectorCancellingCheckBox.selectedProperty().addListener(vectorCancellingListener);
|
|
|
|
|
|
|
|
bitCrushSlider.valueProperty().addListener(bitCrushListener);
|
|
|
|
bitCrushCheckBox.selectedProperty().addListener(bitCrushListener);
|
2021-05-08 21:49:43 +00:00
|
|
|
|
2021-05-19 21:27:18 +00:00
|
|
|
verticalDistortSlider.valueProperty().addListener(verticalDistortListener);
|
|
|
|
verticalDistortCheckBox.selectedProperty().addListener(verticalDistortListener);
|
|
|
|
|
|
|
|
horizontalDistortSlider.valueProperty().addListener(horizontalDistortListener);
|
|
|
|
horizontalDistortCheckBox.selectedProperty().addListener(horizontalDistortListener);
|
|
|
|
|
2021-05-19 19:23:49 +00:00
|
|
|
fileChooser.setInitialFileName("out.wav");
|
|
|
|
fileChooser.getExtensionFilters().addAll(
|
|
|
|
new FileChooser.ExtensionFilter("All Files", "*.*"),
|
|
|
|
new FileChooser.ExtensionFilter("WAV Files", "*.wav"),
|
|
|
|
new FileChooser.ExtensionFilter("Wavefront OBJ Files", "*.obj"),
|
|
|
|
new FileChooser.ExtensionFilter("SVG Files", "*.svg"),
|
|
|
|
new FileChooser.ExtensionFilter("Text Files", "*.txt")
|
|
|
|
);
|
|
|
|
|
2020-11-23 22:56:50 +00:00
|
|
|
chooseFileButton.setOnAction(e -> {
|
2021-04-17 11:14:49 +00:00
|
|
|
File file = fileChooser.showOpenDialog(stage);
|
|
|
|
if (file != null) {
|
|
|
|
chooseFile(file);
|
2020-11-24 20:49:30 +00:00
|
|
|
}
|
2020-11-22 21:07:48 +00:00
|
|
|
});
|
|
|
|
|
2021-05-19 19:23:49 +00:00
|
|
|
recordButton.setOnAction(event -> toggleRecord());
|
2021-05-19 17:08:41 +00:00
|
|
|
|
2021-05-19 20:25:38 +00:00
|
|
|
updateObjectRotateSpeed();
|
2021-05-17 18:54:27 +00:00
|
|
|
|
2021-05-15 17:35:53 +00:00
|
|
|
renderer.addEffect(EffectType.SCALE, scaleEffect);
|
|
|
|
renderer.addEffect(EffectType.ROTATE, rotateEffect);
|
|
|
|
renderer.addEffect(EffectType.TRANSLATE, translateEffect);
|
|
|
|
|
2021-04-16 23:03:43 +00:00
|
|
|
executor.submit(producer);
|
2021-04-16 17:56:17 +00:00
|
|
|
new Thread(renderer).start();
|
2021-05-22 17:42:30 +00:00
|
|
|
new Thread(new FrequencyAnalyser<>(renderer, 2, SAMPLE_RATE)).start();
|
2020-11-22 21:07:48 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 19:23:49 +00:00
|
|
|
private void toggleRecord() {
|
|
|
|
recording = !recording;
|
|
|
|
if (recording) {
|
|
|
|
recordLabel.setText("Recording...");
|
|
|
|
recordButton.setText("Stop Recording");
|
|
|
|
renderer.startRecord();
|
|
|
|
} else {
|
|
|
|
recordButton.setText("Record");
|
|
|
|
AudioInputStream input = renderer.stopRecord();
|
|
|
|
try {
|
|
|
|
File file = fileChooser.showSaveDialog(stage);
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
AudioSystem.write(input, AudioFileFormat.Type.WAVE, file);
|
|
|
|
input.close();
|
|
|
|
recordLabel.setText("Saved to " + file.getAbsolutePath());
|
|
|
|
} catch (IOException e) {
|
2021-05-19 19:36:03 +00:00
|
|
|
recordLabel.setText("Error saving file");
|
2021-05-19 19:23:49 +00:00
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 20:25:38 +00:00
|
|
|
private void updateFocalLength() {
|
|
|
|
double focalLength = focalLengthSlider.getValue();
|
2021-05-19 13:59:49 +00:00
|
|
|
producer.setFrameSettings(ObjSettingsFactory.focalLength(focalLength));
|
2021-04-17 11:14:49 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 20:25:38 +00:00
|
|
|
private void updateObjectRotateSpeed() {
|
|
|
|
double rotateSpeed = objectRotateSpeedSlider.getValue();
|
2021-05-18 18:32:49 +00:00
|
|
|
producer.setFrameSettings(
|
2021-05-18 19:01:29 +00:00
|
|
|
ObjSettingsFactory.rotateSpeed((Math.exp(3 * rotateSpeed) - 1) / 50)
|
2021-05-18 18:32:49 +00:00
|
|
|
);
|
2021-05-17 18:54:27 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 20:25:38 +00:00
|
|
|
private void updateTranslation() {
|
|
|
|
translateEffect.setTranslation(new Vector2(
|
|
|
|
tryParse(translationXTextField.getText()),
|
|
|
|
tryParse(translationYTextField.getText())
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void resetCameraPos() {
|
|
|
|
cameraXTextField.setText("");
|
|
|
|
cameraYTextField.setText("");
|
|
|
|
cameraZTextField.setText("");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateCameraPos() {
|
|
|
|
producer.setFrameSettings(ObjSettingsFactory.cameraPosition(new Vector3(
|
|
|
|
tryParse(cameraXTextField.getText()),
|
|
|
|
tryParse(cameraYTextField.getText()),
|
|
|
|
tryParse(cameraZTextField.getText())
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2020-11-24 20:49:30 +00:00
|
|
|
private double tryParse(String value) {
|
|
|
|
try {
|
|
|
|
return Double.parseDouble(value);
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-08 21:49:43 +00:00
|
|
|
private void updateEffect(EffectType type, boolean checked, Effect effect) {
|
|
|
|
if (checked) {
|
|
|
|
renderer.addEffect(type, effect);
|
2021-05-09 19:00:46 +00:00
|
|
|
effectTypes.get(type).setDisable(false);
|
2021-05-08 21:49:43 +00:00
|
|
|
} else {
|
|
|
|
renderer.removeEffect(type);
|
2021-05-09 19:00:46 +00:00
|
|
|
effectTypes.get(type).setDisable(true);
|
2021-05-08 21:49:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-25 21:28:37 +00:00
|
|
|
private void chooseFile(File file) {
|
2020-11-25 21:03:06 +00:00
|
|
|
try {
|
2021-04-16 23:03:43 +00:00
|
|
|
producer.stop();
|
2020-11-25 21:28:37 +00:00
|
|
|
String path = file.getAbsolutePath();
|
2021-05-19 20:25:38 +00:00
|
|
|
resetCameraPos();
|
2021-04-16 23:03:43 +00:00
|
|
|
producer = new FrameProducer<>(
|
|
|
|
renderer,
|
2021-05-03 20:31:10 +00:00
|
|
|
ParserFactory.getParser(path).parse()
|
2021-04-16 23:03:43 +00:00
|
|
|
);
|
2021-05-19 20:25:38 +00:00
|
|
|
updateObjectRotateSpeed();
|
|
|
|
updateFocalLength();
|
2021-04-16 23:03:43 +00:00
|
|
|
executor.submit(producer);
|
|
|
|
|
2020-11-25 21:28:37 +00:00
|
|
|
if (file.exists() && !file.isDirectory()) {
|
|
|
|
fileLabel.setText(path);
|
|
|
|
objTitledPane.setDisable(!ObjParser.isObjFile(path));
|
|
|
|
} else {
|
|
|
|
objTitledPane.setDisable(true);
|
|
|
|
}
|
2020-11-25 21:03:06 +00:00
|
|
|
} catch (IOException | ParserConfigurationException | SAXException ioException) {
|
|
|
|
ioException.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-22 21:07:48 +00:00
|
|
|
public void setStage(Stage stage) {
|
|
|
|
this.stage = stage;
|
2020-11-22 18:03:20 +00:00
|
|
|
}
|
2021-05-20 17:31:23 +00:00
|
|
|
|
|
|
|
protected boolean mouseRotate() {
|
|
|
|
return rotateCheckBox.isSelected();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void disableMouseRotate() {
|
|
|
|
rotateCheckBox.setSelected(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void setObjRotate(Vector3 vector) {
|
|
|
|
producer.setFrameSettings(ObjSettingsFactory.rotation(vector));
|
|
|
|
}
|
2021-05-22 17:42:30 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void updateFrequency(double frequency) {
|
|
|
|
|
|
|
|
}
|
2020-11-22 14:39:38 +00:00
|
|
|
}
|