@@ -76,6 +76,7 @@ public class ExportFrame extends JFrame {
7676 private final JComboBox <Channels > channels = new JComboBox <>(Channels .values ());
7777 private final JLabel volumeLabel = new JLabel ("Volume:" );
7878 private final JSlider volume = new JSlider (0 , 100 , 50 );
79+ private final JCheckBox timingJitter = new JCheckBox ("Artificial Timing Jitter" );
7980 private JPanel progressPanel ;
8081 private final JProgressBar progressBar = new JProgressBar ();
8182 private final JButton exportButton = new JButton ("Export" );
@@ -138,6 +139,9 @@ private void initComponents() {
138139 this .volume .setPaintTicks (true );
139140 this .volume .setPaintLabels (true );
140141 });
142+ GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .timingJitter , () -> {
143+ 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." );
144+ });
141145
142146 GBC .create (root ).grid (0 , gridy ++).insets (5 , 5 , 0 , 5 ).width (1 ).width (2 ).weight (1 , 1 ).fill (GBC .BOTH ).add (() -> {
143147 JScrollPane scrollPane = new JScrollPane ();
@@ -181,6 +185,7 @@ private void updateVisibility() {
181185
182186 this .volumeLabel .setVisible (outputFormat .isAudioFile ());
183187 this .volume .setVisible (outputFormat .isAudioFile ());
188+ this .timingJitter .setVisible (outputFormat .isAudioFile ());
184189 }
185190
186191 private void initFrameHandler () {
@@ -218,6 +223,7 @@ private void export() {
218223 this .bitDepth .setEnabled (true );
219224 this .channels .setEnabled (true );
220225 this .volume .setEnabled (true );
226+ this .timingJitter .setEnabled (true );
221227 this .progressPanel .removeAll ();
222228 this .exportButton .setText ("Export" );
223229 this .progressBar .setValue (0 );
@@ -235,6 +241,7 @@ private void export() {
235241 this .bitDepth .setEnabled (false );
236242 this .channels .setEnabled (false );
237243 this .volume .setEnabled (false );
244+ this .timingJitter .setEnabled (false );
238245 this .progressPanel .removeAll ();
239246 this .exportButton .setText ("Cancel" );
240247 this .progressBar .setValue (0 );
@@ -318,6 +325,9 @@ private void doExport(final File outFile) {
318325 this .exportSong (this .loadedSongs .get (0 ), outFile , progressConsumer .apply (progressBar ));
319326 } catch (InterruptedException ignored ) {
320327 } catch (Throwable t ) {
328+ if (t .getCause () instanceof InterruptedException ) {
329+ return ;
330+ }
321331 t .printStackTrace ();
322332 JOptionPane .showMessageDialog (this , "Failed to export song:\n " + this .loadedSongs .get (0 ).file ().getAbsolutePath () + "\n " + t .getClass ().getSimpleName () + ": " + t .getMessage (), "Error" , JOptionPane .ERROR_MESSAGE );
323333 } finally {
@@ -350,6 +360,9 @@ private void doExport(final File outFile) {
350360 });
351361 } catch (InterruptedException ignored ) {
352362 } catch (Throwable t ) {
363+ if (t .getCause () instanceof InterruptedException ) {
364+ return ;
365+ }
353366 t .printStackTrace ();
354367 uiQueue .offer (() -> {
355368 songPanel .remove (progressBar );
@@ -384,6 +397,9 @@ private void doExport(final File outFile) {
384397 }
385398 } catch (InterruptedException ignored ) {
386399 } catch (Throwable t ) {
400+ if (t .getCause () instanceof InterruptedException ) {
401+ return ;
402+ }
387403 t .printStackTrace ();
388404 JOptionPane .showMessageDialog (this , "Failed to export songs:\n " + t .getMessage (), "Error" , JOptionPane .ERROR_MESSAGE );
389405 } finally {
@@ -396,6 +412,7 @@ private void doExport(final File outFile) {
396412 this .bitDepth .setEnabled (true );
397413 this .channels .setEnabled (true );
398414 this .volume .setEnabled (true );
415+ this .timingJitter .setEnabled (true );
399416 this .exportButton .setText ("Export" );
400417 this .progressBar .setValue (this .loadedSongs .size ());
401418 this .progressBar .revalidate ();
@@ -424,6 +441,7 @@ private void exportSong(final ListFrame.LoadedSong song, final File file, final
424441 case BASS -> new ProgressSongRenderer (song .song (), progressConsumer , soundData -> new BassAudioSystem (soundData , MAX_SOUNDS , renderAudioFormat ));
425442 };
426443 songRenderer .getAudioSystem ().setMasterVolume (this .volume .getValue () / 100F );
444+ songRenderer .setTimingJitter (this .timingJitter .isSelected ());
427445 final float [] samples ;
428446 try {
429447 samples = songRenderer .renderSong ();
0 commit comments