diff --git a/pom.xml b/pom.xml index 72361bb5..1be87b0e 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ UTF-8 11 - 15 + 17-ea+8 diff --git a/src/main/java/com/utsusynth/utsu/UtsuModule.java b/src/main/java/com/utsusynth/utsu/UtsuModule.java index 410eeea0..9958f772 100644 --- a/src/main/java/com/utsusynth/utsu/UtsuModule.java +++ b/src/main/java/com/utsusynth/utsu/UtsuModule.java @@ -146,11 +146,13 @@ private PreferencesManager providePreferencesManager( defaultBuilder.put("resampler", assetManager.getResamplerFile().getAbsolutePath()); defaultBuilder.put("wavtool", assetManager.getWavtoolFile().getAbsolutePath()); defaultBuilder.put("voicebank", assetManager.getVoicePath().getAbsolutePath()); + defaultBuilder.put("metronome", assetManager.getMetronomeFile().getAbsolutePath()); return new PreferencesManager( settingsPath, documentBuilderFactory, TransformerFactory.newDefaultInstance(), - defaultBuilder.build()); + defaultBuilder.build() + ); } @Provides diff --git a/src/main/java/com/utsusynth/utsu/controller/common/IconManager.java b/src/main/java/com/utsusynth/utsu/controller/common/IconManager.java index d486979b..f908ed2f 100644 --- a/src/main/java/com/utsusynth/utsu/controller/common/IconManager.java +++ b/src/main/java/com/utsusynth/utsu/controller/common/IconManager.java @@ -5,6 +5,7 @@ import javafx.scene.layout.Pane; import javafx.scene.shape.Polygon; import javafx.scene.shape.Rectangle; +import javafx.scene.shape.SVGPath; /** Singleton class, supplier of icon shapes and settings. */ public class IconManager { @@ -51,6 +52,14 @@ public void setStopIcon(Pane parent) { parent.getChildren().add(stopIcon); } + public void setMetronomeIcon(Pane parent) { + SVGPath metronomeIcon = new SVGPath(); + metronomeIcon.setContent("M7 25C7 25 10.662 7.0634 14.2803 7.0634C17.8986 7.0634 14.013 7 17.3848 7C20.7567 7 25 24.8431 25 24.8431L7 25Z"); + metronomeIcon.getStyleClass().addAll("playback-icon", "not-selected"); + parent.getChildren().clear(); + parent.getChildren().add(metronomeIcon); + } + public void selectIcon(Pane selectMe) { Node child = selectMe.getChildren().get(0); if (child instanceof Group) { diff --git a/src/main/java/com/utsusynth/utsu/controller/song/SongController.java b/src/main/java/com/utsusynth/utsu/controller/song/SongController.java index 9190c06a..a2235001 100644 --- a/src/main/java/com/utsusynth/utsu/controller/song/SongController.java +++ b/src/main/java/com/utsusynth/utsu/controller/song/SongController.java @@ -47,10 +47,7 @@ import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; +import javafx.scene.input.*; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BorderPane; import javafx.scene.shape.Rectangle; @@ -124,6 +121,9 @@ public class SongController implements EditorController, Localizable { @FXML // fx:id="stopIcon" private AnchorPane stopIcon; // Value injected by FXMLLoader + @FXML // fx:id="metronomeIcon" + private AnchorPane metronomeIcon; + @FXML // fx:id="quantizeChoiceBox" private ChoiceBox quantizeChoiceBox; // Value injected by FXMLLoader @@ -349,6 +349,14 @@ public List getVoicebankSuffixes() { iconManager.setStopIcon(stopIcon); stopIcon.setOnMousePressed(event -> iconManager.selectIcon(stopIcon)); stopIcon.setOnMouseReleased(event -> iconManager.deselectIcon(stopIcon)); + iconManager.setMetronomeIcon(metronomeIcon); + preferencesManager.getMetronomeEnabled().addListener(((observable, oldValue, newValue) -> { + if (newValue == true) { + iconManager.selectIcon(metronomeIcon); + } else { + iconManager.deselectIcon(metronomeIcon); + } + })); refreshView(); @@ -1370,4 +1378,8 @@ public void invokePlugin(File plugin) { } } } + + public void toggleMetronome(MouseEvent mouseEvent) { + preferencesManager.getMetronomeEnabled().set(!preferencesManager.getMetronomeEnabled().getValue()); + } } diff --git a/src/main/java/com/utsusynth/utsu/engine/Engine.java b/src/main/java/com/utsusynth/utsu/engine/Engine.java index 35b3f81d..a4012d1f 100644 --- a/src/main/java/com/utsusynth/utsu/engine/Engine.java +++ b/src/main/java/com/utsusynth/utsu/engine/Engine.java @@ -1,12 +1,12 @@ package com.utsusynth.utsu.engine; import com.google.common.base.Function; -import com.utsusynth.utsu.common.utils.RegionBounds; import com.utsusynth.utsu.common.StatusBar; import com.utsusynth.utsu.common.data.LyricConfigData; import com.utsusynth.utsu.common.exception.ErrorLogger; import com.utsusynth.utsu.common.quantize.Quantizer; import com.utsusynth.utsu.common.utils.PitchUtils; +import com.utsusynth.utsu.common.utils.RegionBounds; import com.utsusynth.utsu.engine.wavtool.UtsuWavtool; import com.utsusynth.utsu.engine.wavtool.Wavtool; import com.utsusynth.utsu.files.CacheManager; @@ -142,6 +142,16 @@ public boolean startPlayback( instrumentalPlayer.dispose(); } }); + + new Metronome( + mediaPlayer, + song.getTempo(), + String.format("file:%s", this.preferencesManager.getMetronomeFile().getAbsolutePath()), + this.preferencesManager.getMetronomeEnabled() + ); + + + mediaPlayer.play(); } return finalSong.isPresent(); diff --git a/src/main/java/com/utsusynth/utsu/engine/Metronome.java b/src/main/java/com/utsusynth/utsu/engine/Metronome.java new file mode 100644 index 00000000..26ce233b --- /dev/null +++ b/src/main/java/com/utsusynth/utsu/engine/Metronome.java @@ -0,0 +1,86 @@ +package com.utsusynth.utsu.engine; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableBooleanValue; +import javafx.collections.ObservableMap; +import javafx.scene.media.Media; +import javafx.scene.media.MediaMarkerEvent; +import javafx.scene.media.MediaPlayer; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.List; + +public class Metronome { + public Metronome( + MediaPlayer songPlayer, + Double t, + String clickUri, + BooleanProperty enabled + ) { + attach( + songPlayer, + new MediaPlayer(new Media( + clickUri + )), + generateMarkers(songPlayer.getTotalDuration().toSeconds(), t), + enabled + ); + } + + void attach( + MediaPlayer songPlayer, + MediaPlayer metronomePlayer, + List intervals, + ObservableBooleanValue enabled + ) { + ObservableMap mediaMarkers = songPlayer.getMedia().getMarkers(); + ThreadLocal key = new ThreadLocal<>(); + key.set(0); + + intervals.forEach(mme -> { + key.set(key.get() + 1); + mediaMarkers.put( + String.valueOf(key.get()), + mme + ); + }); + + ChangeListener enabledListener = (observable, oldValue, newValue) -> { + metronomePlayer.stop(); + }; + enabled.addListener(enabledListener); + songPlayer.setOnMarker((MediaMarkerEvent mme) -> { + if (enabled.get()) { + metronomePlayer.stop(); + metronomePlayer.play(); + } + }); + + Runnable onStop = songPlayer.getOnStopped(); + songPlayer.setOnStopped(() -> { + metronomePlayer.stop(); + metronomePlayer.dispose(); + + enabled.removeListener(enabledListener); + onStop.run(); + }); + } + + List generateMarkers( + double seconds, + double bpm + ) { + + ArrayList events = new ArrayList<>(); + double bps = bpm * (1.0 / 60.0); + double ms_beat = (1.0 / bps) * (1000.0); + + for (int i = 0; i < seconds * 1000; i+=ms_beat) { + events.add(new Duration(i)); + } + + return events; + } +} \ No newline at end of file diff --git a/src/main/java/com/utsusynth/utsu/files/AssetManager.java b/src/main/java/com/utsusynth/utsu/files/AssetManager.java index 5dd4c151..d4d2d10e 100644 --- a/src/main/java/com/utsusynth/utsu/files/AssetManager.java +++ b/src/main/java/com/utsusynth/utsu/files/AssetManager.java @@ -68,6 +68,7 @@ public boolean initializeAssets() throws IOException, SecurityException, URISynt } copyFile(SOUNDS_SOURCE, soundsPath, "silence.wav", "SILENCE_WAV"); copyFile(SOUNDS_SOURCE, soundsPath, "piano.wav", "PIANO_WAV"); + copyFile(SOUNDS_SOURCE, soundsPath, "metronome.wav", "METRONOME_WAV"); initializeVoicebank(); // Initialize executables. @@ -140,6 +141,10 @@ public File getPianoFile() { return new File(soundsPath, "piano.wav"); } + public File getMetronomeFile() { + return new File(soundsPath, "metronome.wav"); + } + public File getVoicePath() { return voicePath; } diff --git a/src/main/java/com/utsusynth/utsu/files/PreferencesManager.java b/src/main/java/com/utsusynth/utsu/files/PreferencesManager.java index 7d27fc93..0129da9f 100644 --- a/src/main/java/com/utsusynth/utsu/files/PreferencesManager.java +++ b/src/main/java/com/utsusynth/utsu/files/PreferencesManager.java @@ -36,6 +36,8 @@ public class PreferencesManager { private BooleanProperty showVoicebankFaceTemp; private BooleanProperty showVoicebankBodyTemp; + private BooleanProperty isMetronomeEnabled; + public PreferencesManager( @SettingsPath File settingsPath, DocumentBuilderFactory documentBuilderFactory, @@ -325,4 +327,18 @@ public File getVoicebankDefault() { public void setVoicebank(File voicebank) { preferences.put("voicebank", voicebank.getAbsolutePath()); } + + public File getMetronomeFile() { + return preferences.containsKey("metronome") + ? new File(preferences.get("metronome")) + : new File(defaultPreferences.get("metronome")); + } + + public BooleanProperty getMetronomeEnabled() { + if (isMetronomeEnabled == null) { + isMetronomeEnabled = new SimpleBooleanProperty(); + isMetronomeEnabled.setValue(false); + } + return isMetronomeEnabled; + } } diff --git a/src/main/resources/assets/sounds/metronome.wav b/src/main/resources/assets/sounds/metronome.wav new file mode 100644 index 00000000..425eabe2 Binary files /dev/null and b/src/main/resources/assets/sounds/metronome.wav differ diff --git a/src/main/resources/css/metronome.svg b/src/main/resources/css/metronome.svg new file mode 100644 index 00000000..4dd57903 --- /dev/null +++ b/src/main/resources/css/metronome.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/fxml/SongScene.fxml b/src/main/resources/fxml/SongScene.fxml index 98abb585..c831e1f8 100644 --- a/src/main/resources/fxml/SongScene.fxml +++ b/src/main/resources/fxml/SongScene.fxml @@ -26,6 +26,7 @@ +