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.