Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 7e36590

Browse files
committed
Fix playing/pausing in b1.7.3 by mixing into Paulscode's Sound System directly
1 parent 8517aa6 commit 7e36590

File tree

8 files changed

+191
-27
lines changed

8 files changed

+191
-27
lines changed

build-logic/src/main/kotlin/kit_tunes.module.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ loom {
1414
mods.create(metadata.modId()) {
1515
sourceSet(sourceSets.main.get())
1616
}
17+
18+
mixin {
19+
useLegacyMixinAp.set(false) // So we can mixin to libraries
20+
}
1721
}
1822

1923
dependencies {

projects/kitten-sounds/b1.7.3/src/main/java/net/pixaurora/kitten_sounds/impl/MusicControlsImpl.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,30 @@
44

55
import net.pixaurora.kitten_heart.impl.music.control.MusicControls;
66
import net.pixaurora.kitten_heart.impl.music.control.PlaybackState;
7+
import paulscode.sound.Source;
78

89
public class MusicControlsImpl implements MusicControls {
9-
private String source;
10+
private String sourceName;
11+
private Source source;
1012
private final AtomicReference<PlaybackState> playbackState = new AtomicReference<>(PlaybackState.STOPPED);
1113

12-
public void channel(String source) {
14+
public void source(String sourceName, Source source) {
15+
this.sourceName = sourceName;
1316
this.source = source;
1417
}
1518

1619
@Override
1720
public void pause() {
18-
SoundEventsUtils.system().pause(this.source);
21+
if (sourceName != null) {
22+
SoundEventsUtils.system().pause(sourceName);
23+
}
1924
}
2025

2126
@Override
2227
public void unpause() {
23-
SoundEventsUtils.system().play(this.source);
28+
if (sourceName != null) {
29+
SoundEventsUtils.system().play(sourceName);
30+
}
2431
}
2532

2633
@Override
@@ -33,7 +40,9 @@ public void updatePlaybackState() {
3340
}
3441

3542
public PlaybackState computePlaybackState() {
36-
if (SoundEventsUtils.system().playing(this.source)) {
43+
if (this.source == null || this.source.channel == null) {
44+
return PlaybackState.STOPPED;
45+
} else if (this.source.channel.playing()) {
3746
return PlaybackState.PLAYING;
3847
} else {
3948
return PlaybackState.PAUSED;

projects/kitten-sounds/b1.7.3/src/main/java/net/pixaurora/kitten_sounds/impl/MusicPolling.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,42 @@
66
import net.minecraft.client.sound.system.SoundFile;
77
import net.pixaurora.kitten_heart.impl.EventHandling;
88
import net.pixaurora.kitten_heart.impl.music.progress.PolledListeningProgress;
9+
import net.pixaurora.kitten_heart.impl.music.progress.SongProgressTracker;
10+
import paulscode.sound.Source;
911

1012
public class MusicPolling {
11-
public static List<PolledSong> TRACKS_TO_POLL = new ArrayList<>();
12-
public static List<PolledSong> POLLED_TRACKS = new ArrayList<>();
13+
public static List<PolledSong<String>> TRACKS_TO_POLL = new ArrayList<>();
14+
public static List<PolledSong<Source>> POLLED_TRACKS = new ArrayList<>();
1315

1416
public static void onPlaySong(SoundFile sound, String source) {
1517
MusicControlsImpl controls = new MusicControlsImpl();
1618

1719
PolledListeningProgress progress = EventHandling
1820
.handleTrackStart(SoundEventsUtils.minecraftTypeToInternalType(sound), controls);
1921

20-
TRACKS_TO_POLL.add(new PolledSong(source, progress, controls));
22+
TRACKS_TO_POLL.add(new PolledSong<String>(source, progress, controls));
2123
}
2224

2325
public static void pollTrackProgress() {
2426
TRACKS_TO_POLL.removeIf((polledSong) -> {
25-
if (SoundEventsUtils.system().playing(polledSong.polled())) {
26-
POLLED_TRACKS.add(polledSong);
27+
Source source = SoundEventsUtils.system().soundLibrary.getSource(polledSong.polled());
28+
if (source != null && source.playing()) {
29+
POLLED_TRACKS.add(new PolledSong<Source>(source, polledSong));
30+
polledSong.controls().source(polledSong.polled(), source);
2731
return true;
2832
} else {
2933
return false;
3034
}
3135
});
3236

3337
POLLED_TRACKS.removeIf((polledSong) -> {
34-
if (!SoundEventsUtils.system().playing(polledSong.polled())) {
38+
boolean songConnected = polledSong.polled().playing() || polledSong.polled().paused();
39+
if (!songConnected) {
3540
EventHandling.handleTrackEnd(polledSong.progress());
3641

3742
return true;
3843
} else {
39-
polledSong.progress().measureProgress(polledSong);
44+
polledSong.progress().measureProgress((SongProgressTracker) (Object) polledSong.polled().channel);
4045
polledSong.controls().updatePlaybackState();
4146

4247
return false;
Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
package net.pixaurora.kitten_sounds.impl;
22

33
import net.pixaurora.kitten_heart.impl.music.progress.PolledListeningProgress;
4-
import net.pixaurora.kitten_heart.impl.music.progress.SongProgressTracker;
54

6-
public class PolledSong implements SongProgressTracker {
7-
private final String source;
5+
public class PolledSong<T> {
6+
private final T source;
87

98
private final PolledListeningProgress progress;
109
private final MusicControlsImpl controls;
1110

12-
public PolledSong(String source, PolledSong previous) {
11+
public PolledSong(T source, PolledSong<?> previous) {
1312
this.source = source;
1413
this.progress = previous.progress;
1514
this.controls = previous.controls;
1615
}
1716

18-
public PolledSong(String source, PolledListeningProgress progress, MusicControlsImpl controls) {
17+
public PolledSong(T source, PolledListeningProgress progress, MusicControlsImpl controls) {
1918
this.source = source;
2019
this.progress = progress;
2120
this.controls = controls;
2221
}
2322

24-
public String polled() {
23+
public T polled() {
2524
return this.source;
2625
}
2726

@@ -32,10 +31,4 @@ public PolledListeningProgress progress() {
3231
public MusicControlsImpl controls() {
3332
return this.controls;
3433
}
35-
36-
@Override
37-
public float kit_tunes$playbackPosition() {
38-
float millisPlayed = System.currentTimeMillis() - this.progress.startTime().toEpochMilli();
39-
return millisPlayed / 1000;
40-
}
4134
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package net.pixaurora.kitten_sounds.impl.mixin;
2+
3+
import java.nio.IntBuffer;
4+
5+
import org.lwjgl.BufferUtils;
6+
import org.lwjgl.openal.AL10;
7+
import org.lwjgl.openal.AL11;
8+
import org.spongepowered.asm.mixin.Mixin;
9+
import org.spongepowered.asm.mixin.injection.At;
10+
import org.spongepowered.asm.mixin.injection.Inject;
11+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
13+
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
14+
15+
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
16+
import com.llamalad7.mixinextras.sugar.Local;
17+
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
18+
19+
import net.pixaurora.kitten_heart.impl.music.progress.SongProgressTracker;
20+
import paulscode.sound.SoundSystemConfig;
21+
import paulscode.sound.libraries.ChannelLWJGLOpenAL;
22+
23+
/**
24+
* The following code for calculating milliseconds that were played is adapted
25+
* from an updated version of Paulscode's Soundsystem.
26+
* All credit goes to the true authors.
27+
*
28+
* Author: Paul Lamb
29+
* Website: http://www.paulscode.com
30+
*
31+
*/
32+
@Mixin(ChannelLWJGLOpenAL.class)
33+
public class ChannelLWJGLOpenALMixin implements SongProgressTracker {
34+
private float millisPreviouslyPlayed = 0;
35+
36+
@Override
37+
public float kit_tunes$playbackPosition() {
38+
return this.millisPlayed() / 1000f;
39+
}
40+
41+
private float millisPlayed() {
42+
// get number of samples played in current buffer
43+
float offset = (float) AL10.alGetSourcei(this.asChannel().ALSource.get(0), AL11.AL_BYTE_OFFSET);
44+
45+
offset = (((float) offset / this.bytesPerFrame()) / (float) this.asChannel().sampleRate)
46+
* 1000;
47+
48+
// add the milliseconds from stream-buffers that played previously
49+
if (this.asChannel().channelType == SoundSystemConfig.TYPE_STREAMING)
50+
offset += millisPreviouslyPlayed;
51+
52+
// Return millis played:
53+
return offset;
54+
}
55+
56+
private ChannelLWJGLOpenAL asChannel() {
57+
return (ChannelLWJGLOpenAL) (Object) this;
58+
}
59+
60+
@Inject(method = "queueBuffer([B)Z", at = @At(value = "INVOKE", target = "Lorg/lwjgl/openal/AL10;alBufferData(IILjava/nio/ByteBuffer;I)V"), locals = LocalCapture.CAPTURE_FAILHARD)
61+
public void beforeBuffersQueued(byte[] buffer, CallbackInfoReturnable<Boolean> cir, IntBuffer newBuffer) {
62+
if (AL10.alIsBuffer(newBuffer.get(0))) {
63+
millisPreviouslyPlayed += millisInBuffer(newBuffer.get(0));
64+
}
65+
66+
this.asChannel().checkALError();
67+
}
68+
69+
@ModifyExpressionValue(method = "feedRawAudioData([B)I", at = @At(value = "INVOKE", target = "Lpaulscode/sound/libraries/ChannelLWJGLOpenAL;errorCheck(ZLjava/lang/String;)Z"))
70+
public boolean beforeRawBuffersQueued(boolean errorCheck, @Local LocalRef<IntBuffer> newBuffer) {
71+
if (!errorCheck) {
72+
this.beforeRawBuffersQueued0(newBuffer.get());
73+
74+
errorCheck = this.beforeRawBuffersQueued1(newBuffer);
75+
}
76+
77+
return errorCheck;
78+
}
79+
80+
private void beforeRawBuffersQueued0(IntBuffer newBuffer) {
81+
int i;
82+
newBuffer.rewind();
83+
while (newBuffer.hasRemaining()) {
84+
i = newBuffer.get();
85+
if (AL10.alIsBuffer(i)) {
86+
millisPreviouslyPlayed += millisInBuffer(i);
87+
}
88+
this.asChannel().checkALError();
89+
}
90+
AL10.alDeleteBuffers(newBuffer);
91+
this.asChannel().checkALError();
92+
}
93+
94+
private boolean beforeRawBuffersQueued1(LocalRef<IntBuffer> newBuffer) {
95+
newBuffer.set(BufferUtils.createIntBuffer(1));
96+
AL10.alGenBuffers(newBuffer.get());
97+
98+
return this.asChannel().errorCheck(this.asChannel().checkALError(),
99+
"Error generating stream buffers in method 'preLoadBuffers'");
100+
}
101+
102+
@Inject(method = "flush()V", at = @At(value = "RETURN"))
103+
public void afterFlush(CallbackInfo cInfo) {
104+
this.millisPreviouslyPlayed = 0;
105+
}
106+
107+
@ModifyExpressionValue(method = { "stop()V",
108+
"rewind()V" }, at = @At(value = "INVOKE", target = "Lpaulscode/sound/libraries/ChannelLWJGLOpenAL;checkALError()Z"))
109+
public boolean afterStop(boolean stopErrored) {
110+
if (!stopErrored) {
111+
millisPreviouslyPlayed = 0;
112+
}
113+
114+
return stopErrored;
115+
}
116+
117+
private float bytesPerFrame() {
118+
switch (this.asChannel().ALformat) {
119+
case AL10.AL_FORMAT_MONO8:
120+
return 1f;
121+
case AL10.AL_FORMAT_MONO16:
122+
return 2f;
123+
case AL10.AL_FORMAT_STEREO8:
124+
return 2f;
125+
case AL10.AL_FORMAT_STEREO16:
126+
return 4f;
127+
default:
128+
return 1f;
129+
}
130+
}
131+
132+
/**
133+
* Returns the number of milliseconds of audio contained in specified buffer.
134+
*
135+
* @return milliseconds, or 0 if unable to calculate.
136+
*/
137+
private float millisInBuffer(int alBufferi) {
138+
return (((float) AL10.alGetBufferi(alBufferi, AL10.AL_SIZE)
139+
/ (float) AL10.alGetBufferi(alBufferi, AL10.AL_CHANNELS)
140+
/ ((float) AL10.alGetBufferi(alBufferi, AL10.AL_BITS) / 8.0f) / (float) this.asChannel().sampleRate)
141+
* 1000);
142+
}
143+
}

projects/kitten-sounds/b1.7.3/src/main/java/net/pixaurora/kitten_sounds/impl/mixin/SoundEngineMixin.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,16 @@ private SoundFile onRecordQueued(SoundFile sound) {
5555
/*
5656
* There's a short time between when we queue a song and it registers as
5757
* "playing."
58-
* Because of this, we skip ticking music cooldowns until it's playing.
58+
*
59+
* The sound engine also shouldn't tick music when we have a track paused,
60+
* as it will assume it stopped playing.
61+
*
62+
* Because of both of these, we skip ticking music cooldowns until all music has
63+
* stopped.
5964
*/
6065
@ModifyExpressionValue(method = "tickMusic", at = @At(value = "FIELD", target = "Lnet/minecraft/client/sound/system/SoundEngine;started:Z", opcode = Opcodes.GETSTATIC))
61-
private boolean isWaitingForSongToStart(boolean started) {
62-
return started && MusicPolling.TRACKS_TO_POLL.isEmpty();
66+
private boolean tickMusicCooldowns(boolean started) {
67+
return started && MusicPolling.TRACKS_TO_POLL.isEmpty() && MusicPolling.POLLED_TRACKS.isEmpty();
6368
}
6469

6570
@Inject(method = "tickMusic", at = @At("HEAD"), cancellable = true)

projects/kitten-sounds/b1.7.3/src/main/resources/kitten_sounds.accesswidener

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ accessible field net/minecraft/client/Minecraft INSTANCE
44
accessible field net/minecraft/client/sound/system/SoundEngine system Lpaulscode/sound/SoundSystem;
55
accessible field net/minecraft/client/sound/system/SoundEngine musicCooldown I
66
accessible method net/minecraft/client/Minecraft getOs ()Lnet/minecraft/client/Minecraft$OS;
7+
8+
accessible method paulscode/sound/libraries/ChannelLWJGLOpenAL checkALError ()Z
9+
accessible method paulscode/sound/Channel errorCheck (ZLjava/lang/String;)Z
10+
accessible field paulscode/sound/SoundSystem soundLibrary Lpaulscode/sound/Library;

projects/kitten-sounds/b1.7.3/src/main/resources/kitten_sounds.mixins.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"compatibilityLevel": "JAVA_8",
66
"mixins": [ ],
77
"client": [
8+
"ChannelLWJGLOpenALMixin",
89
"MinecraftMixin",
910
"SoundEngineMixin",
1011
"ResourceDownloadThreadMixin"

0 commit comments

Comments
 (0)