PageSourceSearch

https://www.hamienet.com/assets/CinematicVisualizer-vraGk7iL.js

js hamienet.com collected 2026-10-02 05:01:02 UTC 35,805 bytes, 1,006 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports, R as React } from "./vendor-react-Bay9Y7Lb.js";
2import { a3 as Vector3, ax as MathUtils, fK as Object3D, aI as Color, gk as useControls, gl as Leva } from "./vendor-D0P4XgNj.js";
3import { u as useFrame, b as CameraControls, C as Canvas } from "./vendor-react-three-BCww3P1x.js";
4import { a as getInstrumentFamily, b as getInstrumentColor, S as StageEnvironment, M as Musician3D } from "./Musician3D-2CqS9Wxf.js";
5import "./vendor-i18n-BC7ZMXWQ.js";
6const SHOT_TYPES = ["wideStage", "closeUpFace", "overShoulderKeys", "sideProfileAction", "wide", "closeUp", "panLeft", "panRight", "highAngle", "lowAngle", "stageBack"];
7const CinematicDirector = ({
8  activeNotes,
9  processedInstruments,
10  musicianPositions,
11  soloedIndex,
12  cameraSpeed = 1.5,
13  isPlaying = false,
14  currentTime = 0,
15  track,
16  cameraMode = "cinematic"
17}) => {
18  const controlsRef = reactExports.useRef(null);
19  const currentLookAt = reactExports.useRef(new Vector3(0, 1, 0));
20  const currentCamPos = reactExports.useRef(new Vector3(0, 8, 15));
21  const lastCutBar = reactExports.useRef(-1);
22  const shotTimer = reactExports.useRef(0);
23  const currentShotType = reactExports.useRef("wideStage");
24  const focalMusicianIndex = reactExports.useRef(-1);
25  reactExports.useEffect(() => {
26    if (controlsRef.current) {
27      controlsRef.current.setLookAt(0, 8, 15, 0, 1, 0, false);
28    }
29  }, []);
30  useFrame((state, delta) => {
31    var _a;
32    if (!controlsRef.current) return;
33    if (cameraMode === "manual") {
34      return;
35    }
36    shotTimer.current += delta;
37    const density = activeNotes.length;
38    let targetMusicianIndex = -1;
39    if (soloedIndex !== null && soloedIndex >= 0 && soloedIndex < musicianPositions.length) {
40      targetMusicianIndex = soloedIndex;
41    } else if (density > 0) {
42      let maxV = -1;
43      activeNotes.forEach((n) => {
44        const vel = n.v !== void 0 ? n.v : 0.8;
45        let matchedIdx = -1;
46        if (n.trackIdx !== void 0 && n.trackIdx >= 0 && n.trackIdx < musicianPositions.length) {
47          matchedIdx = n.trackIdx;
48        } else if (n.i !== void 0) {
49          const idxFromName = processedInstruments.findIndex((inst) => inst.originalName === n.i);
50          if (idxFromName !== -1) {
51            matchedIdx = idxFromName;
52          } else {
53            const val = Number(n.i);
54            if (!isNaN(val) && val >= 0 && val < musicianPositions.length) {
55              matchedIdx = val;
56            }
57          }
58        }
59        if (vel > maxV && matchedIdx !== -1) {
60          maxV = vel;
61          targetMusicianIndex = matchedIdx;
62        }
63      });
64    }
65    if (cameraMode === "cinematic") {
66      const bpm = ((_a = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _a.tempo) || 120;
67      const beatDuration = 60 / bpm;
68      const currentBeat = currentTime / beatDuration;
69      const currentBar = Math.floor(currentBeat / 4);
70      let shouldCut = false;
71      if (isPlaying) {
72        if (currentBar !== lastCutBar.current && currentBar % 2 === 0) {
73          lastCutBar.current = currentBar;
74          shouldCut = true;
75        }
76      }
77      if (shotTimer.current > 4) {
78        shouldCut = true;
79      }
80      if (shouldCut) {
81        shotTimer.current = 0;
82        const oldShot = currentShotType.current;
83        let eligible = SHOT_TYPES.filter((s) => s !== oldShot);
84        if (targetMusicianIndex === -1) {
85          eligible = eligible.filter((s) => s !== "closeUp" && s !== "lowAngle");
86        }
87        currentShotType.current = eligible[Math.floor(Math.random() * eligible.length)] || "wide";
88        if (targetMusicianIndex !== -1) {
89          focalMusicianIndex.current = targetMusicianIndex;
90        } else if (musicianPositions.length > 0) {
91          focalMusicianIndex.current = Math.floor(Math.random() * musicianPositions.length);
92        } else {
93          focalMusicianIndex.current = -1;
94        }
95      }
96    } else if (cameraMode === "followSolo") {
97      currentShotType.current = "closeUp";
98      if (targetMusicianIndex !== -1) {
99        focalMusicianIndex.current = targetMusicianIndex;
100      }
101    } else if (cameraMode === "staticWide") {
102      currentShotType.current = "wide";
103      focalMusicianIndex.current = -1;
104    }
105    const targetLookAtVec = new Vector3(0, 1, 0);
106    const targetCamPosVec = new Vector3(0, 8, 15);
107    const time = state.clock.elapsedTime;
108    const hasFocalMusician = focalMusicianIndex.current !== -1 && focalMusicianIndex.current < musicianPositions.length;
109    const focalPos = hasFocalMusician ? musicianPositions[focalMusicianIndex.current] : null;
110    switch (currentShotType.current) {
111      case "closeUpFace":
112        if (focalPos) {
113          targetLookAtVec.set(focalPos.x, focalPos.y + 1.5, focalPos.z);
114          const angle = focalPos.rotationY + Math.PI / 8;
115          targetCamPosVec.set(
116            focalPos.x + Math.sin(angle) * 1.5,
117            focalPos.y + 1.6,
118            focalPos.z + Math.cos(angle) * 1.5
119          );
120        } else {
121          targetLookAtVec.set(0, 1.5, -1);
122          targetCamPosVec.set(0, 1.5, 4);
123        }
124        break;
125      case "overShoulderKeys":
126        if (focalPos) {
127          targetLookAtVec.set(focalPos.x, focalPos.y + 0.8, focalPos.z - 1);
128          const angle = focalPos.rotationY + Math.PI + Math.PI / 6;
129          targetCamPosVec.set(
130            focalPos.x + Math.sin(angle) * 2.5,
131            focalPos.y + 2,
132            focalPos.z + Math.cos(angle) * 2.5
133          );
134        } else {
135          targetLookAtVec.set(0, 0.8, -2);
136          targetCamPosVec.set(0, 2, 3);
137        }
138        break;
139      case "sideProfileAction":
140        if (focalPos) {
141          targetLookAtVec.set(focalPos.x, focalPos.y + 1, focalPos.z);
142          const angle = focalPos.rotationY + Math.PI / 2;
143          targetCamPosVec.set(
144            focalPos.x + Math.sin(angle) * 3,
145            focalPos.y + 1.2,
146            focalPos.z + Math.cos(angle) * 3
147          );
148        } else {
149          targetLookAtVec.set(0, 1, -1);
150          targetCamPosVec.set(4, 1.2, -1);
151        }
152        break;
153      case "wideStage":
154        targetLookAtVec.set(0, 2, -2);
155        targetCamPosVec.set(
156          Math.sin(time * 0.1) * 4,
157          4 + Math.cos(time * 0.1) * 1,
158          18
159        );
160        break;
161      case "closeUp":
162        if (focalPos) {
163          targetLookAtVec.set(focalPos.x, focalPos.y + 1.2, focalPos.z);
164          const angle = focalPos.rotationY + Math.PI / 4;
165          const dist = 3.5;
166          targetCamPosVec.set(
167            focalPos.x + Math.sin(angle) * dist,
168            focalPos.y + 1.8,
169            focalPos.z + Math.cos(angle) * dist
170          );
171        } else {
172          targetLookAtVec.set(0, 1.2, -1);
173          targetCamPosVec.set(0, 7, 14);
174        }
175        break;
176      case "panLeft":
177        targetLookAtVec.set(0, 1.2, -1);
178        targetCamPosVec.set(
179          -6 + shotTimer.current * 1.5,
180          6 + Math.sin(time * 0.5) * 1.5,
181          13
182        );
183        break;
184      case "panRight":
185        targetLookAtVec.set(0, 1.2, -1);
186        targetCamPosVec.set(
187          6 - shotTimer.current * 1.5,
188          6 + Math.cos(time * 0.5) * 1.5,
189          13
190        );
191        break;
192      case "highAngle":
193        if (focalPos) {
194          targetLookAtVec.set(focalPos.x, focalPos.y, focalPos.z);
195        } else {
196          targetLookAtVec.set(0, 0, -2);
197        }
198        targetCamPosVec.set(
199          Math.sin(time * 0.1) * 4,
200          12,
201          -1
202          // slightly behind center
203        );
204        break;
205      case "lowAngle":
206        if (focalPos) {
207          targetLookAtVec.set(focalPos.x, focalPos.y + 1.4, focalPos.z);
208          const angle = focalPos.rotationY - Math.PI / 6;
209          targetCamPosVec.set(
210            focalPos.x + Math.sin(angle) * 3,
211            focalPos.y + 0.3,
212            focalPos.z + Math.cos(angle) * 3
213          );
214        } else {
215          targetLookAtVec.set(0, 1.5, -2);
216          targetCamPosVec.set(0, 1.5, 8);
217        }
218        break;
219      case "stageBack":
220        if (focalPos) {
221          targetLookAtVec.set(0, 1, 6);
222          targetCamPosVec.set(
223            focalPos.x * 0.8,
224            focalPos.y + 2.2,
225            focalPos.z - 4
226            // deep behind
227          );
228        } else {
229          targetLookAtVec.set(0, 1, 6);
230          targetCamPosVec.set(0, 6, -10);
231        }
232        break;
233      case "wide":
234      default:
235        targetLookAtVec.set(0, 1.2, -2);
236        targetCamPosVec.set(
237          Math.sin(time * 0.08) * 3,
238          8 + Math.cos(time * 0.12) * 1.5,
239          16 + Math.sin(time * 0.08) * 1.5
240        );
241        break;
242    }
243    const lerpFactor = MathUtils.clamp(delta * cameraSpeed, 0, 1);
244    currentLookAt.current.lerp(targetLookAtVec, lerpFactor);
245    currentCamPos.current.lerp(targetCamPosVec, lerpFactor);
246    controlsRef.current.setLookAt(
247      currentCamPos.current.x,
248      currentCamPos.current.y,
249      currentCamPos.current.z,
250      currentLookAt.current.x,
251      currentLookAt.current.y,
252      currentLookAt.current.z,
253      false
254      // frame-level interpolation is managed by our lerps
255    );
256  });
257  return /* @__PURE__ */ jsxRuntimeExports.jsx(
258    CameraControls,
259    {
260      ref: controlsRef,
261      makeDefault: true,
262      maxPolarAngle: Math.PI / 2 - 0.05,
263      minDistance: 2,
264      maxDistance: 50
265    }
266  );
267};
268const analyzeEnsemble3D = (processedInstruments) => {
269  if (!processedInstruments || processedInstruments.length === 0) {
270    return "Solo / Duet";
271  }
272  if (processedInstruments.length <= 2) {
273    return "Solo / Duet";
274  }
275  let acousticCount = 0;
276  let synthCount = 0;
277  let hasDrums = false;
278  let hasBass = false;
279  let hasGuitar = false;
280  processedInstruments.forEach((inst) => {
281    const prog = inst.program || 0;
282    const isPercussion = inst.midiChannel === 9;
283    const family = getInstrumentFamily(prog, isPercussion);
284    if (family === "percussion") hasDrums = true;
285    if (family === "strings" || family === "brass" || family === "woodwinds") acousticCount++;
286    if (family === "synth") synthCount++;
287    if (family === "guitar") hasGuitar = true;
288    if (family === "bass") hasBass = true;
289  });
290  const total = processedInstruments.length;
291  if (total >= 3 && total <= 8 && hasDrums && (hasBass || hasGuitar)) {
292    return "Live Band";
293  }
294  if (synthCount > total / 2) {
295    return "Electronic / Synth";
296  }
297  if (acousticCount > total / 3 || total > 8) {
298    return "Classical Orchestra";
299  }
300  return "Live Band";
301};
302const getStageLayout3D = (processedInstruments) => {
303  const ensemble = analyzeEnsemble3D(processedInstruments);
304  const positions = [];
305  const count = processedInstruments.length;
306  if (ensemble === "Solo / Duet") {
307    if (count === 1) {
308      positions.push({ x: 0, y: 0, z: 0, rotationY: 0 });
309    } else {
310      positions.push({ x: -2, y: 0, z: 0, rotationY: 0.1 });
311      positions.push({ x: 2, y: 0, z: 0, rotationY: -0.1 });
312    }
313  } else if (ensemble === "Live Band") {
314    let drumPlaced = false;
315    let guitarCount = 0;
316    let keyboardCount = 0;
317    let otherCount = 0;
318    processedInstruments.forEach((inst, index) => {
319      const prog = inst.program || 0;
320      const isPerc = inst.midiChannel === 9;
321      const family = getInstrumentFamily(prog, isPerc);
322      if (family === "percussion" && !drumPlaced) {
323        positions.push({ x: 0, y: 0, z: -4, rotationY: 0 });
324        drumPlaced = true;
325      } else if (family === "bass") {
326        positions.push({ x: -2.5, y: 0, z: -2, rotationY: 0.2 });
327      } else if (family === "guitar") {
328        const xOffset = guitarCount === 0 ? 2.5 : 4.5;
329        positions.push({ x: xOffset, y: 0, z: -2, rotationY: -0.2 });
330        guitarCount++;
331      } else if (family === "keyboard") {
332        const xOffset = keyboardCount === 0 ? -4.5 : -6;
333        positions.push({ x: xOffset, y: 0, z: -1, rotationY: 0.4 });
334        keyboardCount++;
335      } else {
336        const offset = (otherCount % 2 === 0 ? 1 : -1) * Math.ceil(otherCount / 2) * 2;
337        positions.push({ x: offset, y: 0, z: 1.5, rotationY: -offset * 0.05 });
338        otherCount++;
339      }
340    });
341  } else if (ensemble === "Electronic / Synth") {
342    const rows = Math.ceil(Math.sqrt(count));
343    processedInstruments.forEach((_, i) => {
344      const row = Math.floor(i / rows);
345      const col = i % rows;
346      const xOffset = (col - (rows - 1) / 2) * 3.5;
347      const zOffset = -row * 3;
348      const rotationY = Math.atan2(-xOffset, -zOffset + 2);
349      positions.push({ x: xOffset, y: row * 0.4, z: zOffset + 1, rotationY });
350    });
351  } else {
352    const strings = [];
353    const woodwinds = [];
354    const brass = [];
355    const percussion = [];
356    const others = [];
357    processedInstruments.forEach((inst, index) => {
358      const prog = inst.program || 0;
359      const isPerc = inst.midiChannel === 9;
360      const family = getInstrumentFamily(prog, isPerc);
361      const item = { inst, index };
362      if (family === "strings") strings.push(item);
363      else if (family === "woodwinds") woodwinds.push(item);
364      else if (family === "brass") brass.push(item);
365      else if (family === "percussion") percussion.push(item);
366      else others.push(item);
367    });
368    const stageLayout = new Array(count);
369    const placeRow = (items, radius, angleRange) => {
370      if (items.length === 0) return;
371      const step = items.length === 1 ? 0 : angleRange / (items.length - 1);
372      const startAngle = -angleRange / 2;
373      items.forEach((item, i) => {
374        const angle = startAngle + step * i;
375        const x = radius * Math.sin(angle);
376        const z = -radius * Math.cos(angle) + 4;
377        const rotationY = -angle;
378        stageLayout[item.index] = { x, y: (radius - 5) * 0.15, z, rotationY };
379      });
380    };
381    placeRow(strings, 5.5, Math.PI * 0.7);
382    placeRow(woodwinds, 8.5, Math.PI * 0.6);
383    placeRow(brass, 11.5, Math.PI * 0.5);
384    const backPerc = [...percussion, ...others];
385    placeRow(backPerc, 14.5, Math.PI * 0.4);
386    for (let i = 0; i < count; i++) {
387      if (stageLayout[i]) {
388        positions.push(stageLayout[i]);
389      } else {
390        positions.push({ x: (i - count / 2) * 2, y: 0, z: -2, rotationY: 0 });
391      }
392    }
393  }
394  while (positions.length < count) {
395    positions.push({ x: (Math.random() - 0.5) * 10, y: 0, z: (Math.random() - 0.5) * 10, rotationY: 0 });
396  }
397  return positions;
398};
399const PostProcessingPipeline = ({
400  quality,
401  bloomIntensity = 1,
402  focalPosition
403}) => {
404  return null;
405};
406const MusicReactiveSpotlight = ({ pos, isActive, beatPulse, signatureColor }) => {
407  const spotLightRef = reactExports.useRef(null);
408  const targetObj = reactExports.useMemo(() => {
409    const obj = new Object3D();
410    obj.position.set(pos.x, pos.y + 1, pos.z);
411    return obj;
412  }, [pos]);
413  useFrame((state, delta) => {
414    if (!spotLightRef.current) return;
415    const baseColor = new Color("#222233");
416    const activeColor = new Color(signatureColor || "#00f6ff");
417    let targetIntensity = 1;
418    let targetColor = baseColor;
419    if (isActive) {
420      targetIntensity = 8 + beatPulse * 12;
421      targetColor = activeColor;
422    } else {
423      targetIntensity = 1;
424      targetColor = baseColor;
425    }
426    spotLightRef.current.intensity = MathUtils.lerp(
427      spotLightRef.current.intensity,
428      targetIntensity,
429      delta * 8
430    );
431    spotLightRef.current.color.lerp(targetColor, delta * 8);
432  });
433  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
434    /* @__PURE__ */ jsxRuntimeExports.jsx("primitive", { object: targetObj }),
435    /* @__PURE__ */ jsxRuntimeExports.jsx(
436      "spotLight",
437      {
438        ref: spotLightRef,
439        position: [pos.x, pos.y + 6, pos.z],
440        target: targetObj,
441        angle: Math.PI / 6,
442        penumbra: 0.6,
443        castShadow: true,
444        distance: 15,
445        intensity: 1,
446        color: "#222233",
447        "shadow-mapSize-width": 512,
448        "shadow-mapSize-height": 512,
449        "shadow-bias": -1e-3
450      }
451    )
452  ] });
453};
454const MusicReactiveLighting = ({
455  track,
456  activeNotes = [],
457  processedInstruments = [],
458  musicianPositions = [],
459  activeTrackIndices = /* @__PURE__ */ new Set()
460}) => {
461  const ambientLightRef = reactExports.useRef(null);
462  const directionalLightRef = reactExports.useRef(null);
463  const isMajor = reactExports.useMemo(() => {
464    var _a, _b;
465    if (!track) return true;
466    if (typeof track.scaleType === "string") {
467      return track.scaleType.toLowerCase() === "major";
468    }
469    if (typeof track.key === "string") {
470      const k = track.key.toLowerCase();
471      if (k.includes("minor") || k.includes("min") || k.endsWith("m")) return false;
472      if (k.includes("major") || k.includes("maj")) return true;
473    }
474    if (typeof ((_a = track.midiMetadata) == null ? void 0 : _a.keySignature) === "string") {
475      const ks = track.midiMetadata.keySignature.toLowerCase();
476      if (ks.includes("minor") || ks.includes("min") || ks.endsWith("m")) return false;
477      if (ks.includes("major") || ks.includes("maj")) return true;
478    }
479    if (typeof ((_b = track.midiMetadata) == null ? void 0 : _b.scaleType) === "string") {
480      return track.midiMetadata.scaleType.toLowerCase() === "major";
481    }
482    return true;
483  }, [track]);
484  const bpm = reactExports.useMemo(() => {
485    var _a;
486    if (!track) return 120;
487    if (typeof ((_a = track.midiMetadata) == null ? void 0 : _a.tempo) === "number" && track.midiMetadata.tempo > 0) {
488      return track.midiMetadata.tempo;
489    }
490    if (typeof track.bpm === "number" && track.bpm > 0) {
491      return track.bpm;
492    }
493    if (typeof track.tempo === "number" && track.tempo > 0) {
494      return track.tempo;
495    }
496    return 120;
497  }, [track]);
498  useFrame((state, delta) => {
499    const time = state.clock.elapsedTime;
500    const beatInterval2 = 60 / bpm;
501    const currentBeatTime = time % beatInterval2;
502    const beatPulse = Math.exp(-6 * (currentBeatTime / beatInterval2));
503    const density = activeNotes.length;
504    let avgVelocity = 0.8;
505    if (density > 0) {
506      const totalVel = activeNotes.reduce((acc, note) => acc + (note.v !== void 0 ? note.v : 0.8), 0);
507      avgVelocity = totalVel / density;
508    }
509    const energyFactor = Math.min(1.5, density * 0.15 * (avgVelocity / 0.8));
510    if (ambientLightRef.current) {
511      const baseAmbient = isMajor ? new Color("#442c20") : new Color("#202440");
512      const targetColor = baseAmbient.clone().multiplyScalar(1 + energyFactor * 1.5);
513      ambientLightRef.current.color.lerp(targetColor, delta * 4);
514      ambientLightRef.current.intensity = MathUtils.lerp(
515        ambientLightRef.current.intensity,
516        0.75 + energyFactor * 0.5,
517        // raised from 0.4
518        delta * 4
519      );
520    }
521    if (directionalLightRef.current) {
522      const baseDir = isMajor ? new Color("#ffaa66") : new Color("#7766ff");
523      const pulse = 1 + beatPulse * 0.25;
524      const targetColor = baseDir.clone().multiplyScalar(pulse);
525      directionalLightRef.current.color.lerp(targetColor, delta * 5);
526      const baseIntensity = 1.5;
527      const targetIntensity = (baseIntensity + energyFactor * 1) * (0.9 + beatPulse * 0.15);
528      directionalLightRef.current.intensity = MathUtils.lerp(
529        directionalLightRef.current.intensity,
530        targetIntensity,
531        delta * 5
532      );
533    }
534  });
535  const beatInterval = 60 / bpm;
536  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
537    /* @__PURE__ */ jsxRuntimeExports.jsx("ambientLight", { ref: ambientLightRef, intensity: 0.75 }),
538    /* @__PURE__ */ jsxRuntimeExports.jsx(
539      "hemisphereLight",
540      {
541        args: [isMajor ? "#ffeedd" : "#ddecff", isMajor ? "#110c08" : "#080c18", 0.55]
542      }
543    ),
544    /* @__PURE__ */ jsxRuntimeExports.jsx(
545      "directionalLight",
546      {
547        ref: directionalLightRef,
548        position: [15, 22, 12],
549        intensity: 1.5,
550        castShadow: true,
551        "shadow-mapSize-width": 1024,
552        "shadow-mapSize-height": 1024,
553        "shadow-bias": -1e-4
554      }
555    ),
556    musicianPositions.map((pos, index) => {
557      const isActive = activeTrackIndices.has(index);
558      const inst = processedInstruments[index];
559      const isPercussion = (inst == null ? void 0 : inst.midiChannel) === 9;
560      const family = getInstrumentFamily((inst == null ? void 0 : inst.program) || 0, isPercussion);
561      const sigColor = getInstrumentColor(family);
562      return /* @__PURE__ */ jsxRuntimeExports.jsx(
563        MusicReactiveSpotlight,
564        {
565          pos,
566          isActive,
567          beatPulse: Math.exp(-6 * (performance.now() / 1e3 % beatInterval)),
568          signatureColor: sigColor
569        },
570        index
571      );
572    })
573  ] });
574};
575const MAX_PARTICLES = 600;
576const NoteParticles = ({
577  activeNotes = [],
578  processedInstruments = [],
579  musicianPositions = []
580}) => {
581  const meshRef = reactExports.useRef(null);
582  const particles = reactExports.useRef([]);
583  const nextParticleIdx = reactExports.useRef(0);
584  const seenNotes = reactExports.useRef(/* @__PURE__ */ new Set());
585  if (particles.current.length === 0) {
586    for (let i = 0; i < MAX_PARTICLES; i++) {
587      particles.current.push({
588        x: 0,
589        y: 0,
590        z: 0,
591        vx: 0,
592        vy: 0,
593        vz: 0,
594        r: 1,
595        g: 1,
596        b: 1,
597        size: 0.1,
598        age: 0,
599        life: 0,
600        active: false
601      });
602    }
603  }
604  const tempObject = reactExports.useMemo(() => new Object3D(), []);
605  const tempColor = reactExports.useMemo(() => new Color(), []);
606  reactExports.useEffect(() => {
607    if (!activeNotes || activeNotes.length === 0 || !musicianPositions || musicianPositions.length === 0) {
608      return;
609    }
610    const currentKeys = /* @__PURE__ */ new Set();
611    activeNotes.forEach((note) => {
612      if (note.i === void 0 || note.t === void 0 || note.n === void 0) return;
613      const key = `${note.i}-${note.t}-${note.n}`;
614      currentKeys.add(key);
615      if (!seenNotes.current.has(key)) {
616        seenNotes.current.add(key);
617        const instIndex = note.i;
618        if (instIndex >= 0 && instIndex < musicianPositions.length) {
619          const pos = musicianPositions[instIndex];
620          const inst = processedInstruments[instIndex];
621          const isPercussion = (inst == null ? void 0 : inst.midiChannel) === 9;
622          const family = getInstrumentFamily((inst == null ? void 0 : inst.program) || 0, isPercussion);
623          const colorHex = getInstrumentColor(family);
624          const color = new Color(colorHex);
625          const count = isPercussion ? 25 : 12;
626          for (let k = 0; k < count; k++) {
627            const idx = nextParticleIdx.current;
628            const p = particles.current[idx];
629            p.active = true;
630            p.x = pos.x + (Math.random() - 0.5) * 0.5;
631            p.y = pos.y + 1.2 + (Math.random() - 0.5) * 0.3;
632            p.z = pos.z + (Math.random() - 0.5) * 0.5;
633            const theta = Math.random() * Math.PI * 2;
634            const phi = Math.acos(Math.random() * 2 - 1);
635            const speed = 1.2 + Math.random() * 2.8;
636            p.vx = Math.sin(phi) * Math.cos(theta) * speed;
637            p.vy = (Math.abs(Math.sin(phi) * Math.sin(theta)) + 0.3) * speed * 0.8;
638            p.vz = Math.cos(phi) * speed;
639            p.r = color.r;
640            p.g = color.g;
641            p.b = color.b;
642            p.size = 0.08 + Math.random() * 0.12;
643            p.age = 0;
644            p.life = 0.5 + Math.random() * 0.7;
645            nextParticleIdx.current = (nextParticleIdx.current + 1) % MAX_PARTICLES;
646          }
647        }
648      }
649    });
650    seenNotes.current.forEach((key) => {
651      if (!currentKeys.has(key)) {
652        seenNotes.current.delete(key);
653      }
654    });
655  }, [activeNotes, musicianPositions, processedInstruments]);
656  useFrame((state, delta) => {
657    if (!meshRef.current) return;
658    const dt = Math.min(0.03, delta);
659    for (let i = 0; i < MAX_PARTICLES; i++) {
660      const p = particles.current[i];
661      if (!p.active) {
662        tempObject.position.set(0, -999, 0);
663        tempObject.scale.set(0, 0, 0);
664        tempObject.updateMatrix();
665        meshRef.current.setMatrixAt(i, tempObject.matrix);
666        continue;
667      }
668      p.age += dt;
669      if (p.age >= p.life) {
670        p.active = false;
671        tempObject.position.set(0, -999, 0);
672        tempObject.scale.set(0, 0, 0);
673        tempObject.updateMatrix();
674        meshRef.current.setMatrixAt(i, tempObject.matrix);
675        continue;
676      }
677      p.vx *= 0.95;
678      p.vy = p.vy * 0.95 - 2.5 * dt;
679      p.vz *= 0.95;
680      p.x += p.vx * dt;
681      p.y += p.vy * dt;
682      p.z += p.vz * dt;
683      const progress = p.age / p.life;
684      const currentScale = p.size * (1 - progress);
685      tempObject.position.set(p.x, p.y, p.z);
686      tempObject.scale.set(currentScale, currentScale, currentScale);
687      tempObject.updateMatrix();
688      meshRef.current.setMatrixAt(i, tempObject.matrix);
689      const glowFactor = 3;
690      tempColor.setRGB(p.r * glowFactor, p.g * glowFactor, p.b * glowFactor);
691      meshRef.current.setColorAt(i, tempColor);
692    }
693    meshRef.current.instanceMatrix.needsUpdate = true;
694    if (meshRef.current.instanceColor) {
695      meshRef.current.instanceColor.needsUpdate = true;
696    }
697  });
698  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
699    "instancedMesh",
700    {
701      ref: meshRef,
702      args: [null, null, MAX_PARTICLES],
703      castShadow: false,
704      receiveShadow: false,
705      children: [
706        /* @__PURE__ */ jsxRuntimeExports.jsx("dodecahedronGeometry", { args: [0.5] }),
707        /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { toneMapped: false })
708      ]
709    }
710  );
711};
712const CinematicScene = ({
713  processedInstruments = [],
714  pianoRollData = [],
715  currentTime = 0,
716  isPlaying = false,
717  soloedIndex = null,
718  mutedIndices = /* @__PURE__ */ new Set(),
719  hiddenTracks = /* @__PURE__ */ new Set(),
720  uniqueNames = [],
721  bloomIntensity = 1,
722  cameraSpeed = 1.5,
723  showGrid = false,
724  track,
725  quality = "high",
726  cameraMode = "cinematic"
727}) => {
728  const musicianPositions = reactExports.useMemo(() => {
729    return getStageLayout3D(processedInstruments);
730  }, [processedInstruments]);
731  const activeNotes = reactExports.useMemo(() => {
732    if (!pianoRollData || pianoRollData.length === 0) return [];
733    const active = [];
734    const len = pianoRollData.length;
735    for (let i = 0; i < len; i++) {
736      const note = pianoRollData[i];
737      if (currentTime >= note.t && currentTime <= note.t + note.d) {
738        active.push(note);
739      }
740    }
741    return active;
742  }, [pianoRollData, currentTime]);
743  const activeTrackIndices = reactExports.useMemo(() => {
744    const indices = /* @__PURE__ */ new Set();
745    const nameMap = /* @__PURE__ */ new Map();
746    uniqueNames.forEach((name, idx) => {
747      nameMap.set(name, idx);
748    });
749    activeNotes.forEach((n) => {
750      let matchedIdx = -1;
751      if (n.trackIdx !== void 0 && n.trackIdx >= 0 && n.trackIdx < processedInstruments.length) {
752        matchedIdx = n.trackIdx;
753      } else if (n.i !== void 0) {
754        const idxFromName = nameMap.get(n.i);
755        if (idxFromName !== void 0) {
756          matchedIdx = idxFromName;
757        } else {
758          const val = Number(n.i);
759          if (!isNaN(val) && val >= 0 && val < processedInstruments.length) {
760            matchedIdx = val;
761          }
762        }
763      }
764      if (matchedIdx !== -1) {
765        indices.add(matchedIdx);
766      }
767    });
768    return indices;
769  }, [activeNotes, processedInstruments.length, uniqueNames]);
770  const notesByMusician = reactExports.useMemo(() => {
771    const groups = new Array(processedInstruments.length);
772    for (let i = 0; i < processedInstruments.length; i++) {
773      groups[i] = [];
774    }
775    const nameMap = /* @__PURE__ */ new Map();
776    uniqueNames.forEach((name, idx) => {
777      nameMap.set(name, idx);
778    });
779    if (pianoRollData) {
780      for (let j = 0; j < pianoRollData.length; j++) {
781        const note = pianoRollData[j];
782        let matchedIdx = -1;
783        if (note.trackIdx !== void 0 && note.trackIdx >= 0 && note.trackIdx < groups.length) {
784          matchedIdx = note.trackIdx;
785        } else if (note.i !== void 0) {
786          const idxFromName = nameMap.get(note.i);
787          if (idxFromName !== void 0) {
788            matchedIdx = idxFromName;
789          } else {
790            const val = Number(note.i);
791            if (!isNaN(val) && val >= 0 && val < groups.length) {
792              matchedIdx = val;
793            }
794          }
795        }
796        if (matchedIdx !== -1) {
797          groups[matchedIdx].push(note);
798        }
799      }
800    }
801    return groups;
802  }, [pianoRollData, processedInstruments.length, uniqueNames]);
803  const visibleMusicians = reactExports.useMemo(() => {
804    return processedInstruments.map((inst, index) => {
805      const trackName = uniqueNames[index] || "";
806      const isHidden = hiddenTracks.has(index) || trackName && hiddenTracks.has(trackName);
807      const isMuted = mutedIndices.has(index);
808      return {
809        inst,
810        index,
811        position: musicianPositions[index],
812        visible: !isHidden && !isMuted
813      };
814    });
815  }, [processedInstruments, musicianPositions, hiddenTracks, mutedIndices, uniqueNames]);
816  const focalPosition = reactExports.useMemo(() => {
817    let targetMusicianIndex = -1;
818    const nameMap = /* @__PURE__ */ new Map();
819    uniqueNames.forEach((name, idx) => {
820      nameMap.set(name, idx);
821    });
822    if (soloedIndex !== null && soloedIndex >= 0 && soloedIndex < musicianPositions.length) {
823      targetMusicianIndex = soloedIndex;
824    } else if (activeNotes.length > 0) {
825      let maxV = -1;
826      activeNotes.forEach((n) => {
827        const vel = n.v !== void 0 ? n.v : 0.8;
828        let matchedIdx = -1;
829        if (n.trackIdx !== void 0 && n.trackIdx >= 0 && n.trackIdx < musicianPositions.length) {
830          matchedIdx = n.trackIdx;
831        } else if (n.i !== void 0) {
832          const idxFromName = nameMap.get(n.i);
833          if (idxFromName !== void 0) {
834            matchedIdx = idxFromName;
835          } else {
836            const val = Number(n.i);
837            if (!isNaN(val) && val >= 0 && val < musicianPositions.length) {
838              matchedIdx = val;
839            }
840          }
841        }
842        if (vel > maxV && matchedIdx !== -1) {
843          maxV = vel;
844          targetMusicianIndex = matchedIdx;
845        }
846      });
847    }
848    if (targetMusicianIndex !== -1 && musicianPositions[targetMusicianIndex]) {
849      const pos = musicianPositions[targetMusicianIndex];
850      return new Vector3(pos.x, pos.y + 1.2, pos.z);
851    }
852    return new Vector3(0, 1.2, 0);
853  }, [activeNotes, musicianPositions, soloedIndex, uniqueNames]);
854  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { width: "100%", height: "100%" }, onContextMenu: (e) => e.preventDefault(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
855    Canvas,
856    {
857      camera: { position: [0, 8, 15], fov: 50 },
858      shadows: true,
859      gl: { antialias: true, alpha: false, preserveDrawingBuffer: true },
860      style: { width: "100%", height: "100%" },
861      children: [
862        /* @__PURE__ */ jsxRuntimeExports.jsx("color", { attach: "background", args: ["#050508"] }),
863        /* @__PURE__ */ jsxRuntimeExports.jsx(
864          StageEnvironment,
865          {
866            musicianPositions,
867            activeTrackIndices,
868            showGrid,
869            density: activeNotes.length
870          }
871        ),
872        /* @__PURE__ */ jsxRuntimeExports.jsx(
873          MusicReactiveLighting,
874          {
875            track,
876            activeNotes,
877            processedInstruments,
878            musicianPositions,
879            activeTrackIndices
880          }
881        ),
882        /* @__PURE__ */ jsxRuntimeExports.jsx(
883          NoteParticles,
884          {
885            activeNotes,
886            processedInstruments,
887            musicianPositions
888          }
889        ),
890        /* @__PURE__ */ jsxRuntimeExports.jsx(
891          CinematicDirector,
892          {
893            activeNotes,
894            processedInstruments,
895            musicianPositions,
896            soloedIndex,
897            cameraSpeed,
898            isPlaying,
899            currentTime,
900            track,
901            cameraMode
902          }
903        ),
904        visibleMusicians.map(({ inst, index, position, visible }) => {
905          if (!visible || !position) return null;
906          return /* @__PURE__ */ jsxRuntimeExports.jsx(
907            Musician3D,
908            {
909              index,
910              instrument: inst,
911              position,
912              activeNotes,
913              soloedIndex,
914              musicianNotes: notesByMusician[index] || [],
915              currentTime
916            },
917            index
918          );
919        }),
920        /* @__PURE__ */ jsxRuntimeExports.jsx(
921          PostProcessingPipeline,
922          {
923            quality,
924            bloomIntensity,
925            focalPosition
926          }
927        )
928      ]
929    }
930  ) });
931};
932class ErrorBoundary extends reactExports.Component {
933  constructor(props) {
934    super(props);
935    this.state = { hasError: false, error: null };
936  }
937  static getDerivedStateFromError(error) {
938    return { hasError: true, error };
939  }
940  componentDidCatch(error, errorInfo) {
941    console.error("CinematicVisualizer Crash:", error, errorInfo);
942  }
943  render() {
944    var _a;
945    if (this.state.hasError) {
946      return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { color: "red", padding: 20 }, children: [
947        /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: "WebGL Crash" }),
948        /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: (_a = this.state.error) == null ? void 0 : _a.toString() })
949      ] });
950    }
951    return this.props.children;
952  }
953}
954const CinematicVisualizer = ({
955  track,
956  processedInstruments = [],
957  isPlaying = false,
958  currentTime = 0,
959  pianoRollData = [],
960  soloedIndex = null,
961  mutedIndices = /* @__PURE__ */ new Set(),
962  hiddenTracks = /* @__PURE__ */ new Set(),
963  uniqueNames = []
964}) => {
965  const controls = useControls("Cinematic Admin", {
966    showGrid: { value: true },
967    bloomIntensity: { value: 1, min: 0, max: 3, step: 0.1 },
968    cameraSpeed: { value: 1.5, min: 0.1, max: 5, step: 0.1 },
969    quality: { value: "high", options: ["low", "medium", "high", "ultra"] },
970    cameraMode: { value: "cinematic", options: ["cinematic", "followSolo", "staticWide", "manual"] }
971  });
972  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { width: "100%", height: "100%", background: "#050505", position: "relative" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(ErrorBoundary, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { color: "red", padding: 20 }, children: "Crashed" }), children: [
973    /* @__PURE__ */ jsxRuntimeExports.jsx(
974      "div",
975      {
976        style: { position: "absolute", top: 10, right: 10, zIndex: 1e3, pointerEvents: "auto" },
977        onPointerDown: (e) => e.stopPropagation(),
978        onClick: (e) => e.stopPropagation(),
979        children: /* @__PURE__ */ jsxRuntimeExports.jsx(Leva, { titleBar: { title: "Admin Studio" }, fill: true })
980      }
981    ),
982    /* @__PURE__ */ jsxRuntimeExports.jsx(React.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { color: "white", padding: 20 }, children: "Loading 3D Engine..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
983      CinematicScene,
984      {
985        processedInstruments,
986        pianoRollData,
987        currentTime,
988        isPlaying,
989        soloedIndex,
990        mutedIndices,
991        hiddenTracks,
992        uniqueNames,
993        bloomIntensity: controls.bloomIntensity,
994        cameraSpeed: controls.cameraSpeed,
995        showGrid: controls.showGrid,
996        track,
997        quality: controls.quality,
998        cameraMode: controls.cameraMode
999      }
1000    ) })
1001  ] }) });
1002};
1003export {
1004  CinematicVisualizer,
1005  CinematicVisualizer as default
1006};

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.