From 43f776034a0dc4fc46870d67cb05ba300cebff01 Mon Sep 17 00:00:00 2001 From: Garrett Gu Date: Mon, 19 Aug 2019 15:54:46 -0500 Subject: [PATCH] Greatly reduce audio latency, remove popping sound when loading saves --- src/org/the429ers/gameboy/GameBoy.java | 9 +++++-- src/org/the429ers/gameboy/SoundChannel.java | 30 +++++++++++---------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/org/the429ers/gameboy/GameBoy.java b/src/org/the429ers/gameboy/GameBoy.java index 6ec1429..1a6a253 100644 --- a/src/org/the429ers/gameboy/GameBoy.java +++ b/src/org/the429ers/gameboy/GameBoy.java @@ -7,6 +7,7 @@ import java.text.SimpleDateFormat; import java.util.*; +import javax.sound.sampled.SourceDataLine; import javax.swing.*; class MainMenuBar extends MenuBar { @@ -223,6 +224,8 @@ public class GameBoy extends JFrame{ InputStream loadFile = null; LinkedList autoSaves = new LinkedList<>(); + SourceDataLine sourceDL; + private static GameBoy gb; public static GameBoy getInstance() { @@ -255,6 +258,7 @@ public void windowOpened(WindowEvent e) { } }; public void switchRom(String newRom) { + this.sourceDL = mmu.soundChip.getSourceDL(); this.romFileName = newRom; if(mmu != null) mmu.cleanUp(); mmu = new MMU(newRom); @@ -271,11 +275,10 @@ public void switchRom(String newRom) { ppu = new PPU(mmu, gbs); } mmu.setPPU(ppu); + mmu.soundChip.setSourceDL(this.sourceDL); cable = new LinkCable(mmu, cpu.interruptHandler); joypad = new Joypad(mmu, cpu.interruptHandler); gbs.addKeyListener(joypad); - - } public GameBoy(String fileName) { @@ -317,6 +320,7 @@ public GameBoy(String fileName) { } public void saveState() { + this.sourceDL = this.mmu.soundChip.getSourceDL(); try { ObjectOutputStream saveState = new ObjectOutputStream(this.saveFile); saveState.writeObject(mmu); @@ -351,6 +355,7 @@ public void loadState() { } gbs.addKeyListener(this.mmu.getJoypad()); ppu.setGBS(gbs); + this.mmu.soundChip.setSourceDL(this.sourceDL); } public void tick() { diff --git a/src/org/the429ers/gameboy/SoundChannel.java b/src/org/the429ers/gameboy/SoundChannel.java index 2417e6e..7dff695 100644 --- a/src/org/the429ers/gameboy/SoundChannel.java +++ b/src/org/the429ers/gameboy/SoundChannel.java @@ -28,10 +28,12 @@ class SoundChip implements Serializable { public static final int WAVE = 2; public static final int NOISE = 3; - private transient SourceDataLine sourceDL; + transient SourceDataLine sourceDL; - byte[] masterBuffer = new byte[2 * SAMPLES_PER_FRAME]; - byte[] tempBuffer = new byte[SAMPLES_PER_FRAME]; + byte[] masterBuffer = new byte[6 * SAMPLES_PER_FRAME]; + byte[] tempBuffer = new byte[3 * SAMPLES_PER_FRAME]; + + long totalSamplesWritten = 0; boolean[] leftEnabled = new boolean[4]; boolean[] rightEnabled = new boolean[4]; @@ -40,16 +42,13 @@ class SoundChip implements Serializable { Arrays.fill(rightEnabled, true); } - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - - try { - sourceDL = AudioSystem.getSourceDataLine(AUDIO_FORMAT); - sourceDL.open(AUDIO_FORMAT); - sourceDL.start(); - } catch (LineUnavailableException e) { - e.printStackTrace(); - } + public void setSourceDL(SourceDataLine sourceDL){ + this.sourceDL = sourceDL; + this.totalSamplesWritten = sourceDL.getLongFramePosition(); + } + + public SourceDataLine getSourceDL(){ + return this.sourceDL; } SoundChip() { @@ -82,7 +81,10 @@ public void tick() { e.printStackTrace(); } } - int samplesToWrite = Math.min(sourceDL.available() / 3, SAMPLES_PER_FRAME); + long residualSamples = totalSamplesWritten - sourceDL.getLongFramePosition(); + int samplesToWrite = Math.max(0, (int)(3 * SAMPLES_PER_FRAME - residualSamples)); //try to keep 3 frames buffered at all times + samplesToWrite = Math.min(sourceDL.available() / 2, Math.min(3 * SAMPLES_PER_FRAME, samplesToWrite)); //never want to block here + totalSamplesWritten += samplesToWrite; Arrays.fill(masterBuffer, (byte) 0);