{playingSource.type !== "modarchive" && (
@@ -1193,6 +1212,8 @@ function Player({ initialSource, backSideContent, latestId }: PlayerProps) {
setStereoSeparation(val);
player?.setStereoSeparation(val);
},
+ filenameStyle,
+ setFilenameStyle,
}}
/>
@@ -1219,6 +1240,7 @@ function Player({ initialSource, backSideContent, latestId }: PlayerProps) {
)}
+
);
}
diff --git a/components/SoundPane.module.scss b/components/SoundPane.module.scss
index 039ae79..483d4b3 100644
--- a/components/SoundPane.module.scss
+++ b/components/SoundPane.module.scss
@@ -17,11 +17,19 @@
// work, and the banner stays full-opacity so the explanation remains
// readable.
.inactive {
- > :not(.note) {
+ > :not(.note):not(.filenameStyleSection) {
opacity: 0.45;
}
}
+// Filename style is a display preference, not an audio preference, so
+// it stays fully interactive and visually un-dimmed even when the
+// audio-side controls are inactive (e.g. during TFMX playback).
+.filenameStyleSection {
+ // Intentionally empty — exists as a selector hook for .inactive's
+ // exemption list above. Layout inherits from .stereoSection sibling.
+}
+
.optionDisabled {
// pointer-events: none makes the label entirely inert, which is the
// right outcome since the inner radio is already disabled. Side
diff --git a/components/SoundPane.tsx b/components/SoundPane.tsx
index 8ed8853..7ad2b24 100644
--- a/components/SoundPane.tsx
+++ b/components/SoundPane.tsx
@@ -1,6 +1,7 @@
import React from "react";
import Slider from "rc-slider";
import styles from "./SoundPane.module.scss";
+import { type FilenameStyle } from "../lib/filename/amiga-style";
type AmigaModel = "off" | "a500" | "a1200";
@@ -26,8 +27,24 @@ type SoundPaneProps = {
activeEngine?: EngineKind;
stereoSeparation: number;
setStereoSeparation: (v: number) => void;
+ filenameStyle: FilenameStyle;
+ setFilenameStyle: (s: FilenameStyle) => void;
};
+const FILENAME_STYLE_OPTIONS: {
+ value: FilenameStyle;
+ label: string;
+ sub: string;
+}[] = [
+ { value: "auto", label: "Auto", sub: "Render filenames as on disk" },
+ { value: "amiga", label: "Amiga", sub: "Prefix form for Amiga-native formats" },
+ {
+ value: "amiga-all",
+ label: "Amiga everywhere",
+ sub: "Prefix form for all module formats",
+ },
+];
+
const OPTIONS: { value: AmigaModel; label: string; sub: string }[] = [
{ value: "off", label: "Off", sub: "Modern clean resampler" },
{ value: "a500", label: "A500", sub: "Warm, ~4.9 kHz filter" },
@@ -40,6 +57,8 @@ function SoundPane({
activeEngine,
stereoSeparation,
setStereoSeparation,
+ filenameStyle,
+ setFilenameStyle,
}: SoundPaneProps) {
// Per-control disabled predicates per D9. activeEngine === undefined
// means "no track yet" — both controls stay live so users can
@@ -130,6 +149,33 @@ function SoundPane({
+