Imported from distelbus-svg/musicplayer-mod (
AGENTS.md). Install upstream withnpx skills add distelbus-svg/musicplayer-mod. Copyright stays with the author.
Melodix Music Player
Build (all versions)
./gradlew build # builds all versions in parallel
Multi-project structure: ./gradlew :26.2:build or ./gradlew :Backports:1.21.4:build for individual versions.
JARs are output to each subproject's build/libs/ directory:
26.2/build/libs/26.1.2/build/libs/Backports/*/build/libs/
Syncing source across versions
Use sync-source.sh to copy a file from 26.2 to all other versions that already have it:
./sync-source.sh src/main/java/com/musicplayer/AudioEngine.java
Only copies to versions where the target file already exists. To add a new file, create it manually first.
Note: 26.1.2 uses a different screen API (Minecraft.screen / Minecraft.setScreen) vs 26.2 (Minecraft.gui.screen() / Minecraft.gui.setScreen), so files with screen-related calls may need manual adjustment after sync.
Volume fix (cross-version)
Three changes needed in each backport:
- AudioEngine.play() —
primary.fadeVolume = this.volume;afterprimary = new PlaybackSlot(); - SoundtrackStateManager.tick() — when
active && !confirmedActive, callsoundExecutor.setVolume(manualInstance, volume)ifvolume < 1.0f - MusicPlayerManager() —
SoundtrackStateManager.getInstance().setVolume(config.getVolume());in constructor
See 26.2 for reference implementation. Backports 1.21.4, 1.20.1, 1.19.2, 1.16.5 share the same PlaybackSlot-based AudioEngine. 1.18.2 has a simpler engine and different SSM.
Project Structure
26.2/— latest Minecraft version (unobfuscated,net.fabricmc.fabric-loom)26.1.2/— previous Minecraft version (unobfuscated,net.fabricmc.fabric-loom)Backports/*/— each backport version (usesnet.fabricmc.fabric-loom-remap)sync-source.sh— copy a source file from 26.2 to matching files in other versionsreference/— reference mods (overtune-1.0.0.jar), icon.png- Config lives at
.minecraft/config/musicplayer.json - Album art cache at
.minecraft/config/musicplayer/albumart/ - Playlists at
.minecraft/config/musicplayer/playlists/
Key Architecture
- Two engines:
AudioEngine(JLayer/javax.sound for user MP3/WAV) andVanillaMusicPlayer(SoundManager for Minecraft tracks) - Source toggle:
MusicPlayerManager.SongSource.USER/MINECRAFT - UI: Screen-based GUI (
MusicPlayerScreen), HudElement overlay for notifications/visualizer/mini-player (HudElementRegistry.addLast(id, this::render)) - Mixin config:
musicplayer.mixins.jsonincom.musicplayer.mixinpackage - Theme system:
ColorThemerecord with 5 presets, applied viaColorTheme.get(themeId)
Fixed Bugs - Version: 1.0.0
Settings Screen fixes (Jun 2026)
- Search bar loses focus on keystroke:
rebuildWidgets()in the searchsetResponderdestroyed ALL widgets (including the search box) on every keystroke, causing focus loss. Replaced withupdateFolderWidgets()which only recreates folder-related widgets without touching the search box. - Scroll exceeds content:
totalContentHeightwas a static value computed once ininit()and didn't account for collapsed categories. Replaced withcomputeVisibleContentHeight()which dynamically calculates visible content height based on expanded categories and active search filter. - Scroll direction inverted:
-scrollYAmtinmouseScrolledinverted macOS natural scrolling. Removed the negation (scrollYAmtonly). - Cannot drag scrollbar: Added
draggingScrollbarflag,scrollbarDragStartY/scrollbarDragStartScrolltracking, and thumb-drag logic inmouseClicked/mouseDragged/mouseReleased.
Prism instance mapping (for deployment)
- 26.2 →
Fabulously Optimized(7)(usesminecraft.gui.setScreen) - 26.1.2 →
Fabulously Optimized(usesminecraft.setScreen) - 1.21.11 →
Fabulously Optimized(1)(usesminecraft.setScreen, MouseButtonEvent) - 1.21.4 →
Fabulously Optimized(2)(usesminecraft.setScreen, mouseClicked(double,double,int)) - 1.20.1 →
Fabulously Optimized(3)(GuiGraphics, mouseScrolled 3-param) - 1.19.2 →
Fabulously Optimized(4)(PoseStack, old Button constructor) - 1.18.2 →
Fabulously Optimized(5)(PoseStack, TextComponent) - 1.16.5 →
Fabulously Optimized(6)(PoseStack, addButton, TextFieldWidget)
Sync-source notes
After running sync-source.sh, you MUST adapt each backport's source files — the 26.2 API differs significantly from older versions. Common adaptations:
GuiGraphicsExtractor→GuiGraphics(1.21.x) orPoseStack(≤1.20.1)extractRenderState→rendergui.setScreen→minecraft.setScreenMouseButtonEvent→mouseClicked(double,double,int)etc.graphics.text(...)→graphics.drawString(...)(1.21.x) orfont.draw(matrixStack, ...)(≤1.20.1)graphics.fill(...)→fill(matrixStack, ...)(≤1.20.1)graphics.centeredText(...)→ manual centeringButton.builder(...)→new Button(x,y,w,h,text,handler)(≤1.19.2)Component.literal(...)→new TextComponent(...)(≤1.18.2)addRenderableWidget(...)→addButton(...)(1.16.5)removeWidget(...)→children.remove(...)(1.16.5/1.18.2)mouseScrolled(double,double,double,double)→mouseScrolled(double,double,double)(≤1.20.1)EditBox→TextFieldWidget+ different imports (1.16.5)
Fixed Bugs
- Game freeze on Creator (Music Box) / Comforting Memories: Root cause was
NotificationManager.renderPanel()rendering♪(U+266A), which triggered a Minecraft font fallback deadlock on macOS. Fixed by removing Unicode special characters and using char-count text truncation instead offont.width()calls. The freeze was never in the sound engine. - Search plays wrong song: Fixed with
allCategoriesfield +trackIndex = -1for filtered results. - Clicking soundtrack plays random variant: Fixed with
FixedSoundInstance(per-variant resolution viaWeighedSoundEventsAccessor). - Failed-track cascade freeze: Fixed with per-variant failure API in
SoundtrackRegistry(markVariantFailed/isVariantFailedkeyed on variant ID, not event ID). - Wrong play/pause button state during transitions: Fixed with
confirmedActiveflag inSoundtrackStateManager(prevents premature completion detection before sound engine confirms playback). - Fade-on-pause for vanilla tracks: Added
pendingPauseflag;togglePause()triggersstartFade(0)then pauses channel on fade completion. Resume fades back up. - Audio visualizer for vanilla tracks: Added
VanillaPCMCache— decodes OGG via jOrbis (Minecraft runtime dep) to a compact amplitude envelope per ~100ms.SoundtrackStateManager.getVisualizerBars()reads from cache. Falls back silently on decode failure. - Environmental music plays while mod plays custom music:
MusicManagercan start/continue playing its own auto tracks while the mod plays USER source MP3s.SoundManager.stop(autoInstance)alone doesn't tellMusicManagerto stop — it might clearcurrentMusiclazily. Fixed by callingMinecraft.getInstance().getMusicManager().stopPlaying()directly inMusicPlayerManager.playSong(),SoundtrackStateManager.playTrack(), andMusicPlayerManager.seek().
Known Bug Areas to Investigate
- Mini player controls don't respond to clicks (HudElement has no mouse handler — consider using Screen overlay or Minecraft's input events)
- Mini player drag/resize only works when already pressed before render (GLFW poll may miss quick clicks)
- Album art not rendered for user MP3 files that have ID3v2 APIC tags (APIC extraction works, but no texture registration for display)
- Switching source while a song is playing may cause state desync
Implemented Features
- Vanilla track seeking (
SoundManagerlimitation workaround):ChannelSourceAccessormixin exposesChannel.source(OpenAL source ID)FullPcmDecoderdecodes OGG from seek time to end via jOrbisVorbisFile.pcmSeek()PcmStreamAudioStreamwraps the decoded PCMByteBufferSoundThreadExecutor.seek()pauses channel, unqueues/deletes old OpenAL buffers, attachesPcmStream, optionally resumesSoundtrackStateManager.seekManual(float seconds)runs decode on a background threadProgressBarWidgetalready had click/drag — now callsmanager.seek(percent)which delegates toVanillaMusicPlayer.seek(seconds)→SSM.seekManual(seconds)for MINECRAFT source- Seek works while paused (channel stays paused after seek, correct time tracking)
- Uses Mojang's
com.jcraft.jorbis.VorbisFile(Minecraft runtime dep) via reflection, same pattern asVanillaPCMCache
Build Dependencies
com.jcraft:jorbis:0.0.17— OGG Vorbis decoder (Minecraft runtime dep, compile-only via local jar inbuild.gradle). Used byVanillaPCMCachefor visualizer data.javazoom:jlayer:1.0.1— MP3 decoder for user files.net.jthink:jaudiotagger:3.0.1— ID3/album art extraction.
Common Fixes
Identifier.of()→Identifier.fromNamespaceAndPath()orIdentifier.parse()Window.getWindow()→Window.handle()for GLFW handlemc.getLevel()→mc.level(public field)Minecraft.getWindow().getGuiScaledWidth()→Window.getGuiScaledWidth()- HudElement render signature:
(GuiGraphicsExtractor, DeltaTracker)
mod id
musicplayer (used in assets path, mixin config)