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.