@@ -41,19 +41,22 @@ public class SongPlayerFrame extends JFrame {
4141 private static SongPlayerFrame instance ;
4242 private static Point lastPosition ;
4343 private static int lastVolume = 50 ;
44+ private static boolean lastTimingJitter = false ;
4445 private static int lastMaxSounds = 4096 ;
4546
4647 public static void open (final Song song ) {
4748 if (instance != null && instance .isVisible ()) {
4849 lastPosition = instance .getLocation ();
4950 lastVolume = instance .volumeSlider .getValue ();
51+ lastTimingJitter = instance .timingJitter .isSelected ();
5052 lastMaxSounds = (int ) instance .maxSoundsSpinner .getValue ();
5153 instance .dispose ();
5254 }
5355 SwingUtilities .invokeLater (() -> {
5456 instance = new SongPlayerFrame (song );
5557 if (lastPosition != null ) instance .setLocation (lastPosition );
5658 instance .volumeSlider .setValue (lastVolume );
59+ instance .timingJitter .setSelected (lastTimingJitter );
5760 instance .maxSoundsSpinner .setValue (lastMaxSounds );
5861 instance .playStopButton .doClick (0 );
5962 instance .setVisible (true );
@@ -65,6 +68,7 @@ public static void open(final Song song) {
6568 private final Timer updateTimer ;
6669 private final JSlider volumeSlider = new JSlider (0 , 100 , lastVolume );
6770 private final JSpinner maxSoundsSpinner = new JSpinner (new SpinnerNumberModel (lastMaxSounds , 64 , 131_070 , 64 ));
71+ private final JCheckBox timingJitter = new JCheckBox ("Artificial Timing Jitter" , lastTimingJitter );
6872 private final JButton playStopButton = new JButton ("Play" );
6973 private final JButton pauseResumeButton = new JButton ("Pause" );
7074 private final JButton openVisualizerButton = new JButton ("Open Visualizer" );
@@ -116,6 +120,16 @@ private void initComponents() {
116120 });
117121 });
118122
123+ GBC .create (northPanel ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .timingJitter , () -> {
124+ this .timingJitter .setToolTipText ("Adds slight timing jitter (±1ms) to make the song sound more natural and less artificial.\n This emulates the behaviour of playing the song in Note Block Studio." );
125+ this .timingJitter .addChangeListener (e -> {
126+ if (this .songRenderer != null ) {
127+ this .songRenderer .setTimingJitter (this .timingJitter .isSelected ());
128+ }
129+ lastTimingJitter = this .timingJitter .isSelected ();
130+ });
131+ });
132+
119133 GBC .create (northPanel ).grid (0 , gridy ).insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (new JLabel ("Max Sounds:" ));
120134 GBC .create (northPanel ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .maxSoundsSpinner , () -> {
121135 this .maxSoundsSpinner .addChangeListener (e -> lastMaxSounds = (int ) this .maxSoundsSpinner .getValue ());
@@ -237,6 +251,7 @@ private void initSongPlayer() {
237251 this .closeSongPlayerAndVisualizer ();
238252 this .songRenderer = new RealtimeSongRenderer (this .song , maxSounds , true , false , PLAYBACK_AUDIO_FORMAT );
239253 this .songRenderer .setMasterVolume (this .volumeSlider .getValue () / 100F );
254+ this .songRenderer .setTimingJitter (this .timingJitter .isSelected ());
240255 }
241256 }
242257
0 commit comments