Skip to content

Releases: no42-org/CoolModFiles

v0.5.7

26 May 20:57
v0.5.7
2b2adf7

Choose a tag to compare

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.com

Container 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

26 May 13:45
v0.5.6
481143d

Choose a tag to compare

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.com

Container 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)

25 May 23:23
v0.5.5
17272c7

Choose a tag to compare

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

23 May 22:30
v0.5.4
d01d9dd

Choose a tag to compare

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

22 May 16:28
v0.5.3
af5e7fd

Choose a tag to compare

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.com

Credits

Thanks to @srieger1 for requesting Asylum Music Format support.

Full changelog: v0.5.2...v0.5.3

v0.5.2

19 May 22:57
v0.5.2
3ba6641

Choose a tag to compare

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

  • SoundPane gains a trackType?: string prop forwarded from Player.tsx (lower-cased + trimmed at the source) through SourceDrawer's SoundProps.
  • The disable predicate and hint selector are extracted as pure helpers — computeAmigaDisabled and computeAmigaHint — 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.com

v0.5.1

18 May 16:27
v0.5.1
4c5bb65

Choose a tag to compare

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.downloadTrack is 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.
  • .thx canonicalizes to ahx. — 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.com

v0.5.0

17 May 22:53
v0.5.0
1ecb758

Choose a tag to compare

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. downloadTrack and downloadFavoriteMods no longer hardcode .mod for Mod Archive sources — both sniff the fetched blob's first four bytes and save AHX-magic content as .ahx and legacy THX-magic content as .thx. MOD downloads keep their .mod suffix; favorite-zip exports inherit the per-track extension.
  • Local drop zone advertises the new formats: Drop MOD, AHX, or TFMX files here with .ahx .thx added 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 to play(ArrayBuffer), not load(url)).
  • Testsmake verify now runs npm test between 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.com

License

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

17 May 19:35
v0.4.2
92486e6

Choose a tag to compare

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.

Issue: #34 · PR: #35

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.com

Container 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

17 May 00:16
v0.4.1
b372a35

Choose a tag to compare

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.com

Container 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