package jp.yucchi.toypiano;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.HPos;
import javafx.scene.AmbientLight;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Button;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.TouchEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.media.AudioClip;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.transform.Rotate;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* @author Yucchi
*/
public class ToyPiano extends Application implements PianoAction {
// ピアノ
private Group pianoGroup;
// ピアノポジション
private final DoubleProperty pxPos = new SimpleDoubleProperty(0.0d);
private final DoubleProperty pyPos = new SimpleDoubleProperty(0.0d);
private final DoubleProperty pzPos = new SimpleDoubleProperty(0.0d);
// サブカメラ
private PerspectiveCamera subCam;
// サブカメラ移動
private final DoubleProperty xPos = new SimpleDoubleProperty(0.0d);
private final DoubleProperty yPos = new SimpleDoubleProperty(0.0d);
private final DoubleProperty zPos = new SimpleDoubleProperty(-600.0d);
// サブカメラ回転
private final DoubleProperty rxPos = new SimpleDoubleProperty(0.0d);
private final DoubleProperty ryPos = new SimpleDoubleProperty(0.0d);
private final DoubleProperty rzPos = new SimpleDoubleProperty(0.0d);
// 座標
private double anchorAngleX;
private double anchorAngleY;
private final DoubleProperty angleX = new SimpleDoubleProperty(0d);
private final DoubleProperty angleY = new SimpleDoubleProperty(0d);
private final DoubleProperty angleZ = new SimpleDoubleProperty(0d);
private double anchorX;
private double anchorY;
private ToggleButton openCloseButton;
private Button recordingButton;
private Button stopButton;
private Button playbackButton;
private Button saveButton;
private Button loadButton;
private FileChooser fileChooser;
private Box keyboard_f;
private Box keyboard_g;
private Box keyboard_a;
private Box keyboard_b;
private Box keyboard_f_s;
private Box keyboard_g_s;
private Box keyboard_a_s;
private boolean recording;
private List<EventRecording> eventRecording;
private long actionEventTime;
private long recordingLastTime;
private AnimationTimer recordingAnimation;
private AnimationTimer playbackAnimation;
private AudioClip f;
private AudioClip f_s;
private AudioClip g;
private AudioClip g_s;
private AudioClip a;
private AudioClip a_s;
private AudioClip b;
private AudioClip dameyo;
private double whiteKeyBoardDepth;
private double blackKeyBoardDepth;
private boolean activePlayback;
private int touchPressedCount; // ラムダ式からアクセス 取扱注意
private int touchReleasedCount; // ラムダ式からアクセス 取扱注意
private Cursor cursor;
@Override
public void start(Stage primaryStage) {
final SubScene subScene = createSubScene();
final Parent controlScene = createControlScene();
final AnchorPane root = new AnchorPane();
root.getChildren().addAll(subScene, controlScene);
AnchorPane.setRightAnchor(controlScene, 0.0d);
final Scene scene = new Scene(root, 1_920, 1_080, true);
// プレイバック以外のタッチドラッグを禁止させるために
subScene.setOnTouchPressed(te -> {
touchPressedCount++; // フィールドへアクセス 取扱注意
});
subScene.setOnTouchReleased(te -> {
touchReleasedCount++; // フィールドへアクセス 取扱注意
});
// マウスで pianoGroup 操作用
Rotate xRotate;
Rotate yRotate;
Rotate zRotate;
pianoGroup.getTransforms().setAll(
xRotate = new Rotate(0, Rotate.X_AXIS),
yRotate = new Rotate(0, Rotate.Y_AXIS),
zRotate = new Rotate(0, Rotate.Z_AXIS)
);
xRotate.angleProperty().bind(angleX);
yRotate.angleProperty().bind(angleY);
zRotate.angleProperty().bind(angleZ);
subScene.setOnMousePressed(event -> {
anchorX = event.getSceneX();
anchorY = event.getSceneY();
anchorAngleX = angleX.get();
anchorAngleY = angleY.get();
});
subScene.setOnMouseDragged(event -> {
if (touchPressedCount == touchReleasedCount || activePlayback) {
angleX.set(anchorAngleX - (anchorY - event.getSceneY()));
angleY.set(anchorAngleY + anchorX - event.getSceneX());
}
});
pianoGroup.translateXProperty().bindBidirectional(pxPos);
pianoGroup.translateYProperty().bindBidirectional(pyPos);
pianoGroup.translateZProperty().bindBidirectional(pzPos);
// ALT + E キーでアプリケーション終了
root.setOnKeyPressed(key -> {
if (key.getCode() == KeyCode.E && key.isAltDown()) {
Platform.exit();
System.exit(0);
}
});
// Icon 設定
Image myIcon = new Image(this.getClass().getResource("resources/sakura_icon.png").toExternalForm());
primaryStage.getIcons().add(myIcon);
primaryStage.setTitle("Java FX ToyPiano");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitHint("ESC: Full-screen mode end." + "\nALT + E: Application Exit.");
primaryStage.show();
// オプション操作用 UI を閉じておく
openCloseButton.fire();
// オープニングアニメーション
openAnimation();
// ファイルチューザ
fileChooser = new FileChooser();
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
fileChooser.getExtensionFilters()
.addAll(new FileChooser.ExtensionFilter("Piano Files", "*.pia"),
new FileChooser.ExtensionFilter("All Files", "*.*"));
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private SubScene createSubScene() {
// ピアノグループ
pianoGroup = new Group();
// ピアノの音源
f = new AudioClip(this.getClass().getResource("resources/F.wav").toExternalForm());
f_s = new AudioClip(this.getClass().getResource("resources/F_S.wav").toExternalForm());
g = new AudioClip(this.getClass().getResource("resources/G.wav").toExternalForm());
g_s = new AudioClip(this.getClass().getResource("resources/G_S.wav").toExternalForm());
a = new AudioClip(this.getClass().getResource("resources/A.wav").toExternalForm());
a_s = new AudioClip(this.getClass().getResource("resources/A_S.wav").toExternalForm());
b = new AudioClip(this.getClass().getResource("resources/B.wav").toExternalForm());
dameyo = new AudioClip(this.getClass().getResource("resources/Dameyo.wav").toExternalForm());
// ピアノ
// 白鍵盤
final double whiteKeyBoardWidth = 30.0d;
final double whiteKeyBoardHeight = 30.0d;
whiteKeyBoardDepth = whiteKeyBoardWidth * 6;
// 黒鍵盤
final double blackKeyBoardWidth = whiteKeyBoardWidth * 0.7;
final double blackKeyBoardHeight = 30.0d;
blackKeyBoardDepth = whiteKeyBoardDepth * 0.6;
final double blackKeyBoardGap = whiteKeyBoardWidth * 0.15;
// 白鍵盤の間隙
final double whiteKeyBoardGap = 2.0d;
// ボディ
final double bodyHeight = 50.0d;
final double bodyDepth = 350.0d;
final double bodyWidth = whiteKeyBoardWidth * 20 + whiteKeyBoardGap * 18;
// 白鍵盤左端配置開始位置
final double xStart = -(bodyWidth / 2) + whiteKeyBoardWidth;
// 白鍵盤の位置ががボディより手前に収まるようにマージン設定
final double depthMargin = 15d;
// ボディ マテリアル
final PhongMaterial bodyMaterial = new PhongMaterial();
bodyMaterial.setDiffuseColor(Color.RED);
bodyMaterial.setSpecularColor(Color.TOMATO);
// 白鍵盤 マテリアル
final PhongMaterial whiteKeyBoardPhongMaterial = new PhongMaterial();
whiteKeyBoardPhongMaterial.setDiffuseColor(Color.GHOSTWHITE);
whiteKeyBoardPhongMaterial.setSpecularColor(Color.WHITE);
// 黒鍵盤 マテリアル
final PhongMaterial blackKeyBoardPhongMaterial = new PhongMaterial();
blackKeyBoardPhongMaterial.setDiffuseColor(Color.DIMGRAY);
blackKeyBoardPhongMaterial.setSpecularColor(Color.DARKGRAY);
final Box body = new Box(bodyWidth, bodyHeight, bodyDepth);
body.setMaterial(bodyMaterial);
body.setDrawMode(DrawMode.FILL);
final Box body2 = new Box(bodyWidth, bodyHeight, bodyDepth - whiteKeyBoardDepth - depthMargin);
body2.setMaterial(bodyMaterial);
body2.setDrawMode(DrawMode.FILL);
body2.setTranslateY(-bodyHeight);
body2.setTranslateZ(bodyDepth / 2 - (bodyDepth - whiteKeyBoardDepth - depthMargin) / 2);
keyboard_f = new Box(whiteKeyBoardWidth, whiteKeyBoardHeight, whiteKeyBoardDepth);
keyboard_f.setUserData("F");
keyboard_f.setMaterial(whiteKeyBoardPhongMaterial);
keyboard_f.setDrawMode(DrawMode.FILL);
keyboard_f.setTranslateX(xStart);
keyboard_f.setTranslateY(-bodyHeight / 2);
keyboard_f.setTranslateZ(-(bodyDepth / 2 - whiteKeyBoardDepth / 2) + depthMargin);
keyboard_g = new Box(whiteKeyBoardWidth, whiteKeyBoardHeight, whiteKeyBoardDepth);
keyboard_g.setUserData("G");
keyboard_g.setMaterial(whiteKeyBoardPhongMaterial);
keyboard_g.setDrawMode(DrawMode.FILL);
keyboard_g.setTranslateX(xStart + whiteKeyBoardGap + whiteKeyBoardWidth);
keyboard_g.setTranslateY(-bodyHeight / 2);
keyboard_g.setTranslateZ(-(bodyDepth / 2 - whiteKeyBoardDepth / 2) + depthMargin);
keyboard_a = new Box(whiteKeyBoardWidth, whiteKeyBoardHeight, whiteKeyBoardDepth);
keyboard_a.setUserData("A");
keyboard_a.setMaterial(whiteKeyBoardPhongMaterial);
keyboard_a.setDrawMode(DrawMode.FILL);
keyboard_a.setTranslateX(xStart + whiteKeyBoardGap * 2 + whiteKeyBoardWidth * 2);
keyboard_a.setTranslateY(-bodyHeight / 2);
keyboard_a.setTranslateZ(-(bodyDepth / 2 - whiteKeyBoardDepth / 2) + depthMargin);
keyboard_b = new Box(whiteKeyBoardWidth, whiteKeyBoardHeight, whiteKeyBoardDepth);
keyboard_b.setUserData("B");
keyboard_b.setMaterial(whiteKeyBoardPhongMaterial);
keyboard_b.setDrawMode(DrawMode.FILL);
keyboard_b.setTranslateX(xStart + whiteKeyBoardGap * 3 + whiteKeyBoardWidth * 3);
keyboard_b.setTranslateY(-bodyHeight / 2);
keyboard_b.setTranslateZ(-(bodyDepth / 2 - whiteKeyBoardDepth / 2) + depthMargin);
keyboard_f_s = new Box(blackKeyBoardWidth, blackKeyBoardHeight, blackKeyBoardDepth);
keyboard_f_s.setUserData("F_S");
keyboard_f_s.setMaterial(blackKeyBoardPhongMaterial);
keyboard_f_s.setDrawMode(DrawMode.FILL);
keyboard_f_s.setTranslateX(xStart + whiteKeyBoardWidth / 2 + whiteKeyBoardGap / 2 - blackKeyBoardGap);
keyboard_f_s.setTranslateY(-(bodyHeight / 2 + whiteKeyBoardHeight / 2));
keyboard_f_s.setTranslateZ(whiteKeyBoardDepth - blackKeyBoardDepth / 2 - bodyDepth / 2 + depthMargin);
keyboard_g_s = new Box(blackKeyBoardWidth, blackKeyBoardHeight, blackKeyBoardDepth);
keyboard_g_s.setUserData("G_S");
keyboard_g_s.setMaterial(blackKeyBoardPhongMaterial);
keyboard_g_s.setDrawMode(DrawMode.FILL);
keyboard_g_s.setTranslateX(xStart + whiteKeyBoardWidth / 2 + whiteKeyBoardGap / 2 + whiteKeyBoardWidth + whiteKeyBoardGap);
keyboard_g_s.setTranslateY(-(bodyHeight / 2 + whiteKeyBoardHeight / 2));
keyboard_g_s.setTranslateZ(whiteKeyBoardDepth - blackKeyBoardDepth / 2 - bodyDepth / 2 + depthMargin);
keyboard_a_s = new Box(blackKeyBoardWidth, blackKeyBoardHeight, blackKeyBoardDepth);
keyboard_a_s.setUserData("A_S");
keyboard_a_s.setMaterial(blackKeyBoardPhongMaterial);
keyboard_a_s.setDrawMode(DrawMode.FILL);
keyboard_a_s.setTranslateX(xStart + whiteKeyBoardWidth / 2 + whiteKeyBoardGap / 2 + (whiteKeyBoardWidth + whiteKeyBoardGap) * 2 + blackKeyBoardGap);
keyboard_a_s.setTranslateY(-(bodyHeight / 2 + whiteKeyBoardHeight / 2));
keyboard_a_s.setTranslateZ(whiteKeyBoardDepth - blackKeyBoardDepth / 2 - bodyDepth / 2 + depthMargin);
// ピアノ イベント処理
keyboard_f.setOnTouchPressed(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_f, te);
pressedWhiteKeyBoard(f, keyboard_f, whiteKeyBoardDepth);
});
keyboard_f.setOnTouchReleased(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_f, te);
releasedKeyBoard(keyboard_f);
});
keyboard_g.setOnTouchPressed(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_g, te);
pressedWhiteKeyBoard(g, keyboard_g, whiteKeyBoardDepth);
});
keyboard_g.setOnTouchReleased(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_g, te);
releasedKeyBoard(keyboard_g);
});
keyboard_a.setOnTouchPressed(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_a, te);
pressedWhiteKeyBoard(a, keyboard_a, whiteKeyBoardDepth);
});
keyboard_a.setOnTouchReleased(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_a, te);
releasedKeyBoard(keyboard_a);
});
keyboard_b.setOnTouchPressed(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_b, te);
pressedWhiteKeyBoard(b, keyboard_b, whiteKeyBoardDepth);
});
keyboard_b.setOnTouchReleased(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_b, te);
releasedKeyBoard(keyboard_b);
});
keyboard_f_s.setOnTouchPressed(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_f_s, te);
pressedBlackKeyBoard(f_s, keyboard_f_s, blackKeyBoardDepth);
});
keyboard_f_s.setOnTouchReleased(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_f_s, te);
releasedKeyBoard(keyboard_f_s);
});
keyboard_g_s.setOnTouchPressed(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_g_s, te);
pressedBlackKeyBoard(g_s, keyboard_g_s, blackKeyBoardDepth);
});
keyboard_g_s.setOnTouchReleased(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_g_s, te);
releasedKeyBoard(keyboard_g_s);
});
keyboard_a_s.setOnTouchPressed(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_a_s, te);
pressedBlackKeyBoard(g_s, keyboard_a_s, blackKeyBoardDepth);
});
keyboard_a_s.setOnTouchReleased(te -> {
recordingEvent(recording, eventRecording, actionEventTime, keyboard_a_s, te);
releasedKeyBoard(keyboard_a_s);
});
pianoGroup.getChildren().addAll(keyboard_f, keyboard_g, keyboard_a, keyboard_b,
keyboard_f_s, keyboard_g_s, keyboard_a_s,
body, body2);
// サブシーン用のカメラ
subCam = new PerspectiveCamera(true);
// Field of View
subCam.setFieldOfView(45.5);
// Clipping Planes
subCam.setNearClip(1.0);
subCam.setFarClip(1_000_000);
// サブカメラ操作用
// 移動
subCam.translateXProperty().bind(xPos);
subCam.translateYProperty().bind(yPos);
subCam.translateZProperty().bind(zPos);
// 回転 使ってないけど説明のため
Rotate rxRotate;
Rotate ryRotate;
Rotate rzRotate;
subCam.getTransforms().setAll(
rxRotate = new Rotate(0, Rotate.X_AXIS),
ryRotate = new Rotate(0, Rotate.Y_AXIS),
rzRotate = new Rotate(0, Rotate.Z_AXIS)
);
rxRotate.angleProperty().bind(rxPos);
ryRotate.angleProperty().bind(ryPos);
rzRotate.angleProperty().bind(rzPos);
// 環境光設定
final AmbientLight ambient = new AmbientLight();
ambient.setColor(Color.rgb(90, 90, 90, 0.6));
ambient.getScope().addAll(pianoGroup);
// ポイントライト設定
PointLight point = new PointLight();
point.setColor(Color.rgb(255, 255, 255, 1.0));
point.setLayoutX(0);
point.setLayoutY(-300);
point.setTranslateZ(-100);
point.getScope().addAll(pianoGroup);
pianoGroup.getChildren().addAll(ambient, point);
SubScene subScene = new SubScene(pianoGroup, 1_920, 1_080, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.ALICEBLUE);
subScene.setCamera(subCam);
return subScene;
}
// プレイバック、ファイル保存など
private Parent createControlScene() {
recordingButton = new Button("Recording");
stopButton = new Button("Stop");
playbackButton = new Button("Playback");
saveButton = new Button("Save");
loadButton = new Button("Load");
stopButton.setDisable(true);
playbackButton.setDisable(true);
saveButton.setDisable(true);
recordingButton.setOnAction(ae -> {
recording = true;
if (eventRecording != null) {
eventRecording = null;
}
eventRecording = new ArrayList<>();
recordingButton.setDisable(true);
stopButton.setDisable(false);
playbackButton.setDisable(true);
saveButton.setDisable(true);
loadButton.setDisable(true);
resetKeyboard();
subCamReset();
openAnimation();
recording();
});
stopButton.setOnAction(ae -> {
activePlayback = false;
stopButton.setDisable(true);
recordingButton.setDisable(false);
playbackButton.setDisable(false);
if (eventRecording != null) {
saveButton.setDisable(false);
} else {
saveButton.setDisable(true);
}
saveButton.setDisable(false);
loadButton.setDisable(false);
recordingStop();
});
playbackButton.setOnAction(ae -> {
recording = false;
activePlayback = true;
recordingButton.setDisable(true);
stopButton.setDisable(false);
playbackButton.setDisable(true);
saveButton.setDisable(true);
loadButton.setDisable(true);
resetKeyboard();
playBackAnimation();
playback();
});
saveButton.setOnAction(ae -> {
if (eventRecording != null) {
fileChooser.setTitle("Save File");
File outFile = fileChooser.showSaveDialog(null);
if (outFile != null) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(outFile.getAbsolutePath()));
} catch (IOException ex) {
Logger.getLogger(ToyPiano.class.getName()).log(Level.SEVERE, null, ex);
}
try {
oos.writeObject(eventRecording);
} catch (IOException ex) {
Logger.getLogger(ToyPiano.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
});
loadButton.setOnAction(ae -> {
fileChooser.setTitle("Load File");
File inputFile = fileChooser.showOpenDialog(null);
if (inputFile != null) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(inputFile.getAbsolutePath()));
} catch (FileNotFoundException ex) {
Logger.getLogger(ToyPiano.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(ToyPiano.class.getName()).log(Level.SEVERE, null, ex);
}
eventRecording = new ArrayList<>();
try {
eventRecording = (List<EventRecording>) ois.readObject();
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(ToyPiano.class.getName()).log(Level.SEVERE, null, ex);
// 8u40 だったらエラーダイアログをだせるんだけどなぁ・・・
}
recordingButton.setDisable(false);
stopButton.setDisable(true);
playbackButton.setDisable(false);
saveButton.setDisable(true);
loadButton.setDisable(false);
}
});
// オプション操作用 UI
// グリッドペイン スペーサー
Region region = new Region();
region.setId("spacer");
openCloseButton = new ToggleButton("Close");
openCloseButton.setId("open-close-toggle-button");
// マススがホバー状態、クリックした時にカーソル形状変更
changeCursorStyle(openCloseButton);
// グリッドペイン構築
GridPane grid = new GridPane();
int rowIndex = 0;
int colIndex = 0;
grid.add(recordingButton, colIndex++, rowIndex, 1, 2);
grid.add(stopButton, colIndex++, rowIndex, 1, 2);
grid.add(playbackButton, colIndex++, rowIndex, 1, 2);
grid.add(saveButton, colIndex++, rowIndex, 1, 2);
grid.add(loadButton, colIndex++, rowIndex, 1, 2);
int totalColumns = colIndex;
rowIndex++;
rowIndex++;
colIndex = 0;
grid.add(region, colIndex, rowIndex++);
rowIndex++;
grid.add(openCloseButton, colIndex, rowIndex, totalColumns, 1);
GridPane.setHalignment(openCloseButton, HPos.RIGHT);
grid.setId("grid");
// スタイルシート適用
grid.getStylesheets().add("jp/yucchi/toypiano/ControlScene.css");
// トグルスイッチがオンの場合トグルスイッチの raw 以外隠し、テキスト変更
openCloseButton.setOnAction(ae -> {
if (openCloseButton.isSelected()) {
grid.setLayoutY(-grid.sceneToLocal(openCloseButton.localToScene(0, 0)).getY());
openCloseButton.setText("Open");
} else {
grid.setLayoutY(0.0d);
openCloseButton.setText("Close");
}
});
return grid;
}
private void openAnimation() {
Timeline openAnimation = new Timeline(
new KeyFrame(
new Duration(0.0d),
new KeyValue(angleX, 0.0d, Interpolator.EASE_BOTH),
new KeyValue(angleY, 0.0d, Interpolator.EASE_BOTH)
),
new KeyFrame(
new Duration(1_000.0d),
new KeyValue(angleX, 45, Interpolator.EASE_BOTH),
new KeyValue(angleY, 360, Interpolator.EASE_BOTH)
));
openAnimation.setCycleCount(1);
openAnimation.play();
}
private void playBackAnimation() {
Timeline playBackAnimation = new Timeline(
new KeyFrame(
new Duration(0.0d),
new KeyValue(angleX, 0.0d, Interpolator.EASE_BOTH),
new KeyValue(angleY, 0.0d, Interpolator.EASE_BOTH),
new KeyValue(angleZ, 0.0d, Interpolator.EASE_BOTH)
),
new KeyFrame(
new Duration(1_000.0d),
new KeyValue(angleX, 0.0d, Interpolator.EASE_BOTH),
new KeyValue(angleY, 360.0d, Interpolator.EASE_BOTH),
new KeyValue(angleZ, 0.0d, Interpolator.EASE_BOTH)
));
playBackAnimation.setCycleCount(1);
playBackAnimation.play();
}
private void recording() {
recordingAnimation = new AnimationTimer() {
long recordingStartTime;
private boolean rec;
@Override
public void handle(long now) {
if (!rec) {
recordingStartTime = now;
rec = true;
}
// 時間(actionEventTime)の初期化
actionEventTime = now - recordingStartTime;
}
};
recordingAnimation.start();
}
private void recordingStop() {
if (recording) {
recordingLastTime = actionEventTime;
// レコーディング中であればレコーディングストップを記録
eventRecording.add(new EventRecording(recordingLastTime, "FINISH", "FINISH"));
recordingAnimation.stop();
recordingAnimation = null;
recording = false;
} else {
playbackAnimation.stop();
}
resetKeyboard();
}
private void playback() {
playbackAnimation = new AnimationTimer() {
int i = 0;
boolean playback;
long playbackStartTime;
private long playbackTime;
// カメラ用アニメーション
// 方位角
private double azimuth;
// 前回時間
private long previousHandledTime;
// 回転速度
private final DoubleProperty angularVelocityProperty = new SimpleDoubleProperty(5.0d);
// カメラ高さ
private final DoubleProperty heightProperty = new SimpleDoubleProperty(-250.0d);
// 半径
private final double radius = 750.0d;
// 見下ろし角度
private final double downAngle = Math.toDegrees(Math.atan(-1.0d * heightProperty.getValue() / radius));
@Override
public void handle(long now) {
if (!playback) {
playbackStartTime = now;
playback = true;
}
// 時間(playbackTime)の初期化
playbackTime = now - playbackStartTime;
// eventRecording のデータに基づきプレイバックさせる
if (eventRecording.size() > i && eventRecording.get(i).getEventTime() <= playbackTime) {
doEvent(eventRecording.get(i).getEventProducer(), eventRecording.get(i).getEventType());
i++;
}
// サブカメラを半径 SUBCAMERA_RADIUS の円の中心むけ、中心に見下ろすように撮影する
// 回転スピードも前側と後ろ側で変える
if ((0.0d <= azimuth && azimuth < 90.0d || 270.0d < azimuth)
&& (0.0d < (azimuth % 360.0d) && (azimuth % 360.0d) < 90.0d) || 270.0d < (azimuth % 360.0d)) {
azimuth += angularVelocityProperty.get() * (playbackTime - previousHandledTime) / 1_000_000_000.0d;
previousHandledTime = playbackTime;
} else {
azimuth += angularVelocityProperty.get() * 35.0d * (playbackTime - previousHandledTime) / 1_000_000_000.0d;
previousHandledTime = playbackTime;
}
// カメラ移動及び回転
xPos.set(Math.sin(Math.toRadians(azimuth)) * radius);
zPos.set(-1.0d * Math.cos(Math.toRadians(azimuth)) * radius);
yPos.set(heightProperty.getValue());
subCam.getTransforms().setAll(new Rotate(-1.0d * azimuth, Rotate.Y_AXIS), new Rotate(-1.0d * downAngle, Rotate.X_AXIS));
// プレイバック終了処理
if (eventRecording.get(eventRecording.size() - 1).getEventTime() < playbackTime) {
stopButton.setDisable(true);
recordingButton.setDisable(false);
playbackButton.setDisable(false);
saveButton.setDisable(false);
loadButton.setDisable(false);
activePlayback = false;
recordingStop();
}
}
private void doEvent(String eventProducer, String eventType) {
if (eventProducer != null && eventType != null) {
switch (eventProducer) {
case "F":
if (eventType.equals("TOUCH_PRESSED")) {
pressedWhiteKeyBoard(f, keyboard_f, whiteKeyBoardDepth);
} else {
releasedKeyBoard(keyboard_f);
}
break;
case "G":
if (eventType.equals("TOUCH_PRESSED")) {
pressedWhiteKeyBoard(g, keyboard_g, whiteKeyBoardDepth);
} else {
releasedKeyBoard(keyboard_g);
}
break;
case "A":
if (eventType.equals("TOUCH_PRESSED")) {
pressedWhiteKeyBoard(a, keyboard_a, whiteKeyBoardDepth);
} else {
releasedKeyBoard(keyboard_a);
}
break;
case "B":
if (eventType.equals("TOUCH_PRESSED")) {
pressedWhiteKeyBoard(b, keyboard_b, whiteKeyBoardDepth);
} else {
releasedKeyBoard(keyboard_b);
}
break;
case "F_S":
if (eventType.equals("TOUCH_PRESSED")) {
pressedBlackKeyBoard(f_s, keyboard_f_s, blackKeyBoardDepth);
} else {
releasedKeyBoard(keyboard_f_s);
}
break;
case "G_S":
if (eventType.equals("TOUCH_PRESSED")) {
pressedBlackKeyBoard(g_s, keyboard_g_s, blackKeyBoardDepth);
} else {
releasedKeyBoard(keyboard_g_s);
}
break;
case "A_S":
if (eventType.equals("TOUCH_PRESSED")) {
pressedBlackKeyBoard(a_s, keyboard_a_s, blackKeyBoardDepth);
} else {
releasedKeyBoard(keyboard_a_s);
}
break;
case "FINISH":
break;
default:
dameyo.play();
System.out.println("(>_<。)");
}
}
}
};
playbackAnimation.start();
}
private void changeCursorStyle(Node node) {
node.setOnMouseEntered(mouseEvent -> {
node.cursorProperty().setValue(Cursor.OPEN_HAND);
});
node.setOnMousePressed(mouseEvent -> {
node.cursorProperty().setValue(Cursor.CLOSED_HAND);
});
node.setOnMouseExited(mouseEvent -> {
node.cursorProperty().setValue(Cursor.DEFAULT);
});
}
private void resetKeyboard() {
releasedKeyBoard(keyboard_f);
releasedKeyBoard(keyboard_g);
releasedKeyBoard(keyboard_a);
releasedKeyBoard(keyboard_b);
releasedKeyBoard(keyboard_f_s);
releasedKeyBoard(keyboard_g_s);
releasedKeyBoard(keyboard_a_s);
}
private void subCamReset() {
subCam.getTransforms().setAll(new Rotate(0.0d, 0.0d, 0.0d));
xPos.set(0.0d);
yPos.set(0.0d);
zPos.set(-600.0d);
}
@Override
public void pressedWhiteKeyBoard(AudioClip ac, Box keyBoard, double keyBoardDepth) {
ac.play();
keyBoard.getTransforms().setAll(new Rotate(WHITE_KEYBOARD_DOWN_ANGLE, 0.0d, 0.0d, keyBoardDepth / 2, Rotate.X_AXIS));
}
@Override
public void pressedBlackKeyBoard(AudioClip ac, Box keyBoard, double keyBoardDepth) {
ac.play();
keyBoard.getTransforms().setAll(new Rotate(BLACK_KEYBOARD_DOWN_ANGLE, 0.0d, 0.0d, keyBoardDepth / 2, Rotate.X_AXIS));
}
@Override
public void releasedKeyBoard(Box keyBoard) {
keyBoard.getTransforms().setAll(new Rotate(0.0d, 0.0d, 0.0d));
}
@Override
public void recordingEvent(boolean recording, List<EventRecording> eventRecording, long actionEventTime, Box keyboard, TouchEvent te) {
if (recording) {
eventRecording.add(new EventRecording(actionEventTime, keyboard.getUserData().toString(), te.getEventType().toString()));
}
}
}