Skip to content

Commit c26b8b0

Browse files
committed
Added timing jitter option to audio export
1 parent 8d9c232 commit c26b8b0

4 files changed

Lines changed: 34 additions & 4 deletions

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies {
3232
}
3333
includeInJar "net.java.dev.jna:jna:5.18.1"
3434

35-
includeInJar "net.raphimc:thingl:0.1.0-20251227.185131-8"
35+
includeInJar "net.raphimc:thingl:0.1.1-20260125.160939-1:java17"
3636
includeInJar "org.lwjgl:lwjgl-glfw:3.4.0"
3737
includeInJar "org.lwjgl:lwjgl-opengl:3.4.0"
3838
includeInJar "org.lwjgl:lwjgl-freetype:3.4.0"

src/main/java/net/raphimc/noteblocktool/audio/player/impl/SongRenderer.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
import net.raphimc.noteblocktool.audio.system.AudioSystem;
2525

2626
import java.util.Map;
27+
import java.util.concurrent.ThreadLocalRandom;
2728
import java.util.function.Function;
2829

2930
public class SongRenderer extends AudioSystemSongPlayer {
3031

3132
private boolean isRunning;
33+
private boolean timingJitter;
3234

3335
public SongRenderer(final Song song, final Function<Map<String, byte[]>, AudioSystem> audioSystemSupplier) {
3436
super(song, audioSystemSupplier);
@@ -55,11 +57,22 @@ public boolean isRunning() {
5557
return this.isRunning;
5658
}
5759

60+
public void setTimingJitter(final boolean timingJitter) {
61+
this.timingJitter = timingJitter;
62+
}
63+
5864
public float[] renderTick() {
5965
if (this.isRunning()) {
6066
this.tick();
6167
}
62-
return this.getAudioSystem().render(MathUtil.millisToFrameCount(this.getAudioSystem().getLoopbackAudioFormat(), 1000F / this.getCurrentTicksPerSecond()));
68+
float millis = 1000F / this.getCurrentTicksPerSecond();
69+
if (this.timingJitter) {
70+
millis += ThreadLocalRandom.current().nextFloat(-1F, 1F);
71+
if (millis <= 0F) {
72+
millis = 0.1F;
73+
}
74+
}
75+
return this.getAudioSystem().render(MathUtil.millisToFrameCount(this.getAudioSystem().getLoopbackAudioFormat(), millis));
6376
}
6477

6578
public float[] renderSong() throws InterruptedException {

src/main/java/net/raphimc/noteblocktool/elements/VerticalFileChooser.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ public VerticalFileChooser() {
2828

2929
private void changeListToVertical(final JComponent component) {
3030
for (Component c : component.getComponents()) {
31-
if (c instanceof JList<?>) {
32-
JList<?> list = (JList<?>) c;
31+
if (c instanceof final JList<?> list) {
3332
list.setLayoutOrientation(JList.VERTICAL);
3433
}
3534
if (c instanceof JComponent) {

src/main/java/net/raphimc/noteblocktool/frames/ExportFrame.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.\nThis 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

Comments
 (0)