From aa36b49c50015fffae8a9c614c42c9ae09c6ffaf Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 19 Dec 2021 19:50:43 +0000 Subject: [PATCH] Enqueue audio when receiving it While Minecraft will automatically push a new buffer when one is exhausted, this doesn't help if there's only a single buffer in the queue, and you end up with stutter. By enquing a buffer when receiving sound we ensure there's always something queued. I'm not 100% happy with this solution, but it does alleviate some of the concerns in #993. Also reduce the size of the client buffer to 0.5s from 1.5s. This is still enough to ensure seamless audio when the server is running slow (I tested at 13 tps, but should be able to go much worse). --- .../computercraft/client/sound/DfpwmStream.java | 9 ++++++++- .../client/sound/SpeakerInstance.java | 14 +++++++++++++- .../computercraft/client/sound/SpeakerManager.java | 3 +++ .../computercraft/client/sound/SpeakerSound.java | 4 ++++ .../shared/peripheral/speaker/DfpwmState.java | 2 +- .../peripheral/speaker/SpeakerPeripheral.java | 2 +- src/main/resources/META-INF/accesstransformer.cfg | 5 +++++ 7 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java b/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java index 1448c5642..9a5e73ce2 100644 --- a/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java +++ b/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java @@ -114,7 +114,9 @@ class DfpwmStream implements IAudioStream } result.flip(); - return result; + + // This is naughty, but ensures we're not enqueuing empty buffers when the stream is exhausted. + return result.remaining() == 0 ? null : result; } @Override @@ -122,4 +124,9 @@ class DfpwmStream implements IAudioStream { buffers.clear(); } + + public boolean isEmpty() + { + return buffers.isEmpty(); + } } diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java b/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java index b328a65f0..b5cbfe5ef 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java @@ -28,8 +28,20 @@ public class SpeakerInstance public synchronized void pushAudio( ByteBuf buffer ) { - if( currentStream == null ) currentStream = new DfpwmStream(); + SpeakerSound sound = this.sound; + + DfpwmStream stream = currentStream; + if( stream == null ) stream = currentStream = new DfpwmStream(); + boolean exhausted = stream.isEmpty(); currentStream.push( buffer ); + + // If we've got nothing left in the buffer, enqueue an additional one just in case. + if( exhausted && sound != null && sound.stream == stream && sound.source != null ) + { + sound.executor.execute( () -> { + if( !sound.source.stopped() ) sound.source.pumpBuffers( 1 ); + } ); + } } public void playAudio( Vector3d position, float volume ) diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java b/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java index bae3cf801..c7fa9b28b 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java @@ -32,6 +32,9 @@ public class SpeakerManager event.getSource().attachBufferStream( sound.stream ); event.getSource().play(); + + sound.source = event.getSource(); + sound.executor = event.getManager().executor; } public static SpeakerInstance getSound( UUID source ) diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java b/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java index 7f6d78a67..0ca4672b8 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java @@ -8,14 +8,18 @@ package dan200.computercraft.client.sound; import net.minecraft.client.audio.IAudioStream; import net.minecraft.client.audio.ITickableSound; import net.minecraft.client.audio.LocatableSound; +import net.minecraft.client.audio.SoundSource; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.vector.Vector3d; import javax.annotation.Nullable; +import java.util.concurrent.Executor; public class SpeakerSound extends LocatableSound implements ITickableSound { + SoundSource source; + Executor executor; DfpwmStream stream; SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vector3d position, float volume, float pitch ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java index af53ced42..3eaf5167e 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java @@ -28,7 +28,7 @@ class DfpwmState * The minimum size of the client's audio buffer. Once we have less than this on the client, we should send another * batch of audio. */ - private static final long CLIENT_BUFFER = (long) (SECOND * 1.5); + private static final long CLIENT_BUFFER = (long) (SECOND * 0.5); private static final int PREC = 10; diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java index 01fbf5e06..dc896cb43 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java @@ -343,7 +343,7 @@ public abstract class SpeakerPeripheral implements IPeripheral // TODO: Use ArgumentHelpers instead? int length = audio.length(); if( length <= 0 ) throw new LuaException( "Cannot play empty audio" ); - if( length > 1024 * 16 * 8 ) throw new LuaException( "Audio data is too large" ); + if( length > 128 * 1024 ) throw new LuaException( "Audio data is too large" ); DfpwmState state; synchronized( lock ) diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 55977f889..2b28a1390 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -2,7 +2,12 @@ public net.minecraft.client.renderer.FirstPersonRenderer func_178100_c(F)F # getMapAngleFromPitch public net.minecraft.client.renderer.FirstPersonRenderer func_228401_a_(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;IFFLnet/minecraft/util/HandSide;)V # renderArmFirstPerson public net.minecraft.client.renderer.FirstPersonRenderer func_228403_a_(Lcom/mojang/blaze3d/matrix/MatrixStack;Lnet/minecraft/client/renderer/IRenderTypeBuffer;ILnet/minecraft/util/HandSide;)V # renderArm + # ClientTableFormatter public net.minecraft.client.gui.NewChatGui func_146234_a(Lnet/minecraft/util/text/ITextComponent;I)V # printChatMessageWithOptionalDeletion public net.minecraft.client.gui.NewChatGui func_146242_c(I)V # deleteChatLine public net.minecraft.client.Minecraft field_71462_r # currentScreen + +# SpeakerInstance/SpeakerManager +public net.minecraft.client.audio.SoundSource func_216421_a(I)V # pumpBuffers +public net.minecraft.client.audio.SoundEngine field_217940_j # executor