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); mergedList.addAll(files);
final Component component = Component.translatable("Songs - %s", Component.join(JoinConfiguration.separator(Component.space()), mergedList)).color(NamedTextColor.GREEN); 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; 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.SongLoaderException;
import land.chipmunk.chipmunkmod.song.SongLoaderThread; import land.chipmunk.chipmunkmod.song.SongLoaderThread;
import land.chipmunk.chipmunkmod.util.MathUtilities; 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.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 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.client.network.ClientPlayerEntity;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvent;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import java.io.File; import java.io.File;
@ -55,35 +54,42 @@ public class SongPlayer {
// TODO: Less duplicate code // TODO: Less duplicate code
public void loadSong (Path location) { public void loadSong (Path location) {
final ClientPlayerEntity player = client.player;
if (player == null) return;
if (loaderThread != null) { 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; return;
} }
try { try {
final SongLoaderThread _loaderThread = new SongLoaderThread(location); loaderThread = new SongLoaderThread(location);
((Audience) client.player).sendMessage(Component.translatable("Loading %s", Component.text(location.getFileName().toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN)); player.sendMessage(Component.translatable("Loading %s", Component.text(location.getFileName().toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
_loaderThread.start(); loaderThread.start();
loaderThread = _loaderThread;
} catch (SongLoaderException e) { } 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; loaderThread = null;
} }
} }
public void loadSong (URL location) { public void loadSong (URL location) {
final ClientPlayerEntity player = client.player;
if (player == null) return;
if (loaderThread != null) { 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; return;
} }
try { try {
final SongLoaderThread _loaderThread = new SongLoaderThread(location); 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.start();
loaderThread = _loaderThread; loaderThread = _loaderThread;
} catch (SongLoaderException e) { } 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; loaderThread = null;
} }
} }
@ -101,12 +107,12 @@ public class SongPlayer {
return; return;
} }
if (loaderThread != null && !loaderThread.isAlive()) { if (loaderThread != null && !loaderThread.isAlive() && client.player != null) {
if (loaderThread.exception != 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 { } else {
songQueue.add(loaderThread.song); 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; loaderThread = null;
} }
@ -115,7 +121,16 @@ public class SongPlayer {
if (songQueue.isEmpty()) return; if (songQueue.isEmpty()) return;
currentSong = songQueue.poll(); 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(); currentSong.play();
} }
@ -123,7 +138,7 @@ public class SongPlayer {
else ticksUntilPausedActionbar = 20; else ticksUntilPausedActionbar = 20;
try { 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())); else if (actionbar) CommandCore.INSTANCE.run("title " + SELECTOR + " actionbar " + GsonComponentSerializer.gson().serialize(generateActionbar()));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -134,7 +149,7 @@ public class SongPlayer {
handlePlaying(); handlePlaying();
if (currentSong.finished()) { 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; currentSong = null;
} }
} }
@ -211,24 +226,37 @@ public class SongPlayer {
if (!useCore && client.player != null) { if (!useCore && client.player != null) {
final float floatingPitch = (float) (0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12)))); final float floatingPitch = (float) (0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12))));
final String[] thing = note.instrument.sound.split(":"); final String sound = note.instrument.sound;
if (thing[1] == null) return; // idk if this can be null but ill just protect it for now i guess
client.submit(() -> client.world.playSound( client.submit(() -> client.world.playSound(
client.player.getX(), client.player.getX() + note.position.getX(),
client.player.getY(), client.player.getY() + note.position.getY(),
client.player.getZ(), client.player.getZ() + note.position.getZ(),
SoundEvent.of(Identifier.of(thing[0], thing[1])), SoundEvent.of(Identifier.of(Key.MINECRAFT_NAMESPACE, sound)),
SoundCategory.RECORDS, SoundCategory.RECORDS,
note.volume, note.volume,
floatingPitch, floatingPitch,
true true
)); ));
} else { } 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) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View file

@ -34,16 +34,16 @@ public class Instrument {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.offset = offset; this.offset = offset;
this.sound = "minecraft:block.note_block." + name; this.sound = "block.note_block." + name;
} }
public static Instrument of(String sound) { public static Instrument of(String sound) {
return new Instrument(-1, null, 0, 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) { public static Instrument fromId(int id) {
return values[id]; return VALUES[id];
} }
} }

View file

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

View file

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

View file

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

View file

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