Merge pull request #32 from jameshball/folder-load

Implement loading a whole folder of files
pull/35/head
James H Ball 2021-07-10 19:02:39 +01:00 zatwierdzone przez GitHub
commit fa9f84b19b
4 zmienionych plików z 118 dodań i 41 usunięć

Wyświetl plik

@ -6,7 +6,7 @@
<groupId>sh.ball</groupId>
<artifactId>osci-render</artifactId>
<version>1.8.2</version>
<version>1.9.0</version>
<name>osci-render</name>

Wyświetl plik

@ -5,6 +5,7 @@ import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.control.*;
import javafx.stage.DirectoryChooser;
import javafx.util.Duration;
import sh.ball.audio.*;
import sh.ball.audio.effect.*;
@ -14,10 +15,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
@ -47,14 +45,13 @@ import sh.ball.shapes.Vector2;
public class Controller implements Initializable, FrequencyListener, Listener {
private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj");
private final FileChooser fileChooser = new FileChooser();
private final DirectoryChooser folderChooser = new DirectoryChooser();
private final AudioPlayer<List<Shape>> audioPlayer;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final RotateEffect rotateEffect;
private final TranslateEffect translateEffect;
private final WobbleEffect wobbleEffect;
@ -63,8 +60,13 @@ public class Controller implements Initializable, FrequencyListener, Listener {
private int sampleRate;
private FrequencyAnalyser<List<Shape>> analyser;
private final AudioDevice defaultDevice;
private FrameProducer<List<Shape>> producer;
private boolean recording = false;
private String lastVisitedDirectory;
private FrameProducer<List<Shape>> producer;
private final List<FrameSet<List<Shape>>> frameSets = new ArrayList<>();
private final List<String> frameSetPaths = new ArrayList<>();
private int currentFrameSet;
private Stage stage;
@ -73,8 +75,12 @@ public class Controller implements Initializable, FrequencyListener, Listener {
@FXML
private Button chooseFileButton;
@FXML
private Button chooseFolderButton;
@FXML
private Label fileLabel;
@FXML
private Label jkLabel;
@FXML
private Button recordButton;
@FXML
private Label recordLabel;
@ -130,6 +136,9 @@ public class Controller implements Initializable, FrequencyListener, Listener {
public Controller(AudioPlayer<List<Shape>> audioPlayer) throws IOException {
this.audioPlayer = audioPlayer;
FrameSet<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse();
frameSets.add(frames);
frameSetPaths.add("cube.obj");
currentFrameSet = 0;
frames.addListener(this);
this.producer = new FrameProducer<>(audioPlayer, frames);
this.defaultDevice = audioPlayer.getDefaultDevice();
@ -241,6 +250,15 @@ public class Controller implements Initializable, FrequencyListener, Listener {
File file = fileChooser.showOpenDialog(stage);
if (file != null) {
chooseFile(file);
updateLastVisitedDirectory(new File(file.getParent()));
}
});
chooseFolderButton.setOnAction(e -> {
File file = folderChooser.showDialog(stage);
if (file != null) {
chooseFile(file);
updateLastVisitedDirectory(file);
}
});
@ -268,6 +286,13 @@ public class Controller implements Initializable, FrequencyListener, Listener {
});
}
private void updateLastVisitedDirectory(File file) {
lastVisitedDirectory = file != null ? file.getAbsolutePath() : System.getProperty("user.home");
File dir = new File(lastVisitedDirectory);
fileChooser.setInitialDirectory(dir);
folderChooser.setInitialDirectory(dir);
}
private void switchAudioDevice(AudioDevice device) {
try {
audioPlayer.reset();
@ -365,34 +390,59 @@ public class Controller implements Initializable, FrequencyListener, Listener {
}
}
private void chooseFile(File file) {
private void changeFrameSet() {
FrameSet<List<Shape>> frames = frameSets.get(currentFrameSet);
producer.stop();
frames.addListener(this);
producer = new FrameProducer<>(audioPlayer, frames);
updateObjectRotateSpeed();
updateFocalLength();
executor.submit(producer);
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);
fileLabel.setText(frameSetPaths.get(currentFrameSet));
objTitledPane.setDisable(!ObjParser.isObjFile(frameSetPaths.get(currentFrameSet)));
}
private void chooseFile(File chosenFile) {
try {
producer.stop();
String path = file.getAbsolutePath();
FrameSet<List<Shape>> frames = ParserFactory.getParser(path).parse();
frames.addListener(this);
producer = new FrameProducer<>(audioPlayer, frames);
if (chosenFile.exists()) {
frameSets.clear();
frameSetPaths.clear();
updateObjectRotateSpeed();
updateFocalLength();
executor.submit(producer);
if (chosenFile.isDirectory()) {
jkLabel.setVisible(true);
for (File file : chosenFile.listFiles()) {
try {
frameSets.add(ParserFactory.getParser(file.getAbsolutePath()).parse());
frameSetPaths.add(file.getName());
} catch (IOException ignored) {}
}
} else {
jkLabel.setVisible(false);
frameSets.add(ParserFactory.getParser(chosenFile.getAbsolutePath()).parse());
frameSetPaths.add(chosenFile.getName());
}
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);
if (file.exists() && !file.isDirectory()) {
fileLabel.setText(path);
objTitledPane.setDisable(!ObjParser.isObjFile(path));
} else {
objTitledPane.setDisable(true);
currentFrameSet = 0;
changeFrameSet();
}
} catch (IOException | ParserConfigurationException | SAXException ioException) {
ioException.printStackTrace();
// display error to user (for debugging purposes)
String oldPath = fileLabel.getText();
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);
}
}
@ -400,6 +450,22 @@ public class Controller implements Initializable, FrequencyListener, Listener {
this.stage = stage;
}
public void nextFrameSet() {
currentFrameSet++;
if (currentFrameSet >= frameSets.size()) {
currentFrameSet = 0;
}
changeFrameSet();
}
public void previousFrameSet() {
currentFrameSet--;
if (currentFrameSet < 0) {
currentFrameSet = frameSets.size() - 1;
}
changeFrameSet();
}
protected boolean mouseRotate() {
return rotateCheckBox.isSelected();
}

Wyświetl plik

@ -12,7 +12,6 @@ import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import sh.ball.audio.ShapeAudioPlayer;
import sh.ball.audio.engine.ConglomerateAudioEngine;
import sh.ball.audio.engine.JavaAudioEngine;
import sh.ball.engine.Vector3;
import java.util.Objects;
@ -33,20 +32,30 @@ public class Gui extends Application {
stage.setTitle("osci-render");
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm());
scene.addEventHandler(KeyEvent.KEY_PRESSED, (event -> {
switch (event.getCode()) {
case J -> controller.nextFrameSet();
case K -> controller.previousFrameSet();
}
}));
scene.addEventFilter(MouseEvent.MOUSE_MOVED, event -> {
if (controller.mouseRotate()) {
controller.setObjRotate(new Vector3(
3 * Math.PI * (event.getSceneY() / scene.getHeight()),
3 * Math.PI * (event.getSceneX() / scene.getWidth()),
3 * Math.PI * (event.getSceneX() / scene.getWidth()),
0
));
}
});
scene.addEventHandler(KeyEvent.KEY_PRESSED, t -> {
if (t.getCode() == KeyCode.ESCAPE) {
controller.disableMouseRotate();
}
});
stage.setScene(scene);
stage.setResizable(false);

Wyświetl plik

@ -10,18 +10,20 @@
<?import javafx.scene.layout.AnchorPane?>
<?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 id="control-pane" layoutX="422.0" layoutY="10.0" prefHeight="195.0" prefWidth="402.0">
<AnchorPane prefHeight="539.0" prefWidth="837.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane id="control-pane" layoutX="423.0" layoutY="14.0" prefHeight="232.0" prefWidth="402.0">
<children>
<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="58.0" maxWidth="270.0" prefHeight="18.0" prefWidth="246.0" text="cube.obj" />
<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="98.0" maxWidth="270.0" prefHeight="18.0" prefWidth="245.0" />
<Label id="frequency" fx:id="frequencyLabel" layoutX="14.0" layoutY="131.0" prefHeight="58.0" prefWidth="376.0" text="L/R Frequency:&#10; &#10;" />
<Label fx:id="fileLabel" layoutX="144.0" layoutY="57.0" maxWidth="270.0" prefHeight="18.0" prefWidth="246.0" text="cube.obj" />
<Button fx:id="recordButton" layoutX="14.0" layoutY="129.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Record" />
<Label fx:id="recordLabel" layoutX="144.0" layoutY="133.0" maxWidth="270.0" prefHeight="18.0" prefWidth="245.0" />
<Label id="frequency" fx:id="frequencyLabel" layoutX="13.0" layoutY="166.0" prefHeight="58.0" prefWidth="376.0" text="L/R Frequency:&#10; &#10;" />
<ComboBox fx:id="deviceComboBox" layoutX="14.0" layoutY="11.0" prefHeight="26.0" prefWidth="376.0" />
<Button fx:id="chooseFolderButton" layoutX="14.0" layoutY="91.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Choose Folder" />
<Label fx:id="jkLabel" layoutX="143.0" layoutY="95.0" maxWidth="270.0" prefHeight="18.0" prefWidth="246.0" text="Use j and k to cycle between files" visible="false" />
</children>
</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="423.0" layoutY="252.0" prefHeight="272.0" prefWidth="402.0" text="Effects">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="246.0" prefWidth="442.0">
<children>
@ -39,7 +41,7 @@
</AnchorPane>
</content>
</TitledPane>
<TitledPane fx:id="objTitledPane" animated="false" collapsible="false" layoutX="11.0" layoutY="275.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="210.0" prefWidth="402.0" text="3D .obj file settings">
<TitledPane fx:id="objTitledPane" animated="false" collapsible="false" layoutX="12.0" layoutY="280.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="244.0" prefWidth="402.0" text="3D .obj file settings">
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="359.0">
<Slider fx:id="focalLengthSlider" blockIncrement="0.01" layoutX="116.0" layoutY="15.0" majorTickUnit="0.2" max="2.0" min="1.0E-5" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" value="1.0" />
<Label layoutX="30.0" layoutY="14.0" text="Focal length" />
@ -55,7 +57,7 @@
<CheckBox fx:id="rotateCheckBox" layoutX="90.0" layoutY="147.0" mnemonicParsing="false" text="Rotate with Mouse (Esc to disable)" />
</AnchorPane>
</TitledPane>
<TitledPane collapsible="false" layoutX="11.0" layoutY="9.0" prefHeight="257.0" prefWidth="402.0" text="Image settings">
<TitledPane collapsible="false" layoutX="12.0" layoutY="14.0" prefHeight="257.0" prefWidth="402.0" text="Image settings">
<content>
<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" />