PageSourceSearch

https://www.hamienet.com/assets/SoundfontDetailPage-R4ZkN9OB.js

js hamienet.com collected 2026-09-24 18:59:49 UTC 13,016 bytes, 236 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { a as api } from "./index-D0Bmh-UX.js";
3import { u as useSoundfontKeyboard, a as useKeyboardInput, r as renderSharedKeyboard, b as getMidiFromKeyboardClick } from "./SharedKeyboard-B3r3Jaho.js";
4import { u as useTranslation } from "./vendor-i18n-R-bMoYp_.js";
5import { d4 as ShieldCheck, b6 as LoaderCircle, cY as ChevronLeft, bI as Star, bb as User, dM as Calendar, ck as HardDrive, c$ as Download, bU as Play, bT as Volume2, ba as Info } from "./vendor-dVNaLJx_.js";
6import "./vendor-react-three-MDLA5jog.js";
7import "./useMidiKeyboard-DlyEIN8T.js";
8import "./wasm-synth-adapter-BrqgBX1y.js";
9import "./vendor-spessasynth-DH8n6o_V.js";
10const SoundfontDetailPage = ({ isAdmin, slug, onNavigate }) => {
11  var _a, _b, _c, _d;
12  const { t } = useTranslation();
13  const [soundfont, setSoundfont] = reactExports.useState(null);
14  const [loading, setLoading] = reactExports.useState(true);
15  const [program, setProgram] = reactExports.useState(0);
16  const [volume, setVolume] = reactExports.useState(1);
17  const processedInstruments = reactExports.useMemo(() => [{ program }], [program]);
18  const { playNote, stopNote, audioContextRef } = useSoundfontKeyboard(0, processedInstruments, void 0, (soundfont == null ? void 0 : soundfont.slug) || (soundfont == null ? void 0 : soundfont.id.toString()), volume);
19  const canvasRef = reactExports.useRef(null);
20  const renderActiveNotesRef = reactExports.useRef(/* @__PURE__ */ new Set());
21  const baseOctaveRef = reactExports.useRef(4);
22  const mouseActiveKeyRef = reactExports.useRef(null);
23  reactExports.useEffect(() => {
24    if (!slug) return;
25    api.getSoundfont(slug).then((sf) => {
26      var _a2, _b2;
27      setSoundfont(sf);
28      if (((_b2 = (_a2 = sf.metadata) == null ? void 0 : _a2.presets) == null ? void 0 : _b2.length) > 0) {
29        setProgram(sf.metadata.presets[0]);
30      }
31    }).catch((err) => {
32      console.error(err);
33    }).finally(() => {
34      setLoading(false);
35    });
36  }, [slug]);
37  const handleKeyOn = reactExports.useCallback((midi) => {
38    if (audioContextRef.current && audioContextRef.current.state === "suspended") {
39      audioContextRef.current.resume().catch(() => {
40      });
41    }
42    playNote(midi, void 0, 100);
43    renderActiveNotesRef.current.add(midi);
44  }, [playNote, audioContextRef]);
45  const handleKeyOff = reactExports.useCallback((midi) => {
46    stopNote(midi);
47    renderActiveNotesRef.current.delete(midi);
48  }, [stopNote]);
49  useKeyboardInput({
50    playNote: handleKeyOn,
51    stopNote: handleKeyOff,
52    baseOctaveRef,
53    activeKeysRef: reactExports.useRef(/* @__PURE__ */ new Map()),
54    enabled: true,
55    onOctaveChange: (o) => {
56      baseOctaveRef.current = o;
57    }
58  });
59  reactExports.useEffect(() => {
60    let animationFrameId;
61    const render = () => {
62      if (canvasRef.current) {
63        const canvas = canvasRef.current;
64        const ctx = canvas.getContext("2d");
65        if (ctx) {
66          const width = canvas.width;
67          const height = canvas.height;
68          ctx.clearRect(0, 0, width, height);
69          renderSharedKeyboard(ctx, width, height, height, renderActiveNotesRef.current, "pc", baseOctaveRef.current);
70        }
71      }
72      animationFrameId = requestAnimationFrame(render);
73    };
74    render();
75    return () => cancelAnimationFrame(animationFrameId);
76  }, []);
77  const handlePointerDown = (e) => {
78    e.preventDefault();
79    const canvas = canvasRef.current;
80    if (!canvas) return;
81    const rect = canvas.getBoundingClientRect();
82    const x = e.clientX - rect.left;
83    const y = e.clientY - rect.top;
84    const midi = getMidiFromKeyboardClick(x, y, rect.width, rect.height);
85    if (midi !== null) {
86      mouseActiveKeyRef.current = midi;
87      handleKeyOn(midi);
88    }
89  };
90  const handlePointerMove = (e) => {
91    e.preventDefault();
92    if (mouseActiveKeyRef.current === null) return;
93    const canvas = canvasRef.current;
94    if (!canvas) return;
95    const rect = canvas.getBoundingClientRect();
96    const x = e.clientX - rect.left;
97    const y = e.clientY - rect.top;
98    const midi = getMidiFromKeyboardClick(x, y, rect.width, rect.height);
99    if (midi !== null && midi !== mouseActiveKeyRef.current) {
100      handleKeyOff(mouseActiveKeyRef.current);
101      mouseActiveKeyRef.current = midi;
102      handleKeyOn(midi);
103    }
104  };
105  const handlePointerUp = (e) => {
106    e.preventDefault();
107    if (mouseActiveKeyRef.current !== null) {
108      handleKeyOff(mouseActiveKeyRef.current);
109      mouseActiveKeyRef.current = null;
110    }
111  };
112  if (!isAdmin) {
113    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl mx-auto py-24 px-6 text-center", children: [
114      /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { className: "w-24 h-24 mx-auto text-zinc-300 mb-6" }),
115      /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-3xl font-black mb-4", children: "Coming Soon" })
116    ] });
117  }
118  if (loading) {
119    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center py-20", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-8 h-8 animate-spin text-indigo-500" }) });
120  }
121  if (!soundfont) {
122    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-20", children: "Soundfont not found." });
123  }
124  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-5xl mx-auto px-4 py-8", children: [
125    /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => onNavigate("/soundfonts"), className: "flex items-center text-zinc-500 hover:text-indigo-600 mb-6 font-medium transition-colors", children: [
126      /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { className: "w-5 h-5 mr-1" }),
127      "Back to Gallery"
128    ] }),
129    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-3xl shadow-xl overflow-hidden border border-zinc-200 dark:border-zinc-800", children: [
130      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-8 sm:p-10 border-b border-zinc-100 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col md:flex-row gap-8 items-start justify-between", children: [
131        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
132          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
133            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-3 py-1 bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-400 font-bold text-sm rounded-full", children: soundfont.format }),
134            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-yellow-500 font-bold", children: [
135              /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { className: "w-4 h-4 fill-current" }),
136              soundfont.averageRating.toFixed(1),
137              " (",
138              soundfont.ratingCount,
139              ")"
140            ] })
141          ] }),
142          /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-4xl md:text-5xl font-black text-zinc-900 dark:text-white mb-4 tracking-tight", children: soundfont.name }),
143          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-lg text-zinc-600 dark:text-zinc-400 max-w-3xl leading-relaxed", children: soundfont.description || "No description provided for this soundfont." }),
144          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-4 mt-6 text-sm text-zinc-500 dark:text-zinc-400 font-medium", children: [
145            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 bg-zinc-50 dark:bg-zinc-800 px-3 py-1.5 rounded-lg border border-zinc-200 dark:border-zinc-700", children: [
146              /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "w-4 h-4" }),
147              ((_a = soundfont.user) == null ? void 0 : _a.displayName) || ((_b = soundfont.user) == null ? void 0 : _b.username) || "Community"
148            ] }),
149            /* @__P
149URE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 bg-zinc-50 dark:bg-zinc-800 px-3 py-1.5 rounded-lg border border-zinc-200 dark:border-zinc-700", children: [
150              /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { className: "w-4 h-4" }),
151              new Date(soundfont.createdAt).toLocaleDateString()
152            ] }),
153            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 bg-zinc-50 dark:bg-zinc-800 px-3 py-1.5 rounded-lg border border-zinc-200 dark:border-zinc-700", children: [
154              /* @__PURE__ */ jsxRuntimeExports.jsx(HardDrive, { className: "w-4 h-4" }),
155              (soundfont.fileSize / (1024 * 1024)).toFixed(1),
156              " MB"
157            ] })
158          ] })
159        ] }),
160        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
161          "a",
162          {
163            href: `/api/soundfonts/${soundfont.id}/download/SF3`,
164            className: "flex flex-col items-center justify-center gap-2 px-8 py-5 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-2xl transition-all shadow-lg hover:shadow-indigo-500/25 active:scale-95",
165            children: [
166              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-lg", children: [
167                /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "w-6 h-6" }),
168                "Download SF3"
169              ] }),
170              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-indigo-200 font-medium opacity-80", children: "Optimized format" })
171            ]
172          }
173        ) })
174      ] }) }),
175      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-8 sm:p-10 bg-zinc-50 dark:bg-zinc-900", children: [
176        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-6", children: [
177          /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-2xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
178            /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "w-6 h-6 text-indigo-500" }),
179            "Live Preview"
180          ] }),
181          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
182            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 bg-white dark:bg-zinc-800 px-3 py-2 rounded-xl shadow-sm border border-zinc-200 dark:border-zinc-700", children: [
183              /* @__PURE__ */ jsxRuntimeExports.jsx(Volume2, { className: "w-4 h-4 text-zinc-500" }),
184              /* @__PURE__ */ jsxRuntimeExports.jsx(
185                "input",
186                {
187                  type: "range",
188                  min: "0",
189                  max: "2",
190                  step: "0.1",
191                  value: volume,
192                  onChange: (e) => setVolume(parseFloat(e.target.value)),
193                  className: "w-24 accent-indigo-600"
194                }
195              )
196            ] }),
197            /* @__PURE__ */ jsxRuntimeExports.jsx(
198              "select",
199              {
200                value: program,
201                onChange: (e) => setProgram(parseInt(e.target.value)),
202                className: "bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white px-4 py-2 rounded-xl font-bold border border-zinc-200 dark:border-zinc-700 shadow-sm focus:ring-2 focus:ring-indigo-500 outline-none",
203                children: (_d = (_c = soundfont.metadata) == null ? void 0 : _c.presets) == null ? void 0 : _d.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: p, children: [
204                  "Program ",
205                  p
206                ] }, p))
207              }
208            )
209          ] })
210        ] }),
211        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-800 p-6 rounded-2xl shadow-inner border border-zinc-700 flex flex-col items-center", children: [
212          /* @__PURE__ */ jsxRuntimeExports.jsx(
213            "canvas",
214            {
215              ref: canvasRef,
216              width: 800,
217              height: 120,
218              className: "w-full max-w-[800px] h-[120px] rounded-lg shadow-lg cursor-pointer touch-none select-none",
219              onPointerDown: handlePointerDown,
220              onPointerMove: handlePointerMove,
221              onPointerUp: handlePointerUp,
222              onPointerLeave: handlePointerUp
223            }
224          ),
225          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 text-zinc-400 text-sm font-medium flex items-center gap-2", children: [
226            /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { className: "w-4 h-4" }),
227            "Use mouse or QWERTY keyboard (Z-M / Q-P) to play"
228          ] })
229        ] })
230      ] })
231    ] })
232  ] });
233};
234export {
235  SoundfontDetailPage as default
236};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.