refactor: improve a lot of stuff about music, and also stereo support for NBS

This commit is contained in:
Chayapak Supasakul 2025-03-03 16:05:10 +07:00
parent 11d89993b2
commit 1034518f5f
Signed by: ChomeNS
SSH key fingerprint: SHA256:0YoxhdyXsgbc0nfeB2N6FYE60mxMU7DS4uCUMaw2mvA
7 changed files with 101 additions and 49 deletions

View file

@ -220,7 +220,7 @@ public class MusicCommand {
mergedList.addAll(files);
final Component component = Component.translatable("Songs - %s", Component.join(JoinConfiguration.separator(Component.space()), mergedList)).color(NamedTextColor.GREEN);
((Audience) MinecraftClient.getInstance().player).sendMessage(component);
MinecraftClient.getInstance().player.sendMessage(component);
return 1;
}

View file

@ -5,7 +5,7 @@ import land.chipmunk.chipmunkmod.song.Song;
import land.chipmunk.chipmunkmod.song.SongLoaderException;
import land.chipmunk.chipmunkmod.song.SongLoaderThread;
import land.chipmunk.chipmunkmod.util.MathUtilities;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
@ -14,7 +14,6 @@ import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import java.io.File;
@ -55,35 +54,42 @@ public class SongPlayer {
// TODO: Less duplicate code
public void loadSong (Path location) {
final ClientPlayerEntity player = client.player;
if (player == null) return;
if (loaderThread != null) {
((Audience) client.player).sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
player.sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
return;
}
try {
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
((Audience) client.player).sendMessage(Component.translatable("Loading %s", Component.text(location.getFileName().toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
_loaderThread.start();
loaderThread = _loaderThread;
loaderThread = new SongLoaderThread(location);
player.sendMessage(Component.translatable("Loading %s", Component.text(location.getFileName().toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
loaderThread.start();
} catch (SongLoaderException e) {
((Audience) client.player).sendMessage(Component.translatable("Failed to load song: %s", e.message.getString()).color(NamedTextColor.RED));
player.sendMessage(Component.translatable("Failed to load song: %s", e.message.getString()).color(NamedTextColor.RED));
loaderThread = null;
}
}
public void loadSong (URL location) {
final ClientPlayerEntity player = client.player;
if (player == null) return;
if (loaderThread != null) {
((Audience) client.player).sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
player.sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
return;
}
try {
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
((Audience) client.player).sendMessage(Component.translatable("Loading %s", Component.text(location.toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
player.sendMessage(Component.translatable("Loading %s", Component.text(location.toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
_loaderThread.start();
loaderThread = _loaderThread;
} catch (SongLoaderException e) {
((Audience) client.player).sendMessage(Component.translatable("Failed to load song: %s", e.message.getString()).color(NamedTextColor.RED));
player.sendMessage(Component.translatable("Failed to load song: %s", e.message.getString()).color(NamedTextColor.RED));
loaderThread = null;
}
}
@ -101,12 +107,12 @@ public class SongPlayer {
return;
}
if (loaderThread != null && !loaderThread.isAlive()) {
if (loaderThread != null && !loaderThread.isAlive() && client.player != null) {
if (loaderThread.exception != null) {
((Audience) client.player).sendMessage(Component.translatable("Failed to load song: %s", loaderThread.exception.message.getString()).color(NamedTextColor.RED));
client.player.sendMessage(Component.translatable("Failed to load song: %s", loaderThread.exception.message.getString()).color(NamedTextColor.RED));
} else {
songQueue.add(loaderThread.song);
((Audience) client.player).sendMessage(Component.translatable("Added %s to the song queue", Component.empty().append(loaderThread.song.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
client.player.sendMessage(Component.translatable("Added %s to the song queue", Component.empty().append(loaderThread.song.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
}
loaderThread = null;
}
@ -115,7 +121,16 @@ public class SongPlayer {
if (songQueue.isEmpty()) return;
currentSong = songQueue.poll();
((Audience) client.player).sendMessage(Component.translatable("Now playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
if (client.player != null) client.player.sendMessage(
Component
.translatable(
"Now playing %s",
Component.empty()
.append(currentSong.name)
.color(NamedTextColor.DARK_GREEN)
)
.color(NamedTextColor.GREEN)
);
currentSong.play();
}
@ -123,7 +138,7 @@ public class SongPlayer {
else ticksUntilPausedActionbar = 20;
try {
if (!useCore && actionbar && client.player != null) ((Audience) client.player).sendActionBar(generateActionbar());
if (!useCore && actionbar && client.player != null) client.player.sendActionBar(generateActionbar());
else if (actionbar) CommandCore.INSTANCE.run("title " + SELECTOR + " actionbar " + GsonComponentSerializer.gson().serialize(generateActionbar()));
} catch (Exception e) {
e.printStackTrace();
@ -134,7 +149,7 @@ public class SongPlayer {
handlePlaying();
if (currentSong.finished()) {
((Audience) client.player).sendMessage(Component.translatable("Finished playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
if (client.player != null) client.player.sendMessage(Component.translatable("Finished playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
currentSong = null;
}
}
@ -211,24 +226,37 @@ public class SongPlayer {
if (!useCore && client.player != null) {
final float floatingPitch = (float) (0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12))));
final String[] thing = note.instrument.sound.split(":");
if (thing[1] == null) return; // idk if this can be null but ill just protect it for now i guess
final String sound = note.instrument.sound;
client.submit(() -> client.world.playSound(
client.player.getX(),
client.player.getY(),
client.player.getZ(),
SoundEvent.of(Identifier.of(thing[0], thing[1])),
client.player.getX() + note.position.getX(),
client.player.getY() + note.position.getY(),
client.player.getZ() + note.position.getZ(),
SoundEvent.of(Identifier.of(Key.MINECRAFT_NAMESPACE, sound)),
SoundCategory.RECORDS,
note.volume,
floatingPitch,
true
));
} else {
final float floatingPitch = MathUtilities.clamp((float) (0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12)))), 0F, 2F);
final double floatingPitch = MathUtilities.clamp(0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12))), 0F, 2F);
CommandCore.INSTANCE.run("execute as " + SELECTOR + " at @s run playsound " + note.instrument.sound + " record @s ~ ~ ~ " + note.volume + " " + floatingPitch);
CommandCore.INSTANCE.run(
String.format(
"execute as %s at @s run playsound %s record @s ^%f ^%f ^%f %f %f",
SELECTOR,
note.instrument.sound,
note.position.getX(),
note.position.getY(),
note.position.getZ(),
note.volume,
floatingPitch
)
);
}
} catch (Exception e) {
e.printStackTrace();

View file

@ -34,16 +34,16 @@ public class Instrument {
this.id = id;
this.name = name;
this.offset = offset;
this.sound = "minecraft:block.note_block." + name;
this.sound = "block.note_block." + name;
}
public static Instrument of(String sound) {
return new Instrument(-1, null, 0, sound);
}
private static Instrument[] values = {HARP, BASEDRUM, SNARE, HAT, BASS, FLUTE, BELL, GUITAR, CHIME, XYLOPHONE, IRON_XYLOPHONE, COW_BELL, DIDGERIDOO, BIT, BANJO, PLING};
private static final Instrument[] VALUES = {HARP, BASEDRUM, SNARE, HAT, BASS, FLUTE, BELL, GUITAR, CHIME, XYLOPHONE, IRON_XYLOPHONE, COW_BELL, DIDGERIDOO, BIT, BANJO, PLING};
public static Instrument fromId(int id) {
return values[id];
return VALUES[id];
}
}

View file

@ -1,6 +1,7 @@
package land.chipmunk.chipmunkmod.song;
import land.chipmunk.chipmunkmod.util.DownloadUtilities;
import net.minecraft.util.math.Vec3d;
import java.io.*;
import java.net.*;
@ -143,7 +144,7 @@ public class MidiConverter {
float volume = (float) velocity / 127.0f;
long time = microTime / 1000L;
return new Note(instrument, pitch, volume, time);
return new Note(instrument, pitch, volume, time, Vec3d.ZERO);
}
private static Note getMidiPercussionNote(int midiPitch, int velocity, long microTime) {
@ -154,7 +155,7 @@ public class MidiConverter {
Instrument instrument = Instrument.fromId(noteId / 25);
long time = microTime / 1000L;
return new Note(instrument, pitch, volume, time);
return new Note(instrument, pitch, volume, time, Vec3d.ZERO);
}
return null;
}

View file

@ -1,5 +1,7 @@
package land.chipmunk.chipmunkmod.song;
import net.minecraft.util.math.Vec3d;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -156,15 +158,20 @@ public class NBSConverter {
}
for (NBSNote note : nbsNotes) {
Instrument instrument;
int key = note.key;
double key;
if (note.instrument < instrumentIndex.length) {
instrument = instrumentIndex[note.instrument];
key = (double) ((note.key * 100) + note.pitch) / 100;
} else {
int index = note.instrument - instrumentIndex.length;
if (index >= customInstruments.size()) continue;
NBSCustomInstrument customInstrument = customInstruments.get(index);
instrument = Instrument.of(customInstrument.name);
key += customInstrument.pitch;
key = (note.key) + (customInstrument.pitch + (double) note.pitch / 100);
}
byte layerVolume = 100;
@ -172,8 +179,29 @@ public class NBSConverter {
layerVolume = nbsLayers.get(note.layer).volume;
}
int pitch = key - 33;
song.add(new Note(instrument, pitch, (float) note.velocity * (float) layerVolume / 10000f, getMilliTime(note.tick, tempo)));
while (key < 33) key += 12;
while (key > 57) key -= 12;
double pitch = key - 33;
final int layerStereo = Byte.toUnsignedInt(nbsLayers.get(note.layer).stereo);
final int notePanning = Byte.toUnsignedInt(note.panning);
double value;
if (layerStereo == 100 && notePanning != 100) value = notePanning;
else if (notePanning == 100 && layerStereo != 100) value = layerStereo;
else value = (double) (layerStereo + notePanning) / 2;
double x;
if (value > 100) x = (value - 100) / -100;
else if (value == 100) x = 0;
else x = ((value - 100) * -1) / 100;
final Vec3d position = new Vec3d(x, 0 ,0);
song.add(new Note(instrument, pitch, (float) note.velocity * (float) layerVolume / 10000f, getMilliTime(note.tick, tempo), position));
}
song.length = song.get(song.size() - 1).time + 50;

View file

@ -1,31 +1,25 @@
package land.chipmunk.chipmunkmod.song;
import net.minecraft.util.math.Vec3d;
public class Note implements Comparable<Note> {
public Instrument instrument;
public int pitch;
public double pitch;
public float volume;
public long time;
public Vec3d position;
public Note(Instrument instrument, int pitch, float volume, long time) {
public Note(Instrument instrument, double pitch, float volume, long time, Vec3d position) {
this.instrument = instrument;
this.pitch = pitch;
this.volume = volume;
this.time = time;
this.position = position;
}
@Override
public int compareTo(Note other) {
if (time < other.time) {
return -1;
} else if (time > other.time) {
return 1;
} else {
return 0;
}
}
public int noteId() {
return pitch + instrument.id * 25;
return Long.compare(time, other.time);
}
}

View file

@ -54,6 +54,7 @@ public class SongLoaderThread extends Thread {
try {
song = NBSConverter.getSongFromBytes(bytes, name);
} catch (Exception e) {
e.printStackTrace();
}
}