Releases: no42-org/CoolModFiles
v0.5.7
Highlights
Fix for v0.5.6: real-world archival MP3 files with non-standard leading bytes (oversize / malformed ID3v2 tags, custom rip-tool prefixes, padding zeros) were misclassified by mimeForBuffer, fell through to libopenmpt, and surfaced "Couldn't play this track".
mimeForBuffer now deep-scans the first 64 KB for MP3 frame syncs — a stream-shape heuristic that catches MP3s with prefixed garbage while rejecting random binary. The <audio> element does this internally; we replicate enough at the dispatch layer to route the buffer to the PCM engine.
Supply chain
cosign verify ghcr.io/no42-org/coolmodfiles:v0.5.7 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comContainer image
ghcr.io/no42-org/coolmodfiles:v0.5.7
ghcr.io/no42-org/coolmodfiles:v0.5
ghcr.io/no42-org/coolmodfiles:latest
Full changelog: v0.5.6...v0.5.7
v0.5.6
Highlights
Adds a fourth playback engine for archival rescue: .mp3, .ogg, .flac recordings of tracker modules whose original .mod file has been lost. Available from Library and Local drop (Mod Archive stays tracker-only).
No new WASM, no decoder dependency — the browser's native decoders are the engine, wired into the existing master gain so the spectrum analyser and master volume keep working unchanged.
A small "recording" badge next to the title communicates the archival framing. Tracker-specific controls (Amiga emulation, stereo separation, pattern viewer, subsong selector) are hidden or disabled for PCM playback. Filenames render literally — recordings are not Amiga artifacts even in amiga-all filename style.
New /api/library/random?excludeRecordings=1 query parameter for curators who want tracker-only random walks.
Supply chain
cosign verify ghcr.io/no42-org/coolmodfiles:v0.5.6 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comContainer image
ghcr.io/no42-org/coolmodfiles:v0.5.6
ghcr.io/no42-org/coolmodfiles:v0.5
ghcr.io/no42-org/coolmodfiles:latest
Full changelog: v0.5.5...v0.5.6
v0.5.5 — LED spectrum analyzer style (Technics SH-8055-inspired)
What's new
Adds a second visual style to the expanded-player spectrum analyzer: an LED graphic equalizer inspired by the Technics SH-8055 — 12 cyan tile bands, dB scale, dual Hz rows with (Hz) prefix and dB caption, faint grid lines, and a multi-pass cyan glow via Canvas 2D shadowBlur. Not a pixel-perfect replica of the SH-8055 screen — the chrome, palette, and proportions are tuned to fit the player surface rather than reproduce the original verbatim.
Click the analyzer canvas — or focus it and press Enter / Space — to cycle between styles. Your choice persists across sessions in `localStorage` under `display.analyzerStyle`.
Highlights
- The classic 20-bar gradient style is byte-for-byte preserved and remains the default
- Refactored `SpectrumAnalyzer` into a wrapper + two single-purpose renderers (`SpectrumStyleClassic`, `SpectrumStyleLed`) so additional styles can be added trivially
- Canvas is now an interactive control: `role="button"`, dynamic `aria-label` describing the target style, `aria-live="off"`, keyboard activation, focus ring
- Mobile (≤600px) keeps the full chrome — the bar area just compresses
Internals
- New `lib/spectrum-binning.ts` extraction with `logGroupBins` helper + `SpectrumDimensions` type
- New `components/SpectrumStyleClassic.tsx` and `components/SpectrumStyleLed.tsx`
- 153 unit tests (+3 from the wrapper rewrite); production build size delta well under 5 KB gzipped
PRs
- #46 — feat(player): add Technics SH-8055 LED style to spectrum analyzer
v0.5.4
Highlights
- New 20-bar spectrum analyzer next to the disc in the player.
- Fix: stereo separation slider is now disabled for TFMX tracks (the setting has no effect on the TFMX backend).
Full Changelog: v0.5.3...v0.5.4
v0.5.3
Highlights
New: Asylum Music Format playback
.amf is now in the accepted-module allowlist. Both variants of the format — the Asylum Music Format V1.0 (Amiga; magic "ASYLUM Music Format V1.0") and the unrelated DSMI Advanced Module Format (PC) — play through the existing libopenmpt engine, which distinguishes them by content. Drop a .amf into the player and it just plays.
No new engine, worklet, or wasm rebuild was needed — the bundled libopenmpt already decodes both. The change is purely an allowlist widening across local drag-drop, the pages/api/library/* server endpoints, and the Amiga-style filename rewriter (#45).
Dependencies
Routine Dependabot bumps: vitest 4.1.5 → 4.1.7 (#40), @types/node 25.7.0 → 25.9.1 (#42), markdown-to-jsx 9.8.0 → 9.8.1 (#44), @types/react 19.2.14 → 19.2.15 (#41).
Chore
- Pinned Dependabot to hold ESLint at v9 until the ecosystem catches up to v10.
Container image
ghcr.io/no42-org/coolmodfiles:v0.5.3
ghcr.io/no42-org/coolmodfiles:v0.5
ghcr.io/no42-org/coolmodfiles:latest
Verify the image:
cosign verify ghcr.io/no42-org/coolmodfiles:v0.5.3 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comCredits
Thanks to @srieger1 for requesting Asylum Music Format support.
Full changelog: v0.5.2...v0.5.3
v0.5.2
Highlights
A pair of Sound-pane fixes that restore lost behaviour and broaden a control that was previously gated more conservatively than it needed to be.
Amiga emulation gating, restored and refined
The Amiga emulation radio group (A500 / A1200 Paula model) is once again disabled when libopenmpt is rendering a non-MOD format — restoring behaviour that was inadvertently lost in 8e1dcc0 ("per-control Sound pane gating"). Paula emulation makes no sense for tracks that were never authored for Amiga hardware (XM, IT, S3M, MPTM, etc.), so the control now greys out with an inline hint echoing the actual track type.
Engine-based gating remains as a fallback for the moment when libopenmpt's metadata callback hasn't reported a format yet — and now carries per-engine copy: ahx2play's Paula model for AHX, libtfmx's playback engine for TFMX.
The m keyboard shortcut still cycles the stored A500/A1200 preference regardless of the current track type, so users can pre-configure their choice while a non-MOD track plays.
Stereo separation now works on TFMX
Stereo separation was previously disabled for TFMX on the grounds that "libtfmx uses a different scale". That turned out to be solvable: libtfmx's tfx_mixer_init takes a panning argument in [0..100] where 50 = mono and 100 = full stereo. The TFMX worklet now maps the slider's libopenmpt-style 0..100 (where 0 = mono) onto libtfmx's 50..100 range, and stores the value in config for application at mixer init.
Caveat worth knowing: libtfmx's C wrapper exposes panning only at init time, so mid-track slider drag during a TFMX track is best-effort — the value is recorded and takes effect on the next track load. This is a libtfmx limitation, not a worklet bug.
Runtime behaviour matrix
| Track type | Amiga radios | Stereo slider |
|---|---|---|
| MOD | live | live |
| XM / IT / S3M / etc. | disabled (hint shows type) | live |
| AHX | disabled (hint mentions ahx2play) | live |
| TFMX | disabled (hint mentions libtfmx) | live (next-track effective) |
The previous "all-disabled" banner is gone — there's no longer any state where both controls are simultaneously disabled.
Under the hood
SoundPanegains atrackType?: stringprop forwarded fromPlayer.tsx(lower-cased + trimmed at the source) throughSourceDrawer'sSoundProps.- The disable predicate and hint selector are extracted as pure helpers —
computeAmigaDisabledandcomputeAmigaHint— with 19 new Vitest cases covering the engine × type matrix.
Supply chain
Container image is signed with cosign keyless OIDC — no key rotation. Verify with:
cosign verify ghcr.io/no42-org/coolmodfiles:v0.5.2 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.5.1
Highlights
A new Sound-pane preference: Filename style — render Amiga-native module filenames in the historically authentic prefix form (mod.echoing) used on Aminet, ModLand, and BBS listings. Display-only by default; downloads stay canonical so files remain portable. Closes #36.
Three modes in the Sound pane
| Mode | Behaviour |
|---|---|
| Auto (default) | Render filenames verbatim from disk |
| Amiga | Amiga-native formats (.mod / .med / .okt / .ahx / .thx) flip to prefix form (mod.echoing, med.space, okt.quartet, ahx.dexter). PC-era trackers untouched. |
| Amiga everywhere | Extends the prefix transform to PC-era formats (.xm / .it / .s3m / .mptm / .stm / .mtm / .669 / .ult) for visual consistency. |
Persisted under localStorage key display.filenameStyle; falls back to auto on any invalid value. Catalog rows update live with no reload.
Why it matters
AmigaOS never used DOS-style extensions for file typing — the tracker scene developed its own convention, putting the format identifier at the front so an alphabetically sorted Aminet or BBS listing groups by format at a glance. echoing.mod is PC/web-era; mod.echoing is Amiga-scene-authentic. The codebase already half-spoke the convention (lib/tfmx/pairs.ts accepts three TFMX naming styles for parsing); v0.5.1 closes that symmetry on the display side.
Worth knowing
- Downloads keep the canonical filename.
Player.downloadTrackis untouched — files stay portable to other tools and archives regardless of display style. - Tooltips preserve the on-disk basename. Every catalog row's
title=attribute shows the canonical name so hover-copy still yields a searchable string. .thxcanonicalizes toahx.— the AHX engine has two extensions but a single identity, so Amiga mode unifies them.- TFMX pair rows are style-independent, always rendered as
<base> (TFMX). The(TFMX)suffix already carries format identity, and the underlying file shapes vary too much for a single prefix label. - Player title, document title, history and permalinks are unaffected — display transform never touches source identity.
Supply chain
Container image is signed with cosign keyless OIDC — no key rotation. Verify with:
cosign verify ghcr.io/no42-org/coolmodfiles:v0.5.1 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.5.0
Highlights
A new audio engine: AHX / THX (Abyss' Highest eXperience — Dexter & Pink's synth-based Amiga tracker from 1992). Files are tiny synth definitions, not samples — typical .ahx weighs in at 2–15 KB.
Three structural wins land in this release:
New: AHX/THX playback
CoolModFiles now plays three families of Amiga music:
| Format | Engine | Sources |
|---|---|---|
| MOD / XM / IT / S3M / … | libopenmpt | Mod Archive · Library · Local |
| AHX / THX | ahx2play | Mod Archive · Library · Local |
| TFMX pairs | libtfmxaudiodecoder | Library · Local |
Engine selection is automatic — the facade peeks the first four bytes of every buffer and routes AHX/THX magic to the dedicated worklet. Cold start is unchanged (the AHX worklet is lazy-loaded on first AHX play), and the AHX WASM bundle is ~28 KB raw / 14 KB gzipped — about 14 % the size of libtfmx, because ahx2play's ahxLoadFromRAM takes a raw buffer without needing the Emscripten filesystem layer.
Bug-adjacent: ~1,000 modarchive tracks now playable
modarchive.org hosts roughly a thousand .ahx modules indexed across Featured, Top Charts, Artist Charts, By Genre, and the Random walk. Until this release, clicking any of them produced the "Couldn't play this track" toast — the bytes reached libopenmpt's openmpt_module_create_from_memory(), which returned 0 because AHX isn't a sampler format. The new four-byte sniff at the facade's play() dispatch closes that latent papercut as a side-effect of adding the engine — no separate modarchive code paths required.
Notable: the modarchive .ahx catalogue is predominantly THX-magic despite the .ahx filename. The sniff accepts both AHX and THX 3-byte prefixes (with a {0x00, 0x01} version-byte allowlist) so the whole corpus plays uniformly.
Refactor: per-control Sound pane gating
The Sound pane used to gate both controls (Amiga emulation toggle + stereo separation slider) on string-equality against meta.type === "mod". With three engines now in play, the gate is per-control and engine-aware via the facade's new activeEngine: EngineKind getter:
| Engine | Amiga emulation | Stereo separation |
|---|---|---|
| libopenmpt | enabled | enabled |
| TFMX | disabled | disabled (different scale, not wired up) |
| AHX | disabled | enabled — ahx2play accepts the same 0..100 percentage scale as libopenmpt and the slider value forwards live |
When AHX is active, a softer per-control hint replaces the all-disabled banner: "Amiga emulation has no effect for AHX tracks — they render through ahx2play's built-in Paula model." The keyboard m-key shortcut for Amiga emulation cycle migrates to the same EngineKind predicate so the shortcut and the UI can't disagree.
Polish
- Downloads preserve the upstream extension.
downloadTrackanddownloadFavoriteModsno longer hardcode.modfor Mod Archive sources — both sniff the fetched blob's first four bytes and save AHX-magic content as.ahxand legacy THX-magic content as.thx. MOD downloads keep their.modsuffix; favorite-zip exports inherit the per-track extension. - Local drop zone advertises the new formats:
Drop MOD, AHX, or TFMX files herewith.ahx .thxadded to the supported-extensions hint. AudioPlayer.load(url)is now explicitly documented as libopenmpt-only-by-design (no current caller routes AHX URLs through it — the magic-byte sniff applies toplay(ArrayBuffer), notload(url)).- Tests —
make verifynow runsnpm testbetween typecheck and audit, including 24 new unit tests for the magic-byte sniff covering canonical AHX, legacy THX, disallowed version bytes, unrelated prefixes (XM, IT, MED, HTML/WAF), undersized buffers, and non-ArrayBuffer inputs.
Supply chain
Container image is signed with cosign keyless OIDC — no key rotation. Verify with:
cosign verify ghcr.io/no42-org/coolmodfiles:v0.5.0 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comLicense
ahx2play is BSD-3-Clause (Olav Sørensen, 2021–2024) — vendored at vendor/ahx2play/ from upstream commit 7620a9f. BSD-3 is FSF-listed as GPL-compatible; the combined work remains GPL-3.0-or-later. The unmodified upstream LICENSE travels with the source under vendor/ahx2play/LICENSE.
Container image
ghcr.io/no42-org/coolmodfiles:v0.5.0
ghcr.io/no42-org/coolmodfiles:v0.5
ghcr.io/no42-org/coolmodfiles:latest
Full changelog: v0.4.2...v0.5.0
v0.4.2
Highlights
Patch release: fixes pagination in the Mod Archive Artist Charts drill-in. Clicking Next › on an artist with more than one page of mods (e.g. #90 romeoknight) now actually advances the list instead of flashing "Loading…" and re-rendering page 1.
Fix: Artist Charts drill-in paginates correctly
modarchive.org/modules.php?<id> 302-redirects to index.php?request=view_artist_modules&query=<id>, and that redirect silently drops &page=N. The artist endpoint was building URLs in the pre-redirect form, so every page > 1 request resolved upstream as page 1. The Genre drill-in already used the canonical form, which is why this only affected artists.
The endpoint now hits the canonical URL directly. Parser, response shape, and call sites are unchanged — only the upstream URL we fetch differs.
Kudos
Big thanks to /u/IntrinsicPalomides on Reddit for the clean bug report — clear repro steps, the exact artist they hit, and an unambiguous description of the broken behaviour. That made the diagnosis a 60-second curl.
Supply chain
Container image is signed with cosign keyless OIDC — no key rotation. Verify with:
cosign verify ghcr.io/no42-org/coolmodfiles:v0.4.2 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comContainer image
ghcr.io/no42-org/coolmodfiles:v0.4.2
ghcr.io/no42-org/coolmodfiles:v0.4
ghcr.io/no42-org/coolmodfiles:latest
Full changelog: v0.4.1...v0.4.2
v0.4.1
Highlights
This release brings TFMX playback to the server-curated Library and fixes four player-control issues that surfaced once Library TFMX was real.
New: TFMX from the Library tab
TFMX is no longer Local-drop only. Point LIBRARY_ROOT at a directory that contains TFMX pairs (any of .tfx + .sam, mdat.* + smpl.*, or *.mdat + *.smpl) and the pairs surface as <base> (TFMX) rows in the Library catalog alongside MOD files. Pair grouping is server-side and keyed per directory; search matches the pair's user-facing base name (not the on-disk filename); a new /api/library/tfmx-random endpoint backs auto-advance for TFMX-library tracks.
The server-side allowlist broadens to include TFMX-half extensions, but the filename-extension perimeter is preserved by a partner-existence check: a .tfx (or .mdat, or mdat.*) is only served when its partner exists in the same directory. An operator who accidentally drops a misnamed mdat.passwords into the library does not expose it.
Operator drop-in: no env change, no config flag. Add TFMX pairs to your mounted volume and they appear on the next listing fetch.
Fix: Download button does the right thing for every source
Previously the download button always hit Mod Archive — for any non-Mod-Archive source it produced "a random MOD file" because it called https://api.modarchive.org/downloads.php?moduleid=null. Now it dispatches by source type:
| Source | Behaviour |
|---|---|
| Mod Archive | Fetches from Mod Archive (unchanged) |
| Library MOD | Re-fetches from /api/library/file, saves with the on-disk basename |
| Local MOD | Downloads the in-memory File directly |
| TFMX-local | Two browser downloads, using File.name for each half |
| TFMX-library | Both halves fetched from /api/library/file, saved with their on-disk basenames |
Fix: Engine errors no longer silently swap sources
When a track failed to play (canonical case: the undersized Apidya - Load.tfx, 1372 bytes — below libtfmx's TFMX_PROBE_SIZE of 2944), the player used to silently advance to a random other source. Users would click track X and get track Y, with no signal as to why.
For user-picked sources (Library, Local, TFMX-local, TFMX-library) the player now stops on the failed track with the toast "Couldn't play that track — pick another" and the title "Couldn't play this track". Mod Archive's random-walk auto-advance is preserved — when the "play random" flow hits a bad random id, it still silently skips to the next.
Fix: Multi-subsong tracks auto-walk before changing tracks
Many TFMX pairs ship multiple subsongs (Apidya's Apidya-Ongame_1 - Meadow has 8); some MODs ship 2-4. End-of-subsong used to trigger playNext, jumping to a different track. Now end-of-subsong advances to the next subsong within the same track; only the last subsong's end triggers playNext. Engine-agnostic — applies to libopenmpt multi-song MODs as well as TFMX. Repeat-on still re-plays the current source from subsong 0.
Fix: Library auto-advance walks the current folder
Library and TFMX-library auto-advance now walks the current directory's visible listing (pairs then MOD files, matching the catalog's render order), wrapping at the end of the folder. Playing Apidya/Apidya-Ongame_1 ends into Apidya/Apidya-Ongame_2 rather than a random TFMX pair from somewhere else in the library. Falls back to the random endpoints when the folder lookup fails or the current source has been removed from disk.
Supply chain
Container image continues to be signed with cosign keyless OIDC — no key rotation needed. Verify:
cosign verify ghcr.io/no42-org/coolmodfiles:v0.4.1 \
--certificate-identity-regexp '^https://github\.com/no42-org/CoolModFiles/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comContainer image
ghcr.io/no42-org/coolmodfiles:v0.4.1
ghcr.io/no42-org/coolmodfiles:v0.4
ghcr.io/no42-org/coolmodfiles:latest
Full changelog: v0.4.0...v0.4.1