playlist-data-engine 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -50,7 +50,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
50
50
|
${t(i)}
|
|
51
51
|
${t(C)}
|
|
52
52
|
${t(a)}`)}return{easy:i,medium:C,hard:a,natural:e}}generateAtDensity(A,B,g,I,E){this.currentUnifiedBeatMap=g;const i=g?.quarterNoteBpm??120,C=g?.duration??120;this.calculateDensity(A.beats,C);const a=st(B,i),e=Gt(a,i);let o=B.targetDensity,t=!1;B.targetDensity>e&&(o=e,t=!0,console.warn(`[DifficultyVariantGenerator] Target density ${B.targetDensity.toFixed(2)} nps exceeds max achievable ${e.toFixed(2)} nps for grid types [${a.join(", ")}] at ${i} BPM. Clamping to ${o.toFixed(2)} nps.`));const{beats:s,gridLock:G}=this.lockGridPerBeatIndex(A.beats,"custom",i,E,a),n=this.calculateDensity(s,C),h=1e-10,c=Math.max(o,1e-10),d=s.some(l=>!a.includes(l.gridType));if(this.config.logConversions&&console.log(`[DifficultyVariantGenerator] generateAtDensity: current=${n.toFixed(2)} nps, target=${c.toFixed(2)} nps, maxGridType=${B.maxGridType}, allowedTypes=[${a.join(", ")}], clamped=${t}`),n>c*(1+h)){const l=this.simplifyBeats(s,"custom",A.quarterNoteInterval,!1,I,i,G,C,a,o);return{difficulty:"custom",beats:this.enforceSingleGridPerBeat(l.beats),isUnedited:!1,editType:"simplified",editAmount:l.metadata.totalBeatsBefore>0?(l.metadata.totalBeatsBefore-l.metadata.totalBeatsAfter)/l.metadata.totalBeatsBefore:0,conversionMetadata:l.metadata,densityClamped:t}}else if(n<o*(1-h)){const l=this.enhanceBeats(s,"custom",i,g,I,E,A.quarterNoteInterval,G,a,o),F=this.enforceSingleGridPerBeat(l.beats),m=l.metadata.patternsInserted>0?"pattern_inserted":"interpolated";return{difficulty:"custom",beats:F,isUnedited:!1,editType:m,editAmount:l.metadata.totalBeatsBefore>0?(l.metadata.totalBeatsAfter-l.metadata.totalBeatsBefore)/l.metadata.totalBeatsBefore:0,patternsInserted:l.metadata.insertedPatternIds.length>0?l.metadata.insertedPatternIds:void 0,enhancementMetadata:l.metadata}}else{if(d){this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Density approximately equal (${n.toFixed(2)} ≈ ${o.toFixed(2)}), but grid restrictions needed`);const F=this.simplifyBeats(s,"custom",A.quarterNoteInterval,!1,I,i,G,C,a,o);return{difficulty:"custom",beats:this.enforceSingleGridPerBeat(F.beats),isUnedited:F.metadata.beatsRemoved===0&&!t,editType:F.metadata.beatsRemoved>0?"simplified":"none",editAmount:F.metadata.totalBeatsBefore>0?(F.metadata.totalBeatsBefore-F.metadata.totalBeatsAfter)/F.metadata.totalBeatsBefore:0,conversionMetadata:F.metadata,densityClamped:t}}return{difficulty:"custom",beats:this.enforceSingleGridPerBeat(s),isUnedited:!t,editType:"none",editAmount:0}}}generateAtDensities(A,B,g,I,E){const i=new Map;for(const{label:C,config:a}of B){const e={...A,beats:A.beats.map(t=>({...t}))},o=this.generateAtDensity(e,a,g,I,E);i.set(C,o)}return i}validateVariant(A,B){const g=nt(A.beats,B);if(g.isValid)this.config.logConversions&&console.log(`[DifficultyVariantGenerator] ${B} variant validated: ${g.totalBeats} beats, no subdivision violations`);else if(console.warn(`[DifficultyVariantGenerator] ${B} variant has ${g.violationCount} subdivision violations out of ${g.totalBeats} beats. This indicates a bug in the variant generation logic.`),this.config.logConversions){for(const I of g.violations.slice(0,5))console.warn(` - Beat at index ${I.beat.beatIndex}, position ${I.beat.gridPosition}, grid: ${I.gridType} (suggested: ${I.suggestedConversion})`);g.violations.length>5&&console.warn(` ... and ${g.violations.length-5} more violations`)}}validateGridLockResult(A,B){const g=this.validateSingleGridPerBeat(A);if(!g.isValid&&(console.warn(`[DifficultyVariantGenerator] ${B} variant has ${g.violations.length} grid lock violations (mixed grid types at same beat index). This indicates a bug in the grid lock implementation.`),this.config.logConversions)){for(const I of g.violations.slice(0,5))console.warn(` - Beat index ${I.beatIndex} has mixed grids: ${I.gridTypes.join(", ")}`);g.violations.length>5&&console.warn(` ... and ${g.violations.length-5} more violations`)}}validateDensityInRange(A,B){const g=this.calculateDensity(A.beats,B),I=iB[A.difficulty].targetDensityRange,E=g>=I.min&&(I.max===1/0||g<=I.max);if(E){if(this.config.logConversions){const i=I.max===1/0?"∞":I.max.toFixed(2);console.log(`[DifficultyVariantGenerator] ${A.difficulty} variant density ${g.toFixed(2)} nps is within target range [${I.min.toFixed(2)}, ${i}] ✓`)}}else{const i=I.max===1/0?"∞":I.max.toFixed(2);console.warn(`[DifficultyVariantGenerator] ${A.difficulty} variant density ${g.toFixed(2)} nps is OUTSIDE target range [${I.min.toFixed(2)}, ${i}]. This may occur for edge cases (short songs, extreme tempos).`)}return{inRange:E,density:g,targetRange:I,difficulty:A.difficulty}}generateVariant(A,B,g,I,E,i){const C=B===g,a=I?.quarterNoteBpm??120,e=I?.duration??120,o=FB(B,a),{beats:t,gridLock:s}=this.lockGridPerBeatIndex(A.beats,B,a,i);if(C){if(t.every(d=>o.includes(d.gridType))){const{targetCount:d}=this.calculateBeatCountTarget(t.length,e,B);if(t.length<d){const l=this.enhanceBeats(t,B,a,I,E,i,A.quarterNoteInterval,s),F=l.metadata.patternsInserted>0?"pattern_inserted":"interpolated";return{difficulty:B,beats:l.beats,isUnedited:!1,editType:F,editAmount:l.metadata.totalBeatsBefore>0?(l.metadata.totalBeatsAfter-l.metadata.totalBeatsBefore)/l.metadata.totalBeatsBefore:0,enhancementMetadata:l.metadata}}return{difficulty:B,beats:[...t],isUnedited:!0,editType:"none",editAmount:0}}const c=this.simplifyBeats(t,B,A.quarterNoteInterval,!1,E,a,s,e);return{difficulty:B,beats:c.beats,isUnedited:!1,editType:"simplified",editAmount:c.metadata.totalBeatsBefore>0?(c.metadata.totalBeatsBefore-c.metadata.totalBeatsAfter)/c.metadata.totalBeatsBefore:0,conversionMetadata:c.metadata}}const G=this.needsSimplification(B,g),n=this.needsEnhancement(B,g);if(G){const h=this.isHeavySimplification(B,g),c=this.simplifyBeats(t,B,A.quarterNoteInterval,h,E,a,s,e),{targetCount:d}=this.calculateBeatCountTarget(c.beats.length,e,B);if(c.beats.length<d-1){const l=this.enhanceBeats(c.beats,B,a,I,E,i,A.quarterNoteInterval,s),F=l.metadata.patternsInserted>0?"pattern_inserted":"interpolated";return{difficulty:B,beats:l.beats,isUnedited:!1,editType:F,editAmount:1,conversionMetadata:c.metadata,enhancementMetadata:l.metadata}}return{difficulty:B,beats:c.beats,isUnedited:!1,editType:"simplified",editAmount:c.metadata.totalBeatsBefore>0?(c.metadata.totalBeatsBefore-c.metadata.totalBeatsAfter)/c.metadata.totalBeatsBefore:0,conversionMetadata:c.metadata}}if(n){const h=this.enhanceBeats(t,B,a,I,E,i,A.quarterNoteInterval,s);let c=h.beats;if(c.some(F=>!o.includes(F.gridType))){const F=[];for(const m of c)if(o.includes(m.gridType))F.push(m);else{const y=this.convertBeatGridType(m,B,A.quarterNoteInterval,a);y&&F.push(y)}c=this.deduplicateConvertedBeats(F)}const l=h.metadata.patternsInserted>0?"pattern_inserted":"interpolated";return{difficulty:B,beats:c,isUnedited:!1,editType:l,editAmount:h.metadata.totalBeatsBefore>0?(h.metadata.totalBeatsAfter-h.metadata.totalBeatsBefore)/h.metadata.totalBeatsBefore:0,patternsInserted:h.metadata.insertedPatternIds.length>0?h.metadata.insertedPatternIds:void 0,enhancementMetadata:h.metadata}}return{difficulty:B,beats:[...t],isUnedited:!0,editType:"none",editAmount:0}}simplifyBeats(A,B,g,I=!1,E,i=120,C,a=120,e,o){const t={sixteenthToEighth:0,tripletToQuarterTriplet:0,eighthToQuarter:0,beatsRemoved:0,totalBeatsBefore:A.length,totalBeatsAfter:0},s=this.buildPhraseMembershipMap(E),G=C?A:this.enforceSingleGridPerBeat(A);t.totalBeatsBefore=G.length;const n=e??FB(B,i);if(G.every(H=>n.includes(H.gridType))&&!I){const H=o!==void 0?Math.round(o*a):this.calculateBeatCountTarget(G.length,a,B).targetCount,T=this.reduceDensityToTarget(G,B,t,s,i,H,a);return t.totalBeatsAfter=T.length,{beats:T,metadata:t}}let c=G;I&&(c=this.filterBeatsForHeavySimplification(G,t,s));const d=[];for(const H of c){if(n.includes(H.gridType)){d.push(H);continue}const T=C?.get(H.beatIndex),L=this.convertBeatGridType(H,B,g,i,T,n);if(L)if(d.push(L),H.gridType==="straight_16th"){if(t.sixteenthToEighth++,this.config.logConversions){const v=L.gridType==="straight_4th"?"quarter":"8th";console.log(`[DifficultyVariantGenerator] Converted 16th note at beat ${H.beatIndex} position ${H.gridPosition} to ${v} note`)}}else H.gridType==="triplet_8th"?(t.tripletToQuarterTriplet++,this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Converted 8th triplet at beat ${H.beatIndex} position ${H.gridPosition} to quarter triplet`)):H.gridType==="straight_8th"&&(t.eighthToQuarter++,this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Converted 8th note at beat ${H.beatIndex} position ${H.gridPosition} to quarter note`));else t.beatsRemoved++,this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Removed beat at beat ${H.beatIndex} position ${H.gridPosition} (grid: ${H.gridType})`)}const l=this.deduplicateConvertedBeats(d);t.beatsRemoved+=d.length-l.length;const F=d.length-l.length,m=this.calculateDensity(l,a),y=o!==void 0?Math.round(o*a):this.calculateBeatCountTarget(l.length,a,B).targetCount,p=o??this.calculateBeatCountTarget(l.length,a,B).targetDensity;if(l.length<=y)return this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Grid conversion achieved target density: ${m.toFixed(2)} notes/sec (target midpoint ${p.toFixed(2)} nps, ${y} beats) for ${B}`+(F>0?` (${F} beats collapsed)`:"")),t.totalBeatsAfter=l.length,{beats:l,metadata:t};const k=this.reduceDensityToTarget(l,B,t,s,i,y,a);return t.totalBeatsAfter=k.length,{beats:k,metadata:t}}filterBeatsForHeavySimplification(A,B,g=new Map){const I=this.config.heavySimplificationIntensityThreshold,E=[];for(const i of A){const C=this.isStrongBeat(i.beatIndex),a=i.gridPosition===0,e=i.gridPosition===1||i.gridPosition===3,o=this.shouldPreserveForPhrase(i,g,I);C||o||a&&i.intensity>=I*.7||e&&i.intensity>=I||i.gridPosition===2&&i.intensity>=I*.8?E.push(i):(B.beatsRemoved++,this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Heavy simplification: removed beat at beat ${i.beatIndex} position ${i.gridPosition} (intensity: ${i.intensity.toFixed(2)}, strongBeat: ${C})`))}return E}isStrongBeat(A){const B=this.config.rhythmicBalanceConfig?.strongBeatEmphasis??"natural";if(B==="neutral")return!1;const g=this.currentUnifiedBeatMap;if(!g){const a=A%4;return a===0||a===2}const I=g.beats[A];if(!I){const a=A%4;return a===0||a===2}const E=g.downbeatConfig?.segments;if(!E||E.length===0){const a=A%4;return a===0||a===2}const C=it(E,A).timeSignature.beatsPerMeasure;return Ct(I.beatInMeasure,C,B)}calculateDensity(A,B){return A.length===0||B<=0?0:A.length/B}calculateBeatCountTarget(A,B,g){const I=iB[g].targetDensityRange;if(g==="natural"||g==="custom"){const s=B>0?A/B:0;return{targetCount:A,maxCount:A,minCount:A,targetDensity:s}}const i={easy:.9,medium:1.25,hard:1.75}[g];let C;switch(this.config.densityTargetStrategy){case"lower":C=i*.75+I.min*.25;break;case"upper":{const s=I.max===1/0?i*1.5:I.max;C=i*.75+s*.25;break}default:C=i;break}const a=I.max===1/0?C:I.max;C=Math.max(I.min,Math.min(a,C));const e=Math.round(I.min*B),o=I.max===1/0?1/0:Math.round(I.max*B);return{targetCount:Math.round(C*B),maxCount:o,minCount:e,targetDensity:C}}calculateBeatsToAdd(A,B,g,I){const E=this.calculateBeatCountTarget(A,B,I),i=Math.max(0,E.targetCount-A),C=FB(I,g),a=this.getMaxBeatsPerIndexFromGridTypes(C);return{beatsToAdd:i,targetCount:E.targetCount,maxBeatsPerIndex:a}}getMaxBeatsPerIndexFromGridTypes(A){let B=1;for(const g of A){const I=Yi[g];I>B&&(B=I)}return B}getMaxBeatsForGridType(A){return Yi[A]??1}distributeBeatsAcrossIndices(A,B,g,I,E,i){const C=new Map;let a=A;for(let G=0;G<=I;G++){const n=B.get(G)??[];C.set(G,n.length)}if(a<=0)return C;const e=G=>{const n=g.get(G);return n?i&&!i.includes(n)?this.getMaxBeatsPerIndexFromGridTypes(i):this.getMaxBeatsForGridType(n):i?this.getMaxBeatsPerIndexFromGridTypes(i):4},o=[],t=[];for(let G=0;G<=I;G++){const n=C.get(G)??0,h=e(G);n===0?o.push(G):n<h&&t.push(G)}const s=this.groupConsecutiveEmptyIndices(o);s.sort((G,n)=>{const h=G.length-n.length;if(h!==0)return h;const c=bB(XB(E,`gap:${G[0]}`)),d=bB(XB(E,`gap:${n[0]}`));return c-d});for(const G of s){if(a<=0)break;const n=G.length<=2;for(const h of G){if(a<=0)break;const c=e(h);let d;n?d=Math.min(c,a,2):d=Math.min(c,a,1),d>0&&(C.set(h,d),a-=d)}}t.sort((G,n)=>{const h=C.get(G)??0,c=C.get(n)??0,d=e(G),l=e(n),F=d-h,y=l-c-F;if(y!==0)return y;const p=bB(XB(E,`partial:${G}`)),k=bB(XB(E,`partial:${n}`));return p-k});for(const G of t){if(a<=0)break;const n=C.get(G)??0,c=e(G)-n;if(c>0){const d=Math.min(c,a);C.set(G,n+d),a-=d}}if(a>0)for(const G of o){if(a<=0)break;const n=C.get(G)??0,c=e(G)-n;if(c>0){const d=Math.min(c,a);C.set(G,n+d),a-=d}}return C}groupConsecutiveEmptyIndices(A){if(A.length===0)return[];const B=[];let g=[A[0]];for(let I=1;I<A.length;I++){const E=A[I-1],i=A[I];i===E+1?g.push(i):(B.push(g),g=[i])}return B.push(g),B}reduceDensityToTarget(A,B,g,I=new Map,E=120,i,C=120){const a=iB[B].targetDensityRange,e=this.calculateDensity(A,C);if(i!==void 0&&A.length<=i)return this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Density ${e.toFixed(2)} notes/sec already at or below target midpoint (${i} beats) for ${B}`),A;if(e<=a.max&&i===void 0)return this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Density ${e.toFixed(2)} notes/sec already within target range [${a.min}, ${a.max}] for ${B}`),A;this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Reducing density from ${e.toFixed(2)} notes/sec to target max ${a.max} notes/sec for ${B}`);const o=this.buildDownbeatCountMap(A),t=A.map(m=>({beat:m,priority:this.calculateRemovalPriority(m,I,o)}));t.sort((m,y)=>m.priority-y.priority);const s=new Set;let G=A.length;const n=this.config.maxReductionPasses;let h=0;for(let m=1;m<=n;m++){h=m;let y=0;const p=m===n;let k,H,T=!1;m===1?(k=1-this.config.densityReductionMinIntensity,H=this.config.moderateSimplificationIntensityThreshold):m===2?(k=1-this.config.densityReductionMinIntensity-.15,H=this.config.moderateSimplificationIntensityThreshold-.1):(T=!0,k=0,H=0);for(const{beat:L,priority:v}of t)if(!s.has(L)){if(i!==void 0){if(G-1<=i)break}else if((G-1)/C<=a.max)break;if(T){if(this.isStrongBeat(L.beatIndex))continue}else if(v>=k||L.intensity>=H)continue;s.add(L),G--,y++,this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Pass ${m}: Removed beat at index ${L.beatIndex} position ${L.gridPosition} (priority: ${v.toFixed(2)}, intensity: ${L.intensity.toFixed(2)})`)}if(this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Pass ${m}${p?" (final)":""}: Removed ${y} beats, density now: ${(G/C).toFixed(2)} notes/sec`),i!==void 0){if(G<=i)break}else if(G/C<=a.max)break}const c=G/C;if((i!==void 0?G>i:c>a.max)&&this.config.logConversions){const m=i!==void 0?`${i} beats (midpoint ${(i/C).toFixed(2)} nps)`:`${a.max} notes/sec`;console.warn(`[DifficultyVariantGenerator] Could not reach target density ${m} for ${B} after ${n} passes. Final density: ${c.toFixed(2)} notes/sec (${G} beats remaining)`)}const l=A.filter(m=>!s.has(m));g.reductionPasses=h,g.beatsRemoved+=s.size;const F=Math.ceil(a.min*C);if(l.length<F&&A.length>0){const m=t.filter(({beat:y})=>s.has(y)).sort((y,p)=>p.priority-y.priority);for(const{beat:y}of m){if(l.length>=F)break;l.push(y),g.beatsRemoved--}}return l}buildDownbeatCountMap(A){const B=new Map,g=this.currentUnifiedBeatMap;for(const I of A){if(I.gridPosition!==0)continue;let E;g?.beats[I.beatIndex]?E=g.beats[I.beatIndex].measureNumber:E=Math.floor(I.beatIndex/4),B.set(E,(B.get(E)??0)+1)}return B}calculateRemovalPriority(A,B,g=new Map){const I=this.config.rhythmicBalanceConfig?.strongBeatEmphasis??"natural";let E=.5;if(I!=="neutral"&&this.isStrongBeat(A.beatIndex)&&(E+=.3),A.gridPosition===0){E+=.2;const C=this.currentUnifiedBeatMap;let a;C?.beats[A.beatIndex]?a=C.beats[A.beatIndex].measureNumber:a=Math.floor(A.beatIndex/4),(g.get(a)??0)===1&&(E+=.2)}E+=A.intensity*.3;const i=B.get(A.beatIndex);if(i&&i.length>0){const C=Math.max(...i.map(a=>a.significance));E+=Math.min(.15,C*.05)}return(A.gridPosition===1||A.gridPosition===3)&&(E-=.1),Math.max(0,Math.min(1,E))}convertBeatGridType(A,B,g,I=120,E,i){const C=E??A.gridType,a=QQ(C,B,I,i);if(a===C)return{...A,gridType:a};let e,o,t;switch(C){case"straight_16th":{const s=g/4;if(t=A.timestamp-A.gridPosition*s,a==="straight_4th")e=0,o=t;else{A.gridPosition===1||A.gridPosition===3?e=A.gridPosition===1?0:2:e=A.gridPosition;const G=g/2;o=t+(e===0?0:G)}break}case"triplet_8th":{const s=g/3;t=A.timestamp-A.gridPosition*s,e=0,o=t;break}case"straight_8th":{const s=g/2;t=A.timestamp-A.gridPosition*s,e=0,o=t;break}default:return{...A,gridType:A.gridType}}return{...A,gridType:a,gridPosition:e,timestamp:o}}deduplicateConvertedBeats(A){const B=new Map;for(const g of A){const I=`${g.beatIndex}:${g.gridPosition}:${g.gridType}`,E=B.get(I);(!E||g.intensity>E.intensity)&&B.set(I,g)}return Array.from(B.values()).sort((g,I)=>g.timestamp-I.timestamp)}needsSimplification(A,B){const g=["easy","medium","hard"],I=g.indexOf(A),E=g.indexOf(B);return I<E}needsEnhancement(A,B){const g=["easy","medium","hard"],I=g.indexOf(A),E=g.indexOf(B);return I>E}isHeavySimplification(A,B){return B==="hard"&&A==="easy"}enhanceBeats(A,B,g,I,E,i,C=.5,a,e,o){const t=I?.duration??120,s={totalBeatsBefore:A.length,totalBeatsAfter:0,patternsInserted:0,interpolatedBeats:0,insertedPatternIds:[]},G=L=>{if(e){if(e.includes(L))return L;const v=["straight_16th","triplet_8th","straight_8th","quarter_triplet","straight_4th"],BA=v.indexOf(L);for(let iA=BA+1;iA<v.length;iA++)if(e.includes(v[iA]))return v[iA];return e[e.length-1]??L}return QQ(L,B,g)};if(A.length===0)return s.totalBeatsAfter=0,{beats:[],metadata:s};let n=a?A:this.enforceSingleGridPerBeat(A);if(e&&n.some(v=>!e.includes(v.gridType))){const v=[];for(const iA of n)if(e.includes(iA.gridType))v.push(iA);else{const GA=this.convertBeatGridType(iA,B,C,g,void 0,e);GA&&v.push(GA)}n=this.deduplicateConvertedBeats(v)}s.totalBeatsBefore=n.length;const h=this.groupBeatsByIndex(n),c=Math.max(...Array.from(h.keys()));let d;if(o!==void 0){const L=Math.round(o*t);d=Math.max(0,L-n.length);const v=Math.max(0,3*n.length);d=Math.min(d,v)}else d=this.calculateBeatsToAdd(n.length,t,g,B).beatsToAdd;const l=this.config.seed??`enhance:${c}:${n.length}`,F=this.distributeBeatsAcrossIndices(d,h,a??new Map,c,l,e),m=[],y=new Map,p=(L,v)=>`${L}:${v}`;for(const L of n){const v=p(L.beatIndex,L.gridPosition);y.has(v)||y.set(v,L.gridType)}for(let L=0;L<=c;L++){const v=h.get(L)??[],BA=F.get(L)??0;if(v.length>=BA){m.push(...v);continue}const iA=BA-v.length;if(v.length===0){const gA=a?.get(L),wA=gA?G(gA):void 0,yA=this.createBeatsForEmptyIndex(L,iA,I,i,C,h,y,wA),jQ=wA??this.getGridForBeatIndex(L,yA,i);for(const EA of yA){const DA=p(EA.beatIndex,EA.gridPosition);!y.has(DA)&&EA.gridType===jQ&&(y.set(DA,EA.gridType),m.push(EA),s.interpolatedBeats++)}continue}const GA=a?.get(L)??v[0].gridType,rA=G(GA);let P=0;if(this.config.preferPatternInsertion&&E){const gA=this.tryInsertPattern(v,L,iA,E,y,rA);P=gA.beatsAdded,gA.patternId&&(s.patternsInserted++,s.insertedPatternIds.push(gA.patternId))}if(P<iA){const gA=this.interpolateBeats(v,L,iA-P,C,y,rA,I);for(const wA of gA){const yA=p(wA.beatIndex,wA.gridPosition);y.has(yA)||(y.set(yA,wA.gridType),m.push(wA),s.interpolatedBeats++)}}m.push(...v)}const k=m.sort((L,v)=>L.beatIndex!==v.beatIndex?L.beatIndex-v.beatIndex:L.gridPosition-v.gridPosition);let H;a?(this.validateSingleGridPerBeat(k).isValid,H=k):H=this.enforceSingleGridPerBeat(k);const T=this.deduplicateEnhancedBeats(H);return s.totalBeatsAfter=T.length,this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Enhanced beats for ${B}: ${s.totalBeatsBefore} → ${s.totalBeatsAfter} beats (patterns: ${s.patternsInserted}, interpolated: ${s.interpolatedBeats})`),{beats:T,metadata:s}}groupBeatsByIndex(A){const B=new Map;for(const g of A){const I=B.get(g.beatIndex);I?I.push(g):B.set(g.beatIndex,[g])}return B}calculateTargetBeatsPerBeat(A,B,g){const I=new Map;for(let E=0;E<=g;E++){const i=A.get(E);if(i){const C=Math.min(Math.ceil(i.length*B),4);I.set(E,C)}else{const C=Math.min(Math.ceil(B*.7),4);C>=2&&I.set(E,C)}}return I}tryInsertPattern(A,B,g,I,E,i){const a=I.patternLibrary.filter(l=>l.sizeInBeats<=this.config.maxPatternInsertionSize&&l.availableForReuse);if(a.length===0)return{beatsAdded:0};const e=a.filter(l=>l.pattern.some(F=>F.gridType===i));if(e.length===0)return{beatsAdded:0};e.sort((l,F)=>F.significance-l.significance);const o=e[0],t=o.pattern.filter(l=>l.beatIndex===0),s=new Set(A.map(l=>l.gridPosition)),G=(l,F)=>`${l}:${F}`,n=t.filter(l=>!(l.gridType!==i||s.has(l.gridPosition)||E.has(G(B,l.gridPosition))));if(n.length===0)return{beatsAdded:0};const h=n.slice(0,g),c=o.sourceBand,d=h.map(l=>({...l,beatIndex:B,band:c,sourceBand:c,intensity:l.intensity*.8}));for(const l of d)E.set(G(l.beatIndex,l.gridPosition),l.gridType);return{beatsAdded:d.length,patternId:o.id}}createBeatsForEmptyIndex(A,B,g,I,E=.5,i,C,a){let e=a??"straight_16th",o="mid",t="mid";if(!a&&I){const m=I.get(A);m&&(e=m.selectedGrid)}if(!a&&!I?.has(A)&&i)for(const m of[1,-1,2,-2,3,-3]){const y=i.get(A+m);if(y&&y.length>0){e=y[0].gridType,o=y[0].band,t=y[0].sourceBand;break}}const s=new Set;if(C){const m=(y,p)=>`${y}:${p}`;for(let y=0;y<4;y++)C.has(m(A,y))&&s.add(y)}const n={straight_16th:4,straight_8th:2,triplet_8th:3,straight_4th:1,quarter_triplet:1}[e],h=[];for(let m=0;m<n;m++)s.has(m)||h.push(m);h.sort((m,y)=>{const p=k=>k===0?0:k===n-1?2:1;return p(m)-p(y)});const c=h.slice(0,B),d=g?.beats[A]?.timestamp??A*E,F={straight_16th:E/4,straight_8th:E/2,triplet_8th:E/3,straight_4th:E,quarter_triplet:E}[e];return c.map(m=>({timestamp:d+m*F,beatIndex:A,gridPosition:m,gridType:e,intensity:this.config.interpolatedBeatIntensity*.8,band:o,sourceBand:t,quantizationError:0}))}interpolateBeats(A,B,g,I=.5,E,i,C){if(g<=0||A.length===0)return[];const a=i??A[0].gridType,o=(m=>m==="straight_16th"?4:m==="straight_8th"?2:m==="straight_4th"||m==="quarter_triplet"?1:3)(a),t=(m,y)=>`${m}:${y}`,s=new Set(A.map(m=>m.gridPosition));if(E)for(let m=0;m<o;m++)E.has(t(B,m))&&s.add(m);const G=[];for(let m=0;m<o;m++)s.has(m)||G.push(m);G.sort((m,y)=>(m===0||m===3?2:1)-(y===0||y===3?2:1));const n=G.slice(0,g),h=A[0],c=C?.beats[B]?.timestamp??B*I,l={straight_16th:I/4,straight_8th:I/2,triplet_8th:I/3,straight_4th:I,quarter_triplet:I}[a];return n.map(m=>({timestamp:c+m*l,beatIndex:B,gridPosition:m,gridType:a,intensity:this.config.interpolatedBeatIntensity,band:h.band,sourceBand:h.sourceBand,quantizationError:0}))}getGridForBeatIndex(A,B,g,I){if(B.length>0)return B[0].gridType;if(g?.has(A))return g.get(A).selectedGrid;if(I)for(const E of[1,-1,2,-2,3,-3]){const i=I.get(A+E);if(i&&i.length>0)return i[0].gridType}return"straight_16th"}deduplicateEnhancedBeats(A){const B=new Map;for(const E of A){const i=B.get(E.beatIndex)??new Map;i.set(E.gridType,(i.get(E.gridType)??0)+1),B.set(E.beatIndex,i)}const g=new Map;for(const[E,i]of B){let C="straight_16th",a=0;for(const[e,o]of i)o>a&&(a=o,C=e);g.set(E,C)}const I=new Map;for(const E of A){const i=`${E.beatIndex}:${E.gridPosition}`,C=I.get(i),a=g.get(E.beatIndex);C?E.gridType===a&&C.gridType!==a&&I.set(i,E):I.set(i,E)}return Array.from(I.values())}enforceSingleGridPerBeat(A){const B=new Map;for(const I of A){const E=B.get(I.beatIndex)??[];E.push(I),B.set(I.beatIndex,E)}const g=[];for(const[,I]of B){if(I.length<=1){g.push(...I);continue}const E=new Set(I.map(e=>e.gridType));if(E.size<=1){g.push(...I);continue}let i="straight_16th",C=0;for(const e of E){const o=I.filter(t=>t.gridType===e).reduce((t,s)=>t+s.intensity,0);o>C&&(C=o,i=e)}const a=I.filter(e=>e.gridType===i);if(this.config.logConversions&&a.length<I.length){const e=I.filter(o=>o.gridType!==i);console.log(`[DifficultyVariantGenerator] Enforced single grid at beat ${I[0].beatIndex}: kept ${i} (${a.length} beats), removed ${e.map(o=>o.gridType).join(", ")} (${e.length} beats)`)}g.push(...a)}return g.sort((I,E)=>I.timestamp-E.timestamp)}validateSingleGridPerBeat(A){const B=new Map;for(const I of A){const E=B.get(I.beatIndex)??[];E.push(I),B.set(I.beatIndex,E)}const g=[];for(const[I,E]of B){if(E.length<=1)continue;const i=new Set(E.map(C=>C.gridType));i.size>1&&g.push({beatIndex:I,gridTypes:Array.from(i)})}if(g.length>0&&this.config.logConversions){console.warn(`[DifficultyVariantGenerator] Grid validation found ${g.length} violations. This indicates a bug in grid lock implementation.`);for(const I of g.slice(0,5))console.warn(` - Beat index ${I.beatIndex} has mixed grids: ${I.gridTypes.join(", ")}`)}return{isValid:g.length===0,violations:g}}getConfig(){return{...this.config}}buildPhraseMembershipMap(A){const B=new Map;if(!A||!this.config.preservePhraseBoundaries)return B;const g=A.phrases.filter(I=>I.hasVariation);for(const I of g)for(const E of I.occurrences)for(let i=0;i<I.sizeInBeats;i++){const C=E.beatIndex+i,a=B.get(C)??[];a.push(I),B.set(C,a)}return B}shouldPreserveForPhrase(A,B,g){if(!this.config.preservePhraseBoundaries)return!1;const I=B.get(A.beatIndex);if(!I||I.length===0)return!1;const E=I.reduce((o,t)=>t.significance>o.significance?t:o),C=A.intensity-g>=-.15,e=E.significance>=1.5;return C&&e?(this.config.logConversions&&console.log(`[DifficultyVariantGenerator] Preserving beat at index ${A.beatIndex} position ${A.gridPosition} (intensity: ${A.intensity.toFixed(2)}) due to phrase membership (phrase significance: ${E.significance.toFixed(2)})`),!0):!1}}const Ui={casual:{difficulty:"easy",outputMode:"composite",description:"Easy difficulty for relaxed gameplay"},standard:{difficulty:"medium",outputMode:"composite",description:"Balanced experience for most players"},challenge:{difficulty:"hard",outputMode:"composite",description:"Hard difficulty for skilled players"},bass:{difficulty:"medium",outputMode:"low",description:"Focus on bass/low-frequency rhythms"}};function Yd(Q){return Ui[Q]}function Ud(){return Object.keys(Ui)}const kd={difficulty:"medium",outputMode:"composite",measureStartOffset:0,minimumTransientIntensity:0,transientConfig:void 0,scoringConfig:void 0,phraseAnalyzerConfig:void 0,rhythmicBalanceConfig:IQ,seed:void 0,verbose:!1,enableCache:!0,cacheMaxAge:1800*1e3,skipDifficultyVariants:!1};class HB{constructor(A={}){this.cache=new Map,this.cacheHits=0,this.cacheMisses=0;const B={...IQ,...A.rhythmicBalanceConfig};this.options={...kd,...A,rhythmicBalanceConfig:B},this.multiBandAnalyzer=new To,this.transientDetector=new jo({bandConfig:this.options.transientConfig}),this.rhythmQuantizer=new bi({minimumTransientIntensity:this.options.minimumTransientIntensity,densityValidation:this.options.densityValidation}),this.phraseAnalyzer=new _o(this.options.phraseAnalyzerConfig),this.densityAnalyzer=new $o,this.streamScorer=new Bt(this.options.scoringConfig),this.compositeGenerator=new gt,this.variantGenerator=new EQ({seed:this.options.seed,rhythmicBalanceConfig:B}),this.rhythmicBalancer=new at(this.options.rhythmicBalanceConfig),this.tempoAwareQuantizer=new Po(this.options.tempoQuantizationConfig??pi,this.rhythmQuantizer)}generateCacheKey(A,B,g){return`${A}:${B}:${g}`}createConfigFingerprint(){const A={minimumTransientIntensity:this.options.minimumTransientIntensity,measureStartOffset:this.options.measureStartOffset,tempoQuantizationConfig:this.options.tempoQuantizationConfig};return JSON.stringify(A)}getFromCache(A){if(!this.options.enableCache)return null;const B=this.cache.get(A);return B?Date.now()-B.timestamp>this.options.cacheMaxAge?(this.cache.delete(A),this.cacheMisses++,null):(this.cacheHits++,this.options.verbose&&console.log(`[RhythmGenerator] Cache HIT for key: ${A}`),B.result):(this.cacheMisses++,null)}setCache(A,B){this.options.enableCache&&(this.cache.set(A,{result:B,timestamp:Date.now(),key:A}),this.options.verbose&&console.log(`[RhythmGenerator] Cache SET for key: ${A}`))}clearCache(){this.cache.clear(),this.cacheHits=0,this.cacheMisses=0}pruneExpiredCache(){const A=Date.now();for(const[B,g]of this.cache.entries())A-g.timestamp>this.options.cacheMaxAge&&this.cache.delete(B)}getCacheStats(){let A=0;const B=[];let g=null;for(const[I,E]of this.cache.entries()){const i=I.split(":")[1];B.includes(i)||B.push(i);try{A+=JSON.stringify(E.result).length*2}catch{A+=1e3}(g===null||E.timestamp<g)&&(g=E.timestamp)}return{entryCount:this.cache.size,estimatedSize:A,cachedPhases:B,oldestEntry:g,hits:this.cacheHits,misses:this.cacheMisses}}isCached(A,B){const g=this.createConfigFingerprint(),I=this.generateCacheKey(A,B,g);return this.cache.has(I)}getOptions(){return{...this.options}}async generate(A,B,g,I){const E=(y,p,k)=>{this.options.verbose&&console.log(`[RhythmGenerator] [${y}] ${Math.round(p*100)}%: ${k}`),I?.(y,p,k)};g?.throwIfAborted();const i=this.generateCacheKey(B.audioId,"variants",this.createConfigFingerprint()),C=this.getFromCache(i);if(C)return E("Cache",1,"Retrieved complete result from cache"),C;E("Phase 1",0,"Starting multi-band analysis"),g?.throwIfAborted();const a=this.analyzeMultiBand(A);E("Phase 1",.2,`Analyzed ${a.bands.size} frequency bands`),g?.throwIfAborted();const e=this.detectTransients(a);E("Phase 1",.4,`Detected ${e.transients.length} transients`),g?.throwIfAborted();const o=this.quantizeTransients(e,B);E("Phase 1",.8,`Quantized to ${o.streams.low.beats.length+o.streams.mid.beats.length+o.streams.high.beats.length} beats across all bands`),E("Phase 1",1,"Phase 1 complete"),g?.throwIfAborted(),E("Phase 2",0,"Starting phrase and density analysis"),g?.throwIfAborted();const t=this.analyzePhrases(o.streams);E("Phase 2",.3,`Detected ${t.phrases.length} rhythmic phrases`),g?.throwIfAborted();const s=this.analyzeDensity(o,B.quarterNoteBpm,B.duration);E("Phase 2",.7,`Density category: ${s.combinedMetrics.densityCategory}`),E("Phase 2",1,"Phase 2 complete"),g?.throwIfAborted(),E("Phase 3",0,"Starting scoring and composite generation"),g?.throwIfAborted();const G=this.scoreStreams(o,t,s,B.quarterNoteBpm);E("Phase 3",.2,`Scored ${G.sectionWinners.length} sections`),g?.throwIfAborted();const n=this.generateComposite(o,G,s,B.duration);E("Phase 3",.4,`Generated composite with ${n.beats.length} beats, natural difficulty: ${n.naturalDifficulty}`),g?.throwIfAborted();const h=this.rhythmicBalancer.balance(n,B),c=h.composite,d=h.stats;E("Phase 3",.6,`Balanced composite: ${c.beats.length} beats (${d.beatsAdded} added, ${d.beatsShifted} shifted for rhythmic structure)`);const l=this.options.skipDifficultyVariants?null:this.generateDifficultyVariants(c,t,o,B);E("Phase 3",.8,this.options.skipDifficultyVariants?"Skipped preset difficulty variants (density-based mode)":"Generated easy/medium/hard difficulty variants"),E("Phase 3",1,"Phase 3 complete"),g?.throwIfAborted();const F={difficulty:this.options.difficulty,bandsAnalyzed:["low","mid","high"],transientsDetected:e.metadata.totalTransients,transientsFilteredByIntensity:o.metadata.transientsFilteredByIntensity,densityValidationRetries:o.metadata.densityValidation.maxRetryCount,phrasesDetected:t.phrases.length,averageDensity:s.combinedMetrics.notesPerSecond,naturalDifficulty:c.naturalDifficulty,generationConfig:this.options,duration:A.duration,totalBeats:B.beats.length,balanceStats:d},m={difficultyVariants:l,bandStreams:o.streams,composite:c,analysis:{transientAnalysis:e,quantizationResult:o,phraseAnalysis:t,densityAnalysis:s,scoringResult:G},metadata:F};return this.setCache(i,m),m}analyzeMultiBand(A){return this.multiBandAnalyzer.analyze(A)}detectTransients(A){return this.transientDetector.detect(A)}quantizeTransients(A,B){return this.rhythmQuantizer.quantize(A,B,(g,I,E)=>this.tempoAwareQuantizer.decideGrids(g,I,E))}analyzePhrases(A){return this.phraseAnalyzer.analyze(A)}analyzeDensity(A,B,g){return this.densityAnalyzer.analyze(A,B,g)}scoreStreams(A,B,g,I){return this.streamScorer.updateConfig({bpm:I}),this.streamScorer.score(A,B,g)}generateComposite(A,B,g,I){return this.compositeGenerator.generate(A,B,g,I)}generateDifficultyVariants(A,B,g,I){const E=this.collectGridDecisions(g);return this.variantGenerator.generate(A,I,B,E)}collectGridDecisions(A){const B=new Map,g=[...A.streams.low.gridDecisions,...A.streams.mid.gridDecisions,...A.streams.high.gridDecisions];for(const I of g){const E=B.get(I.beatIndex);(!E||I.confidence>E.confidence)&&B.set(I.beatIndex,I)}return B}static async quickGenerate(A,B){return new HB().generate(A,B)}static async generateForDifficulty(A,B,g){return(await new HB().generate(A,B)).difficultyVariants[g]}static toJSON(A){const B={difficultyVariants:A.difficultyVariants?{easy:this.serializeDifficultyVariant(A.difficultyVariants.easy),medium:this.serializeDifficultyVariant(A.difficultyVariants.medium),hard:this.serializeDifficultyVariant(A.difficultyVariants.hard),natural:this.serializeDifficultyVariant(A.difficultyVariants.natural)}:null,bandStreams:{low:this.serializeGeneratedRhythmMap(A.bandStreams.low),mid:this.serializeGeneratedRhythmMap(A.bandStreams.mid),high:this.serializeGeneratedRhythmMap(A.bandStreams.high)},composite:this.serializeCompositeStream(A.composite),analysis:{transientAnalysis:this.serializeTransientAnalysis(A.analysis.transientAnalysis),quantizationResult:this.serializeQuantizedBandStreams(A.analysis.quantizationResult),phraseAnalysis:this.serializePhraseAnalysisResult(A.analysis.phraseAnalysis),densityAnalysis:this.serializeDensityAnalysisResult(A.analysis.densityAnalysis),scoringResult:this.serializeStreamScoringResult(A.analysis.scoringResult)},metadata:A.metadata};return JSON.stringify(B,null,2)}static fromJSON(A){const B=JSON.parse(A);return{difficultyVariants:B.difficultyVariants?{easy:this.deserializeDifficultyVariant(B.difficultyVariants.easy),medium:this.deserializeDifficultyVariant(B.difficultyVariants.medium),hard:this.deserializeDifficultyVariant(B.difficultyVariants.hard),natural:this.deserializeDifficultyVariant(B.difficultyVariants.natural)}:null,bandStreams:{low:this.deserializeGeneratedRhythmMap(B.bandStreams.low),mid:this.deserializeGeneratedRhythmMap(B.bandStreams.mid),high:this.deserializeGeneratedRhythmMap(B.bandStreams.high)},composite:this.deserializeCompositeStream(B.composite),analysis:{transientAnalysis:this.deserializeTransientAnalysis(B.analysis.transientAnalysis),quantizationResult:this.deserializeQuantizedBandStreams(B.analysis.quantizationResult),phraseAnalysis:this.deserializePhraseAnalysisResult(B.analysis.phraseAnalysis),densityAnalysis:this.deserializeDensityAnalysisResult(B.analysis.densityAnalysis),scoringResult:this.deserializeStreamScoringResult(B.analysis.scoringResult)},metadata:B.metadata}}static async saveToFile(A,B){if(typeof process>"u"||!process.versions?.node)throw new Error("saveToFile is only available in Node.js environment");const{writeFile:g}=await Promise.resolve().then(()=>GB),I=HB.toJSON(A);await g(B,I,"utf-8")}static async loadFromFile(A){if(typeof process>"u"||!process.versions?.node)throw new Error("loadFromFile is only available in Node.js environment");const{readFile:B}=await Promise.resolve().then(()=>GB),g=await B(A,"utf-8");return HB.fromJSON(g)}static serializeDifficultyVariant(A){return{difficulty:A.difficulty,beats:A.beats.map(B=>this.serializeVariantBeat(B)),isUnedited:A.isUnedited,editType:A.editType,editAmount:A.editAmount,patternsInserted:A.patternsInserted,conversionMetadata:A.conversionMetadata,enhancementMetadata:A.enhancementMetadata}}static deserializeDifficultyVariant(A){return{difficulty:A.difficulty,beats:A.beats.map(B=>this.deserializeVariantBeat(B)),isUnedited:A.isUnedited,editType:A.editType,editAmount:A.editAmount,patternsInserted:A.patternsInserted,conversionMetadata:A.conversionMetadata,enhancementMetadata:A.enhancementMetadata}}static serializeVariantBeat(A){return{timestamp:A.timestamp,beatIndex:A.beatIndex,gridPosition:A.gridPosition,gridType:A.gridType,intensity:A.intensity,band:A.band,quantizationError:A.quantizationError,sourceBand:A.sourceBand}}static deserializeVariantBeat(A){return{timestamp:A.timestamp,beatIndex:A.beatIndex,gridPosition:A.gridPosition,gridType:A.gridType,intensity:A.intensity,band:A.band,quantizationError:A.quantizationError,sourceBand:A.sourceBand}}static serializeGeneratedRhythmMap(A){return{audioId:A.audioId,duration:A.duration,beats:A.beats.map(B=>this.serializeGeneratedBeat(B)),gridDecisions:A.gridDecisions,quarterNoteInterval:A.quarterNoteInterval}}static deserializeGeneratedRhythmMap(A){return{audioId:A.audioId,duration:A.duration,beats:A.beats.map(B=>this.deserializeGeneratedBeat(B)),gridDecisions:A.gridDecisions,quarterNoteInterval:A.quarterNoteInterval}}static serializeGeneratedBeat(A){return{timestamp:A.timestamp,beatIndex:A.beatIndex,gridPosition:A.gridPosition,gridType:A.gridType,intensity:A.intensity,band:A.band,quantizationError:A.quantizationError}}static deserializeGeneratedBeat(A){return{timestamp:A.timestamp,beatIndex:A.beatIndex,gridPosition:A.gridPosition,gridType:A.gridType,intensity:A.intensity,band:A.band,quantizationError:A.quantizationError}}static serializeCompositeStream(A){return{beats:A.beats.map(B=>this.serializeCompositeBeat(B)),sections:A.sections,naturalDifficulty:A.naturalDifficulty,quarterNoteInterval:A.quarterNoteInterval,metadata:A.metadata}}static deserializeCompositeStream(A){return{beats:A.beats.map(B=>this.deserializeCompositeBeat(B)),sections:A.sections,naturalDifficulty:A.naturalDifficulty,quarterNoteInterval:A.quarterNoteInterval,metadata:A.metadata}}static serializeCompositeBeat(A){return{timestamp:A.timestamp,beatIndex:A.beatIndex,gridPosition:A.gridPosition,gridType:A.gridType,intensity:A.intensity,band:A.band,quantizationError:A.quantizationError,sourceBand:A.sourceBand,balancerAction:A.balancerAction}}static deserializeCompositeBeat(A){return{timestamp:A.timestamp,beatIndex:A.beatIndex,gridPosition:A.gridPosition,gridType:A.gridType,intensity:A.intensity,band:A.band,quantizationError:A.quantizationError,sourceBand:A.sourceBand,balancerAction:A.balancerAction}}static serializeTransientAnalysis(A){return{transients:A.transients.map(B=>this.serializeTransientResult(B)),bandTransients:Array.from(A.bandTransients.entries()).map(([B,g])=>({band:B,transients:g.map(I=>this.serializeTransientResult(I))})),metadata:{totalTransients:A.metadata.totalTransients,transientsPerBand:Array.from(A.metadata.transientsPerBand.entries()).map(([B,g])=>({band:B,count:g})),duration:A.metadata.duration,averageIntensity:A.metadata.averageIntensity,detectionMethodsUsed:A.metadata.detectionMethodsUsed}}}static deserializeTransientAnalysis(A){const B=new Map;for(const I of A.bandTransients)B.set(I.band,I.transients.map(E=>this.deserializeTransientResult(E)));const g=new Map;for(const I of A.metadata.transientsPerBand)g.set(I.band,I.count);return{transients:A.transients.map(I=>this.deserializeTransientResult(I)),bandTransients:B,metadata:{totalTransients:A.metadata.totalTransients,transientsPerBand:g,duration:A.metadata.duration,averageIntensity:A.metadata.averageIntensity,detectionMethodsUsed:A.metadata.detectionMethodsUsed}}}static serializeTransientResult(A){return{timestamp:A.timestamp,intensity:A.intensity,band:A.band,detectionMethod:A.detectionMethod,nearestBeat:A.nearestBeat}}static deserializeTransientResult(A){return{timestamp:A.timestamp,intensity:A.intensity,band:A.band,detectionMethod:A.detectionMethod,nearestBeat:A.nearestBeat}}static serializeQuantizedBandStreams(A){return{streams:{low:this.serializeGeneratedRhythmMap(A.streams.low),mid:this.serializeGeneratedRhythmMap(A.streams.mid),high:this.serializeGeneratedRhythmMap(A.streams.high)},metadata:{densityValidation:this.serializeDensityValidationResult(A.metadata.densityValidation),transientsFilteredByIntensity:A.metadata.transientsFilteredByIntensity,transientsFilteredByBand:A.metadata.transientsFilteredByBand}}}static serializeDensityValidationResult(A){return{isValid:A.isValid,bands:{low:this.serializeBandDensityValidationResult(A.bands.low),mid:this.serializeBandDensityValidationResult(A.bands.mid),high:this.serializeBandDensityValidationResult(A.bands.high)},maxRetryCount:A.maxRetryCount,maxSensitivityReduction:A.maxSensitivityReduction}}static serializeBandDensityValidationResult(A){return{band:A.band,isValid:A.isValid,minIntervalDetected:A.minIntervalDetected,requiredMinInterval:A.requiredMinInterval,retryCount:A.retryCount,sensitivityReduction:A.sensitivityReduction,finalIntensityThreshold:A.finalIntensityThreshold,transientsRemaining:A.transientsRemaining}}static deserializeQuantizedBandStreams(A){return{streams:{low:this.deserializeGeneratedRhythmMap(A.streams.low),mid:this.deserializeGeneratedRhythmMap(A.streams.mid),high:this.deserializeGeneratedRhythmMap(A.streams.high)},metadata:{densityValidation:this.deserializeDensityValidationResult(A.metadata.densityValidation),transientsFilteredByIntensity:A.metadata.transientsFilteredByIntensity,transientsFilteredByBand:A.metadata.transientsFilteredByBand}}}static deserializeDensityValidationResult(A){return{isValid:A.isValid,bands:{low:this.deserializeBandDensityValidationResult(A.bands.low),mid:this.deserializeBandDensityValidationResult(A.bands.mid),high:this.deserializeBandDensityValidationResult(A.bands.high)},maxRetryCount:A.maxRetryCount,maxSensitivityReduction:A.maxSensitivityReduction}}static deserializeBandDensityValidationResult(A){return{band:A.band,isValid:A.isValid,minIntervalDetected:A.minIntervalDetected,requiredMinInterval:A.requiredMinInterval,retryCount:A.retryCount,sensitivityReduction:A.sensitivityReduction,finalIntensityThreshold:A.finalIntensityThreshold,transientsRemaining:A.transientsRemaining}}static serializePhraseAnalysisResult(A){return{phrases:A.phrases.map(B=>this.serializeRhythmicPhrase(B)),phrasesByBand:Array.from(A.phrasesByBand.entries()).map(([B,g])=>({band:B,phrases:g.map(I=>this.serializeRhythmicPhrase(I))})),mostSignificantPhrases:A.mostSignificantPhrases.map(B=>this.serializeRhythmicPhrase(B)),phrasesBySize:Array.from(A.phrasesBySize.entries()).map(([B,g])=>({size:B,phrases:g.map(I=>this.serializeRhythmicPhrase(I))})),patternLibrary:A.patternLibrary.map(B=>this.serializeRhythmicPhrase(B)),bandAnalysis:{low:this.serializeBandPhraseAnalysis(A.bandAnalysis.low),mid:this.serializeBandPhraseAnalysis(A.bandAnalysis.mid),high:this.serializeBandPhraseAnalysis(A.bandAnalysis.high)}}}static deserializePhraseAnalysisResult(A){const B=new Map;for(const I of A.phrasesByBand)B.set(I.band,I.phrases.map(E=>this.deserializeRhythmicPhrase(E)));const g=new Map;for(const I of A.phrasesBySize)g.set(I.size,I.phrases.map(E=>this.deserializeRhythmicPhrase(E)));return{phrases:A.phrases.map(I=>this.deserializeRhythmicPhrase(I)),phrasesByBand:B,mostSignificantPhrases:A.mostSignificantPhrases.map(I=>this.deserializeRhythmicPhrase(I)),phrasesBySize:g,patternLibrary:A.patternLibrary.map(I=>this.deserializeRhythmicPhrase(I)),bandAnalysis:{low:this.deserializeBandPhraseAnalysis(A.bandAnalysis.low),mid:this.deserializeBandPhraseAnalysis(A.bandAnalysis.mid),high:this.deserializeBandPhraseAnalysis(A.bandAnalysis.high)}}}static serializeRhythmicPhrase(A){return{id:A.id,pattern:A.pattern.map(B=>this.serializeGeneratedBeat(B)),sizeInBeats:A.sizeInBeats,sourceBand:A.sourceBand,occurrences:A.occurrences,significance:A.significance,hasVariation:A.hasVariation,availableForReuse:A.availableForReuse}}static deserializeRhythmicPhrase(A){return{id:A.id,pattern:A.pattern.map(B=>this.deserializeGeneratedBeat(B)),sizeInBeats:A.sizeInBeats,sourceBand:A.sourceBand,occurrences:A.occurrences,significance:A.significance,hasVariation:A.hasVariation,availableForReuse:A.availableForReuse}}static serializeBandPhraseAnalysis(A){return{band:A.band,phrases:A.phrases.map(B=>this.serializeRhythmicPhrase(B)),phrasesBySize:Array.from(A.phrasesBySize.entries()).map(([B,g])=>({size:B,phrases:g.map(I=>this.serializeRhythmicPhrase(I))})),phrasesWithVariation:A.phrasesWithVariation.map(B=>this.serializeRhythmicPhrase(B))}}static deserializeBandPhraseAnalysis(A){const B=new Map;for(const g of A.phrasesBySize)B.set(g.size,g.phrases.map(I=>this.deserializeRhythmicPhrase(I)));return{band:A.band,phrases:A.phrases.map(g=>this.deserializeRhythmicPhrase(g)),phrasesBySize:B,phrasesWithVariation:A.phrasesWithVariation.map(g=>this.deserializeRhythmicPhrase(g))}}static serializeDensityAnalysisResult(A){return{bandMetrics:{low:this.serializeBandDensityMetrics(A.bandMetrics.low),mid:this.serializeBandDensityMetrics(A.bandMetrics.mid),high:this.serializeBandDensityMetrics(A.bandMetrics.high)},combinedMetrics:A.combinedMetrics,sections:A.sections,perBeatDensity:A.perBeatDensity}}static deserializeDensityAnalysisResult(A){return{bandMetrics:{low:this.deserializeBandDensityMetrics(A.bandMetrics.low),mid:this.deserializeBandDensityMetrics(A.bandMetrics.mid),high:this.deserializeBandDensityMetrics(A.bandMetrics.high)},combinedMetrics:A.combinedMetrics,sections:A.sections,perBeatDensity:A.perBeatDensity}}static serializeBandDensityMetrics(A){return{band:A.band,totalBeats:A.totalBeats,totalTransients:A.totalTransients,notesPerSecond:A.notesPerSecond,minNotesPerSecond:A.minNotesPerSecond,maxNotesPerSecond:A.maxNotesPerSecond,variance:A.variance,densityCategory:A.densityCategory,naturalDifficulty:A.naturalDifficulty,perBeatDensity:A.perBeatDensity}}static deserializeBandDensityMetrics(A){return{band:A.band,totalBeats:A.totalBeats,totalTransients:A.totalTransients,notesPerSecond:A.notesPerSecond,minNotesPerSecond:A.minNotesPerSecond,maxNotesPerSecond:A.maxNotesPerSecond,variance:A.variance,densityCategory:A.densityCategory,naturalDifficulty:A.naturalDifficulty,perBeatDensity:A.perBeatDensity}}static serializeStreamScoringResult(A){return{sectionScores:A.sectionScores,bandTotals:A.bandTotals,bandAverages:A.bandAverages,sectionWinners:A.sectionWinners,config:A.config}}static deserializeStreamScoringResult(A){return{sectionScores:A.sectionScores,bandTotals:A.bandTotals,bandAverages:A.bandAverages,sectionWinners:A.sectionWinners,config:A.config}}}const Zd={minFrequency:80,maxFrequency:1e3,frameSize:2048,hopSize:512,voicingThreshold:.2,transitionPenalty:.5,selfTransitionProbability:.99,yinThreshold:.4,targetSampleRate:44100},ht=69,ct=440,Wd=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];class ki{constructor(A={}){if(this.states=[],this.transitionMatrix=[],this.config={...Zd,...A},this.config.minFrequency<=0)throw new Error(`minFrequency must be positive, got: ${this.config.minFrequency}`);if(this.config.maxFrequency<=this.config.minFrequency)throw new Error("maxFrequency must be greater than minFrequency");if(this.config.frameSize<=0||(this.config.frameSize&this.config.frameSize-1)!==0)throw new Error("frameSize must be a positive power of 2");if(this.config.hopSize<=0)throw new Error("hopSize must be positive");if(this.config.voicingThreshold<0||this.config.voicingThreshold>1)throw new Error("voicingThreshold must be between 0 and 1");this.initializeHMM()}getConfig(){return{...this.config}}initializeHMM(){const{minFrequency:A,maxFrequency:B}=this.config,g=this.frequencyToMidi(A),I=this.frequencyToMidi(B);this.states=[];for(let E=g;E<=I;E+=.5)this.states.push({frequency:this.midiToFrequency(E),midiNote:Math.round(E),index:this.states.length});this.states.push({frequency:0,midiNote:-1,index:this.states.length}),this.transitionMatrix=this.computeTransitionMatrix()}computeTransitionMatrix(){const A=this.states.length,B=[];for(let g=0;g<A;g++){B[g]=[];for(let I=0;I<A;I++)B[g][I]=this.transitionProbability(g,I)}return B}transitionProbability(A,B){const{selfTransitionProbability:g,transitionPenalty:I}=this.config,i=this.states.length-1;if(A===B)return Math.log(g);if(A===i||B===i)return Math.log((1-g)*.1);const C=this.states[A].midiNote,a=this.states[B].midiNote,e=Math.abs(a-C),o=(1-g)*Math.exp(-I*e);return Math.log(o)}frequencyToMidi(A){return ht+12*Math.log2(A/ct)}midiToFrequency(A){return ct*Math.pow(2,(A-ht)/12)}midiToNoteName(A){const B=Math.floor(A/12)-1,g=Math.round(A)%12;return`${Wd[g]}${B}`}detect(A){const g=Kg(A,this.config.targetSampleRate).data,I=this.config.targetSampleRate,{frameSize:E,hopSize:i}=this.config,C=Math.floor((g.length-E)/i)+1,a=i/I,e=[],o=[];for(let G=0;G<C;G++){const n=G*i,h=g.slice(n,n+E),{filtered:c,all:d}=this.analyzeFrame(h,I);e.push(c),o.push(d)}const t=this.viterbiDecode(e),s=[];for(let G=0;G<C;G++){const n=t[G],h=this.states[n],c=e[G],d=o[G],l={timestamp:G*a,frequency:h.frequency,probability:this.getFrameProbability(n,c),isVoiced:h.frequency>0,midiNote:h.frequency>0?h.midiNote:null,noteName:h.frequency>0?this.midiToNoteName(h.midiNote):null};d.length>1&&(l.alternativeHypotheses=d.slice(0,5).map(F=>({frequency:F.frequency,probability:F.probability}))),s.push(l)}return s}detectSignal(A,B){const{frameSize:g,hopSize:I}=this.config,E=Math.floor((A.length-g)/I)+1,i=I/B,C=[],a=[];for(let t=0;t<E;t++){const s=t*I,G=A.slice(s,s+g),{filtered:n,all:h}=this.analyzeFrame(G,B);C.push(n),a.push(h)}const e=this.viterbiDecode(C),o=[];for(let t=0;t<E;t++){const s=e[t],G=this.states[s],n=C[t],h=a[t],c={timestamp:t*i,frequency:G.frequency,probability:this.getFrameProbability(s,n),isVoiced:G.frequency>0,midiNote:G.frequency>0?G.midiNote:null,noteName:G.frequency>0?this.midiToNoteName(G.midiNote):null};h.length>1&&(c.alternativeHypotheses=h.slice(0,5).map(d=>({frequency:d.frequency,probability:d.probability}))),o.push(c)}return o}detectSignalRaw(A,B){const{frameSize:g,hopSize:I,voicingThreshold:E}=this.config,i=Math.floor((A.length-g)/I)+1,C=I/B,a=[];let e=0,o=0;const t={below01:0,b01_02:0,b02_03:0,b03_04:0,b04_05:0,above05:0};for(let s=0;s<i;s++){const G=s*I,n=A.slice(G,G+g),{filtered:h,all:c}=this.analyzeFrame(n,B),d=s*C;if(h.length>0){e++;const l=h[0].probability;if(l<.1?t.below01++:l<.2?t.b01_02++:l<.3?t.b02_03++:l<.4?t.b03_04++:l<.5?t.b04_05++:t.above05++,l>=E){const F=h[0],m=Math.round(this.frequencyToMidi(F.frequency)),y={timestamp:d,frequency:F.frequency,probability:F.probability,isVoiced:!0,midiNote:m,noteName:this.midiToNoteName(m)};c.length>1&&(y.alternativeHypotheses=c.slice(0,5).map(p=>({frequency:p.frequency,probability:p.probability}))),a.push(y)}else a.push({timestamp:d,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null})}else o++,a.push({timestamp:d,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null})}return console.log("[PitchDetector.detectSignalRaw] Candidate quality:",{numFrames:i,framesWithCandidates:e,framesWithNoCandidates:o,candidateRate:(e/i*100).toFixed(1)+"%",voicingThreshold:E,probBuckets:t}),a}detectAt(A,B,g){const{frameSize:I,hopSize:E}=this.config,i=Math.round(g*B),C=Math.max(0,i-Math.floor(I/2)),a=Math.min(A.length,C+I);if(a-C<I)return{timestamp:g,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null};const e=A.slice(C,a),{filtered:o,all:t}=this.analyzeFrame(e,B);if(o.length===0)return{timestamp:g,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null};const s=o[0],G=Math.round(this.frequencyToMidi(s.frequency)),n={timestamp:g,frequency:s.frequency,probability:s.probability,isVoiced:s.probability>=this.config.voicingThreshold,midiNote:G,noteName:this.midiToNoteName(G)};return t.length>1&&(n.alternativeHypotheses=t.slice(0,5).map(h=>({frequency:h.frequency,probability:h.probability}))),n}analyzeFrame(A,B){const{minFrequency:g,maxFrequency:I,yinThreshold:E}=this.config,i=Math.ceil(B/g),C=Math.max(2,Math.floor(B/I)),a=Math.min(i,Math.floor(A.length/2)),e=this.computeDifferenceFunction(A,a),o=this.computeCMNDF(e,C,a),t=[];for(let n=C+1;n<a-1;n++)if(o[n]<o[n-1]&&o[n]<o[n+1]&&o[n]<E){const h=this.parabolicInterpolation(o,n),c=B/h,d=1-o[n];c>=g&&c<=I&&t.push({lag:h,frequency:c,probability:d})}if(t.sort((n,h)=>h.probability-n.probability),t.length===0)return{filtered:[],all:[]};const s=[],G=t[0].probability;for(const n of t){let h=!1;for(const c of t){if(c.frequency<=n.frequency||c.probability<G*.9)continue;const d=c.frequency/n.frequency,l=Math.round(d),F=Math.abs(d-l);if(l>=2&&l<=5&&F<.1){h=!0;break}}h||s.push(n)}return s.length===0&&t.length>0&&s.push(t[0]),{filtered:s,all:t}}computeDifferenceFunction(A,B){const g=A.length,I=new Float32Array(B+1);I[0]=0;for(let E=1;E<=B;E++){let i=0;for(let C=0;C<g-E;C++){const a=A[C]-A[C+E];i+=a*a}I[E]=i}return I}computeCMNDF(A,B,g){const I=new Float32Array(A.length);I[0]=1;let E=0;for(let i=1;i<=g;i++)E+=A[i],I[i]=A[i]*i/E;return I}parabolicInterpolation(A,B){if(B<=0||B>=A.length-1)return B;const g=A[B-1],I=A[B],E=A[B+1],i=(E-g)/(2*(2*I-g-E));return Math.max(B-1,Math.min(B+1,B+i))}getFrameProbability(A,B){const I=this.states.length-1;if(A===I)return B.length===0?1:1-B[0].probability;const E=this.states[A].frequency;let i=0;for(const C of B)Math.abs(C.frequency-E)/E<.05&&(i=Math.max(i,C.probability));return i}viterbiDecode(A){const B=A.length,g=this.states.length;if(B===0)return[];const I=[];I[0]=[];for(let a=0;a<g;a++){const e=this.priorLogProbability(a,A[0]);I[0][a]={logProbability:e,previousState:-1}}for(let a=1;a<B;a++){I[a]=[];for(let e=0;e<g;e++){const o=this.observationLogProbability(e,A[a]);let t=0,s=-1/0;for(let G=0;G<g;G++){const n=this.transitionMatrix[G][e],h=I[a-1][G].logProbability+n;h>s&&(s=h,t=G)}I[a][e]={logProbability:s+o,previousState:t}}}const E=new Array(B);let i=0,C=I[B-1][0].logProbability;for(let a=1;a<g;a++)I[B-1][a].logProbability>C&&(C=I[B-1][a].logProbability,i=a);E[B-1]=i;for(let a=B-2;a>=0;a--)E[a]=I[a+1][E[a+1]].previousState;return E}priorLogProbability(A,B){const g=this.states.length,I=g-1,E=Math.log(.7/(g-1)),i=Math.log(.3);return A===I?i+this.observationLogProbability(A,B):E+this.observationLogProbability(A,B)}observationLogProbability(A,B){const I=this.states.length-1;if(A===I){if(B.length===0)return Math.log(.9);const C=B[0].probability;return Math.log(1-C*.8)}const E=this.states[A].frequency;let i=0;for(const C of B){const a=C.frequency/E,e=Math.abs(a-1);if(e<.05){const o=1-e/.05*.2,t=C.probability*o;t>i&&(i=t)}if(i===0){const o=Math.abs(a-.5),t=Math.abs(a-2);if(o<.05||t<.05){const s=C.probability*.4;s>i&&(i=s)}}}return Math.log(i===0?.01:i)}}const iQ="https://arweave.net/_HoMwkbF0JUamTiVGWNnCCC5yDjcPry-ENhzN6Xn3U0/model.json",rt={algorithm:"pitch_melodia",minFrequency:80,maxFrequency:2e4,frameSize:2048,hopSize:128,targetSampleRate:44100,crepeModelUrl:iQ},Hd=69,fd=440,Nd=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];class Tg{constructor(A){this.essentiaWASM=null,this.algorithms=null,this.crepeModel=null,this.initialized=!1,this.resolvedUrlCache=new Map,this.config={...rt,...A}}static async create(A={}){const B={...rt,...A};B.resolveUrl||(B.resolveUrl=kA.resolveUrl.bind(kA));const g=new Tg(B);return await g.initializeEssentia(),B.algorithm==="pitch_crepe"&&await g.loadCrepeModel(B.crepeModelUrl),g}getConfig(){return{...this.config}}async initializeEssentia(){if(this.initialized)return;const B=(await Promise.resolve().then(()=>xs)).EssentiaWASM;await B.ready,this.essentiaWASM=B,this.algorithms=new B.EssentiaJS(!1),this.initialized=!0}async loadCrepeModel(A){if(this.config.enableModelCache!==!1)this.crepeModel=await lg.getOrFetchGraphModel(JA,A,{resolveUrl:this.config.resolveUrl,invalidateUrlCache:g=>kA.invalidateCacheForUrl(g),maxRetries:3,baseDelayMs:1e3});else{const g=await this.resolveUrlWithCache(A);let I=null;for(let E=0;E<3;E++)try{this.crepeModel=await JA.loadGraphModel(g);break}catch(i){I=i;const C=String(i).toLowerCase();if(!(C.includes("fetch")||C.includes("network")||C.includes("timeout")||C.includes("failed to fetch")||C.includes("networkerror"))||E===2)throw i;const e=1e3*Math.pow(2,E);console.warn(`[EssentiaPitchDetector] CREPE model load failed (attempt ${E+1}/3), retrying in ${e}ms...`,{originalUrl:A,resolvedUrl:g,error:String(i)}),await new Promise(o=>setTimeout(o,e))}if(!this.crepeModel)throw new Error(`Failed to load CREPE model from ${A}: ${I?.message??String(I)}`)}const B=JA.zeros([1,1024]);JA.tidy(()=>{this.crepeModel.execute(B)}),B.dispose(),console.log("[EssentiaPitchDetector] CREPE model loaded")}async resolveUrlWithCache(A){if(this.resolvedUrlCache.has(A))return this.resolvedUrlCache.get(A);if(!this.config.resolveUrl)return A;try{const B=await this.config.resolveUrl(A);return this.resolvedUrlCache.set(A,B),B!==A&&console.info("[EssentiaPitchDetector] Resolved URL to alternate gateway",{originalUrl:A,resolvedUrl:B}),B}catch(B){return console.warn("[EssentiaPitchDetector] URL resolution failed, using original URL",{url:A,error:String(B)}),this.resolvedUrlCache.set(A,A),A}}detectSignal(A,B){if(!this.initialized)throw new Error("EssentiaPitchDetector not initialized. Use create() factory method.");if(A.length===0)return[];switch(this.config.algorithm){case"predominant_melodia":return this.runPredominantMelodia(A,B);case"pitch_melodia":return this.runPitchMelodia(A,B);case"pitch_yin_probabilistic":return this.runPitchYinProbabilistic(A,B);case"multipitch_melodia":return this.runMultiPitchMelodia(A,B);case"multipitch_klapuri":return this.runMultiPitchKlapuri(A,B);case"pitch_crepe":return this.runCrepe(A,B);default:throw new Error(`Unknown algorithm: ${this.config.algorithm}`)}}runPredominantMelodia(A,B){const g=this.essentiaWASM.arrayToVector(A),I=this.algorithms.PredominantPitchMelodia(g,10,3,this.config.frameSize,!1,.8,this.config.hopSize,1,40,this.config.maxFrequency,100,this.config.minFrequency,20,.9,.9,27.5625,55,B,100,!1,.2),E=Array.from(this.essentiaWASM.vectorToArray(I.pitch)),i=Array.from(this.essentiaWASM.vectorToArray(I.pitchConfidence)),C=this.config.hopSize/B;return this.mapSingleF0(E,i,C)}runPitchMelodia(A,B){const g=this.essentiaWASM.arrayToVector(A),I=this.algorithms.PitchMelodia(g,10,3,this.config.frameSize,!1,.8,this.config.hopSize,1,40,this.config.maxFrequency,100,this.config.minFrequency,20,.9,.9,27.5625,55,B,100),E=Array.from(this.essentiaWASM.vectorToArray(I.pitch)),i=Array.from(this.essentiaWASM.vectorToArray(I.pitchConfidence)),C=this.config.hopSize/B;return this.mapSingleF0(E,i,C)}runPitchYinProbabilistic(A,B){const g=this.essentiaWASM.arrayToVector(A),I=this.algorithms.PitchYinProbabilistic(g,this.config.frameSize,this.config.hopSize,.1,"negative",!1,B),E=Array.from(this.essentiaWASM.vectorToArray(I.pitch)),i=Array.from(this.essentiaWASM.vectorToArray(I.voicedProbabilities)),C=this.config.hopSize/B;return E.map((a,e)=>{const o=e*C;if(a>0&&i[e]>.5){const s=Math.round(this.frequencyToMidi(a));return{timestamp:o,frequency:a,probability:i[e],isVoiced:!0,midiNote:s,noteName:this.midiToNoteName(s)}}return{timestamp:o,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null}})}runMultiPitchMelodia(A,B){const g=this.essentiaWASM.arrayToVector(A),I=this.algorithms.MultiPitchMelodia(g,10,3,this.config.frameSize,!1,.8,this.config.hopSize,1,40,this.config.maxFrequency,100,this.config.minFrequency,20,.9,.9,27.5625,55,B,100);return this.mapMultiPitch(I.pitch,B)}runMultiPitchKlapuri(A,B){const g=this.essentiaWASM.arrayToVector(A),I=this.algorithms.MultiPitchKlapuri(g,10,this.config.frameSize,.8,this.config.hopSize,1,40,this.config.maxFrequency,this.config.minFrequency,10,55,B);return this.mapMultiPitch(I.pitch,B)}runCrepe(A,B){if(!this.crepeModel)throw new Error('CREPE model not loaded. Ensure algorithm is "pitch_crepe" and model URL is valid.');const g=16e3,I=1024,E=256,i=this.resampleSignal(A,B,g),C=Math.floor((i.length-I)/E)+1,a=E/g,e=32.7,o=[];for(let t=0;t<C;t++){const s=t*E,G=i.slice(s,s+I),n=t*a;let h;try{const d=JA.tidy(()=>{const y=JA.tensor(G,[1,I]);return this.crepeModel.execute(y).dataSync().slice()});let l=0,F=0,m=0;for(let y=0;y<360;y++){const p=d[y];l+=y*10*p,F+=p,p>m&&(m=p)}if(F>0&&m>.3){const y=l/F,p=e*Math.pow(2,y/1200),k=Math.round(this.frequencyToMidi(p));h={timestamp:n,frequency:p,probability:m,isVoiced:!0,midiNote:k,noteName:this.midiToNoteName(k)}}else h={timestamp:n,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null}}catch(c){console.error(`[EssentiaPitchDetector] CREPE inference error at frame ${t}:`,c),h={timestamp:n,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null}}o.push(h)}return o}mapSingleF0(A,B,g){return A.map((I,E)=>{const i=E*g;if(I>0){const C=Math.round(this.frequencyToMidi(I));return{timestamp:i,frequency:I,probability:B[E]??0,isVoiced:!0,midiNote:C,noteName:this.midiToNoteName(C)}}return{timestamp:i,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null}})}mapMultiPitch(A,B){const g=A.size(),I=this.config.hopSize/B,E=[];for(let i=0;i<g;i++){const C=A.get(i),e=Array.from(this.essentiaWASM.vectorToArray(C)).filter(t=>t>0).sort((t,s)=>t-s),o=i*I;if(e.length>0){const t=e[0],s=Math.round(this.frequencyToMidi(t)),G=e.slice(1).map(n=>({frequency:n,probability:1}));E.push({timestamp:o,frequency:t,probability:1,isVoiced:!0,midiNote:s,noteName:this.midiToNoteName(s),...G.length>0?{alternativeHypotheses:G}:{}})}else E.push({timestamp:o,frequency:0,probability:0,isVoiced:!1,midiNote:null,noteName:null})}return E}resampleSignal(A,B,g){if(B===g)return A;const I=B/g,E=Math.round(A.length/I),i=new Float32Array(E);for(let C=0;C<E;C++){const a=C*I,e=Math.floor(a),o=Math.min(e+1,A.length-1),t=a-e;i[C]=A[e]*(1-t)+A[o]*t}return i}frequencyToMidi(A){return Hd+12*Math.log2(A/fd)}midiToNoteName(A){const B=Math.floor(A/12)-1,g=Math.round(A)%12;return`${Nd[g]}${B}`}}const lt={targetSampleRate:44100,pitchDetector:{},pitchAlgorithm:"pitch_melodia",crepeModelUrl:iQ};class Dt{constructor(A={}){this.essentiaDetector=null,this.config={...lt,...A,pitchDetector:{...lt.pitchDetector,...A.pitchDetector}},this.fullSpectrumDetector=new ki({...this.config.pitchDetector,minFrequency:this.config.pitchDetector.minFrequency??80,maxFrequency:this.config.pitchDetector.maxFrequency??1e3,targetSampleRate:this.config.targetSampleRate,yinThreshold:this.config.pitchDetector.yinThreshold??.4})}getConfig(){return{...this.config}}async linkWithComposite(A,B){const g=this.config.targetSampleRate,E=Kg(B,g).data;let i,C;this.config.pitchAlgorithm==="pyin_legacy"?(i=this.fullSpectrumDetector.detectSignalRaw(E,g),C=this.fullSpectrumDetector.getConfig().hopSize/g):(this.essentiaDetector||(this.essentiaDetector=await Tg.create({algorithm:this.config.pitchAlgorithm,minFrequency:this.config.pitchDetector.minFrequency??80,maxFrequency:this.config.pitchDetector.maxFrequency??2e4,crepeModelUrl:this.config.crepeModelUrl,resolveUrl:this.config.resolveUrl}),console.log(`[PitchBeatLinker] Essentia detector initialized: ${this.config.pitchAlgorithm}`)),i=this.essentiaDetector.detectSignal(E,g),C=this.essentiaDetector.getConfig().hopSize/g),console.debug("[PitchBeatLinker] Full-spectrum analysis:",{detector:this.config.pitchAlgorithm==="pyin_legacy"?"pYIN":`essentia/${this.config.pitchAlgorithm}`,totalFrames:i.length,voicedFrames:i.filter(e=>e.isVoiced).length,signalLength:E.length,signalSampleRate:g});const a=[];for(const e of A.beats){const o=e.detectedTimestamp??e.timestamp,t=Math.round(o/C);let s=null,G=1/0,n=0;for(let h=0;h<i.length;h++){const c=i[h];if(!c.isVoiced)continue;const d=Math.abs(h-t);(d<G||d===G&&c.probability>n)&&(G=d,n=c.probability,s=c)}a.push({beatIndex:e.beatIndex,timestamp:e.timestamp,band:e.sourceBand,pitch:s,direction:"none",intervalFromPrevious:0})}return a}deriveVariantPitches(A,B){const g=[],I=new Map;for(const E of B){const i=Math.round(E.timestamp*1e3);I.set(i,E)}for(const E of A.beats){const i=Math.round(E.timestamp*1e3);let C=I.get(i);if(!C)for(let a=-2;a<=2&&(C=I.get(i+a),!C);a++);C?g.push({beatIndex:E.beatIndex,timestamp:E.timestamp,band:E.sourceBand,pitch:C.pitch,direction:C.direction,intervalFromPrevious:C.intervalFromPrevious,intervalCategory:C.intervalCategory}):g.push({beatIndex:E.beatIndex,timestamp:E.timestamp,band:E.sourceBand,pitch:null,direction:"none",intervalFromPrevious:0})}return g}deriveAllVariantPitches(A,B){return{easy:this.deriveVariantPitches(A.easy,B),medium:this.deriveVariantPitches(A.medium,B),hard:this.deriveVariantPitches(A.hard,B)}}}const Jd={maxTimeGapForConsecutive:.5};function dt(Q){return Q===0?"unison":Q<=2?"small":Q<=4?"medium":Q<=7?"large":"very_large"}function qd(Q,A){return Math.abs(Q-A)}function xd(Q,A){return Q===null||A===null?"none":Q>A?"up":Q<A?"down":"stable"}function Vd(Q){if(Q.length===0)return"stable";let A=0,B=0,g=0;for(const C of Q)C.direction==="up"?A++:C.direction==="down"?B++:g++;if(A===0&&B===0)return"stable";const I=A-B,i=(A+B+g)*.2;return Math.abs(I)<=i?"mixed":A>B?"ascending":B>A?"descending":"stable"}function Ft(Q){const A=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"],B=Math.floor(Q/12)-1,g=Q%12;return`${A[g]}${B}`}class Ld{constructor(A={}){this.config={...Jd,...A}}getConfig(){return{...this.config}}analyze(A){const B=this.analyzeBandPitches(A),{directionStats:g,intervalStats:I,metadata:E}=this.calculateStatistics(B),i=this.buildMelodyContour(B);return{pitchByBeat:B,melodyContour:i,directionStats:g,intervalStats:I,metadata:E}}analyzeBandPitches(A){if(A.length===0)return[];const B=[];for(let g=0;g<A.length;g++){const I=A[g],E=g>0?A[g-1]:null,i=E!==null&&I.timestamp-E.timestamp<=this.config.maxTimeGapForConsecutive;let C,a,e;if(!i||!E||!E.pitch||!I.pitch)C="none",a=0,e="unison";else{const o=I.pitch.midiNote,t=E.pitch.midiNote;o===null||t===null?(C="none",a=0,e="unison"):(C=xd(o,t),a=qd(o,t),e=dt(a))}B.push({...I,direction:C,intervalFromPrevious:a,intervalCategory:e})}return B}buildMelodyContour(A){const B=A.filter(G=>G.pitch?.isVoiced);if(B.length===0)return{segments:[],direction:"stable",range:{minNote:"N/A",maxNote:"N/A",semitones:0},shortTermDirection:"stable",mediumTermDirection:"stable",longTermDirection:"stable"};const g=B.map(G=>G.pitch?.midiNote).filter(G=>G!==null);if(g.length===0)return{segments:[],direction:"stable",range:{minNote:"N/A",maxNote:"N/A",semitones:0},shortTermDirection:"stable",mediumTermDirection:"stable",longTermDirection:"stable"};const I=Math.min(...g),E=Math.max(...g),i=Ft(I),C=Ft(E),a=this.buildSegments(B),e=Vd(a),o=this.calculateTimeWindowDirection(A,1,2),t=this.calculateTimeWindowDirection(A,4,8),s=this.calculateTimeWindowDirection(A,16,1/0);return{segments:a,direction:e,range:{minNote:i,maxNote:C,semitones:E-I},shortTermDirection:o,mediumTermDirection:t,longTermDirection:s}}calculateTimeWindowDirection(A,B,g){if(A.length===0||B<1)return"stable";const I=Math.min(g,Math.max(B,A.length)),E=A.slice(-I);if(E.length===0)return"stable";let i=0,C=0,a=0;for(const e of E)e.direction==="up"?i++:e.direction==="down"?C++:e.direction==="stable"&&a++;return this.determineOverallDirectionFromCounts(i,C,a)}determineOverallDirectionFromCounts(A,B,g){const I=A+B+g;if(I===0)return"stable";const E=I*.2,i=A-B;return Math.abs(i)<=E?"mixed":A>B?"ascending":B>A?"descending":"stable"}buildSegments(A){if(A.length===0)return[];const B=[];let g=null;for(const I of A){const E=I.direction;if(E==="none"){g&&(B.push({startTime:g.startTime,endTime:g.endTime,startPitch:g.startPitch,endPitch:g.endPitch,direction:g.direction,interval:Math.abs(g.endMidi-g.startMidi)}),g=null);continue}if(!g||g.direction!==E){g&&B.push({startTime:g.startTime,endTime:g.endTime,startPitch:g.startPitch,endPitch:g.endPitch,direction:g.direction,interval:Math.abs(g.endMidi-g.startMidi)});const i=I.pitch?.noteName??"N/A",C=I.pitch?.midiNote??60;g={startTime:I.timestamp,endTime:I.timestamp,startPitch:i,endPitch:i,direction:E,startMidi:C,endMidi:C}}else{const i=I.pitch?.midiNote??g.endMidi,C=I.pitch?.noteName??g.endPitch;g.endTime=I.timestamp,g.endPitch=C,g.endMidi=i}}return g&&B.push({startTime:g.startTime,endTime:g.endTime,startPitch:g.startPitch,endPitch:g.endPitch,direction:g.direction,interval:Math.abs(g.endMidi-g.startMidi)}),B}calculateStatistics(A){const B={up:0,down:0,stable:0,none:0},g={unison:0,small:0,medium:0,large:0,very_large:0};let I=0,E=0;for(const i of A)i.pitch?.isVoiced&&I++,B[i.direction]++,i.direction!=="none"&&E++,i.intervalCategory&&g[i.intervalCategory]++;return{directionStats:B,intervalStats:g,metadata:{totalBeats:A.length,voicedBeats:I,directionCalculatedBeats:E}}}}const Xd={algorithm:"pitch_melodia",minFrequency:80,maxFrequency:2e4,sampleRate:44100,hopSize:1024,crepeModelUrl:iQ,resolveUrl:kA.resolveUrl.bind(kA),includeContour:!0,onProgress:void 0},vd=69,Kd=440,zd=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];class Td{constructor(A={}){this.config={...Xd,...A}}getConfig(){return{...this.config}}async analyze(A){const{onProgress:B}=this.config;B?.("fetching",0);const{signal:g,sampleRate:I,duration:E}=await this.fetchAndDecodeAudio(A);B?.("fetching",1),B?.("detecting",0);const i=this.resolveDefaults(this.config),C=await this.detectPitch(g,I,i);B?.("detecting",1);const a=this.computeSummary(C);let e,o,t;if(this.config.includeContour!==!1){const s=this.computeContour(C);e=s.contour,o=s.directionStats,t=s.intervalStats}return{pitchResults:C,contour:e,voicingRatio:a.voicingRatio,averageFrequency:a.averageFrequency,medianFrequency:a.medianFrequency,minFrequency:a.minFrequency,maxFrequency:a.maxFrequency,pitchRangeSemitones:a.pitchRangeSemitones,lowestNote:a.lowestNote,highestNote:a.highestNote,noteDistribution:a.noteDistribution,totalFrames:a.totalFrames,voicedFrames:a.voicedFrames,directionStats:o,intervalStats:t,analysis_metadata:{algorithm_used:this.config.algorithm,analyzed_at:new Date().toISOString(),duration_analyzed:E}}}async fetchAndDecodeAudio(A){const B=this.config.resolveUrl?await this.config.resolveUrl(A):A,g=await fetch(B);if(!g.ok)throw new Error(`Failed to fetch audio: ${g.statusText}`);const I=await g.arrayBuffer(),E=globalThis.AudioContext||globalThis.window?.AudioContext;if(!E)throw new Error("AudioContext not available in this environment");const i=new E,C=await new Promise((e,o)=>{i.decodeAudioData(I,e,o)}),a=new Float32Array(C.length);for(let e=0;e<C.numberOfChannels;e++){const o=C.getChannelData(e);for(let t=0;t<C.length;t++)a[t]+=o[t]/C.numberOfChannels}return{signal:a,sampleRate:C.sampleRate,duration:C.duration}}resolveDefaults(A){const B={...A};return A.maxFrequency===void 0&&(A.algorithm==="pyin_legacy"?B.maxFrequency=1e3:B.maxFrequency=2e4),B}async detectPitch(A,B,g){const I=g.algorithm??"pitch_melodia";return I==="pyin_legacy"?new ki({minFrequency:g.minFrequency??80,maxFrequency:g.maxFrequency??1e3,targetSampleRate:g.sampleRate??44100,hopSize:g.hopSize??1024}).detectSignal(A,B):(await Tg.create({algorithm:I,minFrequency:g.minFrequency??80,maxFrequency:g.maxFrequency??2e4,targetSampleRate:g.sampleRate??44100,hopSize:g.hopSize??1024,crepeModelUrl:g.crepeModelUrl,resolveUrl:g.resolveUrl})).detectSignal(A,B)}computeSummary(A){const B=A.length,g=A.filter(F=>F.isVoiced&&F.frequency>0),I=g.length;if(I===0)return{voicingRatio:0,averageFrequency:0,medianFrequency:0,minFrequency:0,maxFrequency:0,pitchRangeSemitones:0,lowestNote:null,highestNote:null,noteDistribution:[],totalFrames:B,voicedFrames:0};const E=g.map(F=>F.frequency),i=[...E].sort((F,m)=>F-m),C=E.reduce((F,m)=>F+m,0)/E.length,a=Math.floor(i.length/2),e=i.length%2!==0?i[a]:(i[a-1]+i[a])/2,o=i[0],t=i[i.length-1],s=this.frequencyToMidi(o),G=this.frequencyToMidi(t),n=Math.abs(G-s),h=this.midiToNoteName(Math.round(s)),c=this.midiToNoteName(Math.round(G)),d=new Map;for(const F of g)F.noteName&&d.set(F.noteName,(d.get(F.noteName)??0)+1);const l=Array.from(d.entries()).map(([F,m])=>({note:F,count:m,percentage:m/I*100})).sort((F,m)=>m.count-F.count);return{voicingRatio:I/B,averageFrequency:C,medianFrequency:e,minFrequency:o,maxFrequency:t,pitchRangeSemitones:n,lowestNote:h,highestNote:c,noteDistribution:l,totalFrames:B,voicedFrames:I}}computeContour(A){const B=A.filter(d=>d.isVoiced&&d.midiNote!==null);if(B.length===0)return{contour:{direction:"stable",range:{minNote:"N/A",maxNote:"N/A",semitones:0},segments:[],shortTermDirection:"stable",mediumTermDirection:"stable",longTermDirection:"stable"},directionStats:{up:0,down:0,stable:0,none:A.length},intervalStats:{unison:0,small:0,medium:0,large:0,very_large:0}};const g=B.map(d=>d.midiNote).filter(d=>d!==null),I=Math.min(...g),E=Math.max(...g),i=this.midiToNoteName(I),C=this.midiToNoteName(E),a={up:0,down:0,stable:0,none:0},e={unison:0,small:0,medium:0,large:0,very_large:0},o=[];let t=null,s=null;for(const d of B){const l=d.midiNote;if(l===null){a.none++;continue}if(s===null){s=l;continue}let F;const m=Math.abs(l-s);l>s?(F="up",a.up++):l<s?(F="down",a.down++):(F="stable",a.stable++);const y=dt(m);e[y]++,!t||t.direction!==F?(t&&o.push({startTime:t.startTime,endTime:t.endTime,startNote:t.startNote,endNote:t.endNote,direction:t.direction,interval:Math.abs(t.endMidi-t.startMidi)}),t={startTime:d.timestamp,endTime:d.timestamp,startNote:this.midiToNoteName(s),endNote:this.midiToNoteName(l),direction:F,startMidi:s,endMidi:l}):(t.endTime=d.timestamp,t.endNote=this.midiToNoteName(l),t.endMidi=l),s=l}t&&o.push({startTime:t.startTime,endTime:t.endTime,startNote:t.startNote,endNote:t.endNote,direction:t.direction,interval:Math.abs(t.endMidi-t.startMidi)});const G=this.determineOverallDirection(o),n=this.calculateTimeWindowDirection(A,1,2),h=this.calculateTimeWindowDirection(A,4,8),c=this.calculateTimeWindowDirection(A,16,1/0);return{contour:{direction:G,range:{minNote:i,maxNote:C,semitones:E-I},segments:o,shortTermDirection:n,mediumTermDirection:h,longTermDirection:c},directionStats:a,intervalStats:e}}determineOverallDirection(A){if(A.length===0)return"stable";let B=0,g=0,I=0;for(const a of A)a.direction==="up"?B++:a.direction==="down"?g++:I++;if(B===0&&g===0)return"stable";const E=B-g,C=(B+g+I)*.2;return Math.abs(E)<=C?"mixed":B>g?"ascending":g>B?"descending":"stable"}calculateTimeWindowDirection(A,B,g){const I=A.filter(t=>t.isVoiced&&t.midiNote!==null);if(I.length===0||B<1)return"stable";const E=Math.min(g,Math.max(B,I.length)),i=I.slice(-E);if(i.length===0)return"stable";let C=0,a=0,e=0,o=null;for(const t of i){const s=t.midiNote;s!==null&&(o!==null&&(s>o?C++:s<o?a++:e++),o=s)}return this.determineOverallDirectionFromCounts(C,a,e)}determineOverallDirectionFromCounts(A,B,g){const I=A+B+g;if(I===0)return"stable";const E=I*.2,i=A-B;return Math.abs(i)<=E?"mixed":A>B?"ascending":B>A?"descending":"stable"}frequencyToMidi(A){return vd+12*Math.log2(A/Kd)}midiToNoteName(A){const B=Math.floor(A/12)-1,g=Math.round(A)%12;return`${zd[g]}${B}`}}const jd=[{id:"ddr_basic_single_up",name:"Single Up",controllerMode:"ddr",keys:["up"],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"ddr_basic_single_down",name:"Single Down",controllerMode:"ddr",keys:["down"],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"ddr_basic_single_left",name:"Single Left",controllerMode:"ddr",keys:["left"],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"ddr_basic_single_right",name:"Single Right",controllerMode:"ddr",keys:["right"],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"ddr_basic_alternating_up_down",name:"Alternating Up-Down",controllerMode:"ddr",keys:["up","down","up","down"],measures:1,tags:["basic","alternating","vertical"],category:"basic",difficulty:2},{id:"ddr_basic_alternating_left_right",name:"Alternating Left-Right",controllerMode:"ddr",keys:["left","right","left","right"],measures:1,tags:["basic","alternating","horizontal"],category:"basic",difficulty:2},{id:"ddr_basic_alternating_up_left",name:"Alternating Up-Left",controllerMode:"ddr",keys:["up","left","up","left"],measures:1,tags:["basic","alternating"],category:"basic",difficulty:2},{id:"ddr_basic_alternating_down_right",name:"Alternating Down-Right",controllerMode:"ddr",keys:["down","right","down","right"],measures:1,tags:["basic","alternating"],category:"basic",difficulty:2},{id:"ddr_basic_double_up",name:"Double Up",controllerMode:"ddr",keys:["up","up","down","down"],measures:1,tags:["basic","double","repeat"],category:"basic",difficulty:2},{id:"ddr_basic_double_left",name:"Double Left",controllerMode:"ddr",keys:["left","left","right","right"],measures:1,tags:["basic","double","repeat"],category:"basic",difficulty:2},{id:"ddr_basic_walk_forward",name:"Walk Forward",controllerMode:"ddr",keys:["left","right","left","right"],measures:1,tags:["basic","walk","horizontal"],category:"basic",difficulty:2},{id:"ddr_basic_walk_vertical",name:"Walk Vertical",controllerMode:"ddr",keys:["up","down","up","down"],measures:1,tags:["basic","walk","vertical"],category:"basic",difficulty:2}],Od=[{id:"ddr_roll_clockwise_full",name:"Clockwise Full Roll",controllerMode:"ddr",keys:["up","right","down","left"],measures:1,tags:["roll","clockwise","full"],category:"roll",difficulty:3},{id:"ddr_roll_clockwise_extended",name:"Clockwise Extended Roll",controllerMode:"ddr",keys:["up","right","down","left","up","right","down","left"],measures:2,tags:["roll","clockwise","extended"],category:"roll",difficulty:4},{id:"ddr_roll_counterclockwise_full",name:"Counter-Clockwise Full Roll",controllerMode:"ddr",keys:["up","left","down","right"],measures:1,tags:["roll","counterclockwise","full"],category:"roll",difficulty:3},{id:"ddr_roll_counterclockwise_extended",name:"Counter-Clockwise Extended Roll",controllerMode:"ddr",keys:["up","left","down","right","up","left","down","right"],measures:2,tags:["roll","counterclockwise","extended"],category:"roll",difficulty:4},{id:"ddr_roll_half_right",name:"Half Roll Right",controllerMode:"ddr",keys:["up","right","down"],measures:1,tags:["roll","half","right"],category:"roll",difficulty:3},{id:"ddr_roll_half_left",name:"Half Roll Left",controllerMode:"ddr",keys:["up","left","down"],measures:1,tags:["roll","half","left"],category:"roll",difficulty:3},{id:"ddr_roll_bottom_half_right",name:"Bottom Half Roll Right",controllerMode:"ddr",keys:["down","right","up"],measures:1,tags:["roll","half","bottom","right"],category:"roll",difficulty:3},{id:"ddr_roll_bottom_half_left",name:"Bottom Half Roll Left",controllerMode:"ddr",keys:["down","left","up"],measures:1,tags:["roll","half","bottom","left"],category:"roll",difficulty:3},{id:"ddr_roll_triangle_up",name:"Triangle Roll Up",controllerMode:"ddr",keys:["left","up","right"],measures:1,tags:["roll","triangle","top"],category:"roll",difficulty:3},{id:"ddr_roll_triangle_down",name:"Triangle Roll Down",controllerMode:"ddr",keys:["left","down","right"],measures:1,tags:["roll","triangle","bottom"],category:"roll",difficulty:3}],Pd=[{id:"ddr_stream_vertical_8",name:"Vertical Stream 8",controllerMode:"ddr",keys:["up","down","up","down","up","down","up","down"],measures:2,tags:["stream","vertical","fast"],category:"stream",difficulty:5},{id:"ddr_stream_vertical_16",name:"Vertical Stream 16",controllerMode:"ddr",keys:["up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down"],measures:4,tags:["stream","vertical","extended"],category:"stream",difficulty:7},{id:"ddr_stream_horizontal_8",name:"Horizontal Stream 8",controllerMode:"ddr",keys:["left","right","left","right","left","right","left","right"],measures:2,tags:["stream","horizontal","fast"],category:"stream",difficulty:5},{id:"ddr_stream_diagonal_1",name:"Diagonal Stream 1",controllerMode:"ddr",keys:["up","right","up","right","up","right","up","right"],measures:2,tags:["stream","diagonal"],category:"stream",difficulty:5},{id:"ddr_stream_diagonal_2",name:"Diagonal Stream 2",controllerMode:"ddr",keys:["up","left","up","left","up","left","up","left"],measures:2,tags:["stream","diagonal"],category:"stream",difficulty:5},{id:"ddr_stream_diagonal_3",name:"Diagonal Stream 3",controllerMode:"ddr",keys:["down","right","down","right","down","right","down","right"],measures:2,tags:["stream","diagonal"],category:"stream",difficulty:5},{id:"ddr_stream_diagonal_4",name:"Diagonal Stream 4",controllerMode:"ddr",keys:["down","left","down","left","down","left","down","left"],measures:2,tags:["stream","diagonal"],category:"stream",difficulty:5},{id:"ddr_stream_mixed_box",name:"Box Stream",controllerMode:"ddr",keys:["up","right","down","left","up","right","down","left"],measures:2,tags:["stream","box","circular"],category:"stream",difficulty:6},{id:"ddr_stream_mixed_zigzag",name:"Zigzag Stream",controllerMode:"ddr",keys:["up","left","down","right","up","left","down","right"],measures:2,tags:["stream","zigzag"],category:"stream",difficulty:6}],_d=[{id:"ddr_jump_opposite_vertical",name:"Opposite Jump Vertical",controllerMode:"ddr",keys:["up","down","up","down"],measures:1,tags:["jump","opposite","vertical"],category:"jump",difficulty:4},{id:"ddr_jump_opposite_horizontal",name:"Opposite Jump Horizontal",controllerMode:"ddr",keys:["left","right","left","right"],measures:1,tags:["jump","opposite","horizontal"],category:"jump",difficulty:4},{id:"ddr_jump_diagonal_1",name:"Diagonal Jump 1",controllerMode:"ddr",keys:["up","left","up","left"],measures:1,tags:["jump","diagonal"],category:"jump",difficulty:4},{id:"ddr_jump_diagonal_2",name:"Diagonal Jump 2",controllerMode:"ddr",keys:["up","right","up","right"],measures:1,tags:["jump","diagonal"],category:"jump",difficulty:4},{id:"ddr_jump_diagonal_3",name:"Diagonal Jump 3",controllerMode:"ddr",keys:["down","left","down","left"],measures:1,tags:["jump","diagonal"],category:"jump",difficulty:4},{id:"ddr_jump_diagonal_4",name:"Diagonal Jump 4",controllerMode:"ddr",keys:["down","right","down","right"],measures:1,tags:["jump","diagonal"],category:"jump",difficulty:4},{id:"ddr_jump_cross_pattern",name:"Cross Pattern",controllerMode:"ddr",keys:["up","left","down","right"],measures:1,tags:["jump","cross"],category:"jump",difficulty:5},{id:"ddr_jump_x_pattern",name:"X Pattern",controllerMode:"ddr",keys:["up","right","down","left"],measures:1,tags:["jump","x"],category:"jump",difficulty:5},{id:"ddr_jump_leap_sequence",name:"Leap Sequence",controllerMode:"ddr",keys:["up","down","left","right","up","down"],measures:2,tags:["jump","leap","sequence"],category:"jump",difficulty:6},{id:"ddr_jump_corner_to_corner",name:"Corner to Corner",controllerMode:"ddr",keys:["up","right","down","left","up","right"],measures:2,tags:["jump","corner"],category:"jump",difficulty:6}],$d=[{id:"ddr_transition_build_up",name:"Build Up",controllerMode:"ddr",keys:["left","left","up","up","right","right"],measures:2,tags:["transition","build-up","intensity"],category:"transition",difficulty:4},{id:"ddr_transition_wind_down",name:"Wind Down",controllerMode:"ddr",keys:["right","right","up","up","left","left"],measures:2,tags:["transition","wind-down"],category:"transition",difficulty:4},{id:"ddr_transition_fill_4",name:"Fill 4 Count",controllerMode:"ddr",keys:["up","right","down","left"],measures:1,tags:["transition","fill"],category:"transition",difficulty:3},{id:"ddr_transition_fill_8",name:"Fill 8 Count",controllerMode:"ddr",keys:["up","up","right","right","down","down","left","left"],measures:2,tags:["transition","fill","extended"],category:"transition",difficulty:4}],AF=[{id:"gh_basic_single_1",name:"Single Fret 1",controllerMode:"guitar_hero",keys:[1],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"gh_basic_single_2",name:"Single Fret 2",controllerMode:"guitar_hero",keys:[2],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"gh_basic_single_3",name:"Single Fret 3",controllerMode:"guitar_hero",keys:[3],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"gh_basic_single_4",name:"Single Fret 4",controllerMode:"guitar_hero",keys:[4],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"gh_basic_single_5",name:"Single Fret 5",controllerMode:"guitar_hero",keys:[5],measures:1,tags:["basic","single","beginner"],category:"basic",difficulty:1},{id:"gh_basic_ascending_full",name:"Ascending Full Run",controllerMode:"guitar_hero",keys:[1,2,3,4,5],measures:1,tags:["basic","ascending","full"],category:"basic",difficulty:3},{id:"gh_basic_ascending_quad",name:"Ascending Quad",controllerMode:"guitar_hero",keys:[1,2,3,4],measures:1,tags:["basic","ascending"],category:"basic",difficulty:2},{id:"gh_basic_ascending_tri_low",name:"Ascending Tri Low",controllerMode:"guitar_hero",keys:[1,2,3],measures:1,tags:["basic","ascending","tri"],category:"basic",difficulty:2},{id:"gh_basic_ascending_tri_mid",name:"Ascending Tri Mid",controllerMode:"guitar_hero",keys:[2,3,4],measures:1,tags:["basic","ascending","tri"],category:"basic",difficulty:2},{id:"gh_basic_ascending_tri_high",name:"Ascending Tri High",controllerMode:"guitar_hero",keys:[3,4,5],measures:1,tags:["basic","ascending","tri"],category:"basic",difficulty:2},{id:"gh_basic_descending_full",name:"Descending Full Run",controllerMode:"guitar_hero",keys:[5,4,3,2,1],measures:1,tags:["basic","descending","full"],category:"basic",difficulty:3},{id:"gh_basic_descending_quad",name:"Descending Quad",controllerMode:"guitar_hero",keys:[5,4,3,2],measures:1,tags:["basic","descending"],category:"basic",difficulty:2},{id:"gh_basic_descending_tri_high",name:"Descending Tri High",controllerMode:"guitar_hero",keys:[5,4,3],measures:1,tags:["basic","descending","tri"],category:"basic",difficulty:2},{id:"gh_basic_descending_tri_mid",name:"Descending Tri Mid",controllerMode:"guitar_hero",keys:[4,3,2],measures:1,tags:["basic","descending","tri"],category:"basic",difficulty:2},{id:"gh_basic_descending_tri_low",name:"Descending Tri Low",controllerMode:"guitar_hero",keys:[3,2,1],measures:1,tags:["basic","descending","tri"],category:"basic",difficulty:2},{id:"gh_basic_step_up",name:"Step Up",controllerMode:"guitar_hero",keys:[1,2,3,4],measures:1,tags:["basic","step","up"],category:"basic",difficulty:2},{id:"gh_basic_step_down",name:"Step Down",controllerMode:"guitar_hero",keys:[5,4,3,2],measures:1,tags:["basic","step","down"],category:"basic",difficulty:2}],BF=[{id:"gh_alternating_1_3",name:"Alternating 1-3",controllerMode:"guitar_hero",keys:[1,3,1,3],measures:1,tags:["alternating","skip"],category:"basic",difficulty:3},{id:"gh_alternating_2_4",name:"Alternating 2-4",controllerMode:"guitar_hero",keys:[2,4,2,4],measures:1,tags:["alternating","skip"],category:"basic",difficulty:3},{id:"gh_alternating_3_5",name:"Alternating 3-5",controllerMode:"guitar_hero",keys:[3,5,3,5],measures:1,tags:["alternating","skip"],category:"basic",difficulty:3},{id:"gh_alternating_1_2",name:"Alternating 1-2",controllerMode:"guitar_hero",keys:[1,2,1,2],measures:1,tags:["alternating","adjacent"],category:"basic",difficulty:2},{id:"gh_alternating_2_3",name:"Alternating 2-3",controllerMode:"guitar_hero",keys:[2,3,2,3],measures:1,tags:["alternating","adjacent"],category:"basic",difficulty:2},{id:"gh_alternating_3_4",name:"Alternating 3-4",controllerMode:"guitar_hero",keys:[3,4,3,4],measures:1,tags:["alternating","adjacent"],category:"basic",difficulty:2},{id:"gh_alternating_4_5",name:"Alternating 4-5",controllerMode:"guitar_hero",keys:[4,5,4,5],measures:1,tags:["alternating","adjacent"],category:"basic",difficulty:2},{id:"gh_alternating_tri_1_3_5",name:"Alternating 1-3-5",controllerMode:"guitar_hero",keys:[1,3,5,3],measures:1,tags:["alternating","tri","wide"],category:"basic",difficulty:4},{id:"gh_alternating_tri_1_2_3",name:"Alternating 1-2-3",controllerMode:"guitar_hero",keys:[1,2,3,2],measures:1,tags:["alternating","tri","tight"],category:"basic",difficulty:3},{id:"gh_alternating_tri_3_4_5",name:"Alternating 3-4-5",controllerMode:"guitar_hero",keys:[3,4,5,4],measures:1,tags:["alternating","tri","tight"],category:"basic",difficulty:3},{id:"gh_alternating_extended_1_3",name:"Extended Alternating 1-3",controllerMode:"guitar_hero",keys:[1,3,1,3,1,3,1,3],measures:2,tags:["alternating","extended"],category:"basic",difficulty:4},{id:"gh_alternating_extended_2_4",name:"Extended Alternating 2-4",controllerMode:"guitar_hero",keys:[2,4,2,4,2,4,2,4],measures:2,tags:["alternating","extended"],category:"basic",difficulty:4}],gF=[{id:"gh_chord_power_full",name:"Power Chord Full",controllerMode:"guitar_hero",keys:[1,3,5],measures:1,tags:["chord","power","full"],category:"chord",difficulty:4},{id:"gh_chord_power_low",name:"Power Chord Low",controllerMode:"guitar_hero",keys:[1,3],measures:1,tags:["chord","power","low"],category:"chord",difficulty:3},{id:"gh_chord_power_mid",name:"Power Chord Mid",controllerMode:"guitar_hero",keys:[2,4],measures:1,tags:["chord","power","mid"],category:"chord",difficulty:3},{id:"gh_chord_power_high",name:"Power Chord High",controllerMode:"guitar_hero",keys:[3,5],measures:1,tags:["chord","power","high"],category:"chord",difficulty:3},{id:"gh_chord_triad_low",name:"Triad Low",controllerMode:"guitar_hero",keys:[1,2,3],measures:1,tags:["chord","triad","low"],category:"chord",difficulty:4},{id:"gh_chord_triad_mid",name:"Triad Mid",controllerMode:"guitar_hero",keys:[2,3,4],measures:1,tags:["chord","triad","mid"],category:"chord",difficulty:4},{id:"gh_chord_triad_high",name:"Triad High",controllerMode:"guitar_hero",keys:[3,4,5],measures:1,tags:["chord","triad","high"],category:"chord",difficulty:4},{id:"gh_chord_progression_1",name:"Chord Progression 1",controllerMode:"guitar_hero",keys:[1,3,2,4,3,5],measures:2,tags:["chord","progression"],category:"chord",difficulty:5},{id:"gh_chord_progression_2",name:"Chord Progression 2",controllerMode:"guitar_hero",keys:[1,2,3,4,5,4,3,2],measures:2,tags:["chord","progression","wave"],category:"chord",difficulty:5}],IF=[{id:"gh_jump_wide_1_4",name:"Wide Jump 1-4",controllerMode:"guitar_hero",keys:[1,4,1,4],measures:1,tags:["jump","wide"],category:"jump",difficulty:4},{id:"gh_jump_wide_2_5",name:"Wide Jump 2-5",controllerMode:"guitar_hero",keys:[2,5,2,5],measures:1,tags:["jump","wide"],category:"jump",difficulty:4},{id:"gh_jump_wide_1_5",name:"Wide Jump 1-5",controllerMode:"guitar_hero",keys:[1,5,1,5],measures:1,tags:["jump","widest"],category:"jump",difficulty:5},{id:"gh_jump_shift_up",name:"Position Shift Up",controllerMode:"guitar_hero",keys:[1,2,4,5],measures:1,tags:["jump","shift","up"],category:"jump",difficulty:4},{id:"gh_jump_shift_down",name:"Position Shift Down",controllerMode:"guitar_hero",keys:[5,4,2,1],measures:1,tags:["jump","shift","down"],category:"jump",difficulty:4},{id:"gh_jump_leap_sequence",name:"Leap Sequence",controllerMode:"guitar_hero",keys:[1,3,5,3,1,3,5],measures:2,tags:["jump","leap","sequence"],category:"jump",difficulty:5},{id:"gh_jump_bounce",name:"Bounce Pattern",controllerMode:"guitar_hero",keys:[1,5,2,4,3],measures:1,tags:["jump","bounce"],category:"jump",difficulty:5},{id:"gh_jump_stretch_1_4_5",name:"Stretch 1-4-5",controllerMode:"guitar_hero",keys:[1,4,5,4],measures:1,tags:["jump","stretch"],category:"jump",difficulty:5},{id:"gh_jump_stretch_1_2_5",name:"Stretch 1-2-5",controllerMode:"guitar_hero",keys:[1,2,5,2],measures:1,tags:["jump","stretch"],category:"jump",difficulty:5}],QF=[{id:"gh_transition_build_up",name:"Build Up",controllerMode:"guitar_hero",keys:[1,1,2,2,3,3,4,4,5],measures:2,tags:["transition","build-up"],category:"transition",difficulty:4},{id:"gh_transition_wind_down",name:"Wind Down",controllerMode:"guitar_hero",keys:[5,5,4,4,3,3,2,2,1],measures:2,tags:["transition","wind-down"],category:"transition",difficulty:4},{id:"gh_transition_fill_ascending",name:"Fill Ascending",controllerMode:"guitar_hero",keys:[1,2,3,4,5],measures:1,tags:["transition","fill","ascending"],category:"transition",difficulty:3},{id:"gh_transition_fill_descending",name:"Fill Descending",controllerMode:"guitar_hero",keys:[5,4,3,2,1],measures:1,tags:["transition","fill","descending"],category:"transition",difficulty:3},{id:"gh_transition_fill_wave",name:"Fill Wave",controllerMode:"guitar_hero",keys:[1,3,5,3,1],measures:1,tags:["transition","fill","wave"],category:"transition",difficulty:4},{id:"gh_transition_end_resolve",name:"End Resolve",controllerMode:"guitar_hero",keys:[5,4,3,2,1,1],measures:2,tags:["transition","resolve","end"],category:"transition",difficulty:3}],EF=[...jd,...Od,...Pd,..._d,...$d],iF=[...AF,...BF,...gF,...IF,...QF];function Mt(Q,A){const B=new Map,g=["basic","roll","stream","jump","chord","transition"];for(const E of g){const i=A.filter(C=>C.category===E);i.length>0&&B.set(E,i)}const I=new Map;for(let E=1;E<=10;E++){const i=A.filter(C=>C.difficulty===E);i.length>0&&I.set(E,i)}return{controllerMode:Q,patterns:A,byCategory:B,byDifficulty:I}}const Zi=Mt("ddr",EF),Wi=Mt("guitar_hero",iF);function CF(Q){return Q==="ddr"?Zi:Wi}function aF(Q,A){return Q.byCategory.get(A)??[]}function eF(Q,A=1,B=10){const g=[];for(let I=A;I<=B;I++){const E=Q.byDifficulty.get(I);E&&g.push(...E)}return g}function oF(Q,A){return Q.patterns.filter(B=>A.every(g=>B.tags.includes(g)))}function tF(Q,A){return Q.patterns.filter(B=>B.keys.length===A)}function sF(Q,A){const B=A?Q.patterns.filter(A):Q.patterns;if(B.length===0)return;const g=Math.floor(Math.random()*B.length);return B[g]}function GF(Q,A){return Q.patterns.find(B=>B.id===A)}function nF(Q){const A={basic:0,roll:0,stream:0,jump:0,chord:0,transition:0},B={};for(let i=1;i<=10;i++)B[i]=0;let g=0,I=1/0,E=0;for(const i of Q.patterns)A[i.category]++,B[i.difficulty]++,g+=i.keys.length,I=Math.min(I,i.keys.length),E=Math.max(E,i.keys.length);return{totalPatterns:Q.patterns.length,byCategory:A,byDifficulty:B,averageKeyCount:Q.patterns.length>0?g/Q.patterns.length:0,minKeys:I===1/0?0:I,maxKeys:E}}const wt={up:{ascending:{unison:"up",small:"up",medium:"right",large:"right",very_large:"left"},descending:{unison:"up",small:"left",medium:"right",large:"down",very_large:"down"},stable:"up"},right:{ascending:{unison:"right",small:"up",medium:"up",large:"up",very_large:"left"},descending:{unison:"right",small:"down",medium:"down",large:"down",very_large:"left"},stable:"right"},down:{ascending:{unison:"down",small:"left",medium:"right",large:"up",very_large:"up"},descending:{unison:"down",small:"down",medium:"left",large:"left",very_large:"right"},stable:"down"},left:{ascending:{unison:"left",small:"up",medium:"up",large:"up",very_large:"right"},descending:{unison:"left",small:"down",medium:"down",large:"down",very_large:"right"},stable:"left"}},hF={up:{ascending:"up",descending:"left",stable:"up"},right:{ascending:"up",descending:"down",stable:"right"},down:{ascending:"right",descending:"down",stable:"down"},left:{ascending:"up",descending:"down",stable:"left"}},yt={1:{ascending:{unison:1,small:2,medium:2,large:3,very_large:3},descending:{unison:1,small:4,medium:4,large:3,very_large:3},stable:1},2:{ascending:{unison:2,small:3,medium:3,large:4,very_large:4},descending:{unison:2,small:1,medium:1,large:4,very_large:4},stable:2},3:{ascending:{unison:3,small:4,medium:4,large:5,very_large:5},descending:{unison:3,small:2,medium:2,large:1,very_large:1},stable:3},4:{ascending:{unison:4,small:5,medium:5,large:2,very_large:2},descending:{unison:4,small:3,medium:3,large:2,very_large:1},stable:4},5:{ascending:{unison:5,small:2,medium:2,large:3,very_large:3},descending:{unison:5,small:4,medium:4,large:3,very_large:2},stable:5}},cF={1:{ascending:2,descending:1,stable:1},2:{ascending:3,descending:1,stable:2},3:{ascending:4,descending:2,stable:3},4:{ascending:5,descending:3,stable:4},5:{ascending:5,descending:4,stable:5}};function Rt(Q){return Q.intervalCategory??"small"}function rF(Q,A,B){const g=A??"left";if(Q.direction==="stable"||Q.intervalCategory==="unison")return g;if(B==="easy"){const C=hF[g];return Q.direction==="up"?C.ascending:C.descending}const I=wt[g],E=Q.direction==="up"?"ascending":"descending",i=Rt(Q);return I[E][i]}function lF(Q,A,B){const g=A??3;if(Q.direction==="stable"||Q.intervalCategory==="unison")return g;if(B==="easy"){const C=cF[g];return Q.direction==="up"?C.ascending:C.descending}const I=yt[g],E=Q.direction==="up"?"ascending":"descending",i=Rt(Q);return I[E][i]}function DF(Q,A,B,g){const I=new Map;if(A)for(const a of A){const e=Math.round(a.timestamp*1e3);I.set(e,a)}const E=[],i=[];let C=null;for(const a of Q){const e=Math.round(a.timestamp*1e3);let o=null;for(let G=-2;G<=2&&(o=I.get(e+G)??null,!o);G++);let t=null,s=0;o&&o.direction!=="none"&&(t=B==="ddr"?rF(o,C,g):lF(o,C,g),s=o.pitch?.probability??.5),E.push(t),i.push(s),C=t}return{pitchKeys:E,probabilities:i}}function dF(Q){const A=[];let B=-1;for(let g=0;g<=Q.length;g++){const I=g<Q.length&&Q[g]===null;if(I&&B===-1)B=g;else if(!I&&B!==-1){const E=B>0?Q[B-1]:null,i=g<Q.length?Q[g]:null;A.push({startIndex:B,endIndex:g,length:g-B,previousKey:E,nextKey:i}),B=-1}}return A}function CQ(Q,A,B,g,I){if(Q.keys.length===0||B+Q.keys.length>A.length)return!1;const E=Q.keys[0],i=Q.keys[Q.keys.length-1];if(g!==null&&E===g)return!1;if(I!==null){if(typeof i=="string"&&typeof I=="string"&&!jg[i]?.includes(I)&&i!==I)return!1;if(typeof i=="number"&&typeof I=="number"){if(g!==null&&typeof g=="number"){if(!(g<I&&i>=g&&i<=I||g>I&&i<=g&&i>=I)&&i!==I)return!1}else if(Math.abs(i-I)>1)return!1}}return!0}function FF(Q,A,B,g,I){const E=[],i=A.filter(s=>s.difficulty<=B&&s.keys.length>0);i.sort((s,G)=>G.keys.length-s.keys.length);const C=i[0]?.controllerMode??"ddr";let a=0,e=Q.previousKey,o=null,t=null;for(;a<Q.length;){const s=Q.length-a;let G;const n=i.filter(h=>h.keys.length===s);if(G=Hi(n,Q,a,e,Q.nextKey,o,t,g,I),!G){const h=i.filter(c=>c.keys.length<=s);G=Hi(h,Q,a,e,null,o,t,g,I)}if(!G){const h=i.filter(c=>c.keys.length<=s);G=Hi(h,Q,a,e,null,null,null,null,I)}if(G)E.push({pattern:G,startIndex:Q.startIndex+a,filledLength:G.keys.length}),e=G.keys[G.keys.length-1],o=G.id,t=G.category,a+=G.keys.length;else{const h=a===Q.length-1?Q.nextKey:null,c=mt(e,h);E.push({pattern:{id:"__interpolated__",name:"Interpolated",controllerMode:C,keys:[c],measures:0,tags:["interpolated"],category:"transition",difficulty:1},startIndex:Q.startIndex+a,filledLength:1}),e=c,a++}}return E}function Hi(Q,A,B,g,I,E,i,C,a=Math.random){const e=Q.filter(G=>CQ(G,A,B,g,I)&&G.id!==E&&G.category!==i&&!C?.has(G.id));if(e.length>0)return aQ(e,a);const o=Q.filter(G=>CQ(G,A,B,g,I)&&G.id!==E&&!C?.has(G.id));if(o.length>0)return aQ(o,a);const t=Q.filter(G=>CQ(G,A,B,g,I)&&G.id!==E);if(t.length>0)return aQ(t,a);const s=Q.filter(G=>CQ(G,A,B,g,I));if(s.length>0)return aQ(s,a)}function aQ(Q,A=Math.random){const B=Q[0].keys.length,g=Q.filter(I=>I.keys.length===B);return g[Math.floor(A()*g.length)]}function MF(Q,A,B){const g=Q.length,I=[...Q],E=new Array(g).fill(void 0);for(let i=0;i<A.length;i++){const C=B[i];for(const a of C)for(let e=0;e<a.filledLength;e++){const o=a.startIndex+e;if(o>=g)break;I[o]=a.pattern.keys[e],E[o]=a.pattern.id}}for(let i=0;i<g;i++)if(I[i]===null){const C=i>0?I[i-1]:null,a=i<g-1?I[i+1]:null;I[i]=mt(C,a)}return{keys:I,patternIds:E}}function wF(Q,A){const B=[];if(A<=0||Q.length===0)return{positions:B};let g=1;const I=Q[0];for(let E=1;E<Q.length;E++)Q[E]===I?(g++,g>A&&B.push(E)):g=1;return{positions:B}}function yF(Q,A,B,g=Math.random){const I=A.filter(C=>C.difficulty<=B);if(I.length===0)return Q;const E=I.filter(C=>C.keys[0]!==Q),i=E.length>0?E:I;return i[Math.floor(g()*i.length)].keys[0]}const jg={up:["left","right"],down:["left","right"],left:["up","down"],right:["up","down"]};function mt(Q,A){if(Q===null)return A===null?typeof A=="string"?"left":3:typeof A=="string"?jg[A][0]:Math.max(1,A-1);if(A===null)return Q;if(typeof Q=="string"&&typeof A=="string"){const B=Q,g=A;if(B===g)return Q;if(jg[B].includes(g))return A;const E=jg[B],i=jg[g];for(const C of E)if(i.includes(C))return C;return E[0]}if(typeof Q=="number"&&typeof A=="number"){const B=Q,g=A;return B===g?Q:g>B?Math.min(5,B+1):Math.max(1,B-1)}return Q}class eQ{constructor(A){this.config=sE(A);const B=Qa(this.config);if(!B.valid)throw new Error(`Invalid button mapping config: ${B.errors.join(", ")}`)}seededRandom(A){return this.config.seed?bB(XB(this.config.seed,A)):Math.random()}getConfig(){return{...this.config}}mapVariant(A,B,g){const I=this.mapButtons(A,g,A.difficulty),E=this.buildMetadata(I,g),i=new Map,C=new Map,a=new Map;for(const e of I)i.set(e.beatIndex,String(e.key)),C.set(e.beatIndex,e.source),a.set(e.beatIndex,e.patternId);return{variant:A,rhythmMetadata:B,buttonMetadata:E,keyAssignments:i,mappingSources:C,mappingPatternIds:a}}map(A,B,g){const I=A.difficultyVariants[B];return this.mapVariant(I,A.metadata,g)}mapAll(A,B){return{easy:this.map(A,"easy",B),medium:this.map(A,"medium",B),hard:this.map(A,"hard",B)}}mapButtons(A,B,g){const I=A.beats;if(this.config.controllerMode==="tap")return I.map((l,F)=>({beatIndex:F,timestamp:l.timestamp,key:"tap",source:"pattern",patternId:void 0,probability:void 0}));const E=[],i=this.getMaxPatternDifficulty(g);let C=0;const{pitchKeys:a,probabilities:e}=DF(I,B,this.config.controllerMode,g),o=this.classifyPitchVsPattern(a,e,this.config.pitchInfluenceWeight),t=a.map((l,F)=>l!==null&&o[F]?l:null),s=this.config.controllerMode==="ddr"?Zi.patterns:Wi.patterns,G=dF(t),n=new Set,h=G.map((l,F)=>{const m=FF(l,s,i,n,()=>this.seededRandom(`run:${F}:pick:${C++}`));for(const y of m)y.pattern.id!=="__interpolated__"&&n.add(y.pattern.id);return m}),{keys:c,patternIds:d}=MF(t,G,h);for(let l=0;l<I.length;l++){const F=t[l]!==null;E.push({beatIndex:l,timestamp:I[l].timestamp,key:c[l],source:F?"pitch":"pattern",patternId:F?void 0:d[l],probability:e[l]})}return this.config.consecutiveSameKeyLimit>0&&this.applyConsecutiveLimit(E,s,i),E}classifyPitchVsPattern(A,B,g){const I=new Array(A.length).fill(!1),E=A.map((a,e)=>({index:e,probability:B[e],hasPitch:a!==null})).filter(a=>a.hasPitch);E.sort((a,e)=>a.probability-e.probability);const i=Math.floor(E.length*(1-g)),C=new Set(E.slice(0,i).map(a=>a.index));for(let a=0;a<A.length;a++)A[a]===null||C.has(a)?I[a]=!1:I[a]=!0;return I}getMaxPatternDifficulty(A){switch(A){case"easy":return 3;case"medium":return 6;case"hard":return 10;case"natural":return 10;case"custom":return 6}}applyConsecutiveLimit(A,B,g){const I=A.map(C=>C.key),E=this.config.consecutiveSameKeyLimit,{positions:i}=wF(I,E);for(let C=0;C<i.length;C++){const a=i[C],e=yF(A[a].key,B,g,()=>this.seededRandom(`var:${a}:${C}`));e!==A[a].key&&(A[a].key=e,A[a].source="pattern",A[a].patternId="consecutive_limit_fix")}}buildMetadata(A,B){const g=new Set,I=new Map;let E=0,i=0;const C=new Set;for(const n of A){const h=String(n.key);g.add(h),I.set(h,(I.get(h)??0)+1),n.source==="pitch"?E++:i++,n.patternId&&C.add(n.patternId)}let a,e,o;if(B&&B.length>0){a={up:0,down:0,stable:0,none:0},e={unison:0,small:0,medium:0,large:0,very_large:0},o={low:0,mid:0,high:0};for(const n of B)a[n.direction]++,n.intervalCategory&&e[n.intervalCategory]++,n.band&&o[n.band]++}const t=[];let s,G=0;for(let n=0;n<A.length;n++){const h=A[n].patternId;h&&h===s||(s&&t.push({patternId:s,startIndex:G,length:n-G}),s=h,G=n)}return s&&t.push({patternId:s,startIndex:G,length:A.length-G}),{controllerMode:this.config.controllerMode,keysUsed:Array.from(g),pitchInfluencedBeats:E,patternInfluencedBeats:i,patternsUsed:Array.from(C),buttonDistribution:I,directionStats:a,intervalStats:e,bandStats:o,patternPlacements:t}}static getDDRTransitions(){return wt}static getGuitarHeroTransitions(){return yt}getAvailableButtons(){return this.config.controllerMode==="ddr"?["up","down","left","right"]:[1,2,3,4,5]}isValidButton(A){return this.config.controllerMode==="ddr"?["up","down","left","right"].includes(A):[1,2,3,4,5].includes(A)}}class oQ{convertToChartedBeatMap(A,B,g,I,E,i){const C=[],a=[];for(let o=0;o<A.beats.length;o++){const t=A.beats[o],s=this.convertBeat(t,B,g.get(o),o,E?.get(o),i?.get(o));C.push(s),s.isDetected&&a.push(o)}const e=this.buildChartMetadata(A,I,C,g);return{audioId:B.audioId,duration:B.duration,beats:C,detectedBeatIndices:a,downbeatConfig:B.downbeatConfig,quarterNoteInterval:B.quarterNoteInterval,bpm:B.quarterNoteBpm,chartMetadata:e}}convertWithOptions(A,B){const g={version:"1.0.0",algorithm:"procedural-generation",minBpm:60,maxBpm:200,sensitivity:1,filter:.5,noiseFloorThreshold:.01,hopSizeMs:10,fftSize:2048,dpAlpha:.5,melBands:40,highPassCutoff:80,gaussianSmoothMs:20,tempoCenter:.5,tempoWidth:.5,useOctaveResolution:!1,useTripleMeter:!1,generatedAt:new Date().toISOString()},I={audioId:B.audioId,duration:B.duration,beats:[],detectedBeatIndices:[],quarterNoteInterval:B.quarterNoteInterval,quarterNoteBpm:60/B.quarterNoteInterval,downbeatConfig:B.downbeatConfig,originalMetadata:g};return this.convertToChartedBeatMap(A,I,B.keyAssignments,B.metadata)}convertBeat(A,B,g,I,E,i){const C=A.beatIndex,a=this.getParentBeat(B,C),e=this.calculateBeatInMeasure(a,A.gridPosition,A.gridType),o=this.isBeatDetected(A),t=this.mapGridToSubdivision(A.gridType),s=this.calculateConfidence(A,o);return{timestamp:A.timestamp,beatInMeasure:e,isDownbeat:(a?.isDownbeat??!1)&&A.gridPosition===0,measureNumber:a?.measureNumber??0,intensity:A.intensity,confidence:s,requiredKey:g,quarterNoteIndex:C,subdivisionPosition:A.gridPosition,isDetected:o,subdivisionType:t,sourceBand:A.sourceBand,quantizationError:A.quantizationError,mappingSource:E,patternId:i}}getParentBeat(A,B){return B<0||B>=A.beats.length?null:A.beats[B]}calculateBeatInMeasure(A,B,g){const I=A?.beatInMeasure??0;return ia(I,B,g)}isBeatDetected(A){return A.quantizationError===void 0?!0:A.quantizationError<10}mapGridToSubdivision(A){return Ea(A)}calculateConfidence(A,B){return B?Math.min(1,.8+A.intensity*.2):.8}buildChartMetadata(A,B,g,I){const E=B.pitchInfluencedBeats??0,i=this.getUniqueKeys(g);return this.getSubdivisionTypesUsed(g),{difficulty:A.difficulty,keysUsed:i,pitchInfluencedBeats:E,patternsUsed:B.patternsUsed??[],rhythmMetadata:B.rhythmMetadata??this.createDefaultRhythmMetadata(A),pitchMetadata:B.pitchMetadata??null,generatedAt:B.generatedAt??new Date().toISOString(),seed:B.seed}}getUniqueKeys(A){const B=new Set;for(const g of A)g.requiredKey!==void 0&&B.add(g.requiredKey);return Array.from(B).sort()}getSubdivisionTypesUsed(A){const B=new Set;for(const g of A)B.add(g.subdivisionType);return Array.from(B)}createDefaultRhythmMetadata(A){return{difficulty:A.difficulty,bandsAnalyzed:["low","mid","high"],transientsDetected:A.beats.length,averageDensity:A.beats.length/180,naturalDifficulty:A.difficulty}}static fromMappedResult(A,B,g,I,E,i,C){const a=E??new Map;if(a.size===0&&g.keysUsed.length>0)for(let G=0;G<A.beats.length;G++)a.set(G,g.keysUsed[G%g.keysUsed.length]);const e=new oQ,o={difficulty:I.difficulty,bandsAnalyzed:I.bandsAnalyzed,transientsDetected:I.transientsDetected,averageDensity:I.averageDensity,naturalDifficulty:I.naturalDifficulty},t=g.directionStats?{melodyRange:null,directionStats:g.directionStats,intervalStats:g.intervalStats??null}:null,s={difficulty:A.difficulty,keysUsed:g.keysUsed,pitchInfluencedBeats:g.pitchInfluencedBeats,patternsUsed:g.patternsUsed,rhythmMetadata:o,pitchMetadata:t,generatedAt:new Date().toISOString()};return e.convertToChartedBeatMap(A,B,a,s,i,C)}}const Og={difficulty:"medium",controllerMode:"ddr",rhythm:{},buttons:{},enableCache:!0,cacheMaxAge:1800*1e3};class tQ{constructor(A={}){this.cache=new Map,this.cacheHits=0,this.cacheMisses=0,this.options={...Og,...A,rhythm:{...Og.rhythm,...A.rhythm},buttons:{...Og.buttons,...A.buttons}},this.buttonConfig=sE({...this.options.buttons,difficulty:this.options.difficulty,controllerMode:this.options.controllerMode,seed:this.options.seed})}getOptions(){return{...this.options}}generateCacheKey(A,B,g){return`${A}:${B}:${g}`}createConfigFingerprint(A){const B=this.options.rhythm||{};return A==="rhythm"?JSON.stringify({measureStartOffset:B.measureStartOffset,minimumTransientIntensity:B.minimumTransientIntensity,seed:B.seed}):A==="pitch"?JSON.stringify({pitchAlgorithm:this.options.pitchAlgorithm,crepeModelUrl:this.options.crepeModelUrl,voicingThreshold:this.options.voicingThreshold}):"default"}getFromCache(A){if(!this.options.enableCache)return null;const B=this.cache.get(A);if(!B)return this.cacheMisses++,null;const g=Date.now()-B.timestamp,I=this.options.cacheMaxAge??Og.cacheMaxAge;return g>I?(this.cache.delete(A),this.cacheMisses++,null):(this.cacheHits++,B.result)}setCache(A,B){this.options.enableCache&&this.cache.set(A,{result:B,timestamp:Date.now(),key:A})}clearCache(){this.cache.clear(),this.cacheHits=0,this.cacheMisses=0}pruneExpiredCache(){const A=Date.now(),B=this.options.cacheMaxAge??Og.cacheMaxAge;for(const[g,I]of this.cache.entries())A-I.timestamp>B&&this.cache.delete(g)}getCacheStats(){const A=[];for(const[B]of this.cache.entries()){const g=B.split(":")[1];A.includes(g)||A.push(g)}return{entryCount:this.cache.size,cachedPhases:A,hits:this.cacheHits,misses:this.cacheMisses}}isCached(A,B){const g=this.createConfigFingerprint(B),I=this.generateCacheKey(A,B,g);return this.cache.has(I)}getCacheHitRatio(){const A=this.cacheHits+this.cacheMisses;return A===0?0:this.cacheHits/A}async generate(A,B,g,I){const E=(s,G,n)=>{g?.({stage:s,progress:G,message:n})};I?.throwIfAborted();const i=B.audioId;E("rhythm",0,"Starting rhythm generation...");let C=null;if(this.options.cachedRhythm)C=this.options.cachedRhythm,E("rhythm",1,"Using pre-generated rhythm from store");else{const s=this.generateCacheKey(i,"rhythm",this.createConfigFingerprint("rhythm"));C=this.getFromCache(s),C?E("rhythm",1,"Rhythm loaded from cache"):(C=await this.generateRhythm(A,B,(G,n,h)=>{E("rhythm",n,`[${G}] ${h}`)},I),this.setCache(s,C),E("rhythm",1,"Rhythm generation complete"))}I?.throwIfAborted();let a=null;if(this.buttonConfig.pitchInfluenceWeight>0&&this.buttonConfig.controllerMode!=="tap"){const s=this.generateCacheKey(i,"pitch",this.createConfigFingerprint("pitch"));a=this.getFromCache(s),a?E("pitch",1,"Pitch analysis loaded from cache"):(E("pitch",0,"Starting pitch analysis..."),a=await this.analyzePitch(A,C,(G,n)=>{E("pitch",G,n)}),this.setCache(s,a),E("pitch",1,"Pitch analysis complete"))}else E("pitch",1,"Pitch analysis skipped");I?.throwIfAborted(),E("buttons",0,"Starting button mapping...");const e=this.mapButtons(C,a);E("buttons",1,"Button mapping complete"),I?.throwIfAborted(),E("conversion",0,"Converting to playable chart...");const o=this.convertToChart(e,B);E("conversion",1,"Chart conversion complete"),I?.throwIfAborted(),E("finalizing",0,"Finalizing level...");const t=this.buildLevel(o,e,C,a);return E("finalizing",1,"Level generation complete"),t}async generateAllDifficulties(A,B,g,I){const E=["easy","medium","hard","natural"],i={easy:null,medium:null,hard:null,natural:null};I?.throwIfAborted();const C=B.audioId;let a=null;if(this.options.cachedRhythm)a=this.options.cachedRhythm;else{const o=this.generateCacheKey(C,"rhythm",this.createConfigFingerprint("rhythm"));a=this.getFromCache(o),a||(a=await this.generateRhythm(A,B,void 0,I),this.setCache(o,a))}I?.throwIfAborted();let e=null;if(this.buttonConfig.pitchInfluenceWeight>0&&this.buttonConfig.controllerMode!=="tap"){const o=this.generateCacheKey(C,"pitch",this.createConfigFingerprint("pitch"));e=this.getFromCache(o),e||(e=await this.analyzePitch(A,a),this.setCache(o,e))}I?.throwIfAborted();for(let o=0;o<E.length;o++){const t=E[o];g?.({stage:"buttons",progress:o/E.length,message:`Generating ${t} chart...`}),I?.throwIfAborted();const s=new tQ({...this.options,difficulty:t,buttons:{...this.options.buttons,difficulty:t}}),G=s.mapButtons(a,e),n=s.convertToChart(G,B),h=s.buildLevel(n,G,a,e);i[t]=h}return{easy:i.easy,medium:i.medium,hard:i.hard,natural:i.natural}}async generateAtDensity(A,B,g,I,E){const i=(d,l,F)=>{I?.({stage:d,progress:l,message:F})};E?.throwIfAborted();const C=B.audioId;i("rhythm",0,"Starting rhythm generation...");let a=null;if(this.options.cachedRhythm)a=this.options.cachedRhythm,i("rhythm",1,"Using pre-generated rhythm from store");else{const d=this.generateCacheKey(C,"rhythm",this.createConfigFingerprint("rhythm"));a=this.getFromCache(d),a?i("rhythm",1,"Rhythm loaded from cache"):(a=await this.generateRhythm(A,B,(l,F,m)=>{i("rhythm",F,`[${l}] ${m}`)},E,!0),this.setCache(d,a),i("rhythm",1,"Rhythm generation complete"))}E?.throwIfAborted(),i("buttons",0,"Generating density-based variant...");const e=this.collectGridDecisions(a.analysis.quantizationResult),t=new EQ({seed:this.options.seed}).generateAtDensity(a.composite,g,B,a.analysis.phraseAnalysis,e);i("buttons",.3,"Density variant generated");let s=null;if(this.buttonConfig.pitchInfluenceWeight>0&&this.buttonConfig.controllerMode!=="tap"){const d=this.generateCacheKey(C,"pitch",this.createConfigFingerprint("pitch"));s=this.getFromCache(d),s?i("pitch",.5,"Pitch analysis loaded from cache"):(i("pitch",0,"Starting pitch analysis..."),s=await this.analyzePitch(A,a,(l,F)=>{i("pitch",l,F)}),this.setCache(d,s),i("pitch",1,"Pitch analysis complete"))}else i("pitch",1,"Pitch analysis skipped");E?.throwIfAborted(),i("buttons",.6,"Mapping buttons...");const n=new eQ(this.buttonConfig).mapVariant(t,a.metadata,s?.pitchByBeat);i("buttons",1,"Button mapping complete"),E?.throwIfAborted(),i("conversion",0,"Converting to playable chart...");const h=this.convertToChart(n,B);i("conversion",1,"Chart conversion complete"),E?.throwIfAborted(),i("finalizing",0,"Finalizing level...");const c=this.buildLevel(h,n,a,s,"custom");return i("finalizing",1,"Level generation complete"),c}async generateAtDensities(A,B,g,I,E){const i=new Map;E?.throwIfAborted();const C=B.audioId;I?.({stage:"rhythm",progress:0,message:"Starting rhythm generation..."});let a=null;if(this.options.cachedRhythm)a=this.options.cachedRhythm,I?.({stage:"rhythm",progress:1,message:"Using pre-generated rhythm from store"});else{const s=this.generateCacheKey(C,"rhythm",this.createConfigFingerprint("rhythm"));a=this.getFromCache(s),a?I?.({stage:"rhythm",progress:1,message:"Rhythm loaded from cache"}):(a=await this.generateRhythm(A,B,void 0,E,!0),this.setCache(s,a),I?.({stage:"rhythm",progress:1,message:"Rhythm generation complete"}))}E?.throwIfAborted();let e=null;if(this.buttonConfig.pitchInfluenceWeight>0&&this.buttonConfig.controllerMode!=="tap"){const s=this.generateCacheKey(C,"pitch",this.createConfigFingerprint("pitch"));e=this.getFromCache(s),e?I?.({stage:"pitch",progress:1,message:"Pitch analysis loaded from cache"}):(I?.({stage:"pitch",progress:0,message:"Starting pitch analysis..."}),e=await this.analyzePitch(A,a),this.setCache(s,e),I?.({stage:"pitch",progress:1,message:"Pitch analysis complete"}))}E?.throwIfAborted();const o=this.collectGridDecisions(a.analysis.quantizationResult),t=new EQ({seed:this.options.seed});for(let s=0;s<g.length;s++){const{label:G,config:n}=g[s];I?.({stage:"buttons",progress:s/g.length,message:`Generating ${G} density variant...`}),E?.throwIfAborted();const h=t.generateAtDensity(a.composite,n,B,a.analysis.phraseAnalysis,o),d=new eQ(this.buttonConfig).mapVariant(h,a.metadata,e?.pitchByBeat),l=this.convertToChart(d,B),F=this.buildLevel(l,d,a,e,"custom");i.set(G,F)}return I?.({stage:"finalizing",progress:1,message:"All density variants generated"}),i}collectGridDecisions(A){const B=new Map,g=[...A.streams.low.gridDecisions,...A.streams.mid.gridDecisions,...A.streams.high.gridDecisions];for(const I of g){const E=B.get(I.beatIndex);(!E||I.confidence>E.confidence)&&B.set(I.beatIndex,I)}return B}async generateRhythm(A,B,g,I,E){return await new HB({...this.options.rhythm,skipDifficultyVariants:E}).generate(A,B,I,g?(a,e,o)=>{g(a,e,o)}:void 0)}async analyzePitch(A,B,g){g?.(.1,"Linking pitch to beats...");const I={};this.options.pitchAlgorithm!==void 0&&(I.pitchAlgorithm=this.options.pitchAlgorithm),this.options.crepeModelUrl!==void 0&&(I.crepeModelUrl=this.options.crepeModelUrl),this.options.resolveUrl!==void 0&&(I.resolveUrl=this.options.resolveUrl),this.options.voicingThreshold!==void 0&&(I.pitchDetector={...I.pitchDetector,voicingThreshold:this.options.voicingThreshold});const E=new Dt(I),i=await E.linkWithComposite(B.composite,A);g?.(.4,"Analyzing melody contour...");const a=new Ld().analyze(i);g?.(.8,"Deriving variant pitches...");let e;if(!B.difficultyVariants)e=a.pitchByBeat;else{const o=this.options.difficulty==="custom"?"medium":this.options.difficulty;e=E.deriveVariantPitches(B.difficultyVariants[o],a.pitchByBeat)}return{...a,pitchByBeat:e}}mapButtons(A,B){const g=new eQ(this.buttonConfig),I=this.options.difficulty==="custom"?"medium":this.options.difficulty;return g.map(A,I,B?.pitchByBeat)}convertToChart(A,B){return oQ.fromMappedResult(A.variant,B,A.buttonMetadata,A.rhythmMetadata,A.keyAssignments,A.mappingSources,A.mappingPatternIds)}buildLevel(A,B,g,I,E){const i={difficulty:E??this.options.difficulty,controllerMode:this.options.controllerMode,rhythmMetadata:B.rhythmMetadata,buttonMetadata:B.buttonMetadata,pitchMetadata:I?{melodyRange:I.melodyContour.range.minNote!=="N/A"?{min:I.melodyContour.range.minNote,max:I.melodyContour.range.maxNote}:null,directionStats:I.directionStats,intervalStats:I.intervalStats}:null,chartMetadata:{totalBeats:A.beats.length,detectedBeats:A.detectedBeatIndices.length,generatedBeats:A.beats.length-A.detectedBeatIndices.length},generationConfig:this.options};return{chart:A,variant:B.variant,rhythm:g,pitchAnalysis:I,metadata:i}}}class RF{static toExportData(A,B={}){const{chart:g,metadata:I,pitchAnalysis:E}=A,i=this.buildDetectedBeats(g),C=this.buildMergedBeats(g),a=this.buildInterpolatedMetadata(g),e=this.buildSubdivisionData(g),o=this.buildChartExportData(g,I),t=this.buildGenerationMetadata(I,E);return{version:1,format:"full-beatmap",audioId:g.audioId,audioTitle:B.includeAudioTitle?B.audioTitle:void 0,exportedAt:Date.now(),duration:g.duration,quarterNoteBpm:g.bpm,quarterNoteConfidence:g.chartMetadata.rhythmMetadata.averageDensity,detectedBeats:i,mergedBeats:C,interpolatedMetadata:a,subdivision:e,chart:o,trackReference:B.trackReference,generationSource:"procedural",generationMetadata:t}}static buildDetectedBeats(A){const B=[],g=new Set(A.detectedBeatIndices);for(let I=0;I<A.beats.length;I++){const E=A.beats[I];g.has(I)&&B.push({timestamp:E.timestamp,beatInMeasure:E.beatInMeasure,isDownbeat:E.isDownbeat,measureNumber:E.measureNumber,intensity:E.intensity,confidence:E.confidence,requiredKey:E.requiredKey})}return B}static buildMergedBeats(A){const B=new Set(A.detectedBeatIndices);return A.beats.map((g,I)=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,requiredKey:g.requiredKey,source:B.has(I)?"detected":"interpolated",distanceToAnchor:g.quantizationError}))}static buildInterpolatedMetadata(A){const B=A.detectedBeatIndices.length,g=A.beats.length;return{quarterNoteInterval:A.quarterNoteInterval,quarterNoteBpm:A.bpm,quarterNoteConfidence:.9,detectedBeatCount:B,mergedBeatCount:g}}static buildSubdivisionData(A){const B=A.beats.map((C,a)=>this.chartedBeatToExportBeat(C,a,A.detectedBeatIndices)),g=new Set,I=[];B.forEach((C,a)=>{g.add(C.subdivisionType),I.push([a,C.subdivisionType])});const E={beatSubdivisions:I,defaultSubdivision:"sixteenth"},i={originalBeatCount:A.beats.length-A.detectedBeatIndices.length,subdividedBeatCount:A.beats.length,averageDensityMultiplier:1,explicitBeatCount:A.detectedBeatIndices.length};return{config:E,beats:B,metadata:i}}static chartedBeatToExportBeat(A,B,g){const I=g.includes(B);return{timestamp:A.timestamp,beatInMeasure:A.beatInMeasure,isDownbeat:A.isDownbeat,measureNumber:A.measureNumber,intensity:A.intensity,confidence:A.confidence,requiredKey:A.requiredKey,isDetected:I,originalBeatIndex:I?B:void 0,subdivisionType:A.subdivisionType,quarterNoteIndex:A.quarterNoteIndex,subdivisionPosition:A.subdivisionPosition,sourceBand:A.sourceBand,quantizationError:A.quantizationError}}static buildChartExportData(A,B){const g=A.beats.map(E=>E.requiredKey).filter(E=>E!==void 0),I=[...new Set(g)];return I.length===0?null:{style:B.controllerMode==="ddr"?"ddr":B.controllerMode==="guitar_hero"?"guitar":"tap",keyCount:I.length,usedKeys:I}}static buildGenerationMetadata(A,B){const g=A.rhythmMetadata;return{difficulty:A.difficulty,pitchInfluenceWeight:A.generationConfig.buttons?.pitchInfluenceWeight??1,patternsUsed:A.buttonMetadata.patternsUsed,controllerMode:A.controllerMode,seed:A.generationConfig.seed,generatedAt:new Date().toISOString(),directionStats:B?.directionStats??void 0,intervalStats:B?.intervalStats??void 0,rhythmMetadata:{difficulty:g.difficulty,bandsAnalyzed:g.bandsAnalyzed,transientsDetected:g.transientsDetected,averageDensity:g.averageDensity}}}static fromExportData(A){const B=this.validate(A);if(!B.success||!B.data)throw new Error(`Invalid export data: ${B.error??"Unknown error"}`);const g=B.data,I=this.reconstructChart(g),E=this.reconstructVariant(g,I),i=this.reconstructRhythm(g,I),C=this.reconstructPitchAnalysis(g),a=this.reconstructMetadata(g,I);return{chart:I,variant:E,rhythm:i,pitchAnalysis:C,metadata:a}}static reconstructChart(A){if(!A.subdivision)return this.reconstructChartFromMergedBeats(A);const B=A.subdivision.beats.map((i,C)=>({timestamp:i.timestamp,beatInMeasure:i.beatInMeasure,isDownbeat:i.isDownbeat,measureNumber:i.measureNumber,intensity:i.intensity,confidence:i.confidence,requiredKey:i.requiredKey,quarterNoteIndex:i.quarterNoteIndex??0,subdivisionPosition:i.subdivisionPosition??0,isDetected:i.isDetected,subdivisionType:i.subdivisionType,sourceBand:i.sourceBand??"mid",quantizationError:i.quantizationError})),g=A.subdivision.beats.map((i,C)=>i.isDetected?C:-1).filter(i=>i>=0),I=this.reconstructDownbeatConfig(B),E=this.reconstructChartMetadata(A);return{audioId:A.audioId,duration:A.duration,beats:B,detectedBeatIndices:g,downbeatConfig:I,quarterNoteInterval:A.interpolatedMetadata.quarterNoteInterval,bpm:A.quarterNoteBpm,chartMetadata:E}}static reconstructChartFromMergedBeats(A){const B=A.mergedBeats.map((i,C)=>({timestamp:i.timestamp,beatInMeasure:i.beatInMeasure,isDownbeat:i.isDownbeat,measureNumber:i.measureNumber,intensity:i.intensity,confidence:i.confidence,requiredKey:i.requiredKey,quarterNoteIndex:C,subdivisionPosition:0,isDetected:i.source==="detected",subdivisionType:"quarter",sourceBand:"mid",quantizationError:i.distanceToAnchor})),g=A.mergedBeats.map((i,C)=>i.source==="detected"?C:-1).filter(i=>i>=0),I=this.reconstructDownbeatConfig(B),E=this.reconstructChartMetadata(A);return{audioId:A.audioId,duration:A.duration,beats:B,detectedBeatIndices:g,downbeatConfig:I,quarterNoteInterval:A.interpolatedMetadata.quarterNoteInterval,bpm:A.quarterNoteBpm,chartMetadata:E}}static reconstructDownbeatConfig(A){const B=A.filter(E=>E.isDownbeat);if(B.length===0)return{segments:[{startBeat:0,downbeatBeatIndex:0,timeSignature:{beatsPerMeasure:4}}]};const g={beatsPerMeasure:4};return{segments:[{startBeat:0,downbeatBeatIndex:B[0]?.measureNumber??0,timeSignature:g}]}}static reconstructChartMetadata(A){const B=A.generationMetadata;return{difficulty:this.parseDifficultyLevel(B?.difficulty),keysUsed:A.chart?.usedKeys??[],pitchInfluencedBeats:B?.pitchInfluenceWeight&&B.pitchInfluenceWeight>0?A.subdivision?.beats.filter(g=>g.requiredKey).length??0:0,patternsUsed:B?.patternsUsed??[],rhythmMetadata:{difficulty:B?.rhythmMetadata?.difficulty??"medium",bandsAnalyzed:B?.rhythmMetadata?.bandsAnalyzed??["low","mid","high"],transientsDetected:B?.rhythmMetadata?.transientsDetected??A.detectedBeats.length,averageDensity:B?.rhythmMetadata?.averageDensity??.5,naturalDifficulty:this.parseDifficultyLevel(B?.difficulty)},pitchMetadata:B?.directionStats||B?.intervalStats?{melodyRange:null,directionStats:B.directionStats??null,intervalStats:B.intervalStats??null}:null,generatedAt:B?.generatedAt??new Date().toISOString(),seed:B?.seed}}static parseDifficultyLevel(A){return A==="easy"||A==="medium"||A==="hard"||A==="natural"||A==="custom"?A:"medium"}static parseDifficultyPreset(A){return A==="easy"||A==="medium"||A==="hard"||A==="natural"||A==="custom"?A:"medium"}static reconstructVariant(A,B){const g=A.generationMetadata,I=this.parseDifficultyLevel(g?.difficulty),E=B.beats.map(i=>({timestamp:i.timestamp,beatIndex:i.quarterNoteIndex,gridPosition:i.subdivisionPosition,gridType:this.subdivisionTypeToGridType(i.subdivisionType),intensity:i.intensity,band:i.sourceBand,quantizationError:i.quantizationError,sourceBand:i.sourceBand}));return{difficulty:I,beats:E,isUnedited:!0,editType:"none",editAmount:0,patternsInserted:g?.patternsUsed}}static subdivisionTypeToGridType(A){switch(A){case"sixteenth":case"eighth":case"quarter":return"straight_16th";case"triplet8":case"triplet4":return"triplet_8th";default:return"straight_16th"}}static reconstructRhythm(A,B){const g=A.generationMetadata,I=this.parseDifficultyLevel(g?.difficulty),E=t=>({audioId:A.audioId,duration:A.duration,beats:B.beats.filter(s=>s.sourceBand===t).map(s=>({timestamp:s.timestamp,beatIndex:s.quarterNoteIndex,gridPosition:s.subdivisionPosition,gridType:this.subdivisionTypeToGridType(s.subdivisionType),intensity:s.intensity,band:t,quantizationError:s.quantizationError})),gridDecisions:[],quarterNoteInterval:A.interpolatedMetadata.quarterNoteInterval}),i={low:E("low"),mid:E("mid"),high:E("high")},C=this.reconstructVariant(A,B),a=B.beats.map(t=>({timestamp:t.timestamp,beatIndex:t.quarterNoteIndex,gridPosition:t.subdivisionPosition,gridType:this.subdivisionTypeToGridType(t.subdivisionType),intensity:t.intensity,band:t.sourceBand,quantizationError:t.quantizationError,sourceBand:t.sourceBand})),e={low:0,mid:0,high:0};for(const t of a)e[t.sourceBand]++;const o={beats:a,sections:[],naturalDifficulty:I,quarterNoteInterval:A.interpolatedMetadata.quarterNoteInterval,metadata:{totalBeats:a.length,sectionCount:0,beatsPerBand:e,sectionsPerBand:{low:0,mid:0,high:0}}};return{difficultyVariants:{easy:{...C,difficulty:"easy"},medium:{...C,difficulty:"medium"},hard:{...C,difficulty:"hard"},natural:{...C,difficulty:"natural"}},bandStreams:i,composite:o,analysis:{transientAnalysis:{transients:A.detectedBeats.map(t=>({timestamp:t.timestamp,intensity:t.intensity,band:"mid",detectionMethod:"energy"})),bandTransients:new Map([["low",[]],["mid",A.detectedBeats.map(t=>({timestamp:t.timestamp,intensity:t.intensity,band:"mid",detectionMethod:"energy"}))],["high",[]]]),metadata:{totalTransients:A.detectedBeats.length,transientsPerBand:new Map([["low",0],["mid",A.detectedBeats.length],["high",0]]),duration:A.duration,averageIntensity:.5,detectionMethodsUsed:["energy"]}},quantizationResult:{streams:i,metadata:{densityValidation:{isValid:!0,bands:{low:{band:"low",isValid:!0,minIntervalDetected:1/0,requiredMinInterval:.1,retryCount:0,sensitivityReduction:0,finalIntensityThreshold:0,transientsRemaining:0},mid:{band:"mid",isValid:!0,minIntervalDetected:.1,requiredMinInterval:.1,retryCount:0,sensitivityReduction:0,finalIntensityThreshold:0,transientsRemaining:A.detectedBeats.length},high:{band:"high",isValid:!0,minIntervalDetected:1/0,requiredMinInterval:.1,retryCount:0,sensitivityReduction:0,finalIntensityThreshold:0,transientsRemaining:0}},maxRetryCount:0,maxSensitivityReduction:0},transientsFilteredByIntensity:0,transientsFilteredByBand:{low:0,mid:0,high:0}}},phraseAnalysis:{phrases:[],phrasesByBand:new Map([["low",[]],["mid",[]],["high",[]]]),mostSignificantPhrases:[],phrasesBySize:new Map,patternLibrary:[],bandAnalysis:{low:{band:"low",phrases:[],phrasesBySize:new Map,phrasesWithVariation:[]},mid:{band:"mid",phrases:[],phrasesBySize:new Map,phrasesWithVariation:[]},high:{band:"high",phrases:[],phrasesBySize:new Map,phrasesWithVariation:[]}}},densityAnalysis:{sections:[],perBeatDensity:[],bandMetrics:{low:{band:"low",totalBeats:0,totalTransients:0,notesPerSecond:1,minNotesPerSecond:0,maxNotesPerSecond:2,variance:0,densityCategory:"moderate",naturalDifficulty:"medium",perBeatDensity:[]},mid:{band:"mid",totalBeats:A.mergedBeats.length,totalTransients:A.detectedBeats.length,notesPerSecond:1,minNotesPerSecond:0,maxNotesPerSecond:2,variance:0,densityCategory:"moderate",naturalDifficulty:I,perBeatDensity:[]},high:{band:"high",totalBeats:0,totalTransients:0,notesPerSecond:1,minNotesPerSecond:0,maxNotesPerSecond:2,variance:0,densityCategory:"moderate",naturalDifficulty:"medium",perBeatDensity:[]}},combinedMetrics:{totalTransients:A.detectedBeats.length,notesPerSecond:1,densityCategory:"moderate",naturalDifficulty:I}},scoringResult:{sectionScores:[],bandTotals:{low:0,mid:0,high:0},bandAverages:{low:0,mid:0,high:0},sectionWinners:[],config:{beatsPerSection:8,ioiVarianceWeight:.25,syncopationWeight:.25,phraseSignificanceWeight:.25,densityWeight:.25,offbeatGridPositions:{straight_16th:[1,3],triplet_8th:[1,2],straight_8th:[1]}}}},metadata:this.buildFullRhythmMetadata(A,I)}}static buildFullRhythmMetadata(A,B){const g=A.generationMetadata;return{difficulty:this.parseDifficultyPreset(g?.difficulty),bandsAnalyzed:["low","mid","high"],transientsDetected:A.detectedBeats.length,transientsFilteredByIntensity:0,densityValidationRetries:0,phrasesDetected:0,averageDensity:g?.rhythmMetadata?.averageDensity??.5,naturalDifficulty:B,generationConfig:{difficulty:this.parseDifficultyPreset(g?.difficulty),outputMode:"composite",measureStartOffset:0,minimumTransientIntensity:.1,transientConfig:void 0,rhythmicBalanceConfig:{strongBeatEmphasis:"natural",downbeatProximityRange:2,fillEmptyMeasures:!0,addedBeatIntensity:.45,marginSeconds:.5},seed:g?.seed,verbose:!1,enableCache:!0,cacheMaxAge:1800*1e3,skipDifficultyVariants:!1},duration:A.duration,totalBeats:A.mergedBeats.length}}static buildMinimalMelodyContour(){return{segments:[],direction:"stable",range:{minNote:"N/A",maxNote:"N/A",semitones:0},shortTermDirection:"stable",mediumTermDirection:"stable",longTermDirection:"stable"}}static reconstructPitchAnalysis(A){const B=A.generationMetadata;return!B||!B.directionStats&&!B.intervalStats?null:{pitchByBeat:[],melodyContour:this.buildMinimalMelodyContour(),directionStats:B.directionStats??{up:0,down:0,stable:0,none:0},intervalStats:B.intervalStats??{unison:0,small:0,medium:0,large:0,very_large:0},metadata:{totalBeats:0,voicedBeats:0,directionCalculatedBeats:0}}}static reconstructMetadata(A,B){const g=A.generationMetadata,I=this.parseDifficultyPreset(g?.difficulty),E=g?.controllerMode??"ddr";return{difficulty:I,controllerMode:E,rhythmMetadata:this.buildFullRhythmMetadata(A,this.parseDifficultyLevel(g?.difficulty)),buttonMetadata:{controllerMode:E,keysUsed:A.chart?.usedKeys??[],pitchInfluencedBeats:g?.pitchInfluenceWeight&&g.pitchInfluenceWeight>0?B.beats.filter(i=>i.requiredKey).length:0,patternInfluencedBeats:B.beats.filter(i=>i.requiredKey).length-(g?.pitchInfluenceWeight&&g.pitchInfluenceWeight>0?B.beats.filter(i=>i.requiredKey).length:0),patternsUsed:g?.patternsUsed??[],buttonDistribution:new Map},pitchMetadata:g?.directionStats||g?.intervalStats?{melodyRange:null,directionStats:g.directionStats??null,intervalStats:g.intervalStats??null}:null,chartMetadata:{totalBeats:B.beats.length,detectedBeats:B.detectedBeatIndices.length,generatedBeats:B.beats.length-B.detectedBeatIndices.length},generationConfig:{difficulty:I,controllerMode:E,rhythm:{},buttons:{pitchInfluenceWeight:g?.pitchInfluenceWeight??1},seed:g?.seed}}}static toJSON(A,B={}){const g=this.toExportData(A,B);return JSON.stringify(g,null,2)}static fromJSON(A){let B;try{B=JSON.parse(A)}catch(I){throw new Error(`Invalid JSON: ${I instanceof Error?I.message:"Unknown error"}`)}const g=this.validate(B);if(!g.success||!g.data)throw new Error(`Invalid level data: ${g.error??"Unknown error"}`);return this.fromExportData(g.data)}static async saveToFile(A,B,g={}){const I=await Promise.resolve().then(()=>GB),E=this.toJSON(A,g);await I.writeFile(B,E,"utf-8")}static async loadFromFile(A){const g=await(await Promise.resolve().then(()=>GB)).readFile(A,"utf-8");return this.fromJSON(g)}static validate(A){const B=[];if(typeof A!="object"||A===null)return{success:!1,error:"Data must be a non-null object"};const g=A;if(g.version!==1)return{success:!1,error:`Unsupported version: ${g.version}. Expected 1.`};if(g.format!=="full-beatmap")return{success:!1,error:`Invalid format: ${g.format}. Expected 'full-beatmap'.`};const I=["audioId","duration","quarterNoteBpm","detectedBeats","mergedBeats"];for(const E of I)if(!(E in g))return{success:!1,error:`Missing required field: ${E}`};if(!Array.isArray(g.detectedBeats))return{success:!1,error:"detectedBeats must be an array"};if(!Array.isArray(g.mergedBeats))return{success:!1,error:"mergedBeats must be an array"};if(!g.subdivision||typeof g.subdivision!="object")B.push("No subdivision data - level may not be playable");else{const E=g.subdivision;if(!Array.isArray(E.beats))return{success:!1,error:"subdivision.beats must be an array"}}if(g.chart===null)B.push("No chart data - level has no key assignments");else if(g.chart!==void 0&&typeof g.chart!="object")return{success:!1,error:"chart must be an object or null"};return typeof g.interpolatedMetadata!="object"||g.interpolatedMetadata===null?{success:!1,error:"interpolatedMetadata must be an object"}:Ca(A)?{success:!0,data:A,warnings:B.length>0?B:void 0}:{success:!1,error:"Data does not match FullBeatMapExportData format",warnings:B.length>0?B:void 0}}static toPack(A,B={}){const g={};for(const[I,E]of Object.entries(A))if(E){const i={includeAudioTitle:B.includeAudioTitle,audioTitle:B.audioTitle};g[I]=this.toExportData(E,i)}return{version:1,format:"level-pack",exportedAt:Date.now(),trackReference:B.trackReference,difficulties:g}}static fromPack(A){const B={};for(const[g,I]of Object.entries(A.difficulties))if(I)try{B[g]=this.fromExportData(I)}catch{}return B}static packToJSON(A,B={}){const g=this.toPack(A,B);return JSON.stringify(g,null,2)}static packFromJSON(A){let B;try{B=JSON.parse(A)}catch(g){throw new Error(`Invalid JSON: ${g instanceof Error?g.message:"Unknown error"}`)}if(!aa(B))throw new Error("Data does not match LevelPackExport format");return this.fromPack(B)}static isProcedural(A){return A.generationSource==="procedural"}static getSummary(A){const B=[`Audio: ${A.audioId}`,`Duration: ${A.duration.toFixed(1)}s`,`BPM: ${A.quarterNoteBpm.toFixed(1)}`,`Beats: ${A.mergedBeats.length} (${A.detectedBeats.length} detected)`];return A.chart&&(B.push(`Keys: ${A.chart.keyCount} (${A.chart.style})`),B.push(`Keys used: ${A.chart.usedKeys.join(", ")}`)),A.generationMetadata&&(B.push(`Difficulty: ${A.generationMetadata.difficulty}`),B.push(`Controller: ${A.generationMetadata.controllerMode}`),B.push(`Pitch influence: ${(A.generationMetadata.pitchInfluenceWeight*100).toFixed(0)}%`)),B.join(`
|
|
53
|
-
`)}}function mF(Q){return typeof Q=="string"&&["humanoid","beast","undead","dragon","fiend","construct","elemental","monstrosity"].includes(Q)}function uF(Q){return typeof Q=="string"&&["common","uncommon","elite","boss"].includes(Q)}function SF(Q){return typeof Q=="string"&&["brute","archer","support"].includes(Q)}function bF(Q){return typeof Q=="string"&&["easy","medium","hard","deadly"].includes(Q)}class pF{static calculatePartyLevel(A){if(A.length===0)return 1;const B=A.map(g=>g.level||1);return da(B)}static calculatePartyStrength(A){if(A.length===0)return 0;const B=A.reduce((e,o)=>e+(o.hp?.max||10),0),g=this.getAverageAC(A),I=this.getAverageDamage(A),E=A.length,i=g/10,C=B*i,a=I*E*10;return C+a}static getXPBudget(A,B){if(A.length===0)return 0;const g=A.map(I=>I.level||1);return MI(g,B)}static getAverageAC(A){if(A.length===0)return 10;const B=A.reduce((g,I)=>g+(I.armor_class||10),0);return Math.round(B/A.length)}static getAverageHP(A){if(A.length===0)return 10;const B=A.reduce((g,I)=>g+(I.hp?.max||10),0);return Math.round(B/A.length)}static getPartySize(A){return A.length}static getAverageDamage(A){if(A.length===0)return 0;const B=A.reduce((g,I)=>g+this.estimateCharacterDamage(I),0);return Math.round(B/A.length)}static estimateCharacterDamage(A){const B=A.ability_scores,g=A.equipment?.weapons?.find(C=>C.equipped);let I,E;if(g?.damage?.dice){I=g.damage.dice;const C=g.weaponProperties?.includes("ranged")??!1,a=g.weaponProperties?.includes("finesse")??!1;E=Math.floor(((B?.[C||a?"DEX":"STR"]??10)-10)/2)}else I="1",E=Math.floor(((B?.STR??10)-10)/2);let i;try{const C=hA.parseDiceFormula(I);i=C.diceCount*((C.diceSides+1)/2)+C.modifier+E}catch{i=E+1}return Math.round(i*10)/10}static analyzeParty(A){const B=this.calculatePartyLevel(A),g=this.getPartySize(A),I=this.getAverageAC(A),E=this.getAverageHP(A),i=this.getAverageDamage(A),C=this.calculatePartyStrength(A),a=this.getXPBudget(A,"easy"),e=this.getXPBudget(A,"medium"),o=this.getXPBudget(A,"hard"),t=this.getXPBudget(A,"deadly");return{averageLevel:B,partySize:g,averageAC:I,averageHP:E,averageDamage:i,totalStrength:C,easyXP:a,mediumXP:e,hardXP:o,deadlyXP:t}}}const ZA=[];for(let Q=0;Q<256;++Q)ZA.push((Q+256).toString(16).slice(1));function YF(Q,A=0){return(ZA[Q[A+0]]+ZA[Q[A+1]]+ZA[Q[A+2]]+ZA[Q[A+3]]+"-"+ZA[Q[A+4]]+ZA[Q[A+5]]+"-"+ZA[Q[A+6]]+ZA[Q[A+7]]+"-"+ZA[Q[A+8]]+ZA[Q[A+9]]+"-"+ZA[Q[A+10]]+ZA[Q[A+11]]+ZA[Q[A+12]]+ZA[Q[A+13]]+ZA[Q[A+14]]+ZA[Q[A+15]]).toLowerCase()}let fi;const UF=new Uint8Array(16);function kF(){if(!fi){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");fi=crypto.getRandomValues.bind(crypto)}return fi(UF)}const ut={randomUUID:typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function ZF(Q,A,B){Q=Q||{};const g=Q.random??Q.rng?.()??kF();if(g.length<16)throw new Error("Random bytes length must be >= 16");return g[6]=g[6]&15|64,g[8]=g[8]&63|128,YF(g)}function WF(Q,A,B){return ut.randomUUID&&!Q?ut.randomUUID():ZF(Q)}class HF{constructor(A={}){this.options={validateAudioUrls:!1,strict:!1,audioUrlValidationTimeout:5e3,resolveImageUrls:!1,...A}}async parse(A){const B=A.name,g=A.image,I=A.creator,E=A.description,i=A.genre,C=A.tags;let a=g;g&&this.options.resolveImageUrls&&(a=await kA.resolveUrl(g));const e=A.tracks||[],o=[];for(let t=0;t<e.length;t++)try{const s=await this.parseTrack(e[t],t);if(s){const G=await this.resolveTrackUrls(s);o.push(G)}}catch(s){if(this.options.strict)throw s;console.warn(`Failed to parse track at index ${t}:`,s)}return{name:B,description:E,image:a,creator:I,genre:i,tags:C,version:A.version,playlist_type:A.playlist_type,original_playlist_tx_id:A.original_playlist_tx_id,playlist_artist:A.playlist_artist,tracks:o}}async resolveTrackUrls(A){if(!this.options.resolveImageUrls)return A;const B={...A};return B.image_url&&(B.image_url=await kA.resolveUrl(B.image_url)),B.image_thumb_url&&(B.image_thumb_url=await kA.resolveUrl(B.image_thumb_url)),B}async parseTrack(A,B){const g=QA.parseMetadata(A.metadata);if(!g&&this.options.strict)throw new Error(`Failed to parse metadata for track at index ${B}`);const I=A.chain_name,E=A.token_address,i=A.token_id,C=A.tx_id,a=A.platform,e=A.id||(I==="AR"?`AR-${C}`:`${I}-${E}-${i}`),o=A.uuid||WF(),t=QA.extractTitle(g||{}),s=QA.extractArtist(g||{}),n=typeof A.artwork_url=="string"&&A.artwork_url||typeof A.image_url=="string"&&A.image_url||null||QA.extractImageUrl(g||{}),h=QA.extractImageThumbUrl(g||{}),d=typeof A.audio_url=="string"&&A.audio_url||null||QA.extractAudioUrl(g||{}),l=QA.extractAudioUrlLossless(g||{});if(!d){if(this.options.strict)throw new Error(`No audio URL found for track: ${t||e}`);return console.warn(`Track ${e} has no audio URL - marked as Unsummonable`),null}if(this.options.validateAudioUrls&&!await this.validateAudioUrl(d)){if(this.options.strict)throw new Error(`Audio URL validation failed for track: ${t||e}`);return console.warn(`Track ${e} has invalid audio URL - marked as Unsummonable`),null}if(!t||!s||!n){if(this.options.strict)throw new Error(`Missing required fields for track: ${JSON.stringify({title:t,artist:s,imageUrl:n})}`);return null}const F=typeof g?.description=="string"?g.description:void 0,m=typeof g?.album=="string"?g.album:void 0,y=g?.duration?Number(g.duration):0,p=QA.extractGenre(g||{}),k=g?.tags||[],H=g?.bpm?Number(g.bpm):void 0,T=typeof g?.key=="string"?g.key:void 0,L=QA.convertAttributes(g?.attributes),v=Mo(g||{}),BA={id:e,uuid:o,playlist_index:B,chain_name:I,platform:a,title:t,artist:s,description:F,album:m,image_url:n,audio_url:d,audio_url_lossless:l&&l!==d?l:void 0,duration:y,genre:p,tags:Array.isArray(k)?k.map(iA=>String(iA).toLowerCase()):[],bpm:H,key:T,attributes:L||void 0,extras:v};return h&&(BA.image_thumb_url=h),typeof g?.audio_ipfs_hash=="string"?BA.audio_ipfs_hash=A.audio_ipfs_hash||g.audio_ipfs_hash:A.audio_ipfs_hash&&(BA.audio_ipfs_hash=A.audio_ipfs_hash),typeof g?.artwork_ipfs_hash=="string"?BA.artwork_ipfs_hash=A.artwork_ipfs_hash||g.artwork_ipfs_hash:A.artwork_ipfs_hash&&(BA.artwork_ipfs_hash=A.artwork_ipfs_hash),I!=="AR"?(BA.token_address=E,BA.token_id=i):C&&(BA.tx_id=C),BA}async validateAudioUrl(A){const B=new AbortController,g=setTimeout(()=>B.abort(),this.options.audioUrlValidationTimeout);try{const I=await fetch(A,{method:"HEAD",signal:B.signal});return clearTimeout(g),I.ok}catch(I){return clearTimeout(g),I instanceof Error&&I.name==="AbortError"?console.warn(`Audio URL validation timed out after ${this.options.audioUrlValidationTimeout}ms: ${A}`):console.warn(`Audio URL validation failed for ${A}:`,I),!1}}}class MB{static separateFrequencyBands(A,B){const g=A.length,I=B/2/g,E=[],i=[],C=[];for(let a=0;a<g;a++){const e=a*I,o=A[a]/255;e>=20&&e<400?E.push(o):e>=400&&e<4e3?i.push(o):e>=4e3&&e<=14e3&&C.push(o)}return{bass:E,mid:i,treble:C}}static calculateDominance(A,B){if(A.length===0)return 0;const I=A.reduce((E,i)=>E+i,0)/A.length;return B===void 0?I:I/(B/1e3)}}const sQ={targetSampleRate:8e3,fftWindowSize:32,hopSizeMs:4,hopSizeMode:{mode:"standard"},melBands:40,melBandsMode:{mode:"standard"},highPassCutoff:.4,gaussianSmoothMs:20,gaussianSmoothMode:{mode:"standard"}};class St{constructor(A={}){const B=A.hopSizeMode?QE(A.hopSizeMode):A.hopSizeMs??sQ.hopSizeMs,g=A.melBandsMode?EE(A.melBandsMode):A.melBands??sQ.melBands,I=A.gaussianSmoothMode?iE(A.gaussianSmoothMode):A.gaussianSmoothMs??sQ.gaussianSmoothMs;this.config={...sQ,...A,hopSizeMs:B,melBands:g,gaussianSmoothMs:I}}calculate(A){const B=Kg(A,this.config.targetSampleRate),g=B.targetSampleRate,I=A.duration,E=Math.round(this.config.fftWindowSize/1e3*g),i=Math.pow(2,Math.ceil(Math.log2(E))),C=Math.round(this.config.hopSizeMs/1e3*g),a=this.config.hopSizeMs/1e3,e=ui(B.data,i,C,g),o=xo(this.config.melBands,i,g),t=e.numFrames,s=[];for(let F=0;F<t;F++){const m=e.frames[F],y=new Float32Array(this.config.melBands);for(let p=0;p<this.config.melBands;p++){const k=o[p];let H=0;for(let T=0;T<k.length&&T<m.length;T++)H+=m[T]*k[T];y[p]=20*Math.log10(H+1e-10)}s.push(y)}const G=new Float32Array(t);G[0]=0;for(let F=1;F<t;F++){let m=0;for(let y=0;y<this.config.melBands;y++){const p=s[F][y]-s[F-1][y];p>0&&(m+=p)}G[F]=m}const n=1/a,h=Vo(G,this.config.highPassCutoff,n),c=mi(h,this.config.gaussianSmoothMs,n),d=Lo(c),l=new Float32Array(c.length);if(d>1e-10)for(let F=0;F<c.length;F++)l[F]=c[F]/d;else for(let F=0;F<c.length;F++)l[F]=c[F];return{envelope:l,numFrames:t,hopSizeSeconds:a,effectiveSampleRate:g,duration:I}}getConfig(){return{...this.config}}findPeaks(A,B=.5){const g=[];for(let I=1;I<A.length-1;I++)A[I]>A[I-1]&&A[I]>A[I+1]&&A[I]>=B&&g.push(I);return g}frameToTime(A,B){return A*B}timeToFrame(A,B){return Math.round(A/B)}}const fF={tempoCenter:.5,tempoWidth:1.4,minBpm:90,maxBpm:180,useOctaveResolution:!1,useTripleMeter:!1};class bt{constructor(A={}){this.config={...fF,...A}}estimateTempo(A,B){if(A.length<10)return this.getDefaultEstimate();const g=Math.round(60/this.config.minBpm/B),I=Math.round(60/this.config.maxBpm/B),E=Math.max(I,2),i=Math.min(g,Math.floor(A.length/2));if(i<=E)return this.getDefaultEstimate();const C=this.computeAutocorrelation(A,E,i),a=this.applyPerceptualWeighting(C.values,C.minLag,B);let e=this.findPeak(a,0,a.length);if(this.config.useOctaveResolution){const m=this.calculateTPS2(C.values,e),y=Math.floor(e/2);y>=1&&y<a.length&&this.calculateTPS2(C.values,y)>m&&(e=y)}if(this.config.useTripleMeter){const m=this.calculateTPS3(C.values,e),y=Math.floor(e/3);y>=1&&y<a.length&&this.calculateTPS3(C.values,y)>m&&(e=y)}const o=this.lagToBpm(e+C.minLag,B),t=o/2,s=a[e]||0,G=Math.min(e*2,a.length-1),n=a[G]||0,h=Math.max(Math.floor(e/2),0),c=a[h]||0,d=1,l=Math.min(Math.max(Math.max(n,c)/(s+.001),0),1),F=60/o;return{primaryBpm:o,secondaryBpm:t,primaryWeight:d,secondaryWeight:l,targetIntervalSeconds:F}}getConfig(){return{...this.config}}computeAutocorrelation(A,B,g){const I=g-B+1,E=new Float32Array(I);let i=0;for(let C=0;C<A.length;C++)i+=A[C];i/=A.length;for(let C=B;C<=g;C++){let a=0,e=0;for(let o=C;o<A.length;o++)a+=(A[o]-i)*(A[o-C]-i),e++;E[C-B]=e>0?a/e:0}return{values:E,minLag:B,maxLag:g}}applyPerceptualWeighting(A,B,g){const I=new Float32Array(A.length),E=this.config.tempoCenter/g,i=this.config.tempoWidth;for(let C=0;C<A.length;C++){const a=B+C;if(a<=0||E<=0){I[C]=0;continue}const e=Math.log2(a/E),o=Math.exp(-.5*Math.pow(e/i,2));I[C]=A[C]*o}return I}findPeak(A,B,g){let I=B,E=A[B];for(let i=B+1;i<g&&i<A.length;i++)A[i]>E&&(E=A[i],I=i);return I}lagToBpm(A,B){return A<=0||B<=0?120:60/(A*B)}calculateTPS2(A,B){const g=A[B]||0,I=B*2,E=I-1,i=I+1,C=A[I]||0,a=E>=0&&A[E]||0,e=i<A.length&&A[i]||0;return g+.5*C+.25*a+.25*e}calculateTPS3(A,B){const g=A[B]||0,I=B*3,E=I-1,i=I+1,C=A[I]||0,a=E>=0&&A[E]||0,e=i<A.length&&A[i]||0;return g+.33*C+.33*a+.33*e}getDefaultEstimate(){return{primaryBpm:120,secondaryBpm:60,primaryWeight:1,secondaryWeight:.5,targetIntervalSeconds:.5}}getTempoCandidates(A,B,g=5){if(A.length<10)return[{bpm:120,strength:1}];const I=Math.round(60/this.config.minBpm/B),E=Math.round(60/this.config.maxBpm/B),i=Math.max(E,2),C=Math.min(I,Math.floor(A.length/2));if(C<=i)return[{bpm:120,strength:1}];const a=this.computeAutocorrelation(A,i,C),e=this.applyPerceptualWeighting(a.values,a.minLag,B),o=[];for(let t=1;t<e.length-1;t++)if(e[t]>e[t-1]&&e[t]>e[t+1]){const s=this.lagToBpm(t+a.minLag,B);o.push({bpm:s,strength:e[t]})}return o.sort((t,s)=>s.strength-t.strength),o.slice(0,g)}}const GQ=SA.for("BeatTracker"),NF={dpAlpha:680,sensitivity:1,minPredecessorRatio:.5,maxPredecessorRatio:2};class pt{constructor(A={}){this.config={...NF,...A}}getConfig(){return{...this.config}}trackBeats(A,B,g){const I=A.length;if(I<10)return{beats:[],beatFrames:[],cumulativeScores:new Float32Array(I)};const E=Math.round(B.targetIntervalSeconds/g);if(GQ.debug("BeatTracker: Beat tracking parameters",{targetIntervalSeconds:B.targetIntervalSeconds,hopSizeSeconds:g,periodFrames:E,envelopeLength:I}),E<2||I<E*2)return{beats:[],beatFrames:[],cumulativeScores:new Float32Array(I)};const i=this.config.sensitivity,C=Math.round(this.config.dpAlpha/i),a=Math.max(10,Math.min(1e4,C));GQ.debug("Beat tracking parameters",{sensitivity:i,dpAlpha:this.config.dpAlpha,effectiveDpAlpha:a});const e=Math.max(1,Math.round(E*this.config.minPredecessorRatio)),o=Math.round(E*this.config.maxPredecessorRatio),t=new Float32Array(o-e+1);for(let F=0;F<t.length;F++){const m=e+F,y=Math.log(m/E);t[F]=-a*y*y}const s=new Int32Array(I);s.fill(-1);const G=new Float32Array(A);for(let F=o+1;F<I;F++){let m=-1/0,y=-1;for(let p=0;p<t.length;p++){const k=F-(e+p);if(k>=0&&k<I){const H=t[p]+G[k];H>m&&(m=H,y=k)}}G[F]=m+A[F],s[F]=y}GQ.debug("BeatTracker: Starting backward pass",{maxCumulativeScore:Math.max(...G),envelopeLength:I});let n=0,h=G[0];for(let F=1;F<I;F++)G[F]>h&&(h=G[F],n=F);const c=[];let d=n;for(;d>=0&&s[d]>=0;)c.unshift(d),d=s[d];return d>=0&&A[d]>0&&c.unshift(d),GQ.debug("BeatTracker: Backward pass complete",{beatsFound:c.length,bestEndFrame:n,firstBeatFrame:c[0],lastBeatFrame:c[c.length-1]}),{beats:this.convertToBeats(c,A,G,g,E),beatFrames:c,cumulativeScores:G}}convertToBeats(A,B,g,I,E){if(A.length===0)return[];const i=[];let C=0;for(let o=0;o<B.length;o++)B[o]>C&&(C=B[o]);C<=0&&(C=1);let a=0;for(let o=0;o<A.length;o++)a+=B[A[o]];const e=a/A.length;for(let o=0;o<A.length;o++){const t=A[o],s=t*I,G=Math.max(0,Math.min(1,B[t]/C)),n=B[t],h=e>0?n/e:.5,c=Math.max(0,Math.min(1,.5+.3*(h-1)));i.push({timestamp:s,beatInMeasure:0,isDownbeat:!1,measureNumber:0,intensity:G,confidence:c})}return i}trackBeatsWithOptions(A,B,g,I={}){const E=this.trackBeats(A,B,g);if(I.applyTrimming&&E.beatFrames.length>0&&this.applyDiscountedScoreTrimming(E,A.length,Math.round(B.targetIntervalSeconds/g)),I.minScoreThreshold!==void 0){const i=E.beats.filter((C,a)=>{const e=E.beatFrames[a];return E.cumulativeScores[e]>=I.minScoreThreshold});E.beats=i,E.beatFrames=E.beatFrames.filter((C,a)=>{const e=E.beatFrames[a];return E.cumulativeScores[e]>=I.minScoreThreshold})}return E}applyDiscountedScoreTrimming(A,B,g){if(A.beatFrames.length<3)return;const I=A.cumulativeScores,E=I[B-1],i=new Float32Array(B);for(let s=0;s<B;s++)i[s]=I[s]-s/B*E;let C=0,a=B-1;for(let s=0;s<B;s++)if(i[s]>0){C=s;break}for(let s=B-1;s>=0;s--)if(i[s]>0){a=s;break}const e=Math.floor(g/2),o=[],t=[];for(let s=0;s<A.beatFrames.length;s++){const G=A.beatFrames[s];G>=C-e&&G<=a+e&&(o.push(A.beats[s]),t.push(G))}A.beats=o,A.beatFrames=t}getTrackingStats(A,B){const{beats:g,beatFrames:I}=A;if(g.length===0)return{numBeats:0,avgInterval:0,stdInterval:0,avgIntensity:0,avgConfidence:0,estimatedBpm:0};const E=[];for(let t=1;t<I.length;t++)E.push((I[t]-I[t-1])*B);const i=E.length>0?E.reduce((t,s)=>t+s,0)/E.length:0,C=E.length>0?Math.sqrt(E.reduce((t,s)=>t+Math.pow(s-i,2),0)/E.length):0,a=g.reduce((t,s)=>t+s.intensity,0)/g.length,e=g.reduce((t,s)=>t+s.confidence,0)/g.length,o=i>0?60/i:0;return{numBeats:g.length,avgInterval:i,stdInterval:C,avgIntensity:a,avgConfidence:e,estimatedBpm:o}}}const Yt=SA.for("BeatMapGenerator");class tB{constructor(A={}){this.state=null;const B={...Zg,...A},g=A.hopSizeMode,I=A.melBandsMode,E=A.gaussianSmoothMode,i=g?QE(g):A.hopSizeMs??Zg.hopSizeMs,C=I?EE(I):A.melBands??Zg.melBands,a=E?iE(E):A.gaussianSmoothMs??Zg.gaussianSmoothMs;this.options={...B,hopSizeMs:i,melBands:C,gaussianSmoothMs:a,...g?{hopSizeMode:g}:{},...I?{melBandsMode:I}:{},...E?{gaussianSmoothMode:E}:{}}}getConfig(){return{...this.options}}async generateBeatMap(A,B,g,I){this.state={cancelled:!1,progress:{phase:"loading",progress:0,message:"Loading audio..."}};try{this.updateProgress("loading",0,"Loading audio...");const E=await this.fetchAndDecode(A);if(this.state.cancelled)throw new Error("Generation cancelled");return await this.generateBeatMapFromBuffer(E,B,g,I)}finally{this.state=null}}async generateBeatMapFromBuffer(A,B,g,I){this.state||(this.state={cancelled:!1,progress:{phase:"preprocessing",progress:0,message:"Starting..."}}),g&&AE(g);try{const E=A.duration;if(this.updateProgress("preprocessing",5,"Preparing audio..."),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("ose_calculation",10,"Calculating onset strength envelope..."),this.state.cancelled)throw new Error("Generation cancelled");const C=new St({targetSampleRate:8e3,fftWindowSize:32,hopSizeMs:this.options.hopSizeMs,melBands:this.options.melBands,highPassCutoff:this.options.highPassCutoff,gaussianSmoothMs:this.options.gaussianSmoothMs}).calculate(A);if(this.updateProgress("ose_calculation",35,"Onset strength envelope calculated."),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("tempo_estimation",40,"Estimating tempo..."),this.state.cancelled)throw new Error("Generation cancelled");const e=new bt({tempoCenter:this.options.tempoCenter,tempoWidth:this.options.tempoWidth,minBpm:this.options.minBpm,maxBpm:this.options.maxBpm,useOctaveResolution:this.options.useOctaveResolution}).estimateTempo(C.envelope,C.hopSizeSeconds);if(this.updateProgress("tempo_estimation",55,`Tempo estimated: ${Math.round(e.primaryBpm)} BPM`),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("beat_tracking",60,"Tracking beats..."),this.state.cancelled)throw new Error("Generation cancelled");const t=new pt({dpAlpha:this.options.dpAlpha,sensitivity:this.options.sensitivity}).trackBeats(C.envelope,e,C.hopSizeSeconds);if(this.updateProgress("beat_tracking",85,`${t.beats.length} beats detected.`),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("measure_labeling",87,"Applying measure labels..."),this.state.cancelled)throw new Error("Generation cancelled");const s=g??GI;g&&BE(g,t.beats.length);const G=this.applyMeasureLabels(t.beats,s);if(this.updateProgress("measure_labeling",97,"Measure labels applied."),this.state.cancelled)throw new Error("Generation cancelled");this.updateProgress("finalizing",98,"Finalizing beat map...");let n=G;const h=this.options.filter;h>0&&(n=this.filterBeatsByGridAlignment(n,e,h));const c=n.length;n=this.applyIntensityThreshold(n),Yt.debug("BeatMapGenerator: Intensity filter applied",{beatsBefore:c,beatsAfter:n.length,beatsFiltered:c-n.length,noiseFloorThreshold:this.options.noiseFloorThreshold});const d={version:jC,algorithm:OC,minBpm:this.options.minBpm,maxBpm:this.options.maxBpm,sensitivity:this.options.sensitivity,filter:this.options.filter,noiseFloorThreshold:this.options.noiseFloorThreshold,hopSizeMs:this.options.hopSizeMs,fftSize:this.options.fftSize,dpAlpha:this.options.dpAlpha,melBands:this.options.melBands,highPassCutoff:this.options.highPassCutoff,gaussianSmoothMs:this.options.gaussianSmoothMs,tempoCenter:this.options.tempoCenter,tempoWidth:this.options.tempoWidth,useOctaveResolution:this.options.useOctaveResolution??!1,useTripleMeter:this.options.useTripleMeter??!1,generatedAt:new Date().toISOString()},l={audioId:B,duration:E,beats:n,bpm:e.primaryBpm,metadata:d,...g?{downbeatConfig:g}:{}};return this.updateProgress("complete",100,"Beat map generation complete."),I?.(this.state.progress),l}finally{this.state=null}}getProgress(){return this.state?.progress??null}cancel(){this.state&&(this.state.cancelled=!0,this.state.progress={phase:"error",progress:this.state.progress.progress,message:"Generation cancelled",error:"Cancelled by user"})}applyIntensityThreshold(A){const B=this.options.noiseFloorThreshold;return A.filter(g=>g.intensity>=B)}applyMeasureLabels(A,B){const g=this.computeMeasureOffsets(B.segments,A.length);return A.map((I,E)=>{const i=this.findActiveSegmentIndex(B.segments,E),C=B.segments[i],{downbeatBeatIndex:a,timeSignature:e}=C,{beatsPerMeasure:o}=e,t=E-a,s=(t%o+o)%o,G=s===0,h=Math.max(0,Math.floor(t/o))+g[i];return{...I,beatInMeasure:s,isDownbeat:G,measureNumber:h}})}computeMeasureOffsets(A,B){const g=[];for(let I=0;I<A.length;I++)if(I===0)g.push(0);else{const E=A[I-1],{downbeatBeatIndex:i,timeSignature:C}=E,a=C.beatsPerMeasure,o=A[I].startBeat-1-i,t=Math.max(0,Math.floor(o/a));g.push(t+1)}return g}findActiveSegmentIndex(A,B){let g=0;for(let I=0;I<A.length&&A[I].startBeat<=B;I++)g=I;return g}filterBeatsByGridAlignment(A,B,g){if(g<=0)return A;const I=60/B.primaryBpm,E=(1-g)*(I/2),i=A.filter(C=>{const e=Math.round(C.timestamp/I)*I;return Math.abs(C.timestamp-e)<=E});return Yt.debug("Filtered beats by grid alignment",{originalCount:A.length,filteredCount:i.length,threshold:g,maxDeviationMs:E*1e3}),i}updateProgress(A,B,g){this.state&&(this.state.progress={phase:A,progress:B,message:g})}async fetchAndDecode(A){const B=await fetch(A);if(!B.ok)throw new Error(`Failed to fetch audio: ${B.statusText}`);const g=await B.arrayBuffer(),I=globalThis.AudioContext||window?.AudioContext;if(!I)throw new Error("AudioContext not available in this environment");const E=new I;return await new Promise((i,C)=>{E.decodeAudioData(g,i,C)})}static toJSON(A){const B={audioId:A.audioId,duration:A.duration,beats:A.beats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),bpm:A.bpm,metadata:A.metadata};return JSON.stringify(B,null,2)}static fromJSON(A){const B=JSON.parse(A);return{audioId:B.audioId,duration:B.duration,beats:B.beats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),bpm:B.bpm,metadata:B.metadata}}static async saveToFile(A,B){if(typeof process>"u"||!process.versions?.node)throw new Error("saveToFile is only available in Node.js environment");const{writeFile:g}=await Promise.resolve().then(()=>GB),I=tB.toJSON(A);await g(B,I,"utf-8")}static async loadFromFile(A){if(typeof process>"u"||!process.versions?.node)throw new Error("loadFromFile is only available in Node.js environment");const{readFile:B}=await Promise.resolve().then(()=>GB),g=await B(A,"utf-8");return tB.fromJSON(g)}}function JF(Q){return"mergedBeats"in Q&&"detectedBeats"in Q}function qF(Q){return"subdivisionConfig"in Q&&"subdivisionMetadata"in Q}class Ut{constructor(A,B,g={},I=8){this.beatMap=A,this.audioContext=B,this.options={...vC,...g},this.rollingBpmWindowSize=I,this.normalizedBeatMap=this.createNormalizedBeatMap(A),this.state={isRunning:!1,currentTime:0,startTime:0,isPaused:!1,pauseTime:0,rafId:null,lastButtonPress:null,thresholds:this.resolveThresholds()},this.subscribers=new Set,this.scheduledBeats=this.initializeScheduledBeats()}createNormalizedBeatMap(A){if(qF(A)){let B=120;if(A.beats.length>=2){const I=A.beats[1].timestamp-A.beats[0].timestamp;I>0&&(B=60/I)}const g={version:"1.0.0",algorithm:"subdivision",minBpm:B,maxBpm:B,sensitivity:1,filter:0,noiseFloorThreshold:0,hopSizeMs:4,fftSize:2048,dpAlpha:680,melBands:40,highPassCutoff:.4,gaussianSmoothMs:20,tempoCenter:.5,tempoWidth:1.4,useOctaveResolution:!1,useTripleMeter:!1,generatedAt:new Date().toISOString()};return{audioId:A.audioId,duration:A.duration,beats:A.beats,bpm:B,metadata:g,downbeatConfig:A.downbeatConfig}}else return JF(A)?this.options.useInterpolatedBeats?{audioId:A.audioId,duration:A.duration,beats:A.mergedBeats,bpm:A.quarterNoteBpm,metadata:A.originalMetadata}:{audioId:A.audioId,duration:A.duration,beats:A.detectedBeats,bpm:A.quarterNoteBpm,metadata:A.originalMetadata}:A}initializeScheduledBeats(){return this.normalizedBeatMap.beats.map((A,B)=>({beat:A,index:B,upcomingEmitted:!1,exactEmitted:!1,passedEmitted:!1}))}getOptions(){return{...this.options}}getAccuracyThresholds(){return{...this.state.thresholds}}setDifficulty(A){A.preset!==void 0&&(this.options.difficultyPreset=A.preset),A.customThresholds!==void 0&&(this.options.customThresholds=A.customThresholds),this.state.thresholds=this.resolveThresholds()}subscribe(A){return this.subscribers.add(A),()=>{this.subscribers.delete(A)}}start(){this.state.isRunning||(this.state.isRunning=!0,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.state.isPaused=!1,this.scheduleUpdate())}stop(){this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null),this.state.isRunning=!1,this.state.isPaused=!1,this.state.pauseTime=0,this.scheduledBeats=this.initializeScheduledBeats()}pause(){!this.state.isRunning||this.state.isPaused||(this.state.isPaused=!0,this.state.pauseTime=this.getCurrentAudioTime(),this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null))}resume(){this.state.isPaused&&(this.state.isPaused=!1,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.scheduleUpdate())}seek(A){const B=Math.max(0,Math.min(A,this.normalizedBeatMap.duration));this.state.pauseTime=B,this.state.isRunning&&!this.state.isPaused&&(this.state.startTime=this.audioContext.currentTime-B),this.scheduledBeats=this.initializeScheduledBeats()}getCurrentAudioTime(){return this.state.isRunning?this.state.isPaused?this.state.pauseTime:this.audioContext.currentTime-this.state.startTime+this.getTotalLatencyCompensation():this.state.pauseTime}getTotalLatencyCompensation(){let A=0;if(this.options.compensateOutputLatency){const B=this.audioContext.outputLatency??0,g=this.audioContext.baseLatency??0;A+=B+g}return A+=this.options.userOffsetMs/1e3,A}getSyncState(){const A=this.audioContext.currentTime,B=this.getCurrentAudioTime(),g=this.audioContext.outputLatency??0,I=this.audioContext.baseLatency??0,E=A-this.state.startTime,i=B-E-this.getTotalLatencyCompensation();return{audioContextTime:A,audioElementTime:B,drift:i,isSynchronized:Math.abs(i)<=this.options.timingTolerance,outputLatency:g,baseLatency:I,userOffsetMs:this.options.userOffsetMs,totalCompensation:this.getTotalLatencyCompensation()}}scheduleUpdate(){!this.state.isRunning||this.state.isPaused||(this.state.rafId=requestAnimationFrame(()=>{this.update(),this.scheduleUpdate()}))}update(){if(!this.state.isRunning||this.state.isPaused)return;const A=this.getCurrentAudioTime(),B=this.getCurrentBpm();for(const g of this.scheduledBeats){const E=g.beat.timestamp-A;!g.upcomingEmitted&&E<=this.options.anticipationTime&&E>0&&(this.emitEvent(g,"upcoming",A,B,E),g.upcomingEmitted=!0),!g.exactEmitted&&Math.abs(E)<=this.options.timingTolerance&&(this.emitEvent(g,"exact",A,B,E),g.exactEmitted=!0),!g.passedEmitted&&E<-this.options.timingTolerance&&(this.emitEvent(g,"passed",A,B,E),g.passedEmitted=!0)}}emitEvent(A,B,g,I,E){const i={beat:A.beat,currentBpm:I,audioTime:g,timeUntilBeat:E,type:B};for(const C of this.subscribers)try{C(i)}catch(a){console.error("BeatStream callback error:",a)}}getUpcomingBeats(A){const B=this.getCurrentAudioTime(),g=[];for(const I of this.normalizedBeatMap.beats){const E=I.timestamp-B;if(E>=0&&E<=this.options.anticipationTime&&(g.push(I),g.length>=A))break}return g}getBeatAtTime(A){const B=this.options.timingTolerance;for(const g of this.normalizedBeatMap.beats)if(Math.abs(g.timestamp-A)<=B)return g;return null}getCurrentBeat(){const A=this.getCurrentAudioTime();let B=null;for(const g of this.normalizedBeatMap.beats)if(g.timestamp<=A)B=g;else break;return B}getNextBeat(){const A=this.getCurrentAudioTime();for(const B of this.normalizedBeatMap.beats)if(B.timestamp>A)return B;return null}getCurrentBpm(){const A=this.getCurrentAudioTime(),B=[];for(const a of this.normalizedBeatMap.beats)a.timestamp<=A+.1&&B.push(a);if(B.length<2)return this.normalizedBeatMap.bpm;const g=Math.min(this.rollingBpmWindowSize,B.length),I=B.slice(-g);let E=0;for(let a=1;a<I.length;a++)E+=I[a].timestamp-I[a-1].timestamp;const C=60/(E/(I.length-1));return Math.max(30,Math.min(300,C))}resolveThresholds(){return this.options.customThresholds&&Object.keys(this.options.customThresholds).length>0?{...nI(this.options.difficultyPreset||"medium"),...this.options.customThresholds}:nI(this.options.difficultyPreset||"medium")}checkButtonPress(A,B){let g=null,I=1/0;for(const G of this.normalizedBeatMap.beats){const n=A-G.timestamp,h=Math.abs(n);h<I&&(I=h,g=G)}if(!g){const G={accuracy:"miss",offset:A,matchedBeat:null,absoluteOffset:A,keyMatch:!0,pressedKey:B,requiredKey:void 0};return this.state.lastButtonPress=G,G}const E=A-g.timestamp,i=Math.abs(E);let C;const a=this.state.thresholds;i<=a.perfect?C="perfect":i<=a.great?C="great":i<=a.good?C="good":i<=a.ok?C="ok":C="miss";const e=this.options.ignoreKeyRequirements??!1,o=g.requiredKey!==void 0;let t=!0;!e&&o&&(B===void 0?(C="miss",t=!1):B!==g.requiredKey&&(C="wrongKey",t=!1));const s={accuracy:C,offset:E,matchedBeat:g,absoluteOffset:i,keyMatch:t,pressedKey:B,requiredKey:g.requiredKey};return this.state.lastButtonPress=s,s}getLastBeatAccuracy(){return this.state.lastButtonPress}isRunning(){return this.state.isRunning&&!this.state.isPaused}isPaused(){return this.state.isPaused}getCurrentTime(){return this.getCurrentAudioTime()}getDuration(){return this.normalizedBeatMap.duration}getBeatMap(){return this.beatMap}getNormalizedBeatMap(){return this.normalizedBeatMap}setBeatMap(A){this.beatMap=A,this.normalizedBeatMap=this.createNormalizedBeatMap(A),this.scheduledBeats=this.initializeScheduledBeats()}dispose(){this.stop(),this.subscribers.clear()}}const VA=SA.for("BeatInterpolator");class Pg{constructor(A={}){this.options={...KC,...A}}getConfig(){return{...this.options}}interpolate(A){const{beats:B,audioId:g,duration:I,metadata:E,bpm:i,downbeatConfig:C}=A;if(VA.debug("Starting beat interpolation",{audioId:g,detectedBeats:B.length,duration:I,originalBpm:i}),B.length===0)return VA.warn("No beats to interpolate"),this.createEmptyInterpolatedBeatMap(A);if(B.length===1)return VA.warn("Only one beat, cannot determine quarter note"),this.createSingleBeatInterpolatedBeatMap(A);const a=this.detectQuarterNote(B);VA.debug("Quarter note detected",{intervalSeconds:a.intervalSeconds,bpm:a.bpm,confidence:a.confidence,method:a.method,denseSectionCount:a.denseSectionCount});const e=this.analyzeGaps(B,a.intervalSeconds);VA.debug("Gap analysis complete",{totalGaps:e.totalGaps,halfNoteGaps:e.halfNoteGaps,anomalies:e.anomalies.length,gridAlignmentScore:e.gridAlignmentScore});const o=this.generateGrid(A,a,e);let t=this.mergeBeats(B,o,C);const s=t.filter(p=>p.source==="interpolated").length,G=s>0?t.filter(p=>p.source==="interpolated").reduce((p,k)=>p+k.confidence,0)/s:0,n=this.calculateTempoDriftRatio(t),h=this.identifyTempoClusters(B),c=h.map(p=>Math.round(p.bpm)),d=this.filterOctaveMultiples(c),l=d.length>1;let F,m;if(this.options.enableMultiTempo&&l){const p=this.runMultiTempoAnalysis(B,h,I);p&&(F=p.tempoSections,m=!0,F.length>1&&(t=this.reinterpolateBoundaryRegions(t,F,B,C)),VA.debug("Multi-tempo analysis complete",{sectionCount:F.length,tempos:F.map(k=>k.bpm)}))}const y={quarterNoteDetection:a,gapAnalysis:e,detectedBeatCount:B.length,interpolatedBeatCount:s,totalBeatCount:t.length,interpolationRatio:t.length>0?s/t.length:0,avgInterpolatedConfidence:G,tempoDriftRatio:n,detectedClusterTempos:d.length>0?d:void 0,hasMultipleTempos:l,tempoSections:F,hasMultiTempoApplied:m};return VA.debug("Interpolation complete",{detectedBeats:B.length,interpolatedBeats:s,totalBeats:t.length,interpolationRatio:y.interpolationRatio.toFixed(2),hasMultipleTempos:l,hasMultiTempoApplied:m}),{audioId:g,duration:I,detectedBeats:[...B],mergedBeats:t,quarterNoteInterval:a.intervalSeconds,quarterNoteBpm:a.bpm,quarterNoteConfidence:a.confidence,originalMetadata:E,interpolationMetadata:y,downbeatConfig:C}}detectQuarterNote(A){const B=this.identifyDenseSections(A);VA.debug("Identified dense sections",{count:B.length,totalBeats:B.reduce((s,G)=>s+G.beatCount,0)});const g=this.calculateWeightedIntervals(A,B),I=this.buildIntervalHistogram(g),E=this.findHistogramPeaks(I);if(E.length===0){const s=this.calculateAverageInterval(A);return{intervalSeconds:s,bpm:60/s,confidence:.3,histogramPeak:0,secondaryPeaks:[],method:"tempo-detector-fallback",denseSectionCount:B.length,denseSectionBeats:B.reduce((G,n)=>G+n.beatCount,0)}}const i=E[0],C=E.slice(1,4).map(s=>s.interval),a=g.reduce((s,G)=>s+G.weight,0),e=a>0?i.weight/a:0,o=B.reduce((s,G)=>s+G.beatCount,0)/A.length,t=Math.min(1,e*.6+o*.4);return{intervalSeconds:i.interval,bpm:60/i.interval,confidence:t,histogramPeak:i.weight,secondaryPeaks:C,method:"histogram",denseSectionCount:B.length,denseSectionBeats:B.reduce((s,G)=>s+G.beatCount,0)}}identifyDenseSections(A){const B=this.options.denseSectionMinBeats,g=[];if(A.length<B)return g;const I=[];for(let C=1;C<A.length;C++)I.push(A[C].timestamp-A[C-1].timestamp);let E=0;for(let C=1;C<I.length;C++){const a=I.slice(E,C+1),e=a.reduce((s,G)=>s+G,0)/a.length,o=a.reduce((s,G)=>s+Math.pow(G-e,2),0)/a.length,t=Math.pow(e*.2,2);if(o>t){const s=C-E+1;if(s>=B){const G=I.slice(E,C),n=G.reduce((c,d)=>c+d,0)/G.length,h=G.reduce((c,d)=>c+Math.pow(d-n,2),0)/G.length;g.push({startIndex:E,endIndex:C-1,beatCount:s,avgInterval:n,intervalVariance:h})}E=C}}const i=I.length-E+1;if(i>=B){const C=I.slice(E),a=C.reduce((o,t)=>o+t,0)/C.length,e=C.reduce((o,t)=>o+Math.pow(t-a,2),0)/C.length;g.push({startIndex:E,endIndex:I.length-1,beatCount:i,avgInterval:a,intervalVariance:e})}return g}identifyTempoClusters(A){const B=this.identifyDenseSections(A);if(B.length===0)return[];const g=this.options.tempoSectionThreshold,I=this.options.minClusterBeats,E=B.map(C=>({...C,bpm:60/C.avgInterval,isVerified:C.beatCount>=I}));if(E.length===1)return E[0].isVerified?E:[];E.sort((C,a)=>C.startIndex-a.startIndex);const i=[E[0]];for(let C=1;C<E.length;C++){const a=E[C],e=i[i.length-1],o=e.endIndex+1>=a.startIndex,s=1-Math.min(e.avgInterval,a.avgInterval)/Math.max(e.avgInterval,a.avgInterval)<=g;if(o&&s){const G=[];for(let c=e.startIndex;c<=a.endIndex&&c<A.length-1;c++)G.push(A[c+1].timestamp-A[c].timestamp);const n=G.reduce((c,d)=>c+d,0)/G.length,h=G.reduce((c,d)=>c+Math.pow(d-n,2),0)/G.length;i[i.length-1]={startIndex:e.startIndex,endIndex:a.endIndex,beatCount:e.beatCount+a.beatCount,avgInterval:n,intervalVariance:h,bpm:60/n,isVerified:e.beatCount+a.beatCount>=I}}else i.push(a)}return i.filter(C=>C.isVerified)}findConflictingClusters(A){if(A.length<2)return null;const B=this.options.tempoSectionThreshold,g=[];for(let I=0;I<A.length;I++)for(let E=I+1;E<A.length;E++){const i=A[I],C=A[E];if(this.isOctaveMultiple(i.bpm,C.bpm))continue;1-Math.min(i.bpm,C.bpm)/Math.max(i.bpm,C.bpm)>B&&g.push({cluster1:i,cluster2:C})}return g.length>0?g:null}isOctaveMultiple(A,B,g=.1){const I=[.5,2],E=A/B;return I.some(i=>Math.abs(E-i)<=g)}filterOctaveMultiples(A){if(A.length<=1)return A;const B=[...A].sort((I,E)=>I-E),g=[];for(const I of B)g.some(i=>this.isOctaveMultiple(I,i))||g.push(I);return g}findCrossingPoint(A,B,g){const I=this.options.tempoSectionThreshold,E=this.options.tempoAdaptationRate,i=g.slice(A.startIndex,A.endIndex+1),C=g.slice(B.startIndex,B.endIndex+1),a=g.slice(A.endIndex+1,B.startIndex),e=i[i.length-1],o=C[0];if(a.length===0){const n=(e.timestamp+o.timestamp)/2;return VA.debug("No connecting beats between clusters → automatic boundary",{cluster1Bpm:A.bpm,cluster2Bpm:B.bpm,boundaryTimestamp:n}),{hasBoundary:!0,boundaryTimestamp:n,gapRatio:1,beatsInSection1:i,beatsInSection2:C}}const t=this.interpolateForwardsWithDrift(e.timestamp,A.avgInterval,a,E,o.timestamp),s=this.interpolateBackwardsWithDrift(o.timestamp,B.avgInterval,a,E,e.timestamp),G=this.measureGapAtCrossing(t,s,A.avgInterval,B.avgInterval);if(VA.debug("Crossing point analysis",{cluster1Bpm:A.bpm,cluster2Bpm:B.bpm,connectingBeats:a.length,gapRatio:G.gapRatio.toFixed(3),threshold:I,hasBoundary:G.gapRatio>I}),G.gapRatio>I){const n=G.crossingTimestamp,{beatsInSection1:h,beatsInSection2:c}=this.assignBeatsToSections(a,A,B,n);return{hasBoundary:!0,boundaryTimestamp:n,gapRatio:G.gapRatio,beatsInSection1:[...i,...h],beatsInSection2:[...c,...C]}}else return{hasBoundary:!1,gapRatio:G.gapRatio,beatsInSection1:[...i,...a,...C],beatsInSection2:[]}}measureGapAtCrossing(A,B,g,I){const E=A.endTimestamp,i=B.endTimestamp,C=(E+i)/2,a=A.beatPositions,e=B.beatPositions;let o=E,t=1/0;for(const d of a){const l=Math.abs(d-C);l<t&&(t=l,o=d)}let s=i,G=1/0;for(const d of e){const l=Math.abs(d-C);l<G&&(G=l,s=d)}a.length===0&&(o=E),e.length===0&&(s=i);const n=Math.abs(o-s),h=(g+I)/2;return{gapRatio:h>0?n/h:0,crossingTimestamp:C,forwardsInterval:A.finalInterval,backwardsInterval:B.finalInterval}}assignBeatsToSections(A,B,g,I){const E=[],i=[];A.length>0&&A[0].timestamp>I,A[0],A[A.length-1];for(const C of A)C.timestamp<I?E.push(C):i.push(C);return VA.debug("Assigned connecting beats to sections",{totalConnectingBeats:A.length,toSection1:E.length,toSection2:i.length,boundaryTimestamp:I}),{beatsInSection1:E,beatsInSection2:i}}calculatePhaseAlignment(A,B,g,I=.1){const E=A-B,i=Math.round(E/g),C=B+i*g,a=Math.abs(A-C),e=g*I;return a<=e?1:0}runMultiTempoAnalysis(A,B,g){if(B.length<2)return null;const I=this.findConflictingClusters(B);if(!I||I.length===0)return VA.debug("No conflicting tempo clusters found"),null;VA.debug("Found conflicting tempo clusters",{conflictCount:I.length,conflicts:I.map(o=>({c1:Math.round(o.cluster1.bpm),c2:Math.round(o.cluster2.bpm)}))});const E=[...B].sort((o,t)=>o.startIndex-t.startIndex),i=[];let C=0,a=0;for(let o=0;o<E.length-1;o++){const t=E[o],s=E[o+1];if(!I.some(h=>h.cluster1===t&&h.cluster2===s||h.cluster1===s&&h.cluster2===t))continue;const n=this.findCrossingPoint(t,s,A);if(n.hasBoundary){const h=n.boundaryTimestamp;A[t.endIndex],i.push({start:C,end:h,bpm:Math.round(t.bpm*10)/10,intervalSeconds:t.avgInterval,beatCount:t.beatCount,startBeatIndex:a,endBeatIndex:t.endIndex}),C=h,a=s.startIndex}}const e=E[E.length-1];return i.push({start:C,end:g,bpm:Math.round(e.bpm*10)/10,intervalSeconds:e.avgInterval,beatCount:e.beatCount,startBeatIndex:a,endBeatIndex:e.endIndex}),i.length===1?(VA.debug("Multi-tempo analysis found no section boundaries"),null):{tempoSections:i}}reinterpolateBoundaryRegions(A,B,g,I){return VA.debug("Boundary regions identified",{sectionCount:B.length,sections:B.map(E=>({bpm:E.bpm,start:E.start.toFixed(2),end:E.end.toFixed(2)}))}),A}canApplyMultiTempo(A){const{interpolationMetadata:B}=A,g=B.hasMultipleTempos,I=B.hasMultiTempoApplied===!0;return g&&!I}calculateWeightedIntervals(A,B){const g=[];for(let I=1;I<A.length;I++){const E=A[I].timestamp-A[I-1].timestamp,i=B.some(t=>I>t.startIndex&&I<=t.endIndex),C=(A[I-1].confidence+A[I].confidence)/2,a=i?2:1;let e=1;if(I>1){const t=A[I-1].timestamp-A[I-2].timestamp;e=.5+Math.min(E,t)/Math.max(E,t)*.5}if(I<A.length-1){const t=A[I+1].timestamp-A[I].timestamp,s=Math.min(E,t)/Math.max(E,t);e=Math.max(e,.5+s*.5)}const o=C*a*e;g.push({intervalSeconds:E,weight:o,beatIndex:I,isFromDenseSection:i,confidence:C})}return g}buildIntervalHistogram(A){const B=new Map,g=.005;for(const I of A){const E=Math.round(I.intervalSeconds/g)*g,i=B.get(E)||0;B.set(E,i+I.weight)}return B}findHistogramPeaks(A){const B=[],g=Array.from(A.entries()).sort((i,C)=>i[0]-C[0]);for(let i=0;i<g.length;i++){const[C,a]=g[i],e=i>0?g[i-1][1]:0,o=i<g.length-1?g[i+1][1]:0;a>=e&&a>=o&&a>0&&B.push({interval:C,weight:a})}B.sort((i,C)=>C.weight-i.weight);const I=.05,E=[];for(const i of B)E.some(a=>Math.abs(a.interval-i.interval)<I)||E.push(i);return E}calculateAverageInterval(A){if(A.length<2)return .5;let B=0;for(let g=1;g<A.length;g++)B+=A[g].timestamp-A[g-1].timestamp;return B/(A.length-1)}analyzeGaps(A,B){const g=this.options.anomalyThreshold;let I=0;const E=[],i=[];let C=0;for(let t=1;t<A.length;t++){const G=(A[t].timestamp-A[t-1].timestamp)/B,n=Math.round(G);n>1&&i.push(n),Math.abs(G-2)<.2&&I++,(G<1-g||G>1+g&&Math.abs(G-2)>.3&&Math.abs(G-3)>.3&&Math.abs(G-4)>.3)&&E.push(t);const c=Math.round(A[t].timestamp/B)*B,d=Math.abs(A[t].timestamp-c)/B;C+=d}const a=A.length>1?C/(A.length-1):0,e=Math.max(0,1-a*2),o=i.length>0?i.reduce((t,s)=>t+s,0)/i.length:1;return{totalGaps:i.length,halfNoteGaps:I,anomalies:E,avgGapSize:o,gridAlignmentScore:e}}generateGrid(A,B,g){return this.interpolateAdaptivePhaseLocked(A,B)}interpolateForwardsWithDrift(A,B,g,I,E){const i=[];let C=B,a=A;const e=g.filter(n=>!(n.timestamp<=A||E!==void 0&&n.timestamp>E));let o=A;for(let n=0;n<e.length;n++){const h=e[n],c=h.timestamp-o,d=Math.round(c/C);if(d>0){const l=a+d*C,m=(h.timestamp-l)/d*I;C=C+m;for(let y=1;y<d;y++){const p=o+y*C;if(E!==void 0&&p>E)break;i.push(p)}}a=h.timestamp,o=h.timestamp}const t=i.length,s=A+t*B,G=i.length>0?i[i.length-1]-s:0;return{finalInterval:C,beatPositions:i,phaseError:G,endTimestamp:o}}interpolateBackwardsWithDrift(A,B,g,I,E){const i=[];let C=B,a=A;const e=g.filter(n=>!(n.timestamp>=A||E!==void 0&&n.timestamp<E)).reverse();let o=A;for(let n=0;n<e.length;n++){const h=e[n],c=o-h.timestamp,d=Math.round(c/C);if(d>0){const l=a-d*C,m=(h.timestamp-l)/d*I;C=C+m;for(let y=1;y<d;y++){const p=o-y*C;if(E!==void 0&&p<E)break;i.unshift(p)}}a=h.timestamp,o=h.timestamp}const t=i.length,s=A-t*B,G=i.length>0?i[0]-s:0;return{finalInterval:C,beatPositions:i,phaseError:G,endTimestamp:o}}interpolateAdaptivePhaseLocked(A,B){const{beats:g,duration:I}=A,E=B.intervalSeconds,i=this.options.gridSnapTolerance,C=this.options.tempoAdaptationRate,a=[],e=this.options.gridAlignmentWeight,o=this.options.anchorConfidenceWeight,t=this.options.paceConfidenceWeight;if(this.options.extrapolateStart){let n=g[0].timestamp-E;for(;n>=0;){const h=this.calculateConfidence(e,o*.5,t*B.confidence);a.unshift(this.createInterpolatedBeat(n,h,g[0].timestamp)),n-=E}}let s=g[0].timestamp,G=E;for(let n=0;n<g.length;n++){const h=g[n];if(a.push(this.createDetectedBeat(h)),n<g.length-1){const c=g[n+1],d=c.timestamp-h.timestamp,l=Math.round(d/G),F=s+l*G,m=c.timestamp-F;if(l>0){const y=m/l*C;G=G+y}for(let y=1;y<l;y++){const p=h.timestamp+y*G;if(p>=c.timestamp-i)break;const k=this.calculateConfidence(e,o*((h.confidence+c.confidence)/2),t*B.confidence);a.push(this.createInterpolatedBeat(p,k,h.timestamp))}s=c.timestamp}}if(this.options.extrapolateEnd){let n=g[g.length-1].timestamp+G;for(;n<=I;){const h=this.calculateConfidence(e,o*.5,t*B.confidence);a.push(this.createInterpolatedBeat(n,h,g[g.length-1].timestamp)),n+=G}}return a.sort((n,h)=>n.timestamp-h.timestamp),a}mergeBeats(A,B,g){const I=this.options.gridSnapTolerance,E=[],i=[...B].sort((o,t)=>o.timestamp-t.timestamp),C=[...A].sort((o,t)=>o.timestamp-t.timestamp),a=new Map;for(const o of C)a.set(o.timestamp,o);const e=new Set;for(const o of i){const t=this.findBeatNear(a,o.timestamp,I);t&&!e.has(t.timestamp)?(E.push(this.createDetectedBeat(t)),e.add(t.timestamp)):o.source==="interpolated"&&E.push(o)}for(const o of C)e.has(o.timestamp)||E.push(this.createDetectedBeat(o));return E.sort((o,t)=>o.timestamp-t.timestamp),this.reassignBeatPositions(E,g),E}reassignBeatPositions(A,B){const g=B??GI,I=this.computeMeasureOffsets(g.segments,A.length);for(let E=0;E<A.length;E++){const i=A[E],C=this.findActiveSegmentIndex(g.segments,E),a=g.segments[C],{downbeatBeatIndex:e,timeSignature:o}=a,{beatsPerMeasure:t}=o,s=E-e,G=(s%t+t)%t,n=G===0,c=Math.max(0,Math.floor(s/t))+I[C];i.beatInMeasure=G,i.isDownbeat=n,i.measureNumber=c}}computeMeasureOffsets(A,B){const g=[];for(let I=0;I<A.length;I++)if(I===0)g.push(0);else{const E=A[I-1],{downbeatBeatIndex:i,timeSignature:C}=E,a=C.beatsPerMeasure,o=A[I].startBeat-1-i,t=Math.max(0,Math.floor(o/a));g.push(t+1)}return g}findActiveSegmentIndex(A,B){let g=0;for(let I=0;I<A.length&&A[I].startBeat<=B;I++)g=I;return g}calculateConfidence(A,B,g){return Math.max(0,Math.min(1,A+B+g))}createInterpolatedBeat(A,B,g){return{timestamp:A,beatInMeasure:0,isDownbeat:!1,measureNumber:0,intensity:.5,confidence:B,source:"interpolated",distanceToAnchor:Math.abs(A-g),nearestAnchorTimestamp:g}}createDetectedBeat(A){return{...A,source:"detected",distanceToAnchor:0,nearestAnchorTimestamp:A.timestamp}}findBeatNear(A,B,g){const I=A.get(B);if(I)return I;for(const[E,i]of A)if(Math.abs(E-B)<=g)return i;return null}findNearestAnchor(A,B,g){const I=g>=0&&g<A.length?A[g]:A[0],E=g+1<A.length?A[g+1]:A[A.length-1],i=Math.abs(B-I.timestamp),C=Math.abs(B-E.timestamp);return i<=C?{timestamp:I.timestamp,confidence:I.confidence}:{timestamp:E.timestamp,confidence:E.confidence}}calculateTempoDriftRatio(A){if(A.length<3)return 1;const B=[];for(let E=1;E<A.length;E++){const i=A[E].timestamp-A[E-1].timestamp;i>0&&B.push(60/i)}if(B.length===0)return 1;const g=Math.min(...B),I=Math.max(...B);return g>0?I/g:1}createEmptyInterpolatedBeatMap(A){return{audioId:A.audioId,duration:A.duration,detectedBeats:[],mergedBeats:[],quarterNoteInterval:.5,quarterNoteBpm:120,quarterNoteConfidence:0,originalMetadata:A.metadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:.5,bpm:120,confidence:0,histogramPeak:0,secondaryPeaks:[],method:"tempo-detector-fallback",denseSectionCount:0,denseSectionBeats:0},gapAnalysis:{totalGaps:0,halfNoteGaps:0,anomalies:[],avgGapSize:1,gridAlignmentScore:0},detectedBeatCount:0,interpolatedBeatCount:0,totalBeatCount:0,interpolationRatio:0,avgInterpolatedConfidence:0,tempoDriftRatio:1,detectedClusterTempos:void 0,hasMultipleTempos:!1,tempoSections:void 0,hasMultiTempoApplied:void 0},downbeatConfig:A.downbeatConfig}}createSingleBeatInterpolatedBeatMap(A){const B=A.beats[0],g={...B,source:"detected",distanceToAnchor:0,nearestAnchorTimestamp:B.timestamp};return{audioId:A.audioId,duration:A.duration,detectedBeats:[B],mergedBeats:[g],quarterNoteInterval:.5,quarterNoteBpm:120,quarterNoteConfidence:.1,originalMetadata:A.metadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:.5,bpm:120,confidence:.1,histogramPeak:0,secondaryPeaks:[],method:"tempo-detector-fallback",denseSectionCount:0,denseSectionBeats:0},gapAnalysis:{totalGaps:0,halfNoteGaps:0,anomalies:[],avgGapSize:1,gridAlignmentScore:0},detectedBeatCount:1,interpolatedBeatCount:0,totalBeatCount:1,interpolationRatio:0,avgInterpolatedConfidence:0,tempoDriftRatio:1,detectedClusterTempos:void 0,hasMultipleTempos:!1,tempoSections:void 0,hasMultiTempoApplied:void 0},downbeatConfig:A.downbeatConfig}}static toJSON(A){const B={audioId:A.audioId,duration:A.duration,detectedBeats:A.detectedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),mergedBeats:A.mergedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey},source:g.source,distanceToAnchor:g.distanceToAnchor,nearestAnchorTimestamp:g.nearestAnchorTimestamp})),quarterNoteInterval:A.quarterNoteInterval,quarterNoteBpm:A.quarterNoteBpm,quarterNoteConfidence:A.quarterNoteConfidence,originalMetadata:A.originalMetadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:A.interpolationMetadata.quarterNoteDetection.intervalSeconds,bpm:A.interpolationMetadata.quarterNoteDetection.bpm,confidence:A.interpolationMetadata.quarterNoteDetection.confidence,histogramPeak:A.interpolationMetadata.quarterNoteDetection.histogramPeak,secondaryPeaks:A.interpolationMetadata.quarterNoteDetection.secondaryPeaks,method:A.interpolationMetadata.quarterNoteDetection.method,denseSectionCount:A.interpolationMetadata.quarterNoteDetection.denseSectionCount,denseSectionBeats:A.interpolationMetadata.quarterNoteDetection.denseSectionBeats},gapAnalysis:{totalGaps:A.interpolationMetadata.gapAnalysis.totalGaps,halfNoteGaps:A.interpolationMetadata.gapAnalysis.halfNoteGaps,anomalies:A.interpolationMetadata.gapAnalysis.anomalies,avgGapSize:A.interpolationMetadata.gapAnalysis.avgGapSize,gridAlignmentScore:A.interpolationMetadata.gapAnalysis.gridAlignmentScore},detectedBeatCount:A.interpolationMetadata.detectedBeatCount,interpolatedBeatCount:A.interpolationMetadata.interpolatedBeatCount,totalBeatCount:A.interpolationMetadata.totalBeatCount,interpolationRatio:A.interpolationMetadata.interpolationRatio,avgInterpolatedConfidence:A.interpolationMetadata.avgInterpolatedConfidence,tempoDriftRatio:A.interpolationMetadata.tempoDriftRatio,detectedClusterTempos:A.interpolationMetadata.detectedClusterTempos,hasMultipleTempos:A.interpolationMetadata.hasMultipleTempos,tempoSections:A.interpolationMetadata.tempoSections?.map(g=>({start:g.start,end:g.end,bpm:g.bpm,intervalSeconds:g.intervalSeconds,beatCount:g.beatCount,startBeatIndex:g.startBeatIndex,endBeatIndex:g.endBeatIndex})),hasMultiTempoApplied:A.interpolationMetadata.hasMultiTempoApplied},downbeatConfig:A.downbeatConfig};return JSON.stringify(B,null,2)}static fromJSON(A){const B=JSON.parse(A);return{audioId:B.audioId,duration:B.duration,detectedBeats:B.detectedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),mergedBeats:B.mergedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey},source:g.source,distanceToAnchor:g.distanceToAnchor,nearestAnchorTimestamp:g.nearestAnchorTimestamp})),quarterNoteInterval:B.quarterNoteInterval,quarterNoteBpm:B.quarterNoteBpm,quarterNoteConfidence:B.quarterNoteConfidence,originalMetadata:B.originalMetadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:B.interpolationMetadata.quarterNoteDetection.intervalSeconds,bpm:B.interpolationMetadata.quarterNoteDetection.bpm,confidence:B.interpolationMetadata.quarterNoteDetection.confidence,histogramPeak:B.interpolationMetadata.quarterNoteDetection.histogramPeak,secondaryPeaks:B.interpolationMetadata.quarterNoteDetection.secondaryPeaks,method:B.interpolationMetadata.quarterNoteDetection.method,denseSectionCount:B.interpolationMetadata.quarterNoteDetection.denseSectionCount,denseSectionBeats:B.interpolationMetadata.quarterNoteDetection.denseSectionBeats},gapAnalysis:{totalGaps:B.interpolationMetadata.gapAnalysis.totalGaps,halfNoteGaps:B.interpolationMetadata.gapAnalysis.halfNoteGaps,anomalies:B.interpolationMetadata.gapAnalysis.anomalies,avgGapSize:B.interpolationMetadata.gapAnalysis.avgGapSize,gridAlignmentScore:B.interpolationMetadata.gapAnalysis.gridAlignmentScore},detectedBeatCount:B.interpolationMetadata.detectedBeatCount,interpolatedBeatCount:B.interpolationMetadata.interpolatedBeatCount,totalBeatCount:B.interpolationMetadata.totalBeatCount,interpolationRatio:B.interpolationMetadata.interpolationRatio,avgInterpolatedConfidence:B.interpolationMetadata.avgInterpolatedConfidence,tempoDriftRatio:B.interpolationMetadata.tempoDriftRatio,detectedClusterTempos:B.interpolationMetadata.detectedClusterTempos,hasMultipleTempos:B.interpolationMetadata.hasMultipleTempos??!1,tempoSections:B.interpolationMetadata.tempoSections?.map(g=>({start:g.start,end:g.end,bpm:g.bpm,intervalSeconds:g.intervalSeconds,beatCount:g.beatCount,startBeatIndex:g.startBeatIndex,endBeatIndex:g.endBeatIndex})),hasMultiTempoApplied:B.interpolationMetadata.hasMultiTempoApplied},downbeatConfig:B.downbeatConfig}}static async saveToFile(A,B){if(typeof process>"u"||!process.versions?.node)throw new Error("saveToFile is only available in Node.js environment");const{writeFile:g}=await Promise.resolve().then(()=>GB),I=Pg.toJSON(A);await g(B,I,"utf-8")}static async loadFromFile(A){if(typeof process>"u"||!process.versions?.node)throw new Error("loadFromFile is only available in Node.js environment");const{readFile:B}=await Promise.resolve().then(()=>GB),g=await B(A,"utf-8");return Pg.fromJSON(g)}}function nQ(Q){const{mergedBeats:A,interpolationMetadata:B,downbeatConfig:g}=Q,I=[],E=[];for(let C=0;C<A.length;C++){const a=A[C],e={timestamp:a.timestamp,beatInMeasure:a.beatInMeasure,isDownbeat:a.isDownbeat,measureNumber:a.measureNumber,intensity:a.intensity,confidence:a.confidence};I.push(e),a.source==="detected"&&E.push(C)}return{audioId:Q.audioId,duration:Q.duration,beats:I,detectedBeatIndices:E,quarterNoteInterval:Q.quarterNoteInterval,quarterNoteBpm:Q.quarterNoteBpm,downbeatConfig:g??GI,tempoSections:B.tempoSections,originalMetadata:Q.originalMetadata}}const oI=class oI{constructor(A={}){this.options={includeAdvancedMetrics:!1,sampleRate:44100,fftSize:2048,trebleBoost:1,bassBoost:1,midBoost:1,...A}}async extractSonicFingerprint(A){const B=await this.fetchAndDecode(A),g=B.duration,I=g<3;let E;I?E=[0]:E=[.05,.4,.7];const i=[];for(const d of E){const l=await this.analyzeAtPosition(B,d,I);i.push(l)}const C=this.averageFrequencyBands(i);let a=MB.calculateDominance(C.bass,380),e=MB.calculateDominance(C.mid,3600),o=MB.calculateDominance(C.treble,1e4);a=a*this.options.bassBoost,e=e*this.options.midBoost,o=o*this.options.trebleBoost;const t=a+e+o;a=a/(t||1),e=e/(t||1),o=o/(t||1);const s=this.calculateAverageAmplitude(B),G=this.calculateRMS(B),h=this.calculatePeak(B)-G,c={bass_dominance:a,mid_dominance:e,treble_dominance:o,average_amplitude:s,analysis_metadata:{duration_analyzed:I?g:g*E.length*.01,full_buffer_analyzed:I,sample_positions:E,analyzed_at:new Date().toISOString()},rms_energy:G,dynamic_range:h};if(this.options.includeAdvancedMetrics){const d=[...C.bass,...C.mid,...C.treble];c.spectral_centroid=this.calculateSpectralCentroid(d),c.spectral_rolloff=this.calculateSpectralRolloff(d),c.zero_crossing_rate=this.calculateZeroCrossingRate(B)}return c}async analyzeTimeline(A,B){const g=await this.fetchAndDecode(A),I=g.duration,E=g.sampleRate,i=[];let C=[];if(B.type==="interval"){const a=B.intervalSeconds;for(let e=0;e<I;e+=a)C.push(e)}else{const a=B.count,e=I/a;for(let o=0;o<a;o++)C.push(o*e)}for(const a of C){const e=I-a,o=Math.min(1,e);if(o<=0)break;const t=Math.floor(a*E),s=Math.floor((a+o)*E),G=this.extractAudioSegment(g,t,s),n=this.performFFT(G,this.options.fftSize),h=new Uint8Array(n.length);for(let H=0;H<n.length;H++)h[H]=Math.min(255,Math.round(n[H]));const c=MB.separateFrequencyBands(h,E),d=MB.calculateDominance(c.bass)*this.options.bassBoost,l=MB.calculateDominance(c.mid)*this.options.midBoost,F=MB.calculateDominance(c.treble)*this.options.trebleBoost,m=d+l+F||1,y=this.calculateRMS(g,t,s),p=this.calculatePeak(g,t,s),k=[...c.bass,...c.mid,...c.treble];i.push({timestamp:a,duration:o,bass:d/m,mid:l/m,treble:F/m,amplitude:y,rms_energy:y,peak:p,dynamic_range:p-y,spectral_centroid:this.calculateSpectralCentroid(k),spectral_rolloff:this.calculateSpectralRolloff(k),zero_crossing_rate:this.calculateZeroCrossingRate(g,t,s)})}return i}async generateBeatMap(A,B,g,I,E){return new tB(g).generateBeatMap(A,B,I,E)}async generateBeatMapFromBuffer(A,B,g,I,E){return new tB(g).generateBeatMapFromBuffer(A,B,I,E)}createBeatStream(A,B,g){return new Ut(A,B,g)}interpolateBeatMap(A,B){return new Pg(B).interpolate(A)}async generateBeatMapWithInterpolation(A,B,g,I,E,i){const C=await this.generateBeatMap(A,B,g,I,i);return this.interpolateBeatMap(C,E)}async generateBeatMapWithInterpolationFromBuffer(A,B,g,I,E,i){const C=await this.generateBeatMapFromBuffer(A,B,g,I,i);return this.interpolateBeatMap(C,E)}static beatMapToJSON(A){return tB.toJSON(A)}static beatMapFromJSON(A){return tB.fromJSON(A)}static async saveBeatMapToFile(A,B){return tB.saveToFile(A,B)}static async loadBeatMapFromFile(A){return tB.loadFromFile(A)}async generateRhythm(A,B,g,I,E,i){const C=await this.fetchAndDecode(A);return this.generateRhythmFromBuffer(C,B,g,I,E,i)}async generateRhythmFromBuffer(A,B,g,I,E,i){const C=await this.generateBeatMapWithInterpolationFromBuffer(A,B,I,E),a=nQ(C);return new HB(g).generate(A,a,void 0,i)}async generateRhythmLevel(A,B,g,I,E,i){const C=await this.fetchAndDecode(A);return this.generateRhythmLevelFromBuffer(C,B,g,I,E,i)}async generateRhythmLevelFromBuffer(A,B,g,I,E,i){const C=await this.generateBeatMapWithInterpolationFromBuffer(A,B,I,E),a=nQ(C);return new tQ(g).generate(A,a,i)}async generateRhythmLevelWithPreset(A,B,g,I,E,i){const C=oI.LEVEL_PRESETS[g],a={difficulty:C.difficulty,controllerMode:C.controllerMode,buttons:C.buttons};return this.generateRhythmLevel(A,B,a,I,E,i)}async generateRhythmLevelWithPresetFromBuffer(A,B,g,I,E,i){const C=oI.LEVEL_PRESETS[g],a={difficulty:C.difficulty,controllerMode:C.controllerMode,buttons:C.buttons};return this.generateRhythmLevelFromBuffer(A,B,a,I,E,i)}async fetchAndDecodeAudio(A){return this.fetchAndDecode(A)}async fetchAndDecode(A){const B=await fetch(A);if(!B.ok)throw new Error(`Failed to fetch audio: ${B.statusText}`);const g=await B.arrayBuffer(),I=globalThis.AudioContext||window?.AudioContext;if(!I)throw new Error("AudioContext not available in this environment");const E=new I;return await new Promise((i,C)=>{E.decodeAudioData(g,i,C)})}async analyzeAtPosition(A,B,g){const I=A.sampleRate,E=A.duration;let i,C;if(g)i=0,C=A.length;else{const t=E*B;i=Math.floor(t*I),C=Math.min(i+I,A.length)}const a=this.extractAudioSegment(A,i,C),e=this.performFFT(a,this.options.fftSize),o=new Uint8Array(e.length);for(let t=0;t<e.length;t++)o[t]=Math.min(255,Math.round(e[t]));return MB.separateFrequencyBands(o,I)}extractAudioSegment(A,B,g){const I=g-B,E=new Float32Array(I);for(let i=0;i<A.numberOfChannels;i++){const C=A.getChannelData(i);for(let a=0;a<I;a++)E[a]+=C[B+a]/A.numberOfChannels}return E}performFFT(A,B){const g=Math.pow(2,Math.ceil(Math.log2(Math.min(A.length,B)))),I=new Float32Array(g),E=new Float32Array(g);for(let e=0;e<Math.min(A.length,g);e++){const o=.5-.5*Math.cos(2*Math.PI*e/(g-1));I[e]=A[e]*o}this.fft(I,E);const i=new Array(g/2);for(let e=0;e<i.length;e++){const o=I[e],t=E[e];i[e]=Math.sqrt(o*o+t*t)}const C=Math.max(...i);return i.map(e=>{const o=e/(C||1);return 20*Math.log10(Math.max(o,.001))})}fft(A,B){const g=A.length;if(g<=1)return;let I=0;for(let i=0;i<g-1;i++){i<I&&([A[i],A[I]]=[A[I],A[i]],[B[i],B[I]]=[B[I],B[i]]);let C=g/2;for(;I>=C;)I-=C,C/=2;I+=C}let E=2;for(;E<=g;){const i=-2*Math.PI/E,C=Math.cos(i),a=Math.sin(i);for(let e=0;e<g;e+=E){let o=1,t=0;for(let s=0;s<E/2;s++){const G=e+s,n=e+s+E/2,h=A[n]*o-B[n]*t,c=A[n]*t+B[n]*o;A[n]=A[G]-h,B[n]=B[G]-c,A[G]+=h,B[G]+=c;const d=o*C-t*a;t=o*a+t*C,o=d}}E*=2}}averageFrequencyBands(A){if(A.length===0)return{bass:[],mid:[],treble:[]};if(A.length===1)return A[0];const B=Math.max(...A.map(i=>Math.max(i.bass.length,i.mid.length,i.treble.length))),g=[],I=[],E=[];for(let i=0;i<B;i++){const C=A.map(o=>o.bass[i]||0),a=A.map(o=>o.mid[i]||0),e=A.map(o=>o.treble[i]||0);g.push(C.reduce((o,t)=>o+t,0)/C.length),I.push(a.reduce((o,t)=>o+t,0)/a.length),E.push(e.reduce((o,t)=>o+t,0)/e.length)}return{bass:g,mid:I,treble:E}}calculateAverageAmplitude(A){let B=0,g=0;for(let I=0;I<A.numberOfChannels;I++){const E=A.getChannelData(I);for(let i=0;i<E.length;i++)B+=Math.abs(E[i]),g++}return g>0?B/g:0}calculateRMS(A,B=0,g=A.length){let I=0,E=0;for(let i=0;i<A.numberOfChannels;i++){const C=A.getChannelData(i),a=Math.min(g,C.length);for(let e=B;e<a;e++)I+=C[e]*C[e],E++}return E>0?Math.sqrt(I/E):0}calculatePeak(A,B=0,g=A.length){let I=0;for(let E=0;E<A.numberOfChannels;E++){const i=A.getChannelData(E),C=Math.min(g,i.length);for(let a=B;a<C;a++){const e=Math.abs(i[a]);e>I&&(I=e)}}return I}calculateSpectralCentroid(A){let B=0,g=0;for(let I=0;I<A.length;I++)B+=I*A[I],g+=A[I];return g>0?B/g:0}calculateSpectralRolloff(A){const g=A.reduce((E,i)=>E+i,0)*.85;let I=0;for(let E=0;E<A.length;E++)if(I+=A[E],I>=g)return E/A.length;return 1}calculateZeroCrossingRate(A,B=0,g=A.length){let I=0,E=0;for(let i=0;i<A.numberOfChannels;i++){const C=A.getChannelData(i),a=Math.min(g,C.length),e=Math.max(0,B);for(let o=Math.max(1,e+1);o<a;o++)(C[o-1]>=0&&C[o]<0||C[o-1]<0&&C[o]>=0)&&I++,E++}return E>0?I/E:0}};oI.LEVEL_PRESETS={casual:{difficulty:"easy",controllerMode:"ddr",buttons:{pitchInfluenceWeight:.3},description:"Easy difficulty, low pitch influence - for relaxed gameplay"},standard:{difficulty:"medium",controllerMode:"ddr",buttons:{pitchInfluenceWeight:.7},description:"Medium difficulty, high pitch influence - balanced experience"},challenge:{difficulty:"hard",controllerMode:"ddr",buttons:{pitchInfluenceWeight:1},description:"Hard difficulty, full pitch influence - for skilled players"},insane:{difficulty:"hard",controllerMode:"ddr",buttons:{pitchInfluenceWeight:1,consecutiveSameKeyLimit:4},description:"Maximum difficulty with strict limits - for experts only"}};let Ni=oI;const xF=["60s","70s","80s","90s","acidjazz","alternative","alternativerock","ambient","atmospheric","blues","bluesrock","bossanova","bossa","celtic","chanson","chillout","choir","classical","classicrock","club","comedy","country","cuban","dance","darkambient","darkwave","deephouse","disco","downtempo","drumandbass","dub","dubstep","easylistening","edm","electronic","electro","electropop","ethno","eurodance","experimental","folk","funk","fusion","groove","grunge","hardcore","hardrock","hiphop","house","indie","indiepop","indierock","industrial","instrumentalpop","instrumentalrock","jazz","jazzfusion","latin","lounge","metal","minimal","newage"," orchestrall","pop","popfolk","poprock","postrock","progressive","psychedelic","punkrock","rap","reggae","rnb","rock","rockandroll","ska","soul","soundtrack","synthpop","techno","trance","trip","triphop","underground","world","worldbeat","worldmusic"],Ji=["Blues---Boogie Woogie","Blues---Chicago Blues","Blues---Country Blues","Blues---Delta Blues","Blues---Electric Blues","Blues---Harmonica Blues","Blues---Jump Blues","Blues---Louisiana Blues","Blues---Modern Electric Blues","Blues---Piano Blues","Blues---Rhythm & Blues","Blues---Texas Blues","Brass & Military---Brass Band","Brass & Military---Marches","Brass & Military---Military","Children's-- - Educational","Children's-- - Nursery Rhymes","Children's-- - Story","Classical---Baroque","Classical---Choral","Classical---Classical","Classical---Contemporary","Classical---Impressionist","Classical---Medieval","Classical---Modern","Classical---Neo-Classical","Classical---Neo-Romantic","Classical---Opera","Classical---Post-Modern","Classical---Renaissance","Classical---Romantic","Electronic---Abstract","Electronic---Acid","Electronic---Acid House","Electronic---Acid Jazz","Electronic---Ambient","Electronic---Bassline","Electronic---Beatdown","Electronic---Berlin-School","Electronic---Big Beat","Electronic---Bleep","Electronic---Breakbeat","Electronic---Breakcore","Electronic---Breaks","Electronic---Broken Beat","Electronic---Chillwave","Electronic---Chiptune","Electronic---Dance-pop","Electronic---Dark Ambient","Electronic---Darkwave","Electronic---Deep House","Electronic---Deep Techno","Electronic---Disco","Electronic---Disco Polo","Electronic---Donk","Electronic---Downtempo","Electronic---Drone","Electronic---Drum n Bass","Electronic---Dub","Electronic---Dub Techno","Electronic---Dubstep","Electronic---Dungeon Synth","Electronic---EBM","Electronic---Electro","Electronic---Electro House","Electronic---Electroclash","Electronic---Euro House","Electronic---Euro-Disco","Electronic---Eurobeat","Electronic---Eurodance","Electronic---Experimental","Electronic---Freestyle","Electronic---Future Jazz","Electronic---Gabber","Electronic---Garage House","Electronic---Ghetto","Electronic---Ghetto House","Electronic---Glitch","Electronic---Goa Trance","Electronic---Grime","Electronic---Halftime","Electronic---Hands Up","Electronic---Happy Hardcore","Electronic---Hard House","Electronic---Hard Techno","Electronic---Hard Trance","Electronic---Hardcore","Electronic---Hardstyle","Electronic---Hi NRG","Electronic---Hip Hop","Electronic---Hip-House","Electronic---House","Electronic---IDM","Electronic---Illbient","Electronic---Industrial","Electronic---Italo House","Electronic---Italo-Disco","Electronic---Italodance","Electronic---Jazzdance","Electronic---Juke","Electronic---Jumpstyle","Electronic---Jungle","Electronic---Latin","Electronic---Leftfield","Electronic---Makina","Electronic---Minimal","Electronic---Minimal Techno","Electronic---Modern Classical","Electronic---Musique Concrète","Electronic---Neofolk","Electronic---New Age","Electronic---New Beat","Electronic---New Wave","Electronic---Noise","Electronic---Nu-Disco","Electronic---Power Electronics","Electronic---Progressive Breaks","Electronic---Progressive House","Electronic---Progressive Trance","Electronic---Psy-Trance","Electronic---Rhythmic Noise","Electronic---Schranz","Electronic---Sound Collage","Electronic---Speed Garage","Electronic---Speedcore","Electronic---Synth-pop","Electronic---Synthwave","Electronic---Tech House","Electronic---Tech Trance","Electronic---Techno","Electronic---Trance","Electronic---Tribal","Electronic---Tribal House","Electronic---Trip Hop","Electronic---Tropical House","Electronic---UK Garage","Electronic---Vaporwave","Folk, World, & Country---African","Folk, World, & Country---Bluegrass","Folk, World, & Country---Cajun","Folk, World, & Country---Canzone Napoletana","Folk, World, & Country---Catalan Music","Folk, World, & Country---Celtic","Folk, World, & Country---Country","Folk, World, & Country---Fado","Folk, World, & Country---Flamenco","Folk, World, & Country---Folk","Folk, World, & Country---Gospel","Folk, World, & Country---Highlife","Folk, World, & Country---Hillbilly","Folk, World, & Country---Hindustani","Folk, World, & Country---Honky Tonk","Folk, World, & Country---Indian Classical","Folk, World, & Country---Laïkó","Folk, World, & Country---Nordic","Folk, World, & Country---Pacific","Folk, World, & Country---Polka","Folk, World, & Country---Raï","Folk, World, & Country---Romani","Folk, World, & Country---Soukous","Folk, World, & Country---Séga","Folk, World, & Country---Volksmusik","Folk, World, & Country---Zouk","Folk, World, & Country---Éntekhno","Funk / Soul---Afrobeat","Funk / Soul---Boogie","Funk / Soul---Contemporary R&B","Funk / Soul---Disco","Funk / Soul---Free Funk","Funk / Soul---Funk","Funk / Soul---Gospel","Funk / Soul---Neo Soul","Funk / Soul---New Jack Swing","Funk / Soul---P.Funk","Funk / Soul---Psychedelic","Funk / Soul---Rhythm & Blues","Funk / Soul---Soul","Funk / Soul---Swingbeat","Funk / Soul---UK Street Soul","Hip Hop---Bass Music","Hip Hop---Boom Bap","Hip Hop---Bounce","Hip Hop---Britcore","Hip Hop---Cloud Rap","Hip Hop---Conscious","Hip Hop---Crunk","Hip Hop---Cut-up/DJ","Hip Hop---DJ Battle Tool","Hip Hop---Electro","Hip Hop---G-Funk","Hip Hop---Gangsta","Hip Hop---Grime","Hip Hop---Hardcore Hip-Hop","Hip Hop---Horrorcore","Hip Hop---Instrumental","Hip Hop---Jazzy Hip-Hop","Hip Hop---Miami Bass","Hip Hop---Pop Rap","Hip Hop---Ragga HipHop","Hip Hop---RnB/Swing","Hip Hop---Screw","Hip Hop---Thug Rap","Hip Hop---Trap","Hip Hop---Trip Hop","Hip Hop---Turntablism","Jazz---Afro-Cuban Jazz","Jazz---Afrobeat","Jazz---Avant-garde Jazz","Jazz---Big Band","Jazz---Bop","Jazz---Bossa Nova","Jazz---Contemporary Jazz","Jazz---Cool Jazz","Jazz---Dixieland","Jazz---Easy Listening","Jazz---Free Improvisation","Jazz---Free Jazz","Jazz---Fusion","Jazz---Gypsy Jazz","Jazz---Hard Bop","Jazz---Jazz-Funk","Jazz---Jazz-Rock","Jazz---Latin Jazz","Jazz---Modal","Jazz---Post Bop","Jazz---Ragtime","Jazz---Smooth Jazz","Jazz---Soul-Jazz","Jazz---Space-Age","Jazz---Swing","Latin---Afro-Cuban","Latin---Baião","Latin---Batucada","Latin---Beguine","Latin---Bolero","Latin---Boogaloo","Latin---Bossanova","Latin---Cha-Cha","Latin---Charanga","Latin---Compas","Latin---Cubano","Latin---Cumbia","Latin---Descarga","Latin---Forró","Latin---Guaguancó","Latin---Guajira","Latin---Guaracha","Latin---MPB","Latin---Mambo","Latin---Mariachi","Latin---Merengue","Latin---Norteño","Latin---Nueva Cancion","Latin---Pachanga","Latin---Porro","Latin---Ranchera","Latin---Reggaeton","Latin---Rumba","Latin---Salsa","Latin---Samba","Latin---Son","Latin---Son Montuno","Latin---Tango","Latin---Tejano","Latin---Vallenato","Non-Music---Audiobook","Non-Music---Comedy","Non-Music---Dialogue","Non-Music---Education","Non-Music---Field Recording","Non-Music---Interview","Non-Music---Monolog","Non-Music---Poetry","Non-Music---Political","Non-Music---Promotional","Non-Music---Radioplay","Non-Music---Religious","Non-Music---Spoken Word","Pop---Ballad","Pop---Bollywood","Pop---Bubblegum","Pop---Chanson","Pop---City Pop","Pop---Europop","Pop---Indie Pop","Pop---J-pop","Pop---K-pop","Pop---Kayōkyoku","Pop---Light Music","Pop---Music Hall","Pop---Novelty","Pop---Parody","Pop---Schlager","Pop---Vocal","Reggae---Calypso","Reggae---Dancehall","Reggae---Dub","Reggae---Lovers Rock","Reggae---Ragga","Reggae---Reggae","Reggae---Reggae-Pop","Reggae---Rocksteady","Reggae---Roots Reggae","Reggae---Ska","Reggae---Soca","Rock---AOR","Rock---Acid Rock","Rock---Acoustic","Rock---Alternative Rock","Rock---Arena Rock","Rock---Art Rock","Rock---Atmospheric Black Metal","Rock---Avantgarde","Rock---Beat","Rock---Black Metal","Rock---Blues Rock","Rock---Brit Pop","Rock---Classic Rock","Rock---Coldwave","Rock---Country Rock","Rock---Crust","Rock---Death Metal","Rock---Deathcore","Rock---Deathrock","Rock---Depressive Black Metal","Rock---Doo Wop","Rock---Doom Metal","Rock---Dream Pop","Rock---Emo","Rock---Ethereal","Rock---Experimental","Rock---Folk Metal","Rock---Folk Rock","Rock---Funeral Doom Metal","Rock---Funk Metal","Rock---Garage Rock","Rock---Glam","Rock---Goregrind","Rock---Goth Rock","Rock---Gothic Metal","Rock---Grindcore","Rock---Grunge","Rock---Hard Rock","Rock---Hardcore","Rock---Heavy Metal","Rock---Indie Rock","Rock---Industrial","Rock---Krautrock","Rock---Lo-Fi","Rock---Lounge","Rock---Math Rock","Rock---Melodic Death Metal","Rock---Melodic Hardcore","Rock---Metalcore","Rock---Mod","Rock---Neofolk","Rock---New Wave","Rock---No Wave","Rock---Noise","Rock---Noisecore","Rock---Nu Metal","Rock---Oi","Rock---Parody","Rock---Pop Punk","Rock---Pop Rock","Rock---Pornogrind","Rock---Post Rock","Rock---Post-Hardcore","Rock---Post-Metal","Rock---Post-Punk","Rock---Power Metal","Rock---Power Pop","Rock---Power Violence","Rock---Prog Rock","Rock---Progressive Metal","Rock---Psychedelic Rock","Rock---Psychobilly","Rock---Pub Rock","Rock---Punk","Rock---Rock & Roll","Rock---Rockabilly","Rock---Shoegaze","Rock---Ska","Rock---Sludge Metal","Rock---Soft Rock","Rock---Southern Rock","Rock---Space Rock","Rock---Speed Metal","Rock---Stoner Rock","Rock---Surf","Rock---Symphonic Rock","Rock---Technical Death Metal","Rock---Thrash","Rock---Twist","Rock---Viking Metal","Rock---Yé-Yé","Stage & Screen---Musical","Stage & Screen---Score","Stage & Screen---Soundtrack","Stage & Screen---Theme"],VF=["Blues","Classical","Country","Disco","Hip-Hop","Jazz","Metal","Pop","Reggae","Rock"],LF=["guitar","classical","slow","techno","strings","drums","electronic","rock","fast","piano","ambient","beat","violin","vocal","synth","female","indian","opera","male","singing","vocals","no vocals","harpsichord","loud","quiet","flute","woman","male vocal","no vocal","pop","soft","sitar","solo","man","classic","choir","voice","new age","dance","male voice","female vocal","beats","harp","cello","no voice","weird","country","metal","female voice","choral"],XF=["happy","not happy"],vF=["action","adventure","advertising","background","ballad","calm","children","christmas","commercial","cool","corporate","dark","deep","documentary","drama","dramatic","dream","emotional","energetic","epic","fast","film","fun","funny","game","groovy","happy","heavy","holiday","hopeful","inspiring","love","meditative","melancholic","melodic","motivational","movie","nature","nostalgic","party","peaceful","positive","relaxing","retro","romantic","sad","scary","science","sea","sentimental","serene","sexy","space","sport","summer","suspense","trailer","travel","upbeat","uplifting","video","war"],KF=["danceable","non-danceable"],zF=["voice","instrumental"],TF=["acoustic","electronic"];function _g(Q){return typeof Q=="object"&&Q!==null&&"embedding"in Q&&"classifier"in Q}function qi(Q){return typeof Q=="object"&&Q!==null&&"modelUrl"in Q}function hQ(Q,A){if(A)return A;const B=Q.toLowerCase();return B.includes("effnet")||B.includes("discogs")?"effnet":B.includes("vggish")?"vggish":B.includes("tempocnn")||B.includes("tempo")&&!B.includes("temple")?"tempocnn":"musicnn"}function kt(Q,A){if(A)return A;const B=Q.toLowerCase();return B.includes("jamendo")?"jamendo":B.includes("discogs400")||B.includes("discogs")?"discogs400":B.includes("tzanetakis")?"tzanetakis":B.includes("mtt_musicnn")?"mtt_musicnn":"jamendo"}function Zt(Q){switch(Q){case"jamendo":return xF;case"discogs400":return Ji;case"tzanetakis":return VF;case"mtt_musicnn":return LF;default:return Ji}}function Dg(Q){return _g(Q)?`${Q.embedding} -> ${Q.classifier}`:Q.modelUrl}function Wt(Q){if(!Q||Q.length===0)return[];const A=Q[0].length;if(A===0)return[];const B=[];for(let g=0;g<A;g++){const I=Q.reduce((E,i)=>E+(i[g]??0),0);B.push(I/Q.length)}return B}const cQ={genre:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",classifier:"https://arweave.net/ZY-GSfMe7crJUITAtHITcoLCNfNWVP1HMwywivZ_LAQ/model.json",embeddingType:"effnet",classifierType:"discogs400"},mood:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",classifier:"https://arweave.net/BUXf3AoFuIsrNDkV2hW6BhiwSVTuFllWOUQv5mu6qQ8/model.json",embeddingType:"effnet"},danceability:{modelUrl:"https://turbo-gateway.com/nX9KX1OVhEaT1dStNcsRiZKCQTWuHjAMl4MWprIFyZU/model.json",modelType:"musicnn"}},xi={discogs400:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",embeddingType:"effnet",classifier:"https://arweave.net/ZY-GSfMe7crJUITAtHITcoLCNfNWVP1HMwywivZ_LAQ/model.json",classifierType:"discogs400"},jamendo:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",embeddingType:"effnet",classifier:"https://arweave.net/MuhF5mek1BJPZLoPNY1TBTPUUBEXbVmMfAGgBp-_MyA/model.json",classifierType:"jamendo"},tzanetakis:{modelUrl:"https://arweave.net/7MQD4W5yJeUUK2tRg8TEdomew-ZY7s0K91nk35FxleM/model.json",modelType:"musicnn",genreType:"tzanetakis"},musicnn:{modelUrl:"https://arweave.net/KCZQ1geu4ymxp8axAql95FDY98VjnOzSymdkCiM9BXo/model.json",modelType:"musicnn",genreType:"mtt_musicnn"}},Vi={jamendo:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",embeddingType:"effnet",classifier:"https://arweave.net/BUXf3AoFuIsrNDkV2hW6BhiwSVTuFllWOUQv5mu6qQ8/model.json",classifierType:"jamendo"},happyMusicnn:{modelUrl:"https://arweave.net/kUIS-Xxr4k3MZ4K2gHdvMgZxK7aBYce_FPGIkGA_hjM/model.json",modelType:"musicnn"}},Li={default:{modelUrl:"https://arweave.net/nX9KX1OVhEaT1dStNcsRiZKCQTWuHjAMl4MWprIFyZU/model.json",modelType:"musicnn"}},jF={genre:Object.keys(xi),mood:Object.keys(Vi),danceability:Object.keys(Li)};function OF(Q){const A={};return Q.genre&&(A.genre=xi[Q.genre]),Q.mood&&(A.mood=Vi[Q.mood]),Q.danceability&&(A.danceability=Li[Q.danceability]),A}class PF{constructor(A={}){this.essentiaWASM=null,this.essentiaModel=null,this.extractor=null,this.initialized=!1,this.embeddingModelCache=new Map,this.classifierModelCache=new Map,this.resolvedUrlCache=new Map,this.extractors=new Map;const B=A.preset?OF(A.preset):{};this.options={models:{genre:cQ.genre,mood:cQ.mood,danceability:cQ.danceability,...B,...A.models},topN:5,threshold:.05,cacheEmbeddings:!0,enableModelCache:!0,...A},this.options.resolveUrl||(this.options.resolveUrl=kA.resolveUrl.bind(kA)),delete this.options.preset}async initializeEssentia(){if(this.initialized)return;const B=(await Promise.resolve().then(()=>xs)).EssentiaWASM;await B.ready,this.essentiaWASM=B;const g=await Promise.resolve().then(()=>nR);this.essentiaModel=g,this.extractor=new g.EssentiaTFInputExtractor(this.essentiaWASM,"musicnn");const I=new g.EssentiaTFInputExtractor(this.essentiaWASM,"vggish");this.extractors.set("vggish",I),this.initialized=!0}async resolveUrlWithCache(A){if(this.resolvedUrlCache.has(A))return this.resolvedUrlCache.get(A);if(!this.options.resolveUrl)return A;try{const B=await this.options.resolveUrl(A);return this.resolvedUrlCache.set(A,B),B!==A&&console.info("[MusicClassifier] Resolved URL to alternate gateway",{originalUrl:A,resolvedUrl:B}),B}catch(B){return console.warn("[MusicClassifier] URL resolution failed, using original URL",{url:A,error:String(B)}),this.resolvedUrlCache.set(A,A),A}}async loadModelWithRetry(A,B=3,g=1e3){if(this.options.enableModelCache)return lg.getOrFetchGraphModel(JA,A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:i=>kA.invalidateCacheForUrl(i),maxRetries:B,baseDelayMs:g});let I=null;const E=await this.resolveUrlWithCache(A);for(let i=0;i<B;i++)try{return await JA.loadGraphModel(E)}catch(C){I=C;const a=String(C).toLowerCase();if(!(a.includes("fetch")||a.includes("network")||a.includes("timeout")||a.includes("failed to fetch")||a.includes("networkerror"))||i===B-1)throw C;const o=g*Math.pow(2,i);console.warn(`[MusicClassifier] Model load failed (attempt ${i+1}/${B}), retrying in ${o}ms...`,{originalUrl:A,resolvedUrl:E,error:String(C)}),await new Promise(t=>setTimeout(t,o))}throw I}async initializeEssentiaModelWithRetry(A,B,g=3,I=1e3){for(let E=0;E<g;E++){const i=await this.resolveUrlWithCache(A),C=B==="vggish"?this.essentiaModel.TensorflowVGGish:this.essentiaModel.TensorflowMusiCNN,a=new C(JA,i);try{return await a.initialize(),a}catch(e){const o=String(e).toLowerCase();if(!(o.includes("fetch")||o.includes("network")||o.includes("timeout")||o.includes("failed to fetch")||o.includes("networkerror")||o.includes("404"))||E===g-1)throw e;kA.invalidateCacheForUrl(A);const s=I*Math.pow(2,E);console.warn(`[MusicClassifier] Essentia model init failed (attempt ${E+1}/${g}), re-resolving and retrying in ${s}ms...`,{url:A,resolvedUrl:i,error:String(e)}),await new Promise(G=>setTimeout(G,s))}}throw new Error(`[MusicClassifier] Essentia model failed to load after ${g} retries: ${A}`)}async getEmbeddingModel(A,B){if(this.embeddingModelCache.has(A))return this.embeddingModelCache.get(A);const g=hQ(A,B);let I;if(g==="effnet")I=await this.loadModelWithRetry(A);else if(g==="vggish"){if(!this.essentiaModel)throw new Error("Essentia not initialized. Call initializeEssentia() first.");if(this.options.enableModelCache){const E=await lg.getOrFetchEssentiaUrl(A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:i=>kA.invalidateCacheForUrl(i)});I=new this.essentiaModel.TensorflowVGGish(JA,E),await I.initialize()}else I=await this.initializeEssentiaModelWithRetry(A,"vggish")}else{if(!this.essentiaModel)throw new Error("Essentia not initialized. Call initializeEssentia() first.");if(this.options.enableModelCache){const E=await lg.getOrFetchEssentiaUrl(A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:i=>kA.invalidateCacheForUrl(i)});I=new this.essentiaModel.TensorflowMusiCNN(JA,E),await I.initialize()}else I=await this.initializeEssentiaModelWithRetry(A,"musicnn")}return this.options.cacheEmbeddings&&this.embeddingModelCache.set(A,I),I}clearEmbeddingCache(){for(const A of this.embeddingModelCache.values())A.dispose?A.dispose():A.terminate&&A.terminate();this.embeddingModelCache.clear()}clearClassifierCache(){for(const A of this.classifierModelCache.values())A.terminate&&A.terminate();this.classifierModelCache.clear()}clearAllCaches(){this.clearEmbeddingCache(),this.clearClassifierCache(),this.resolvedUrlCache.clear()}createEssentiaWrapper(){const A=new this.essentiaWASM.EssentiaJS(!1);return{Windowing:A.Windowing.bind(A),Spectrum:A.Spectrum.bind(A),MelBands:A.MelBands.bind(A),UnaryOperator:A.UnaryOperator.bind(A),arrayToVector:this.essentiaWASM.arrayToVector,vectorToArray:this.essentiaWASM.vectorToArray}}computeEffnetFeatures(A){if(!this.essentiaWASM)throw new Error("Essentia WASM not initialized. Call initializeEssentia() first.");const B=this.createEssentiaWrapper(),g=[],I=512,E=256,i=16e3,C=128;for(let a=0;a<=A.length-I;a+=E){const e=A.slice(a,a+I),o=B.Windowing(B.arrayToVector(e),!0,I,"hann",0,!0),t=B.Spectrum(o.frame,I),s=B.MelBands(t.spectrum,8e3,I/2,!1,0,"unit_sum",C,i,"power","slaneyMel","linear"),G=B.UnaryOperator(s.bands,1e4,1,"log10"),n=Array.from(B.vectorToArray(G.array));g.push(n)}return g}getFeaturesForArchitecture(A,B){switch(B){case"effnet":return this.computeEffnetFeatures(A);case"vggish":const g=this.extractors.get("vggish");return g?g.computeFrameWise(A,512):(console.warn("VGGish extractor not initialized, falling back to default 96-band extractor."),this.extractor.computeFrameWise(A,512));case"tempocnn":return console.warn("TempoCNN architecture requested but using default 96-band extractor. Results may be suboptimal for TempoCNN models."),this.extractor.computeFrameWise(A,512);default:return this.extractor.computeFrameWise(A,512)}}sliceAudioSignal(A,B){if(!this.options.analysisDurationSeconds&&this.options.analysisStartPosition===void 0)return A;const g=A.length,I=g/B,E=Math.min(this.options.analysisDurationSeconds??I,I),i=this.options.analysisStartPosition??.5,C=Math.floor(E*B),a=g-C,e=Math.max(0,Math.floor(i*a)),o=Math.min(e+C,g);return console.info(`[MusicClassifier] Analyzing segment: ${E.toFixed(1)}s starting at ${i} (samples ${e}-${o} of ${g}, ${(e/B).toFixed(1)}s-${(o/B).toFixed(1)}s of ${I.toFixed(1)}s)`),A.slice(e,o)}async analyze(A){try{await this.initializeEssentia();const g=await(await fetch(A)).arrayBuffer(),I=new(window.AudioContext||window.webkitAudioContext),E=await I.decodeAudioData(g),i=await this.extractor.downsampleAudioBuffer(E,I.sampleRate),C=this.sliceAudioSignal(i,I.sampleRate),a=this.extractor.computeFrameWise(C,512),e=[],o={genres:[],moods:[],mood_tags:[],vibe_metrics:{}};if(this.options.models?.genre){const t=this.options.models.genre,s=_g(t)?t.classifier:t.modelUrl;let G;_g(t)&&t.classifierType?G=t.classifierType:qi(t)&&t.genreType?G=t.genreType:G=kt(s);const n=Zt(G);o.genres=await this.runModelPrediction(t,C,n),o.primary_genre=o.genres.length>0?o.genres[0].name:"Unknown",e.push(Dg(t))}if(this.options.models?.mood){const t=this.options.models.mood,s=qi(t)?XF:vF;o.moods=await this.runModelPrediction(t,C,s),o.mood_tags=o.moods.slice(0,3).map(G=>G.name),e.push(Dg(t))}if(this.options.models?.danceability){const t=this.options.models.danceability,G=(await this.runModelPrediction(t,C,KF)).find(n=>n.name==="danceable");o.vibe_metrics.danceability=G?.confidence??0,e.push(Dg(t))}if(this.options.models?.voice){const t=this.options.models.voice,G=(await this.runModelPrediction(t,C,zF)).find(n=>n.name==="instrumental");o.vibe_metrics.instrumental_probability=G?.confidence??0,e.push(Dg(t))}if(this.options.models?.acoustic){const t=this.options.models.acoustic,G=(await this.runModelPrediction(t,C,TF)).find(n=>n.name==="electronic");o.vibe_metrics.electronic_probability=G?.confidence??0,e.push(Dg(t))}if(o.moods){const t=o.moods.find(G=>G.name==="energetic"||G.name==="upbeat"||G.name==="epic"),s=o.moods.find(G=>G.name==="happy"||G.name==="positive"||G.name==="uplifting");t&&(o.vibe_metrics.energy=t.confidence),s&&(o.vibe_metrics.valence=s.confidence)}return{genres:o.genres||[],moods:o.moods||[],primary_genre:o.primary_genre||"Unknown",mood_tags:o.mood_tags||[],vibe_metrics:o.vibe_metrics,analysis_metadata:{models_used:e,model_used:e.length>0?e[0]:void 0,frames_analyzed:a.length,duration_analyzed:E.duration,analyzed_at:new Date().toISOString()}}}catch(B){throw console.error("Music analysis failed:",B),B}}async runClassifierOnEmbeddings(A,B){const g=Wt(B);if(g.length===0)return console.warn("Empty embeddings provided to classifier"),[];const I=await this.loadModelWithRetry(A),E=JA.tensor2d([g],[1,g.length]);let i=[];try{const C=I.predict(E);i=await C.data().then(a=>Array.from(a)),C.dispose()}finally{E.dispose(),I.dispose()}return i}async predictWithTwoStepModel(A,B){const g=hQ(A.embedding,A.embeddingType),I=this.getFeaturesForArchitecture(B,g);if(I.length===0)return console.warn("No features extracted from audio signal"),[];const E=await this.getEmbeddingModel(A.embedding,A.embeddingType);let i;return g==="effnet"?i=await this.runEffnetEmbedding(E,I):g==="vggish"?i=await this.runEssentiaEmbedding(E,I):i=await this.runEssentiaEmbedding(E,I),i.length===0?(console.warn("No embeddings produced by embedding model"),[]):await this.runClassifierOnEmbeddings(A.classifier,i)}getModelInputNames(A){const B=[];try{if(A.inputs&&Array.isArray(A.inputs))for(const g of A.inputs)g&&g.name&&B.push(g.name);if(B.length===0&&A.executor){const g=A.executor;if(g._signature&&g._signature.inputs)for(const I of Object.keys(g._signature.inputs))B.push(I)}if(B.length===0&&A.inputNodes){const g=A.inputNodes;Array.isArray(g)&&B.push(...g)}if(B.length===0&&A.metadata){const g=A.metadata;if(g.signature&&g.signature.inputs)for(const I of Object.keys(g.signature.inputs))B.push(I)}if(B.length===0){const g=A;if(g.graph&&g.graph.inputs)for(const I of Object.keys(g.graph.inputs))B.push(I)}}catch(g){console.warn("[MusicClassifier] Error discovering input names:",g)}return B}async runEffnetEmbedding(A,B){const g=B[0]?.length||128,I=B.length,E=64,i=128,C=96;let a=[],e=B;if(I<C){const G=Array(g).fill(0);for(;e.length<C;)e=[...e,G]}const o=[];for(let G=0;G<E;G++){const n=G*C%e.length,h=[];for(let c=0;c<C;c++){const d=(n+c)%e.length;h.push(e[d]||Array(g).fill(0))}o.push(h)}const t=[];for(let G=0;G<E;G++)for(let n=0;n<i;n++)for(let h=0;h<C;h++)t.push(o[G][h]?.[n]??0);const s=JA.tensor3d(t,[E,i,C]);try{const G=this.getModelInputNames(A);if(G.length===0)throw new Error("Could not discover model input names");const n={};for(const F of G)F.includes("melspectrogram")||F.includes("mel")||F.includes("spectrogram")?n[F]=s:F.includes("saver")||F.includes("filename")?n[F]=JA.scalar(""):n[F]=s;const h=A.execute(n);let c;if(Array.isArray(h)?c=h[0]:h&&typeof h=="object"&&!("data"in h)?c=Object.values(h)[0]:c=h,!c||typeof c.data!="function")throw new Error("Model execution did not return a valid tensor");const d=await c.data(),l=c.shape;if(l.length===2){const F=l[1],m=[];for(let k=0;k<F;k++){let H=0;for(let T=0;T<E;T++)H+=d[T*F+k];m.push(H/E)}const y=Math.sqrt(m.reduce((k,H)=>k+H*H,0)),p=y>0?m.map(k=>k/y):m;a.push(p)}else{const F=Array.from(d.slice(0,l[l.length-1])),m=Math.sqrt(F.reduce((p,k)=>p+k*k,0)),y=m>0?F.map(p=>p/m):F;a.push(y)}c.dispose();for(const[F,m]of Object.entries(n))m!==s&&m.dispose()}finally{s.dispose()}return a}async runEssentiaEmbedding(A,B){const g=await A.predict(B,!0);return!g||g.length===0?[]:g}async runModelPrediction(A,B,g){let I;if(_g(A))I=await this.predictWithTwoStepModel(A,B);else{const E=hQ(A.modelUrl,A.modelType),i=this.getFeaturesForArchitecture(B,E);I=await this.predictWithModel(A.modelUrl,i)}return this.mapPredictions(I,g)}async predictWithModel(A,B){if(!this.essentiaModel)throw new Error("Essentia not initialized. Call initializeEssentia() first.");let g;if(this.options.enableModelCache){const C=await lg.getOrFetchEssentiaUrl(A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:a=>kA.invalidateCacheForUrl(a)});g=new this.essentiaModel.TensorflowMusiCNN(JA,C),await g.initialize()}else g=await this.initializeEssentiaModelWithRetry(A,"musicnn");const I=await g.predict(B,!0);if(!I||I.length===0)return[];const E=I[0].length,i=[];for(let C=0;C<E;C++){const a=I.reduce((e,o)=>e+o[C],0);i.push(a/I.length)}return g.terminate&&g.terminate(),i}mapPredictions(A,B){return A.map((g,I)=>({name:B[I]||`unknown_${I}`,confidence:g})).sort((g,I)=>I.confidence-g.confidence).filter(g=>g.confidence>=this.options.threshold).slice(0,this.options.topN)}}class _F{constructor(A={}){this.canvas=null,this.context=null,typeof document<"u"&&(this.canvas=document.createElement("canvas"),this.canvas.width=100,this.canvas.height=100,this.context=this.canvas.getContext("2d",{willReadFrequently:!0}))}async extractPalette(A){try{if(!this.context||!this.canvas)throw new Error("Canvas not supported in this environment");const B=await kA.resolveUrl(A),g=await this.loadImage(B);this.context.drawImage(g,0,0,100,100);const I=this.context.getImageData(0,0,100,100),E=this.getPixels(I);if(E.length===0)return this.getFallbackPalette();let i=this.kMeans(E,4);i.length<4&&(i=this.medianCut(E,4));const C=this.sortColorsByFrequency(E,i);return this.analyzePalette(C)}catch(B){return console.warn("Color extraction failed, using fallback:",B),this.getFallbackPalette()}}loadImage(A){return new Promise((B,g)=>{const I=new Image;I.crossOrigin="Anonymous",I.onload=()=>B(I),I.onerror=E=>g(E),I.src=A})}getPixels(A){const B=[];for(let g=0;g<A.data.length;g+=4){const I=A.data[g],E=A.data[g+1],i=A.data[g+2];A.data[g+3]>=128&&B.push([I,E,i])}return B}kMeans(A,B){if(A.length===0)return[];if(A.length<B)return A;let g=[];const I=new Set;for(;g.length<B&&g.length<A.length;){const E=Math.floor(Math.random()*A.length);I.has(E)||(I.add(E),g.push([...A[E]]))}for(let E=0;E<10;E++){const i=Array.from({length:B},()=>[]);for(const a of A){let e=1/0,o=0;for(let t=0;t<g.length;t++){const s=this.distance(a,g[t]);s<e&&(e=s,o=t)}i[o].push(a)}const C=[];for(let a=0;a<B;a++)if(i[a].length===0)C.push(g[a]);else{const e=i[a].reduce((o,t)=>[o[0]+t[0],o[1]+t[1],o[2]+t[2]],[0,0,0]);C.push([Math.round(e[0]/i[a].length),Math.round(e[1]/i[a].length),Math.round(e[2]/i[a].length)])}if(C.every((a,e)=>a[0]===g[e][0]&&a[1]===g[e][1]&&a[2]===g[e][2]))break;g=C}return g}medianCut(A,B){if(A.length===0)return[];const g=[],I=[{pixels:[...A],depth:0}];for(;I.length<B&&I.length>0;){let E=I[0],i=0,C=0;for(let o=0;o<I.length;o++){const t=this.getBoxRange(I[o].pixels);t>C&&(C=t,E=I[o],i=o)}if(E.pixels.length<=1)break;const a=this.sortPixelsByChannel(E.pixels,E.depth%3),e=Math.floor(a.length/2);I.splice(i,1),I.push({pixels:a.slice(0,e),depth:E.depth+1},{pixels:a.slice(e),depth:E.depth+1})}for(const E of I)if(E.pixels.length>0){const i=E.pixels.reduce((C,a)=>[C[0]+a[0],C[1]+a[1],C[2]+a[2]],[0,0,0]);g.push([Math.round(i[0]/E.pixels.length),Math.round(i[1]/E.pixels.length),Math.round(i[2]/E.pixels.length)])}return g.slice(0,B)}getBoxRange(A){if(A.length===0)return 0;let B=1/0,g=-1/0,I=1/0,E=-1/0,i=1/0,C=-1/0;for(const[a,e,o]of A)B=Math.min(B,a),g=Math.max(g,a),I=Math.min(I,e),E=Math.max(E,e),i=Math.min(i,o),C=Math.max(C,o);return Math.max(g-B,E-I,C-i)}sortPixelsByChannel(A,B){return[...A].sort((g,I)=>g[B]-I[B])}distance(A,B){return Math.sqrt(Math.pow(A[0]-B[0],2)+Math.pow(A[1]-B[1],2)+Math.pow(A[2]-B[2],2))}sortColorsByFrequency(A,B){const g=new Map;for(const I of A){let E=B[0],i=1/0;for(const a of B){const e=this.distance(I,a);e<i&&(i=e,E=a)}const C=`${E[0]},${E[1]},${E[2]}`;g.set(C,(g.get(C)||0)+1)}return B.sort((I,E)=>{const i=`${I[0]},${I[1]},${I[2]}`,C=`${E[0]},${E[1]},${E[2]}`;return(g.get(C)||0)-(g.get(i)||0)})}analyzePalette(A){const B=A.map(e=>this.rgbToHex(e[0],e[1],e[2]));if(B.length===0)return this.getFallbackPalette();const g=B[0],I=B[1],E=B[2],i=this.getBrightness(A[0]),C=this.getSaturation(A[0]),a=C<.1;return{colors:B,primary_color:g,secondary_color:I,accent_color:E,brightness:i/255,saturation:C,is_monochrome:a}}getBrightness(A){return(A[0]*299+A[1]*587+A[2]*114)/1e3}getSaturation(A){const B=Math.max(A[0],A[1],A[2]),g=Math.min(A[0],A[1],A[2]);return B===0?0:(B-g)/B}rgbToHex(A,B,g){return"#"+[A,B,g].map(I=>{const E=I.toString(16);return E.length===1?"0"+E:E}).join("").toUpperCase()}getFallbackPalette(){return{colors:["#000000","#333333","#666666"],primary_color:"#000000",secondary_color:"#333333",accent_color:"#666666",brightness:0,saturation:0,is_monochrome:!0}}}const Xi=SA.for("BeatSubdivider"),$F={tolerance:.02,defaultIntensity:.5,defaultConfidence:.7};class dg{constructor(A){this.options={...$F,...A},Xi.debug("BeatSubdivider initialized",{options:this.options})}subdivide(A,B=CE){if(Xi.debug("Starting per-beat subdivision",{audioId:A.audioId,beatCount:A.beats.length,explicitAssignments:B.beatSubdivisions.size,defaultSubdivision:B.defaultSubdivision}),oE(B),zC(B,A.beats.length),A.beats.length===0)return this.createEmptySubdividedBeatMap(A,B);const g=new Set;let I=0;const E=!!A.tempoSections&&A.tempoSections.length>1;let i=0;const C=h=>{let c=0;for(let d=h-1;d>=0&&(B.beatSubdivisions.get(d)??B.defaultSubdivision)==="triplet4";d--)c++;return c%2},a=[];for(let h=0;h<A.beats.length;h++){const c=B.beatSubdivisions.get(h)??B.defaultSubdivision;B.beatSubdivisions.has(h)&&i++,g.add(c);const d=eE(c);I=Math.max(I,d);const l=A.beats[h],F=h<A.beats.length-1?A.beats[h+1]:null,m=A.detectedBeatIndices.includes(h);if(c==="rest"||c==="half"&&l.beatInMeasure%2!==0)continue;const y=c==="dotted4"&&l.beatInMeasure%2!==0;if(c==="offbeat8"||c==="triplet4"&&C(h)===1||y||a.push({...l,isDetected:m,originalBeatIndex:h,subdivisionType:c}),F){const k=this.createInterpolatedBeatsForSubdivision(l,F,h,c,A,E,B,C(h));a.push(...k)}}const e=this.buildDetectedBeatIndices(a),o=A.beats.length,t=a.length,s=o>0?t/o:1,G={originalBeatCount:o,subdividedBeatCount:t,averageDensityMultiplier:s,explicitBeatCount:i,subdivisionsUsed:Array.from(g),hasMultipleTempos:E,maxDensity:I},n={audioId:A.audioId,duration:A.duration,beats:a,detectedBeatIndices:e,subdivisionConfig:B,downbeatConfig:A.downbeatConfig,tempoSections:A.tempoSections,subdivisionMetadata:G};return Xi.debug("Per-beat subdivision complete",{originalBeats:o,subdividedBeats:t,densityMultiplier:s,subdivisionsUsed:Array.from(g)}),n}createInterpolatedBeatsForSubdivision(A,B,g,I,E,i,C,a=0){const e=[],o=E.quarterNoteInterval,t=i?this.getQuarterNoteIntervalForTimestamp(E,A.timestamp):o;switch(I){case"quarter":break;case"half":break;case"eighth":e.push(this.createInterpolatedBeat(A,B,.5,"eighth",t,this.options));break;case"sixteenth":for(let G=.25;G<1;G+=.25)e.push(this.createInterpolatedBeat(A,B,G,"sixteenth",t,this.options));break;case"triplet8":for(const G of[1/3,2/3])e.push(this.createInterpolatedBeat(A,B,G,"triplet8",t,this.options));break;case"triplet4":{a===0?e.push(this.createInterpolatedBeat(A,B,2/3,"triplet4",t,this.options)):(C.beatSubdivisions.get(g-1)??C.defaultSubdivision)==="triplet4"&&e.push(this.createInterpolatedBeat(A,B,1/3,"triplet4",t,this.options));break}case"dotted4":A.beatInMeasure%2!==0&&e.push(this.createInterpolatedBeat(A,B,.5,"dotted4",t,this.options));break;case"dotted8":e.push(this.createInterpolatedBeat(A,B,3/4,"dotted8",t,this.options));break;case"swing":e.push(this.createInterpolatedBeat(A,B,2/3,"swing",t,this.options));break;case"offbeat8":e.push(this.createInterpolatedBeat(A,B,.5,"offbeat8",t,this.options));break;case"rest":break;default:const s=I;throw new Error(`Unknown subdivision type: ${s}`)}return e}createEmptySubdividedBeatMap(A,B){return{audioId:A.audioId,duration:A.duration,beats:[],detectedBeatIndices:[],subdivisionConfig:B,downbeatConfig:A.downbeatConfig,tempoSections:A.tempoSections,subdivisionMetadata:{originalBeatCount:0,subdividedBeatCount:0,averageDensityMultiplier:1,explicitBeatCount:0,subdivisionsUsed:[B.defaultSubdivision],hasMultipleTempos:!!A.tempoSections&&A.tempoSections.length>1,maxDensity:1}}}createInterpolatedBeat(A,B,g,I,E,i){const C=B.timestamp-A.timestamp,a=A.timestamp+C*g,e=A.beatInMeasure+g,o=(A.intensity+B.intensity)/2,t=(A.confidence+B.confidence)/2;return{timestamp:a,beatInMeasure:e,isDownbeat:!1,measureNumber:A.measureNumber,intensity:o,confidence:t,isDetected:!1,subdivisionType:I}}findClosestBeat(A,B){let g=A[0],I=Math.abs(A[0].timestamp-B);for(const E of A){const i=Math.abs(E.timestamp-B);i<I&&(I=i,g=E)}return g}calculateBeatInMeasure(A,B,g){return(A-B)/g%4}calculateMeasureNumber(A,B,g,I){const i=(A-B)/g,C=I.segments[0]?.timeSignature?.beatsPerMeasure??4;return Math.floor(i/C)}buildDetectedBeatIndices(A){const B=[];for(let g=0;g<A.length;g++)A[g].isDetected&&B.push(g);return B}getTempoSectionForTimestamp(A,B){if(!(!A||A.length===0)){for(const g of A)if(B>=g.start&&B<g.end)return g;return A[A.length-1]}}getQuarterNoteIntervalForTimestamp(A,B){const g=this.getTempoSectionForTimestamp(A.tempoSections,B);return g?g.intervalSeconds:A.quarterNoteInterval}getTempoSectionForBeatIndex(A,B){if(!(!A||A.length===0)){for(const g of A)if(B>=g.startBeatIndex&&B<=g.endBeatIndex)return g;return A[A.length-1]}}static toJSON(A){const B=Array.from(A.subdivisionConfig.beatSubdivisions.entries()),g={audioId:A.audioId,duration:A.duration,beats:A.beats.map(I=>({timestamp:I.timestamp,beatInMeasure:I.beatInMeasure,isDownbeat:I.isDownbeat,measureNumber:I.measureNumber,intensity:I.intensity,confidence:I.confidence,requiredKey:I.requiredKey,isDetected:I.isDetected,originalBeatIndex:I.originalBeatIndex,subdivisionType:I.subdivisionType})),detectedBeatIndices:A.detectedBeatIndices,subdivisionConfig:{beatSubdivisions:B,defaultSubdivision:A.subdivisionConfig.defaultSubdivision},downbeatConfig:A.downbeatConfig,tempoSections:A.tempoSections?.map(I=>({start:I.start,end:I.end,bpm:I.bpm,intervalSeconds:I.intervalSeconds,beatCount:I.beatCount,startBeatIndex:I.startBeatIndex,endBeatIndex:I.endBeatIndex})),subdivisionMetadata:{originalBeatCount:A.subdivisionMetadata.originalBeatCount,subdividedBeatCount:A.subdivisionMetadata.subdividedBeatCount,averageDensityMultiplier:A.subdivisionMetadata.averageDensityMultiplier,explicitBeatCount:A.subdivisionMetadata.explicitBeatCount,subdivisionsUsed:A.subdivisionMetadata.subdivisionsUsed,hasMultipleTempos:A.subdivisionMetadata.hasMultipleTempos,maxDensity:A.subdivisionMetadata.maxDensity}};return JSON.stringify(g)}static fromJSON(A){const B=JSON.parse(A),g=new Map(B.subdivisionConfig.beatSubdivisions||[]);return{audioId:B.audioId,duration:B.duration,beats:B.beats.map(I=>({timestamp:I.timestamp,beatInMeasure:I.beatInMeasure,isDownbeat:I.isDownbeat,measureNumber:I.measureNumber,intensity:I.intensity,confidence:I.confidence,requiredKey:I.requiredKey,isDetected:I.isDetected,originalBeatIndex:I.originalBeatIndex,subdivisionType:I.subdivisionType})),detectedBeatIndices:B.detectedBeatIndices,subdivisionConfig:{beatSubdivisions:g,defaultSubdivision:B.subdivisionConfig.defaultSubdivision},downbeatConfig:B.downbeatConfig,tempoSections:B.tempoSections?.map(I=>({start:I.start,end:I.end,bpm:I.bpm,intervalSeconds:I.intervalSeconds,beatCount:I.beatCount,startBeatIndex:I.startBeatIndex,endBeatIndex:I.endBeatIndex})),subdivisionMetadata:{originalBeatCount:B.subdivisionMetadata.originalBeatCount,subdividedBeatCount:B.subdivisionMetadata.subdividedBeatCount,averageDensityMultiplier:B.subdivisionMetadata.averageDensityMultiplier,explicitBeatCount:B.subdivisionMetadata.explicitBeatCount,subdivisionsUsed:B.subdivisionMetadata.subdivisionsUsed,hasMultipleTempos:B.subdivisionMetadata.hasMultipleTempos,maxDensity:B.subdivisionMetadata.maxDensity}}}static async saveToFile(A,B){if(typeof process>"u"||!process.versions?.node)throw new Error("saveToFile is only available in Node.js environment");const{writeFile:g}=await Promise.resolve().then(()=>GB),I=dg.toJSON(A);await g(B,I,"utf-8")}static async loadFromFile(A){if(typeof process>"u"||!process.versions?.node)throw new Error("loadFromFile is only available in Node.js environment");const{readFile:B}=await Promise.resolve().then(()=>GB),g=await B(A,"utf-8");return dg.fromJSON(g)}}class AM{constructor(A){this.recentOffsets=[],this.establishedOffset=0,this.pocketDirection="neutral",this.hotness=0,this.streakLength=0,this.hitCount=0,this.lastBpm=120,this.grooveStartTime=null,this.maxHotness=0,this.maxGrooveStreak=0,this.hotnessSamples=[],this.grooveHitCount=0,this.previousDirection="neutral",this.options={..._C,...A}}setDifficulty(A){const B=ga(A.preset,A.customPenalties);this.options.hotnessLossOnMiss=B.hotnessLossOnMiss,this.options.hotnessLossOnBreak=B.hotnessLossOnBreak}recordHit(A,B,g=0,I="perfect"){if(this.hitCount++,this.lastBpm=B,I==="miss"||I==="wrongKey")return this.recordMiss(g);const E=this.pocketDirection;this.recentOffsets.push(A),this.recentOffsets.length>this.options.averagingWindowSize&&this.recentOffsets.shift(),this.updateRunningAverage(),this.pocketDirection=this.determineDirection(this.establishedOffset);const i=this.calculatePocketWindow(B),C=Math.abs(A-this.establishedOffset),a=this.calculateConsistency(C,i),e=this.hasPocket()&&C<=i;this.hasPocket()&&(e?(this.hotness=this.hotness+this.options.hotnessGainPerHit,this.streakLength++):this.hotness=Math.max(0,this.hotness-this.options.hotnessLossOnBreak),this.maxGrooveStreak=Math.max(this.maxGrooveStreak,this.streakLength));let o;const t=E!==this.pocketDirection,s=t||this.hotness===0;if(this.hotness>0&&!s)this.grooveStartTime===null&&(this.grooveStartTime=g??this.hitCount*(60/B)),this.maxHotness=Math.max(this.maxHotness,this.hotness),this.hotnessSamples.push(this.hotness),this.grooveHitCount++;else if(s&&this.grooveStartTime!==null&&this.grooveHitCount>0){const G=g??this.hitCount*(60/B),n=this.hotnessSamples.length>0?this.hotnessSamples.reduce((h,c)=>h+c,0)/this.hotnessSamples.length:0;o={maxStreak:this.maxGrooveStreak,maxHotness:this.maxHotness,avgHotness:n,duration:G-this.grooveStartTime,totalHits:this.grooveHitCount,startTime:this.grooveStartTime,endTime:G},this.streakLength=0,this.hotness=0,this.resetGrooveStats()}return t&&(this.streakLength=0,this.hotness=0,this.resetGrooveStats()),this.previousDirection=this.pocketDirection,{pocketDirection:this.pocketDirection,establishedOffset:this.establishedOffset,consistency:a,hotness:this.hotness,tier:rI(this.hotness),streakLength:this.streakLength,inPocket:e,pocketWindow:i,endedGrooveStats:o}}recordMiss(A){const g=this.hotness>0&&this.grooveStartTime!==null&&this.grooveHitCount>0;this.hotness=Math.max(0,this.hotness-this.options.hotnessLossOnMiss),this.streakLength=0;const I=this.calculatePocketWindow(this.lastBpm);let E;if(g&&this.hotness===0){const i=A??this.hitCount*(60/this.lastBpm),C=this.hotnessSamples.length>0?this.hotnessSamples.reduce((a,e)=>a+e,0)/this.hotnessSamples.length:0;E={maxStreak:this.maxGrooveStreak,maxHotness:this.maxHotness,avgHotness:C,duration:i-this.grooveStartTime,totalHits:this.grooveHitCount,startTime:this.grooveStartTime,endTime:i},this.resetGrooveStats()}return{pocketDirection:this.pocketDirection,establishedOffset:this.establishedOffset,consistency:0,hotness:this.hotness,tier:rI(this.hotness),streakLength:this.streakLength,inPocket:!1,pocketWindow:I,endedGrooveStats:E}}getState(){const A=this.hotnessSamples.length>0?this.hotnessSamples.reduce((B,g)=>B+g,0)/this.hotnessSamples.length:0;return{pocketDirection:this.pocketDirection,establishedOffset:this.establishedOffset,hotness:this.hotness,tier:rI(this.hotness),streakLength:this.streakLength,hitCount:this.hitCount,pocketWindow:this.calculatePocketWindow(this.lastBpm),grooveStartTime:this.grooveStartTime,grooveDuration:this.grooveStartTime!==null?this.grooveHitCount*(60/this.lastBpm):0,maxHotness:this.maxHotness,avgHotness:A,grooveHitCount:this.grooveHitCount}}reset(){this.recentOffsets=[],this.establishedOffset=0,this.pocketDirection="neutral",this.hotness=0,this.streakLength=0,this.hitCount=0,this.lastBpm=120,this.resetGrooveStats()}getGrooveStats(A){if(this.grooveStartTime===null||this.grooveHitCount===0)return null;const B=A??this.hitCount*(60/this.lastBpm),g=this.hotnessSamples.length>0?this.hotnessSamples.reduce((I,E)=>I+E,0)/this.hotnessSamples.length:0;return{maxStreak:this.maxGrooveStreak,maxHotness:this.maxHotness,avgHotness:g,duration:B-this.grooveStartTime,totalHits:this.grooveHitCount,startTime:this.grooveStartTime,endTime:B}}resetGrooveStats(){this.grooveStartTime=null,this.maxHotness=0,this.maxGrooveStreak=0,this.hotnessSamples=[],this.grooveHitCount=0}hasPocket(){return this.hitCount>=this.options.minHitsForPocket}calculatePocketWindow(A){const g=PC(this.hotness)/1e3,I=120/A;return g*I}updateRunningAverage(){this.recentOffsets.length!==0&&(this.establishedOffset=this.recentOffsets.reduce((A,B)=>A+B,0)/this.recentOffsets.length)}calculateConsistency(A,B){if(!this.hasPocket())return 0;const g=A/B;return g>=1?0:1-g*g}determineDirection(A){return Math.abs(A)<this.options.neutralDeadZone?"neutral":A<0?"push":"pull"}}function BM(Q,A=CE,B){const g=nQ(Q);return new dg(B).subdivide(g,A)}function jB(Q){return"mergedBeats"in Q?Q.mergedBeats:Q.beats}function vi(Q,A){return"mergedBeats"in Q?{...Q,mergedBeats:A}:{...Q,beats:A}}function gM(Q,A,B){const g=jB(Q);if(A<0||A>=g.length)throw new Error(`beatIndex ${A} is out of bounds (beats array length: ${g.length})`);const I=g.map((E,i)=>{if(i!==A)return E;if(B===null){const{requiredKey:C,...a}=E;return a}return{...E,requiredKey:B}});return vi(Q,I)}function IM(Q,A){const B=jB(Q),g=B.length;for(const i of A)if(i.beatIndex<0||i.beatIndex>=g)throw new Error(`beatIndex ${i.beatIndex} is out of bounds (beats array length: ${g})`);const I=new Map;for(const{beatIndex:i,key:C}of A)I.set(i,C);const E=B.map((i,C)=>{const a=I.get(C);if(a===void 0)return i;if(a===null){const{requiredKey:e,...o}=i;return o}return{...i,requiredKey:a}});return vi(Q,E)}function QM(Q){const A=jB(Q),B=new Map;for(let g=0;g<A.length;g++){const I=A[g];I.requiredKey!==void 0&&B.set(g,I.requiredKey)}return B}function EM(Q){const B=jB(Q).map(g=>{if(g.requiredKey===void 0)return g;const{requiredKey:I,...E}=g;return E});return vi(Q,B)}function iM(Q){return jB(Q).some(B=>B.requiredKey!==void 0)}function CM(Q){return jB(Q).filter(B=>B.requiredKey!==void 0).length}function aM(Q){const A=jB(Q),B=new Set;for(const g of A)g.requiredKey!==void 0&&B.add(g.requiredKey);return Array.from(B).sort()}const bA=SA.for("SubdivisionPlaybackController");class eM{constructor(A,B,g={}){this.unifiedMap=A,this.audioContext=B,this.options={...TC,...g};const I={tolerance:.02,defaultIntensity:.5,defaultConfidence:.7};this.subdivider=new dg(I),this.state={isRunning:!1,isPaused:!1,currentTime:0,startTime:0,pauseTime:0,rafId:null,currentSubdivision:this.options.initialSubdivision,pendingSubdivision:null,pendingMeasureNumber:null,transitionTimestamp:null,scheduledBeats:new Map,thresholds:nI("medium")},this.subscribers=new Set,this.regenerateBeats(),bA.debug("SubdivisionPlaybackController initialized",{audioId:A.audioId,duration:A.duration,initialSubdivision:this.state.currentSubdivision,beatCount:A.beats.length})}get subdivision(){return this.state.currentSubdivision}get beatMap(){return this.unifiedMap}getOptions(){return{...this.options}}subscribe(A){return this.subscribers.add(A),()=>{this.subscribers.delete(A)}}setSubdivision(A){if(!cI(A))throw new Error(`Invalid subdivision type: ${A}`);if(this.state.currentSubdivision===A)return;const B=this.state.currentSubdivision;switch(bA.debug("Subdivision change requested",{from:B,to:A,transitionMode:this.options.transitionMode}),this.options.transitionMode){case"immediate":this.applySubdivisionChange(A,B);break;case"next-downbeat":this.state.pendingSubdivision=A,this.state.pendingMeasureNumber=null,bA.debug("Subdivision change deferred until next downbeat",{pendingType:A,transitionMode:this.options.transitionMode});break;case"next-measure":{const I=this.getCurrentBeat()?.measureNumber??null;this.state.pendingSubdivision=A,this.state.pendingMeasureNumber=I,bA.debug("Subdivision change deferred until next measure",{pendingType:A,transitionMode:this.options.transitionMode,currentMeasureNumber:I});break}default:this.applySubdivisionChange(A,B)}}setTransitionMode(A){if(!["immediate","next-downbeat","next-measure"].includes(A))throw new Error(`Invalid transition mode: ${A}`);const B=this.options.transitionMode;B!==A&&(bA.debug("Transition mode change requested",{from:B,to:A}),this.options={...this.options,transitionMode:A},A==="immediate"&&this.state.pendingSubdivision&&(bA.debug("Applying pending subdivision immediately due to mode switch",{pendingSubdivision:this.state.pendingSubdivision}),this.applySubdivisionChange(this.state.pendingSubdivision,this.state.currentSubdivision)),bA.debug("Transition mode changed",{from:B,to:A}))}applySubdivisionChange(A,B){this.state.currentSubdivision=A,this.state.pendingSubdivision=null,this.state.pendingMeasureNumber=null;const g=this.state.transitionTimestamp;if(this.state.transitionTimestamp=null,this.regenerateBeats(g),this.options.onSubdivisionChange)try{this.options.onSubdivisionChange(B,A)}catch(I){bA.error("Error in onSubdivisionChange callback",I)}bA.debug("Subdivision changed",{from:B,to:A,transitionTimestamp:g})}regenerateBeats(A=null){const B={beatSubdivisions:new Map,defaultSubdivision:this.state.currentSubdivision},g=this.subdivider.subdivide(this.unifiedMap,B),I=new Map;if(A!==null)for(const[E,i]of this.state.scheduledBeats)i.beat.timestamp<A&&I.set(E,i);this.state.scheduledBeats.clear();for(const[E,i]of I)this.state.scheduledBeats.set(E,i);for(const E of g.beats){if(A!==null&&E.timestamp<A)continue;const i=this.getBeatKey(E);this.state.scheduledBeats.has(i)||this.state.scheduledBeats.set(i,{beat:E,upcomingEmitted:!1,exactEmitted:!1,passedEmitted:!1})}bA.debug("Beats regenerated",{subdivision:this.state.currentSubdivision,beatCount:g.beats.length,preservedBeatCount:I.size,totalScheduledBeats:this.state.scheduledBeats.size,fromTimestamp:A})}getBeatKey(A){return`${A.timestamp.toFixed(6)}-${A.subdivisionType}`}play(){this.state.isRunning||(this.state.isRunning=!0,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.state.isPaused=!1,this.scheduleUpdate(),bA.debug("Playback started",{startTime:this.state.startTime,pauseTime:this.state.pauseTime}))}stop(){this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null),this.state.isRunning=!1,this.state.isPaused=!1,this.state.pauseTime=0,this.regenerateBeats(),bA.debug("Playback stopped")}pause(){!this.state.isRunning||this.state.isPaused||(this.state.isPaused=!0,this.state.pauseTime=this.getCurrentAudioTime(),this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null),bA.debug("Playback paused",{pauseTime:this.state.pauseTime}))}resume(){this.state.isPaused&&(this.state.isPaused=!1,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.scheduleUpdate(),bA.debug("Playback resumed",{startTime:this.state.startTime}))}seek(A){const B=Math.max(0,Math.min(A,this.unifiedMap.duration));this.state.pauseTime=B,this.state.isRunning&&!this.state.isPaused&&(this.state.startTime=this.audioContext.currentTime-B),this.regenerateBeats(),bA.debug("Seeked to time",{time:B})}getBeatsInRange(A,B){const g=[];for(const[,I]of this.state.scheduledBeats){const E=I.beat;E.timestamp>=A&&E.timestamp<=B&&g.push(E)}return g.sort((I,E)=>I.timestamp-E.timestamp),g}getUpcomingBeats(A){const B=this.getCurrentAudioTime(),g=[];for(const[,I]of this.state.scheduledBeats){const E=I.beat,i=E.timestamp-B;if(i>=0&&i<=this.options.anticipationTime&&(g.push(E),g.length>=A))break}return g.sort((I,E)=>I.timestamp-E.timestamp),g}getBeatAtTime(A){const B=this.options.timingTolerance;for(const[,g]of this.state.scheduledBeats)if(Math.abs(g.beat.timestamp-A)<=B)return g.beat;return null}getCurrentBeat(){const A=this.getCurrentAudioTime();let B=null;for(const[,g]of this.state.scheduledBeats)if(g.beat.timestamp<=A)B=g.beat;else break;return B}getNextBeat(){const A=this.getCurrentAudioTime(),B=Array.from(this.state.scheduledBeats.values()).map(g=>g.beat).sort((g,I)=>g.timestamp-I.timestamp);for(const g of B)if(g.timestamp>A)return g;return null}getCurrentAudioTime(){return this.state.isRunning?this.state.isPaused?this.state.pauseTime:this.audioContext.currentTime-this.state.startTime+this.getTotalLatencyCompensation():this.state.pauseTime}getTotalLatencyCompensation(){let A=0;if(this.options.compensateOutputLatency){const B=this.audioContext.outputLatency??0,g=this.audioContext.baseLatency??0;A+=B+g}return A+=this.options.userOffsetMs/1e3,A}scheduleUpdate(){!this.state.isRunning||this.state.isPaused||(this.state.rafId=requestAnimationFrame(()=>{this.update(),this.scheduleUpdate()}))}update(){if(!this.state.isRunning||this.state.isPaused)return;const A=this.getCurrentAudioTime();this.checkPendingSubdivisionChange(A);for(const B of this.state.scheduledBeats.values()){const I=B.beat.timestamp-A;!B.upcomingEmitted&&I<=this.options.anticipationTime&&I>0&&(this.emitEvent(B.beat,"upcoming",A,I),B.upcomingEmitted=!0),!B.exactEmitted&&Math.abs(I)<=this.options.timingTolerance&&(this.emitEvent(B.beat,"exact",A,I),B.exactEmitted=!0),!B.passedEmitted&&I<-this.options.timingTolerance&&(this.emitEvent(B.beat,"passed",A,I),B.passedEmitted=!0)}}checkPendingSubdivisionChange(A){if(!this.state.pendingSubdivision)return;const B=this.getCurrentBeat();if(!B)return;let g=!1;if(this.options.transitionMode==="next-downbeat")g=B.isDownbeat;else if(this.options.transitionMode==="next-measure"){const I=this.state.pendingMeasureNumber;I!==null?g=B.measureNumber>I&&Math.floor(B.beatInMeasure)===0:g=B.beatInMeasure===0}g&&(this.state.transitionTimestamp=B.timestamp,bA.debug("Applying pending subdivision change",{newSubdivision:this.state.pendingSubdivision,transitionMode:this.options.transitionMode,currentMeasureNumber:B.measureNumber,pendingMeasureNumber:this.state.pendingMeasureNumber,beatInMeasure:B.beatInMeasure,transitionTimestamp:this.state.transitionTimestamp}),this.applySubdivisionChange(this.state.pendingSubdivision,this.state.currentSubdivision))}emitEvent(A,B,g,I){const E={beat:A,currentSubdivision:this.state.currentSubdivision,audioTime:g,timeUntilBeat:I,type:B};for(const i of this.subscribers)try{i(E)}catch(C){bA.error("SubdivisionPlaybackController callback error:",C)}}isRunning(){return this.state.isRunning&&!this.state.isPaused}isPaused(){return this.state.isPaused}getCurrentTime(){return this.getCurrentAudioTime()}getDuration(){return this.unifiedMap.duration}checkButtonPress(A,B){const g=B??this.state.thresholds,I=Array.from(this.state.scheduledBeats.values()).map(o=>o.beat);let E=null,i=1/0;for(const o of I){const t=A-o.timestamp,s=Math.abs(t);s<i&&(i=s,E=o)}if(!E)return{accuracy:"miss",offset:A,matchedBeat:null,absoluteOffset:A,keyMatch:!0,pressedKey:void 0,requiredKey:void 0};const C=A-E.timestamp,a=Math.abs(C);let e;return a<=g.perfect?e="perfect":a<=g.great?e="great":a<=g.good?e="good":a<=g.ok?e="ok":e="miss",{accuracy:e,offset:C,matchedBeat:E,absoluteOffset:a,keyMatch:!0,pressedKey:void 0,requiredKey:void 0}}setBeatMap(A){this.unifiedMap=A,this.regenerateBeats(),bA.debug("Beat map updated",{audioId:A.audioId,beatCount:A.beats.length})}forceUpdate(){this.update()}getSubdividedBeatMap(){const A=[];for(const[,I]of this.state.scheduledBeats)A.push(I.beat);A.sort((I,E)=>I.timestamp-E.timestamp);const B=new Set(A.map(I=>I.subdivisionType)),g=A.filter(I=>I.isDetected).length;return{audioId:this.unifiedMap.audioId,duration:this.unifiedMap.duration,beats:A,detectedBeatIndices:[],subdivisionConfig:{beatSubdivisions:new Map,defaultSubdivision:this.state.currentSubdivision},downbeatConfig:this.unifiedMap.downbeatConfig,subdivisionMetadata:{originalBeatCount:g,subdividedBeatCount:A.length,averageDensityMultiplier:A.length/Math.max(1,this.unifiedMap.beats.length),explicitBeatCount:0,subdivisionsUsed:Array.from(B),hasMultipleTempos:!1,maxDensity:Math.max(...Array.from(B).map(I=>I==="eighth"?2:I==="sixteenth"?4:I==="triplet8"||I==="triplet4"?3:1))}}}dispose(){this.stop(),this.subscribers.clear(),this.state.scheduledBeats.clear(),bA.debug("SubdivisionPlaybackController disposed")}}const oM=[0,300,900,2700,6500,14e3,23e3,34e3,48e3,64e3,85e3,1e5,12e4,14e4,165e3,195e3,225e3,265e3,305e3,355e3],tM={stationary:1,walking:1.2,running:1.5,driving:1.3,night_time:1.25,extreme_weather:1.4,high_altitude:1.3,rhythm_game_base:1.25,rhythm_game_combo:.5,rhythm_game_groove:.5};class Ki{constructor(A){this.config={level_thresholds:A?.level_thresholds||oM,xp_per_second:A?.xp_per_second??1,xp_per_track_completion:A?.xp_per_track_completion??50,activity_bonuses:{...tM,...A?.activity_bonuses},track_mastery_threshold:A?.track_mastery_threshold??10,mastery_bonus_xp:A?.mastery_bonus_xp??100}}calculateSessionXP(A,B){let g=A.duration_seconds*this.config.xp_per_second;if(A.activity_type&&A.activity_type in this.config.activity_bonuses){const I=this.config.activity_bonuses[A.activity_type];g*=I}return A.environmental_context&&(g=this.applyEnvironmentalBonus(g,A.environmental_context)),A.gaming_context&&(g=this.applyGamingBonus(g,A.gaming_context)),A.rhythm_game_context&&(g=this.applyRhythmGameBonus(g,A.rhythm_game_context)),B&&A.duration_seconds>=B.duration*.95&&(g+=this.config.xp_per_track_completion),Math.floor(g)}applyEnvironmentalBonus(A,B){let g=1;return B.weather?.isNight&&(g*=this.config.activity_bonuses.night_time),B.weather&&(B.weather.weatherType==="Thunderstorm"&&(g*=this.config.activity_bonuses.extreme_weather),(B.weather.weatherType==="Rain"||B.weather.weatherType==="Snow")&&(g*=this.config.activity_bonuses.extreme_weather)),B.geolocation?.altitude&&B.geolocation.altitude>=2e3&&(g*=this.config.activity_bonuses.high_altitude),A*g}applyGamingBonus(A,B){if(!B.isActivelyGaming)return A;let g=1;if(g+=.25,B.currentGame){if(B.currentGame.genre){const I=B.currentGame.genre;(I.includes("Action")||I.includes("FPS"))&&(g+=.15),I.includes("RPG")&&(g+=.2),I.includes("Strategy")&&(g+=.1)}if(B.currentGame.partySize&&B.currentGame.partySize>1&&(g+=.15),B.currentGame.sessionDuration){const I=B.currentGame.sessionDuration/60;I>=1&&(g+=Math.min(.2,I*.05))}}return A*g}applyRhythmGameBonus(A,B){if(!B.isActive)return A;let g=this.config.activity_bonuses.rhythm_game_base;if(B.currentCombo>0){const I=B.currentCombo/B.maxComboCap;g+=I*this.config.activity_bonuses.rhythm_game_combo}if(B.grooveHotness>0){const I=B.grooveHotness/100;g+=I*this.config.activity_bonuses.rhythm_game_groove}return A*g}calculateTotalModifier(A,B,g){let I=1;return A&&(I*=this.calculateEnvironmentalModifier(A)),B&&B.isActivelyGaming&&(I*=this.calculateGamingModifier(B)),g&&g.isActive&&(I*=this.calculateRhythmGameModifier(g)),Math.min(I,3)}calculateEnvironmentalModifier(A){let B=1;return A.weather?.weatherType==="Thunderstorm"&&(B*=this.config.activity_bonuses.extreme_weather),A.weather?.isNight&&(B*=this.config.activity_bonuses.night_time),A.geolocation?.altitude&&A.geolocation.altitude>=2e3&&(B*=this.config.activity_bonuses.high_altitude),Math.min(B,3)}calculateGamingModifier(A){let B=1;if(B+=.25,A.currentGame){if(A.currentGame.genre){const g=A.currentGame.genre;(g.includes("Action")||g.includes("FPS"))&&(B+=.15),g.includes("RPG")&&(B+=.2),g.includes("Strategy")&&(B+=.1)}if(A.currentGame.partySize&&A.currentGame.partySize>1&&(B+=.15),A.currentGame.sessionDuration){const g=A.currentGame.sessionDuration/60;g>=1&&(B+=Math.min(.2,g*.05))}}return Math.min(B,1.75)}calculateRhythmGameModifier(A){let B=this.config.activity_bonuses.rhythm_game_base;if(A.currentCombo>0){const g=A.currentCombo/A.maxComboCap;B+=g*this.config.activity_bonuses.rhythm_game_combo}if(A.grooveHotness>0){const g=A.grooveHotness/100;B+=g*this.config.activity_bonuses.rhythm_game_groove}return Math.min(B,2.25)}getXPThresholdForLevel(A){if(A<1||A>20)throw new Error("Level must be between 1 and 20");return this.config.level_thresholds[A-1]}getXPToNextLevel(A){if(A<1||A>19)throw new Error("Current level must be between 1 and 19");const B=this.config.level_thresholds[A-1];return this.config.level_thresholds[A]-B}getLevelFromXP(A){for(let B=20;B>=1;B--)if(A>=this.config.level_thresholds[B-1])return B;return 1}isTrackMastered(A){return A>=this.config.track_mastery_threshold}getMasteryBonusXP(){return this.config.mastery_bonus_xp}getConfig(){return{...this.config}}}class sM{constructor(A){this.activeSessions=new Map,this.sessionHistory=[],this.sessionCounter=0,this.xpCalculator=A||new Ki}startSession(A,B,g){const I=`session-${this.sessionCounter++}-${A}-${Date.now()}`;return this.activeSessions.set(I,{track_uuid:A,start_time:Date.now(),track:B,environmental_context:g?.environmental_context,gaming_context:g?.gaming_context}),I}endSession(A,B,g){const I=this.activeSessions.get(A);if(!I)return null;const E=Date.now(),i=B||Math.max(1,Math.ceil((E-I.start_time)/1e3)),C={track_uuid:I.track_uuid,start_time:I.start_time,end_time:E,duration_seconds:Math.round(i),base_xp_earned:0,bonus_xp:0,activity_type:g,environmental_context:I.environmental_context,gaming_context:I.gaming_context,total_xp_earned:0};return C.base_xp_earned=Math.floor(C.duration_seconds*this.xpCalculator.getConfig().xp_per_second),C.total_xp_earned=this.xpCalculator.calculateSessionXP(C,I.track),C.bonus_xp=C.total_xp_earned-C.base_xp_earned,this.sessionHistory.push(C),this.activeSessions.delete(A),C}getActiveSession(A){return this.activeSessions.get(A)||null}getActiveSessionDuration(A){const B=this.activeSessions.get(A);return B?(Date.now()-B.start_time)/1e3:null}updateSessionContext(A,B){const g=this.activeSessions.get(A);return g?(B.environmental_context&&(g.environmental_context=B.environmental_context),B.gaming_context&&(g.gaming_context=B.gaming_context),!0):!1}getSessionHistory(){return[...this.sessionHistory]}getSessionsForTrack(A){return this.sessionHistory.filter(B=>B.track_uuid===A)}getTotalListeningTime(){return this.sessionHistory.reduce((A,B)=>A+B.duration_seconds,0)}getTotalXPEarned(){return this.sessionHistory.reduce((A,B)=>A+B.total_xp_earned,0)}getTrackListeningTime(A){return this.getSessionsForTrack(A).reduce((B,g)=>B+g.duration_seconds,0)}getTrackListenCount(A){return this.getSessionsForTrack(A).length}isTrackMastered(A,B=10){return this.getTrackListenCount(A)>=B}getSessionsInRange(A,B){return this.sessionHistory.filter(g=>g.start_time>=A&&g.end_time<=B)}getAverageSessionLength(){return this.sessionHistory.length===0?0:this.getTotalListeningTime()/this.sessionHistory.length}getLongestSession(){return this.sessionHistory.length===0?null:this.sessionHistory.reduce((A,B)=>B.duration_seconds>A.duration_seconds?B:A)}getTrackXPTotal(A){return this.getSessionsForTrack(A).reduce((B,g)=>B+g.total_xp_earned,0)}clearTrackSessions(A){const B=this.sessionHistory.length;return this.sessionHistory=this.sessionHistory.filter(g=>g.track_uuid!==A),B-this.sessionHistory.length}clearHistory(){this.sessionHistory=[]}clearActiveSessions(){this.activeSessions.clear()}getActiveSessionCount(){return this.activeSessions.size}getActiveSessionIds(){return Array.from(this.activeSessions.keys())}}const Ht=[4,8,12,16,19];class sB{static setUncappedConfig(A){this.uncappedConfig=A}static getUncappedConfig(){return this.uncappedConfig}static setStatManager(A){this.statManager=A}static getStatManager(){return this.statManager}static processLevelUp(A,B,g){const E=(A.gameMode||"standard")==="uncapped",i=E?1/0:20;if(B<1||B>i)throw new Error(`Level must be between 1 and ${i}`);const C=cB[A.class];if(!C)throw new Error(`Unknown class: ${A.class}`);const a=this.calculateHPIncrease(C.hit_die,A.ability_modifiers.CON,g),e=A.hp.max+a,o=this.getProficiencyBonus(B,E),t=o-A.proficiency_bonus,s={newLevel:B,hitPointIncrease:a,newHitPointsTotal:e,proficiencyBonusIncrease:t,newProficiencyBonus:o},G=E||Ht.includes(B);if(this.statManager&&G){const h=this.statManager.processLevelUp(A,B);h&&h.increases.length>0&&(s.abilityScoreIncreases=h.increases.map(c=>({ability:c.ability,increase:c.delta})),h.increases.length===1&&(s.abilityScoreIncrease={ability:h.increases[0].ability,increase:h.increases[0].delta}))}else Ht.includes(B)&&(s.abilityScoreIncrease={ability:"STR",increase:2});if(this.isSpellcaster(A.class)){const h=this.calculateSpellSlots(B),c={};for(const[d,l]of Object.entries(h))c[Number(d)]={total:l,used:0};s.newSpellSlots=c}const n=this.getClassFeaturesForLevel(A,A.class,B);if(n.length>0){s.classFeatures=n.map(h=>h.id),s.featureEffects=[];for(const h of n)if(h.effects&&h.effects.length>0){const c={...A,level:B},d=vg.applyFeatureEffects(c,h);d.applied&&s.featureEffects.push({featureId:h.id,featureName:h.name,effectsApplied:d.count})}}return s}static getClassFeaturesForLevel(A,B,g){const I=YA.getInstance(),E=I.getFeaturesForLevel(B,g),i={...A,level:g},C=[];for(const a of E){const e=I.validatePrerequisites(a,i);e.valid?C.push(a):(console.warn(`Feature "${a.name}" (level ${a.level}) failed prerequisite validation at level ${g}:`,e.errors),a.source==="default"&&C.push(a))}return C}static applyLevelUp(A,B){const g={...A};g.level=B.newLevel,g.hp.max=B.newHitPointsTotal,g.hp.current=Math.min(g.hp.current+B.hitPointIncrease,g.hp.max),g.proficiency_bonus=B.newProficiencyBonus;const I=g.gameMode==="uncapped"?1/0:20;if(B.abilityScoreIncreases&&B.abilityScoreIncreases.length>0)for(const E of B.abilityScoreIncreases){const i=E.ability;g.ability_scores[i]=Math.min(I,g.ability_scores[i]+E.increase);const C=g.ability_scores[i];g.ability_modifiers[i]=Math.floor((C-10)/2)}else if(B.abilityScoreIncrease){const E=B.abilityScoreIncrease.ability;g.ability_scores[E]=Math.min(I,g.ability_scores[E]+B.abilityScoreIncrease.increase);const i=g.ability_scores[E];g.ability_modifiers[E]=Math.floor((i-10)/2)}return B.newSpellSlots&&g.spells&&(g.spells.spell_slots=B.newSpellSlots),B.classFeatures&&(g.class_features=[...new Set([...g.class_features,...B.classFeatures])]),this.reapplyEquipmentEffects(g),g}static calculateHPIncrease(A,B,g){let I;return g?I=new QB(g).randomInt(1,A+1):I=Math.floor(Math.random()*A)+1,Math.max(1,I+B)}static isSpellcaster(A){return["Bard","Cleric","Druid","Paladin","Ranger","Sorcerer","Warlock","Wizard"].includes(A)}static calculateSpellSlots(A){const B={1:0};for(let g=1;g<=9;g++)B[g]=this.getSpellSlotCount(A,g);return B}static getSpellSlotCount(A,B){return B>Math.ceil(A/2)||A<1?0:A<3?B===1?2:0:A<5?B===1?3:B===2?2:0:A<7?B===1?4:B===2?3:0:A<9?B===1||B===2?4:B===3?2:0:Math.min(5,Math.ceil(A/2))}static getXPThreshold(A,B=!1){if(A<1)throw new Error("Level must be at least 1");if(!B){if(A>20)throw new Error("Standard mode only supports levels 1-20");return Jg[A]}if(this.uncappedConfig?.xpFormula)return this.uncappedConfig.xpFormula(A);if(A<=20)return Jg[A];let g=355e3;for(let I=21;I<=A;I++)g+=(I-1)*I*500;return g}static getProficiencyBonus(A,B=!1){if(!B){if(A<1||A>20)throw new Error("Standard proficiency bonus only defined for levels 1-20");return mE[A]}return this.uncappedConfig?.proficiencyBonusFormula?this.uncappedConfig.proficiencyBonusFormula(A):A<=4?2:A<=8?3:A<=12?4:A<=16?5:2+Math.floor((A-1)/4)}static calculateLevel(A,B=!1){if(!B){for(let I=20;I>=1;I--)if(A>=this.getXPThreshold(I,!1))return I;return 1}let g=1;for(;A>=this.getXPThreshold(g+1,!0);)g++;return g}static getXPToNextLevel(A,B=!1){if(!B&&A>=20)return 0;const g=this.getXPThreshold(A,B);return this.getXPThreshold(A+1,B)-g}static getProgressPercentage(A,B,g=!1){if(!g&&A>=20)return 100;const I=this.getXPThreshold(A,g),E=this.getXPThreshold(A+1,g),i=B-I,C=E-I;return Math.floor(i/C*100)}static processLevelUpWithoutStats(A,B,g){const E=(A.gameMode||"standard")==="uncapped",i=E?1/0:20;if(B<1||B>i)throw new Error(`Level must be between 1 and ${i}`);const C=cB[A.class];if(!C)throw new Error(`Unknown class: ${A.class}`);const a=this.calculateHPIncrease(C.hit_die,A.ability_modifiers.CON,g),e=A.hp.max+a,o=this.getProficiencyBonus(B,E),t=o-A.proficiency_bonus,s={newLevel:B,hitPointIncrease:a,newHitPointsTotal:e,proficiencyBonusIncrease:t,newProficiencyBonus:o};if(this.isSpellcaster(A.class)){const n=this.calculateSpellSlots(B),h={};for(const[c,d]of Object.entries(n))h[Number(c)]={total:d,used:0};s.newSpellSlots=h}const G=this.getClassFeaturesForLevel(A,A.class,B);if(G.length>0){s.classFeatures=G.map(n=>n.id),s.featureEffects=[];for(const n of G)if(n.effects&&n.effects.length>0){const h={...A,level:B},c=vg.applyFeatureEffects(h,n);c.applied&&s.featureEffects.push({featureId:n.id,featureName:n.name,effectsApplied:c.count})}}return s}static applyAutomaticBenefitsOnly(A,B){const g={...A};return g.level=B.newLevel,g.hp.max=B.newHitPointsTotal,g.hp.current=Math.min(g.hp.current+B.hitPointIncrease,g.hp.max),g.proficiency_bonus=B.newProficiencyBonus,B.newSpellSlots&&g.spells&&(g.spells.spell_slots=B.newSpellSlots),B.classFeatures&&(g.class_features=[...new Set([...g.class_features,...B.classFeatures])]),this.reapplyEquipmentEffects(g),g}static applyStatIncreasesOnly(A,B){const g={...A};g.ability_scores={...g.ability_scores},g.ability_modifiers={...g.ability_modifiers};const E=(g.gameMode||"standard")==="uncapped"?1/0:20;for(const i of B){const C=i.ability;g.ability_scores[C]=Math.min(E,g.ability_scores[C]+i.amount),g.ability_modifiers[C]=Math.floor((g.ability_scores[C]-10)/2)}return g}static reapplyEquipmentEffects(A){A.equipment&&HA.reapplyEquipmentEffects(A)}}class ft{constructor(){this.name="D&D 5e Standard"}selectIncreases(A,B,g){if(g?.forcedAbilities&&g.forcedAbilities.length>0){const I=[];for(const E of g.forcedAbilities)A.ability_scores[E]>=20||I.push({ability:E,amount:B/g.forcedAbilities.length});return I}return[]}requiresManualInput(A){return!A?.forcedAbilities||A.forcedAbilities.length===0}}class Nt{constructor(){this.name="D&D 5e Smart"}selectIncreases(A,B,g){const I=new Set(g?.excludedAbilities||[]),E=["STR","DEX","CON","INT","WIS","CHA"].filter(e=>!I.has(e)&&A.ability_scores[e]<20);if(E.length===0)return[];const C=cB[A.class]?.primary_ability||"STR";if(!I.has(C)&&A.ability_scores[C]<18&&E.includes(C)){if(B===2&&this.shouldSplit(A,C)){const e=this.findLowestStat(A,E,C);return[{ability:C,amount:1},{ability:e,amount:1}]}return[{ability:C,amount:B}]}if(B===2&&E.length>=2){const e=this.findLowestStat(A,E),o=this.findLowestStat(A,E.filter(t=>t!==e));return[{ability:e,amount:1},{ability:o,amount:1}]}return[{ability:this.findLowestStat(A,E),amount:B}]}requiresManualInput(){return!1}shouldSplit(A,B){const g=A.ability_scores[B];if(g<15)return!1;const I=Object.values(A.ability_scores);return Math.min(...I)<g-4}findLowestStat(A,B,g){let I=B[0],E=A.ability_scores[I];for(const i of B){if(i===g)continue;const C=A.ability_scores[i];C<E&&(I=i,E=C)}return I}}class Jt{constructor(){this.name="Balanced"}selectIncreases(A,B,g){const I=new Set(g?.excludedAbilities||[]),E=["STR","DEX","CON","INT","WIS","CHA"].filter(a=>!I.has(a)).filter(a=>A.ability_scores[a]<20).sort((a,e)=>A.ability_scores[a]-A.ability_scores[e]);if(E.length===0)return[];if(g?.forcedAbilities&&g.forcedAbilities.length>=2)return[{ability:g.forcedAbilities[0],amount:1},{ability:g.forcedAbilities[1],amount:1}];const i=[];let C=B;for(let a=0;a<E.length&&C>0;a++)i.push({ability:E[a],amount:1}),C--;return i}requiresManualInput(){return!1}}class qt{constructor(){this.name="Primary Only"}selectIncreases(A,B,g){const E=cB[A.class]?.primary_ability||"STR";return g?.excludedAbilities?.includes(E)?[{ability:this.findLowestStat(A,g?.excludedAbilities),amount:B}]:A.ability_scores[E]>=20?[{ability:this.findLowestStat(A,[E]),amount:B}]:[{ability:E,amount:B}]}requiresManualInput(){return!1}findLowestStat(A,B){const g=["STR","DEX","CON","INT","WIS","CHA"].filter(i=>!B?.includes(i)).filter(i=>A.ability_scores[i]<20);if(g.length===0)return"STR";let I=g[0],E=A.ability_scores[I];for(const i of g){const C=A.ability_scores[i];C<E&&(I=i,E=C)}return I}}class xt{constructor(){this.name="Random"}selectIncreases(A,B,g){const I=["STR","DEX","CON","INT","WIS","CHA"].filter(i=>!g?.excludedAbilities?.includes(i)).filter(i=>A.ability_scores[i]<20);if(I.length===0)return[];const E=[...I].sort(()=>Math.random()-.5);return B===2&&E.length>=2?[{ability:E[0],amount:1},{ability:E[1],amount:1}]:[{ability:E[0],amount:B}]}requiresManualInput(){return!1}}class Vt{constructor(){this.name="Manual"}selectIncreases(A,B,g){return[]}requiresManualInput(){return!0}}class GM{constructor(A,B){this.fn=A,this.name=B||"Custom Function"}selectIncreases(A,B,g){return this.fn(A,B,g)}requiresManualInput(){return!1}}function zi(Q){if(typeof Q!="string"&&typeof Q!="function")return Q;if(typeof Q=="function")return new GM(Q);switch(Q){case"dnD5e":return new ft;case"dnD5e_smart":return new Nt;case"balanced":return new Jt;case"primary_only":return new qt;case"random":return new xt;case"manual":return new Vt;default:const A=Q;throw new Error(`Unknown strategy type: ${A}`)}}const Lt=[4,8,12,16,19],Xt={strategy:"dnD5e",autoApply:!0};class vt{constructor(A){this.config=this.mergeWithDefaults(A),this.strategy=zi(this.config.strategy)}increaseStats(A,B,g="manual"){const I={...A},E=[],i=[];I.ability_scores={...I.ability_scores},I.ability_modifiers={...I.ability_modifiers};const a=(I.gameMode||"standard")==="uncapped"?1/0:this.config.maxStatCap??20;for(const{ability:e,amount:o}of B){const t=I.ability_scores[e],s=t+o,G=Math.min(s,a),n=G-t;n>0?(I.ability_scores[e]=G,I.ability_modifiers[e]=Math.floor((G-10)/2),E.push({ability:e,oldValue:t,newValue:G,delta:n})):s>a&&i.push({ability:e,attemptedValue:s,cappedAt:a})}return{character:I,increases:E,capped:i,source:g,timestamp:Date.now()}}decreaseStats(A,B,g="event"){const I=B.map(({ability:E,amount:i})=>({ability:E,amount:-i}));return this.increaseStats(A,I,g)}setStat(A,B,g,I="manual"){const E=A.ability_scores[B],i=g-E;return this.increaseStats(A,[{ability:B,amount:i}],I)}processLevelUp(A,B,g){const E=(A.gameMode||"standard")==="uncapped",i=this.config.statIncreaseLevels?.length?this.config.statIncreaseLevels:Lt;if(!(E||i.includes(B)))return null;const e=this.strategy.selectIncreases(A,2,g);return e.length===0?null:this.increaseStats(A,e,"level_up")}updateConfig(A){const B=this.config.strategy;this.config=this.mergeWithDefaults(A),A.strategy&&A.strategy!==B&&(this.strategy=zi(this.config.strategy))}getConfig(){return this.config}canIncrease(A,B,g=1){const I=A.ability_scores[B],i=(A.gameMode||"standard")==="uncapped"?1/0:this.config.maxStatCap??20;return I+g<=i}getStatCap(A,B){return(A.gameMode||"standard")==="uncapped"?1/0:this.config.maxStatCap??20}validateDnD5eStatSelection(A,B,g=2){const I=B.reduce((o,t)=>o+t.amount,0);if(I!==g)return{error:`Total stat increase must be ${g}, got ${I}`,reason:"invalid_amount",allowedPatterns:[`+${g} to one ability`,"+1 to two abilities"]};const E=[{count:1,amount:g},{count:2,amount:1}];if(!E.some(o=>B.length===o.count&&B.every(t=>t.amount===o.amount)))return{error:"Invalid pattern: must be +2 to one ability or +1 to two abilities",reason:"wrong_pattern",allowedPatterns:E.map(o=>o.count===1?`+${o.amount} to one ability`:"+1 to two abilities")};const C=this.getStatCap(A,"STR");for(const o of B){const t=A.ability_scores[o.ability];if(t+o.amount>C&&C!==1/0)return{error:`${o.ability} would exceed stat cap of ${C}`,reason:"exceeds_cap",allowedPatterns:[`Choose different abilities or use ${C-t} points`]}}const a=["STR","DEX","CON","INT","WIS","CHA"];for(const o of B)if(!a.includes(o.ability))return{error:`Invalid ability: ${o.ability}`,reason:"invalid_ability",allowedPatterns:a};const e=new Set;for(const o of B){if(e.has(o.ability))return{error:`Duplicate ability: ${o.ability}`,reason:"duplicate_ability",allowedPatterns:["Each ability can only be increased once"]};e.add(o.ability)}return{valid:!0}}mergeWithDefaults(A){return{maxStatCap:A?.maxStatCap??20,strategy:A?.strategy??Xt.strategy,autoApply:A?.autoApply??Xt.autoApply,statIncreaseLevels:A?.statIncreaseLevels??Lt}}}const bC=class bC{static getPlaysThreshold(A){const B=this.customThresholds.get(A);return B?.playsThreshold!==void 0&&B.playsThreshold!==null?B.playsThreshold:Math.floor(oa*Math.pow(GE,A))}static getXPThreshold(A){const B=this.customThresholds.get(A);return B?.xpThreshold!==void 0&&B.xpThreshold!==null?B.xpThreshold:Math.floor(ta*Math.pow(GE,A))}static setCustomThresholds(A,B){this.customThresholds.set(A,{...B})}static clearCustomThresholds(A){A!==void 0?this.customThresholds.delete(A):this.customThresholds.clear()}static hasCustomThresholds(A){return this.customThresholds.has(A)}static getCustomThresholds(A){return this.customThresholds.get(A)}static isMastered(A,B,g){const I=this.getPlaysThreshold(g),E=this.getXPThreshold(g);return A>=I&&B>=E}static canPrestige(A,B,g){return A>=Wg?!1:this.isMastered(B,g,A)}static isJustMastered(A,B,g,I,E){const i=this.isMastered(A,g,E),C=this.isMastered(B,I,E);return!i&&C}static calculateMasteryBonus(A){return A?ja:0}static getPrestigeInfo(A,B,g){const I=this.getPlaysThreshold(A),E=this.getXPThreshold(A),i=this.isMastered(B,g,A),C=A>=Wg;return{prestigeLevel:A,currentPlays:B,currentXP:g,playsThreshold:I,xpThreshold:E,playsProgress:Math.min(1,B/I),xpProgress:Math.min(1,g/E),isMastered:i,canPrestige:i&&!C,isMaxPrestige:C}}static toRomanNumeral(A){return ea[A]}static getNextPrestigeLevel(A){return A>=Wg?null:sa(A+1)}static createSuccessResult(A,B){const g=this.toRomanNumeral(B);return{success:!0,newPrestigeLevel:B,previousPrestigeLevel:A,message:`Successfully prestiged to level ${g}! New mastery requirements: ${this.getPlaysThreshold(B)} plays, ${this.getXPThreshold(B).toLocaleString()} XP`}}static createFailureResult(A,B){return{success:!1,newPrestigeLevel:B,previousPrestigeLevel:B,message:`Prestige failed: ${A}`}}static getAllThresholds(){const A=[];for(let B=0;B<=Wg;B++){const g=B;A.push({level:g,plays:this.getPlaysThreshold(g),xp:this.getXPThreshold(g)})}return A}};bC.customThresholds=new Map;let WA=bC;class nM{constructor(A){this.xpCalculator=new Ki,this.statManager=A,this.statManager&&sB.setStatManager(this.statManager)}addXP(A,B,g="custom"){const I={...A,xp:{...A.xp}};if(I.xp.current+=B,!this.statManager){const t=(I.gameMode||"standard")==="standard"?"dnD5e":"dnD5e_smart";this.statManager=new vt({strategy:t}),sB.setStatManager(this.statManager)}let E=!1,i;const C=[],a=I.gameMode==="uncapped",e=sB.calculateLevel(I.xp.current,a);if(e>I.level){E=!0,i=e;for(let o=I.level+1;o<=e;o++)if(this.isStatIncreaseLevel(o,a)&&this.statManager.getConfig().strategy==="dnD5e"){I.pendingStatIncreases=(I.pendingStatIncreases||0)+1;const G=sB.processLevelUpWithoutStats(I,o),n=sB.applyAutomaticBenefitsOnly(I,G),h={fromLevel:o-1,toLevel:o,hpIncrease:G.hitPointIncrease,newMaxHP:G.newHitPointsTotal,proficiencyIncrease:G.proficiencyBonusIncrease,newProficiency:G.newProficiencyBonus,statIncreases:void 0,featuresGained:G.classFeatures,newSpellSlots:G.newSpellSlots};C.push(h),I.level=o,I.hp=n.hp,I.proficiency_bonus=n.proficiency_bonus,I.class_features=n.class_features,n.spells&&(I.spells=n.spells)}else{const G={...I.ability_scores},n=[...I.class_features],h=sB.processLevelUp(I,o),c=sB.applyLevelUp(I,h),d={fromLevel:o-1,toLevel:o,hpIncrease:h.hitPointIncrease,newMaxHP:h.newHitPointsTotal,proficiencyIncrease:h.proficiencyBonusIncrease,newProficiency:h.newProficiencyBonus,statIncreases:h.abilityScoreIncreases?.map(l=>({ability:l.ability,oldValue:G[l.ability],newValue:G[l.ability]+l.increase,delta:l.increase})),featuresGained:h.classFeatures?.filter(l=>!n.includes(l)),newSpellSlots:h.newSpellSlots};C.push(d),I.level=c.level,I.hp=c.hp,I.ability_scores=c.ability_scores,I.ability_modifiers=c.ability_modifiers,I.proficiency_bonus=c.proficiency_bonus,I.class_features=c.class_features,c.spells&&(I.spells=c.spells)}}return!a&&I.level>=20?I.xp.next_level=0:I.xp.next_level=sB.getXPThreshold(I.level+1,a),{character:I,xpEarned:B,leveledUp:E,newLevel:i,levelUpDetails:C.length>0?C:void 0}}addRhythmXP(A,B,g="rhythm_game"){const I=this.addXP(A,B.finalXP,g);return{character:I.character,xpEarned:I.xpEarned,leveledUp:I.leveledUp,newLevel:I.newLevel,levelUpDetails:I.levelUpDetails}}isStatIncreaseLevel(A,B){return B?!0:[4,8,12,16,19].includes(A)}updateCharacterFromSession(A,B,g,I){const E=I?.previousListenCount??0,i=I?.previousXP??0,C=I?.prestigeLevel??A.prestige_level??0,a=this.xpCalculator.calculateSessionXP(B,g);let e=!1,o=0;if(g){const G=E+1,n=i+a;WA.isJustMastered(E,G,i,n,C)&&(e=!0,o=WA.calculateMasteryBonus(!0))}const t=a+o;return{...this.addXP(A,t,"listening"),masteredTrack:e,masteryBonusXP:o}}applyPendingStatIncrease(A,B,g){const I=A.pendingStatIncreases||0;if(I===0)throw new Error("No pending stat increases to apply");let E;if(g&&g.length>0){if(g.length>1)throw new Error("Only one secondary stat allowed for +1/+1 distribution");E=[{ability:B,amount:1},{ability:g[0],amount:1}]}else E=[{ability:B,amount:2}];const i=this.statManager.validateDnD5eStatSelection(A,E);if("error"in i)throw new Error(i.error);const C=sB.applyStatIncreasesOnly(A,E);C.pendingStatIncreases=I-1,C.pendingStatIncreases===0&&delete C.pendingStatIncreases;const a=E.map(e=>({ability:e.ability,oldValue:A.ability_scores[e.ability],newValue:C.ability_scores[e.ability],delta:e.amount}));return{character:C,statIncreases:a,remainingPending:C.pendingStatIncreases||0,timestamp:Date.now()}}hasPendingStatIncreases(A){return!!(A.pendingStatIncreases&&A.pendingStatIncreases>0)}getPendingStatIncreaseCount(A){return A.pendingStatIncreases||0}resetCharacterForPrestige(A,B,g,I,E){const i=A.prestige_level??0;if(i>=10)return WA.createFailureResult("Already at maximum prestige level",i);const C=B.getTrackListenCount(g),a=B.getTrackXPTotal(g);if(!WA.canPrestige(i,C,a)){const G=WA.getPlaysThreshold(i),n=WA.getXPThreshold(i);return C<G&&a<n?WA.createFailureResult(`Need ${G-C} more plays and ${(n-a).toLocaleString()} more XP to prestige`,i):C<G?WA.createFailureResult(`Need ${G-C} more plays to prestige (XP requirement met)`,i):WA.createFailureResult(`Need ${(n-a).toLocaleString()} more XP to prestige (plays requirement met)`,i)}const e=A.equipment?{weapons:[...A.equipment.weapons],armor:[...A.equipment.armor],items:[...A.equipment.items],totalWeight:A.equipment.totalWeight,equippedWeight:A.equipment.equippedWeight}:null;B.clearTrackSessions(g);const o=WA.getNextPrestigeLevel(i);if(o===null)return WA.createFailureResult("Cannot prestige beyond maximum level",i);const t=gQ.generate(A.seed,I,E,{level:1,gameMode:A.gameMode,forceName:A.name});return e&&(t.equipment=e),t.prestige_level=o,{...WA.createSuccessResult(i,o),character:t}}canPrestige(A,B,g){const I=A.prestige_level??0,E=B.getTrackListenCount(g),i=B.getTrackXPTotal(g);return WA.canPrestige(I,E,i)}getPrestigeInfo(A,B,g){const I=A.prestige_level??0,E=B.getTrackListenCount(g),i=B.getTrackXPTotal(g);return WA.getPrestigeInfo(I,E,i)}}class hM{constructor(A){this.sessionTotals=null,this.sessionStartTime=null,this.config=WI(A)}calculateButtonPressXP(A,B){const g=B?.comboLength??0,I=B?.grooveHotness,E=this.getBaseXP(A),i=E*this.config.xpRatio,C=this.config.combo.enabled?this.getComboMultiplier(g):1;let a=0;this.config.groove.perHitMultiplier&&I!==void 0&&I>0&&(a=I/100*this.config.groove.perHitScale);const e=Math.min(C+a,this.config.maxMultiplier),o=E*e,t=i*e,s=Math.max(0,t);return{scorePoints:E,baseXP:i,comboMultiplier:C,grooveMultiplier:a,totalMultiplier:e,finalScore:o,finalXP:s,breakdown:{accuracy:A,comboLength:g,grooveHotness:I}}}calculateComboEndBonus(A){if(!this.config.combo.endBonus.enabled||A<=0)return{comboLength:A,bonusScore:0,bonusXP:0};const B=this.config.combo.endBonus.formula?this.config.combo.endBonus.formula(A):A*2,g=B*this.config.xpRatio;return{comboLength:A,bonusScore:B,bonusXP:g}}calculateGrooveEndBonus(A){if(!this.config.groove.endBonus.enabled)return{bonusScore:0,bonusXP:0};if(A.maxStreak<2)return{bonusScore:0,bonusXP:0};const{maxStreakWeight:g,avgHotnessWeight:I,durationWeight:E}=this.config.groove.endBonus,i=A.maxStreak*g,C=A.avgHotness*I,a=A.duration*E,e=i+C+a,o=e*this.config.xpRatio;return{bonusScore:e,bonusXP:o}}getBaseXP(A){return this.config.baseXP[A]}getComboMultiplier(A){if(!this.config.combo.enabled)return 1;const B=this.config.combo.formula?this.config.combo.formula(A):this.defaultComboFormula(A);return Math.min(B,this.config.combo.cap)}defaultComboFormula(A){return 1+A/25}startSession(){this.sessionTotals=this.createEmptyTotals(),this.sessionStartTime=Date.now()}recordHit(A,B){this.sessionTotals||this.startSession();const g=this.calculateButtonPressXP(A,B);this.sessionTotals.totalScore+=g.finalScore,this.sessionTotals.totalXP+=g.finalXP;const I=B?.comboLength??0;return I>this.sessionTotals.maxCombo&&(this.sessionTotals.maxCombo=I),this.sessionTotals.accuracyDistribution[A]++,this.recalculateAccuracyPercentage(),g}getSessionTotals(){return!this.sessionTotals||!this.sessionStartTime?null:{...this.sessionTotals,duration:(Date.now()-this.sessionStartTime)/1e3}}endSession(){const A=this.getSessionTotals();return this.sessionTotals=null,this.sessionStartTime=null,A}createEmptyTotals(){return{totalScore:0,totalXP:0,maxCombo:0,accuracyDistribution:{perfect:0,great:0,good:0,ok:0,miss:0,wrongKey:0},accuracyPercentage:0,duration:0}}recalculateAccuracyPercentage(){if(!this.sessionTotals)return;const{perfect:A,great:B,good:g,ok:I,miss:E,wrongKey:i}=this.sessionTotals.accuracyDistribution,C=A+B+g+I+E+i;if(C===0){this.sessionTotals.accuracyPercentage=0;return}const a=A+B+g+I;this.sessionTotals.accuracyPercentage=a/C*100}getConfig(){return this.config}updateConfig(A){this.config=WI({...this.config,...A})}}class cM{static enchant(A,B,g,I){return uI.validateModification(g).valid?this.addModificationToEquipment(A,B,g,I):A}static applyTemplate(A,B,g,I){const C=AA.getInstance().get("equipment.templates").find(t=>t.id===g);if(!C)return A;const a={id:`template_${g}_${Date.now()}`,name:C.name||`Template: ${g}`,properties:C.properties||[],addsFeatures:C.addsFeatures,addsSkills:C.addsSkills,addsSpells:C.addsSpells,appliedAt:new Date().toISOString(),source:"template"},e=this.addModificationToEquipment(A,B,a,I),o=this.findItem(e,B);return o&&(o.templateId=g),e}static curse(A,B,g,I){return uI.validateModification(g).valid?this.addModificationToEquipment(A,B,g,I):A}static upgrade(A,B,g,I){return this.enchant(A,B,g,I)}static removeModification(A,B,g,I){const E={weapons:A.weapons.map(t=>({...t,modifications:t.modifications?[...t.modifications]:[]})),armor:A.armor.map(t=>({...t,modifications:t.modifications?[...t.modifications]:[]})),items:A.items.map(t=>({...t,modifications:t.modifications?[...t.modifications]:[]})),totalWeight:A.totalWeight,equippedWeight:A.equippedWeight},i=this.findItem(E,B);if(!i||!i.modifications)return A;const C=i.equipped,a=i.instanceId,e=this.getEquipmentData(B);C&&I&&e&&HA.unequipItem(I,B,a);const o=i.modifications.findIndex(t=>t.id===g);if(o!==-1&&i.modifications.splice(o,1),C&&I&&e){HA.equipItem(I,e,a);for(const t of i.modifications)this.applyModificationEffects(I,e,t,a)}return E}static getModificationHistory(A,B){const g=this.findItem(A,B);return!g||!g.modifications?[]:g.modifications.map(I=>({...I}))}static getCombinedEffects(A,B,g){const I=this.getEquipmentData(B);if(!I)return[];const E=I.properties?[...I.properties]:[],i=this.findItem(A,B);if(i&&i.modifications)for(const C of i.modifications)C.properties&&E.push(...C.properties);return E}static hasTemplate(A,B,g){const I=this.findItem(A,B);return I?I.templateId===g?!0:I.modifications?I.modifications.some(E=>E.source==="template"&&E.name.includes(g)):!1:!1}static getAppliedTemplates(A,B){const g=[],I=this.findItem(A,B);if(!I)return g;if(I.templateId&&g.push(I.templateId),I.modifications){for(const E of I.modifications)if(E.source==="template"){const i=E.id.match(/template_([^_]+)/);i&&g.push(i[1])}}return g}static removeAllModifications(A,B,g){const I=this.findItem(A,B);if(!I||!I.modifications||I.modifications.length===0)return A;const E=I.modifications.map(C=>C.id);let i=A;for(const C of E)i=this.removeModification(i,B,C,g);return i}static disenchant(A,B,g){const I=this.findItem(A,B);if(!I||!I.modifications)return A;const E=I.modifications.filter(C=>C.source==="enchantment"||C.source==="upgrade").map(C=>C.id);let i=A;for(const C of E)i=this.removeModification(i,B,C,g);return i}static liftCurse(A,B,g){const I=this.findItem(A,B);if(!I||!I.modifications)return A;const E=I.modifications.filter(C=>C.source==="curse").map(C=>C.id);let i=A;for(const C of E)i=this.removeModification(i,B,C,g);return i}static addModificationToEquipment(A,B,g,I){const E={weapons:A.weapons.map(o=>({...o,modifications:o.modifications?[...o.modifications]:[]})),armor:A.armor.map(o=>({...o,modifications:o.modifications?[...o.modifications]:[]})),items:A.items.map(o=>({...o,modifications:o.modifications?[...o.modifications]:[]})),totalWeight:A.totalWeight,equippedWeight:A.equippedWeight},i=this.findItem(E,B);if(!i)return A;const C=this.getEquipmentData(B);if(!C)return A;i.modifications||(i.modifications=[]),i.instanceId||(i.instanceId=`${B}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`);const a=i.equipped,e=i.instanceId;if(a&&I&&HA.unequipItem(I,B,e),i.modifications.push(g),a&&I){HA.equipItem(I,C,e);for(const o of i.modifications)this.applyModificationEffects(I,C,o,e)}return E}static findItem(A,B){return A.weapons.find(I=>I.name===B)||A.armor.find(I=>I.name===B)||A.items.find(I=>I.name===B)}static getEquipmentData(A){return AA.getInstance().get("equipment").find(I=>I.name===A)}static applyModificationEffects(A,B,g,I){const E={...B,properties:[...B.properties||[],...g.properties],grantsFeatures:g.addsFeatures,grantsSkills:g.addsSkills,grantsSpells:g.addsSpells};HA.equipItem(A,E,I)}static createModification(A,B,g,I){return{id:A,name:B,properties:g,appliedAt:new Date().toISOString(),source:I}}static createFeatureModification(A,B,g,I,E){return{id:A,name:B,properties:g,addsFeatures:I,appliedAt:new Date().toISOString(),source:E}}static createSkillModification(A,B,g,I,E){return{id:A,name:B,properties:g,addsSkills:I,appliedAt:new Date().toISOString(),source:E}}static createSpellModification(A,B,g,I,E){return{id:A,name:B,properties:g,addsSpells:I,appliedAt:new Date().toISOString(),source:E}}static generateModificationId(A="mod"){return`${A}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}static getModificationSources(A,B){const g=this.findItem(A,B);if(!g||!g.modifications)return[];const I=new Set(g.modifications.map(E=>E.source));return Array.from(I)}static countModificationsBySource(A,B){const g=this.findItem(A,B);if(!g||!g.modifications)return{};const I={};for(const E of g.modifications)I[E.source]=(I[E.source]||0)+1;return I}static countModificationsForSource(A,B,g){return this.countModificationsBySource(A,B)[g]||0}static isCursed(A,B){return this.countModificationsForSource(A,B,"curse")>0}static isEnchanted(A,B){return this.getModificationSources(A,B).some(I=>I==="enchantment"||I==="upgrade")}static getItemSummary(A,B){const g=this.findItem(A,B);return g?{name:g.name,quantity:g.quantity,equipped:g.equipped,instanceId:g.instanceId,templateId:g.templateId,modificationCount:g.modifications?.length||0,isCursed:this.isCursed(A,B),isEnchanted:this.isEnchanted(A,B),sources:this.getModificationSources(A,B),effects:this.getCombinedEffects(A,B)}:null}}const rQ={plus_one_weapon:{rarity:"uncommon",properties:[{type:"passive_modifier",target:"attack_roll",value:1,description:"+1 to attack and damage rolls",stackable:!0}],tags:["magic","enhanced","+1"]},plus_two_weapon:{rarity:"rare",properties:[{type:"passive_modifier",target:"attack_roll",value:2,description:"+2 to attack and damage rolls",stackable:!0}],tags:["magic","enhanced","+2"]},plus_three_weapon:{rarity:"very_rare",properties:[{type:"passive_modifier",target:"attack_roll",value:3,description:"+3 to attack and damage rolls",stackable:!0}],tags:["magic","enhanced","+3"]},flaming_weapon_template:{rarity:"rare",properties:[{type:"damage_bonus",target:"fire_damage",value:"1d6",description:"+1d6 fire damage",condition:{type:"on_hit",value:!0}},{type:"special_property",target:"light",value:"bright_light_20ft",description:"Sheds bright light 20ft, dim 20ft"}],tags:["magic","fire","flaming"]},frost_weapon_template:{rarity:"rare",properties:[{type:"damage_bonus",target:"cold_damage",value:"1d6",description:"+1d6 cold damage",condition:{type:"on_hit",value:!0}}],tags:["magic","cold","frost"]},shocking_weapon_template:{rarity:"rare",properties:[{type:"damage_bonus",target:"lightning_damage",value:"1d6",description:"+1d6 lightning damage",condition:{type:"on_hit",value:!0}}],tags:["magic","lightning","shocking"]},vicious_weapon_template:{rarity:"rare",properties:[{type:"passive_modifier",target:"attack_roll",value:1,description:"+1 to attack and damage rolls"},{type:"damage_bonus",target:"extra_damage_on_hit",value:"1d8",description:"Deals 1d8 extra damage, but wielder takes 1d8 necrotic",condition:{type:"on_hit",value:!0}}],tags:["magic","vicious","necrotic"]},plus_one_armor:{rarity:"rare",properties:[{type:"passive_modifier",target:"ac",value:1,description:"+1 AC bonus",stackable:!0}],tags:["magic","enhanced","+1","armor"]},plus_two_armor:{rarity:"very_rare",properties:[{type:"passive_modifier",target:"ac",value:2,description:"+2 AC bonus",stackable:!0}],tags:["magic","enhanced","+2","armor"]}};class Kt{static openBox(A,B,g){if(!A.boxContents)return{success:!1,items:[],gold:0,consumeBox:!1,error:{code:"NO_BOX_CONTENTS",message:`Box "${A.name}" has no contents defined.`}};if(g!==void 0&&A.boxContents.openRequirements){const e=this.checkRequirements(A,g);if(e)return{success:!1,items:[],gold:0,consumeBox:!1,error:e}}const I=A.boxContents,E=[];let i=0;for(const e of I.drops){const o=this.selectFromPool(e.pool,B);if(o){if(o.gold!==void 0&&o.gold>0)i+=o.gold;else if(o.itemName){const t=this.getEquipmentData(o.itemName);if(t){const s=o.quantity??1;if(t.type==="box")E.push({...t});else for(let G=0;G<s;G++)E.push(t)}else console.warn(`BoxOpener: Item "${o.itemName}" not found in equipment registry. Skipping drop.`)}}}const C=I.consumeOnOpen!==!1;let a;return g!==void 0&&I.openRequirements&&(a=I.openRequirements.map(e=>({name:e.itemName,quantity:e.quantity??1}))),{success:!0,items:E,gold:i,consumeBox:C,consumedItems:a}}static selectFromPool(A,B){if(!A||A.length===0)return;const g=A.map(I=>[I,I.weight]);return B.weightedChoice(g)}static getEquipmentData(A){return AA.getInstance().get("equipment").find(I=>I.name===A)}static isBox(A){return A.type==="box"&&A.boxContents!==void 0}static checkRequirements(A,B){if(!A.boxContents?.openRequirements)return null;const g=A.boxContents.openRequirements;for(const I of g){const E=I.quantity??1,i=B.find(C=>C.name===I.itemName);if(!i)return{code:"MISSING_ITEM",message:`Missing required item: ${I.itemName}`,requirement:I};if(i.quantity<E)return{code:"INSUFFICIENT_QUANTITY",message:`Insufficient ${I.itemName}: have ${i.quantity}, need ${E}`,requirement:I}}return null}static canOpen(A,B){return A.boxContents?.openRequirements?this.checkRequirements(A,B)===null:!0}static getRequirementsDescription(A){if(!A.boxContents?.openRequirements||A.boxContents.openRequirements.length===0)return null;const B=A.boxContents.openRequirements,g=[];for(const I of B){const E=I.quantity??1;E===1?g.push(I.itemName):g.push(`${E} ${I.itemName}`)}return`Requires: ${g.join(", ")}`}static previewContents(A){if(!A.boxContents)return{possibleItems:[],possibleGold:{min:0,max:0},totalDrops:0};const B=new Set;let g=0,I=0;for(const i of A.boxContents.drops)for(const C of i.pool)C.itemName&&B.add(C.itemName),C.gold!==void 0&&(g=Math.min(g,C.gold),I=Math.max(I,C.gold));const E={possibleItems:Array.from(B),possibleGold:{min:g,max:I},totalDrops:A.boxContents.drops.length};return A.boxContents.openRequirements&&A.boxContents.openRequirements.length>0&&(E.openRequirements=A.boxContents.openRequirements),E}}const Fg={common:0,uncommon:1,rare:2,very_rare:3,legendary:4};class rM{static spawnFromList(A,B){const I=AA.getInstance().get("equipment");let E=A;return B&&(E=B.shuffle([...A])),E.map(i=>I.find(C=>C.name===i))}static spawnByRarity(A,B,g){const i=AA.getInstance().get("equipment").filter(t=>t.rarity===A&&(t.spawnWeight??1)>0);if(i.length===0)return[];const C=Math.min(B,i.length),a=[],e=g?()=>g.random():Math.random,o=[...i];for(let t=o.length-1;t>0;t--){const s=Math.floor(e()*(t+1));[o[t],o[s]]=[o[s],o[t]]}for(let t=0;t<C;t++)a.push(o[t]);return a}static spawnByTags(A,B,g,I){const C=AA.getInstance().get("equipment").filter(t=>{if(I?.excludeZeroWeight&&(t.spawnWeight??1)<=0||I?.includeTypes&&I.includeTypes.length>0&&!I.includeTypes.includes(t.type))return!1;const s=Fg[t.rarity];return I?.minRarity&&s<Fg[I.minRarity]||I?.maxRarity&&s>Fg[I.maxRarity]?!1:t.tags&&A.some(G=>t.tags.includes(G))});if(C.length===0)return[];const a=Math.min(B,C.length),e=[],o=g?()=>g.random():Math.random;for(let t=0;t<a;t++){const s=C.map(c=>[c,c.spawnWeight??1]);let G=s.reduce((c,[,d])=>c+d,0),n=o()*G,h;for(const[c,d]of s)if(n-=d,n<=0){h=c;break}if(h){e.push(h);const c=C.indexOf(h);c>-1&&C.splice(c,1)}}return e}static spawnRandom(A,B,g){const i=AA.getInstance().get("equipment").filter(a=>{const e=a.spawnWeight??1;if(g?.excludeZeroWeight&&e<=0||g?.includeTypes&&g.includeTypes.length>0&&!g.includeTypes.includes(a.type))return!1;const o=Fg[a.rarity];return g?.minRarity&&o<Fg[g.minRarity]||g?.maxRarity&&o>Fg[g.maxRarity]?!1:e>0});if(i.length===0)return[];const C=[];for(let a=0;a<A&&i.length>0;a++){const e=i.map(h=>[h,h.spawnWeight??1]),o=e.reduce((h,[,c])=>h+c,0);let t=B.random()*o,s=0;for(let h=0;h<e.length;h++)if(t-=e[h][1],t<=0){s=h;break}const G=e[s][0];C.push(G);const n=i.indexOf(G);n>-1&&i.splice(n,1)}return C}static spawnFromTemplate(A,B){const g=AA.getInstance();let I;const E=g.get("equipment.templates");if(Array.isArray(E)?I=E.find(C=>C.id===A||C.name===A):E&&typeof E=="object"&&(I=E[A]),!I&&A in rQ&&(I=rQ[A]),!I)return null;let i;return B?i=g.get("equipment").find(a=>a.name===B):i=this.getDefaultItemForTemplate(I),i?{...i,...I,name:`${i.name} (${A.replace(/_/g," ")})`,templateId:A,properties:[...i.properties||[],...I.properties||[]],tags:[...i.tags||[],...I.tags||[]]}:null}static spawnTreasureHoard(A,B){const g=[];let I=0;const E=this.getTreasureItemCount(A,B);for(const[i,C]of Object.entries(E))for(let a=0;a<C;a++){const e=i,o=this.spawnByRarity(e,1,B);o.length>0&&(g.push(o[0]),I+=this.estimateItemValue(o[0]))}if(g.length===0){const i=this.spawnByRarity("common",1,B);i.length>0&&(g.push(i[0]),I+=this.estimateItemValue(i[0]))}return{items:g,totalValue:I,cr:A}}static addToCharacter(A,B,g=!1){A.equipment||(A.equipment={weapons:[],armor:[],items:[],totalWeight:0,equippedWeight:0});for(const I of B){const E={name:I.name,quantity:1,equipped:g,instanceId:`${I.name}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`};switch(I.type){case"weapon":A.equipment.weapons.push(E);break;case"armor":A.equipment.armor.push(E);break;case"item":case"box":A.equipment.items.push(E);break}A.equipment.totalWeight+=I.weight,g&&(A.equipment.equippedWeight+=I.weight)}return A}static openBoxForCharacter(A,B,g){if(!A.equipment?.items||A.equipment.items.findIndex(t=>t.name===B)===-1)return null;const C=AA.getInstance().get("equipment").find(t=>t.name===B);if(!C||C.type!=="box")return null;const a=A.equipment.items,e=Kt.openBox(C,g,a);if(!e.success)return{character:A,result:e};if(e.consumedItems&&e.consumedItems.length>0)for(const t of e.consumedItems)A=this.consumeItemFromCharacter(A,t.name,t.quantity).character;if(e.consumeBox&&A.equipment?.items){const t=A.equipment.items.findIndex(s=>s.name===B);t!==-1&&(A.equipment.items.splice(t,1),A.equipment.totalWeight-=C.weight)}const o=e.items;return A=this.addToCharacter(A,o,!1),{character:A,result:e}}static consumeItemFromCharacter(A,B,g){if(!A.equipment?.items)return{success:!1,character:A};const I=A.equipment.items.findIndex(o=>o.name===B);if(I===-1)return{success:!1,character:A};const E=A.equipment.items[I];if(E.quantity<g)return{success:!1,character:A};const e=AA.getInstance().get("equipment").find(o=>o.name===B)?.weight??0;return E.quantity-=g,A.equipment.totalWeight-=e*g,E.quantity<=0&&A.equipment.items.splice(I,1),{success:!0,character:A}}static getDefaultItemForTemplate(A){const g=AA.getInstance().get("equipment");let I="item";return A.tags&&(A.tags.includes("weapon")?I="weapon":A.tags.includes("armor")&&(I="armor")),g.find(E=>E.type===I)}static getTreasureItemCount(A,B){const g={},I=()=>B.random();return A<5?(g.common=Math.floor(I()*3)+1,g.uncommon=I()>.7?1:0):A<11?(g.common=Math.floor(I()*2)+1,g.uncommon=Math.floor(I()*2)+1,g.rare=I()>.6?1:0):A<17?(g.uncommon=Math.floor(I()*2)+1,g.rare=Math.floor(I()*2)+1,g.very_rare=I()>.7?1:0):(g.rare=Math.floor(I()*2)+1,g.very_rare=Math.floor(I()*2)+1,g.legendary=I()>.5?1:0),g}static estimateItemValue(A){let g={common:50,uncommon:400,rare:4e3,very_rare:4e4,legendary:2e5}[A.rarity]||50;return A.type==="weapon"?g*=1.2:A.type==="armor"&&(g*=1.5),A.weight>20&&(g*=1.1),Math.round(g)}}const Ti="geolocation_cache";class zt{constructor(A,B){if(this.cache=null,this.cacheTTL=300*1e3,this.cacheStats={hits:0,misses:0},this.logger=SA.for("GeolocationProvider"),typeof A=="number"||A===void 0)this.cacheTTL=(A??5)*60*1e3,this.useLocalStorage=(B??!0)&&this.isLocalStorageAvailable();else{const g=A;this.cacheTTL=g.cacheTTL??300*1e3,this.useLocalStorage=(g.useLocalStorage??!0)&&this.isLocalStorageAvailable()}this.useLocalStorage&&this.loadFromLocalStorage()}isLocalStorageAvailable(){try{const A="__localStorage_test__";return localStorage.setItem(A,A),localStorage.removeItem(A),!0}catch{return!1}}loadFromLocalStorage(){try{const A=localStorage.getItem(Ti);if(A){const B=JSON.parse(A),g=Date.now(),I=Object.values(B)[0];I&&g-I.timestamp<this.cacheTTL&&(this.cache=I)}}catch(A){this.logger.warn("Failed to load geolocation cache from localStorage",{error:A})}}saveToLocalStorage(){if(!(!this.useLocalStorage||!this.cache))try{const A={position:this.cache};localStorage.setItem(Ti,JSON.stringify(A))}catch(A){this.logger.warn("Failed to save geolocation cache to localStorage",{error:A})}}isCacheEntryValid(A){return A?Date.now()-A.timestamp<this.cacheTTL:!1}getCacheAge(){return this.cache?Date.now()-this.cache.timestamp:null}async getCurrentPosition(A=!1){return!A&&this.isCacheEntryValid(this.cache)?(this.cacheStats.hits++,this.cache.data):(this.cacheStats.misses++,typeof navigator>"u"||!navigator.geolocation?null:new Promise(B=>{navigator.geolocation.getCurrentPosition(g=>{const I={latitude:g.coords.latitude,longitude:g.coords.longitude,altitude:g.coords.altitude,accuracy:g.coords.accuracy,heading:g.coords.heading,speed:g.coords.speed,timestamp:g.timestamp};this.cache={data:I,timestamp:Date.now()},this.saveToLocalStorage(),B(I)},g=>{this.logger.warn("Geolocation error",{error:g.message}),B(null)},{enableHighAccuracy:!0,timeout:5e3,maximumAge:0})}))}invalidateCache(){if(this.cache=null,this.useLocalStorage)try{localStorage.removeItem(Ti)}catch(A){this.logger.warn("Failed to clear geolocation cache from localStorage",{error:A})}}getCacheStats(){return{...this.cacheStats}}resetCacheStats(){this.cacheStats={hits:0,misses:0}}isCacheExpired(){return!this.isCacheEntryValid(this.cache)}getCachedPosition(){return this.cache?.data??null}getBiome(A,B,g=null){const I=Math.abs(A),E=this.normalizeLongitude(B),i=this.isCoastal(A,B),C=i?"_coastal":"";if(g!==null&&!isNaN(g)){const a=this.getElevationBiome(g,C);if(a)return a}if(I>66.5)return`tundra${C}`;if(this.isInSwampRegion(A,E))return`swamp${C}`;if(I>=15&&I<=45&&this.isInDesertRegion(A,E))return i?"coastal_desert":"desert";if(I<=23.5){if(this.isInJungleRegion(A,E))return`jungle${C}`;if(this.isInSavannaRegion(A,E))return`savanna${C}`;if(A>0){if(E>=290&&E<=310)return`forest${C}`;if(E>=10&&E<=30)return`forest${C}`;if(E>=70&&E<=120)return`forest${C}`}else{if(E>=10&&E<=30)return`forest${C}`;if(E>=100&&E<=140)return`forest${C}`}return`forest${C}`}if(A>0&&I>=30&&I<=50&&(E>=235&&E<=290||E>=0&&E<=40||E>=110&&E<=145))return i?"coastal_urban":"urban";if(I>23.5&&I<=66.5)if(A>0){if(this.isInTaigaRegion(A,E))return`taiga${C}`;if(E>=235&&E<=290)return I>=45?`forest${C}`:`plains${C}`;if(E>=0&&E<=40)return`forest${C}`;if(E>40&&E<=180)return I>=50?`mountain${C}`:`plains${C}`}else{if(E>=280&&E<=320)return`plains${C}`;if(E>=15&&E<=40)return`plains${C}`;if(E>=110&&E<=180)return`plains${C}`}return`plains${C}`}getElevationBiome(A,B){return A>3500?`mountain${B}`:A>1500?`mountain${B}`:A<0?`valley${B}`:null}normalizeLongitude(A){let B=A%360;return B<0&&(B+=360),B}isInJungleRegion(A,B){return Math.abs(A)>15?!1:A>=-15&&A<=5&&B>=290&&B<=310||A>=-5&&A<=5&&B>=10&&B<=30||A>=-10&&A<=10&&B>=95&&B<=140}isInSwampRegion(A,B){return A>=25&&A<=26&&B>=279&&B<=281||A>=-20&&A<=-18&&B>=22&&B<=24||A>=21&&A<=22&&B>=89&&B<=90||A>=-20&&A<=-15&&B>=300&&B<=305}isInTaigaRegion(A,B){return A<50||A>70?!1:A>=50&&A<=70&&B>=230&&B<=300||A>=60&&A<=70&&B>=5&&B<=30||A>=55&&A<=70&&B>=30&&B<=180}isInSavannaRegion(A,B){return Math.abs(A)>20?!1:A>=-5&&A<=15&&B>=30&&B<=40||A>=-20&&A<=-15&&B>=15&&B<=35||A>=-25&&A<=-5&&B>=300&&B<=315||A>=-20&&A<=-10&&B>=130&&B<=135}isInDesertRegion(A,B){return A>15&&A<30&&(B>=345||B<=40)||A>15&&A<30&&B>=35&&B<=55||A>28&&A<35&&B>=35&&B<=40||A>25&&A<35&&B>=50&&B<=65||A>23&&A<30&&B>=68&&B<=75||A>35&&A<45&&B>=100&&B<=115||A<-20&&A>-30&&B>=115&&B<=145||A<-20&&A>-25&&B>=290&&B<=292||A>25&&A<35&&B>=245&&B<=250||A<-20&&A>-30&&B>=20&&B<=30}isCoastal(A,B){const g=Math.abs(A),I=this.normalizeLongitude(B);return!!(this.isInSmallIslandRegion(A,I)||this.isInNarrowLandmass(A,I)||g>60||A>30&&A<45&&I>=0&&I<=25||A>30&&A<40&&I>=25&&I<=35||A>15&&A<30&&I>=35&&I<=43||A>24&&A<30&&I>=48&&I<=55||A>41&&A<47&&I>=27&&I<=42||A>36&&A<47&&I>=46&&I<=55||A>53&&A<66&&I>=15&&I<=30||A>50&&A<60&&I>=0&&I<=10||A>15&&A<25&&I>=65&&I<=75||A>15&&A<23&&I>=80&&I<=90||A>35&&A<43&&I>=130&&I<=142||A>10&&A<25&&I>=105&&I<=122||A>20&&A<30&&I>=265&&I<=280||A>15&&A<25&&I>=275&&I<=300)}isInSmallIslandRegion(A,B){return A>=50&&A<=60&&B>=358&&B<=360||A>=50&&A<=60&&B>=0&&B<=10||A>=30&&A<=46&&B>=128&&B<=146||A>=4&&A<=22&&B>=116&&B<=127||A>=-10&&A<=6&&B>=94&&B<=142||A>=-47&&A<=-34&&B>=165&&B<=179||A>=-26&&A<=-12&&B>=43&&B<=51||A>=63&&A<=67&&B>=338&&B<=344||A>=10&&A<=23&&B>=275&&B<=300||A>=5&&A<=10&&B>=79&&B<=82||A>=18&&A<=23&&B>=204&&B<=206||A>=-18&&A<=-12&&B>=176&&B<=181}isInNarrowLandmass(A,B){return A>=7&&A<=18&&B>=275&&B<=290||A>=33&&A<=43&&B>=124&&B<=132||A>=36&&A<=47&&B>=8&&B<=19||A>=36&&A<=44&&B>=358&&B<=360||A>=36&&A<=44&&B>=0&&B<=10||A>=55&&A<=71&&B>=4&&B<=32||A>=24&&A<=31&&B>=268&&B<=272||A>=55&&A<=62&&B>=210&&B<=220||A>=50&&A<=62&&B>=156&&B<=163}}class lM{constructor(){this.logger=SA.for("MotionDetector"),this.isListening=!1,this.lastMotion=null,this.motionCallback=null,this.lastProcessedTime=0,this.minSampleIntervalMs=83,this.deltaBuffer=[],this.bufferSize=24,this.confirmedActivity="unknown",this.candidateActivity=null,this.candidateStartTime=0,this.confirmationMs=1500,this.handleMotion=A=>{if(!this.isListening||!this.motionCallback)return;const B=Date.now(),g={acceleration:{x:A.acceleration?.x??null,y:A.acceleration?.y??null,z:A.acceleration?.z??null},accelerationIncludingGravity:{x:A.accelerationIncludingGravity?.x??0,y:A.accelerationIncludingGravity?.y??0,z:A.accelerationIncludingGravity?.z??0},rotationRate:{alpha:A.rotationRate?.alpha??null,beta:A.rotationRate?.beta??null,gamma:A.rotationRate?.gamma??null},interval:A.interval,timestamp:B};if(this.lastMotion=g,B-this.lastProcessedTime<this.minSampleIntervalMs)return;this.lastProcessedTime=B;const I=g.accelerationIncludingGravity;if(I.x!=null&&I.y!=null&&I.z!=null&&!(I.x===0&&I.y===0&&I.z===0)){const E=Math.sqrt(I.x**2+I.y**2+I.z**2),i=Math.abs(E-9.8);this.updateSmoothing(i)}this.logger.debug("Motion event received",{acceleration:g.accelerationIncludingGravity,rotation:g.rotationRate,activity:this.detectActivity(g)}),this.motionCallback(g)}}startMonitoring(A){if(typeof window>"u"||!("DeviceMotionEvent"in window)){this.logger.warn("DeviceMotionEvent not supported");return}this.motionCallback=A,this.isListening=!0,this.deltaBuffer=[],this.confirmedActivity="unknown",this.candidateActivity=null,window.addEventListener("devicemotion",this.handleMotion)}stopMonitoring(){typeof window<"u"&&window.removeEventListener("devicemotion",this.handleMotion),this.isListening=!1,this.motionCallback=null,this.deltaBuffer=[],this.confirmedActivity="unknown",this.candidateActivity=null}getLastMotion(){return this.lastMotion}detectActivity(A){const B=A.accelerationIncludingGravity;if(B.x==null||B.y==null||B.z==null||B.x===0&&B.y===0&&B.z===0)return"unknown";if(this.deltaBuffer.length>=10)return this.confirmedActivity;const g=Math.sqrt(B.x**2+B.y**2+B.z**2),I=Math.abs(g-9.8);return I<.5?"stationary":I<2?"walking":I<5?"running":"driving"}updateSmoothing(A){this.deltaBuffer.push(A),this.deltaBuffer.length>this.bufferSize&&this.deltaBuffer.shift();const B=this.deltaBuffer.reduce((I,E)=>I+E,0)/this.deltaBuffer.length;let g;if(B<.5?g="stationary":B<2?g="walking":B<5?g="running":g="driving",g===this.confirmedActivity){this.candidateActivity=null;return}if(g!==this.candidateActivity){this.candidateActivity=g,this.candidateStartTime=Date.now();return}Date.now()-this.candidateStartTime>=this.confirmationMs&&(this.confirmedActivity=g,this.candidateActivity=null)}}const Tt=nA({id:X(),main:z(),description:z(),icon:z().optional()}),jt=nA({temp:X(),feels_like:X().optional(),temp_min:X().optional(),temp_max:X().optional(),pressure:X(),humidity:X().nonnegative(),sea_level:X().optional(),grnd_level:X().optional(),temp_kf:X().optional()}),Ot=nA({speed:X().nonnegative(),deg:X().min(0).max(360).optional(),gust:X().optional()}),Pt=nA({all:X().min(0).max(100)}),DM=nA({type:X().optional(),id:X().optional(),country:z().optional(),sunrise:X().nonnegative(),sunset:X().nonnegative(),pod:z().optional()}),_t=nA({"1h":X().optional(),"3h":X().optional()}),$t=nA({"1h":X().optional(),"3h":X().optional()}),As=nA({lon:X().min(-180).max(180),lat:X().min(-90).max(90)}),dM=nA({coord:As.optional(),weather:UA(Tt).min(1),base:z().optional(),main:jt,visibility:X().nonnegative().optional(),wind:Ot.optional(),rain:_t.optional(),snow:$t.optional(),clouds:Pt.optional(),dt:X().nonnegative(),sys:DM,timezone:X().optional(),id:X().optional(),name:z().optional(),cod:X().optional()}),FM=nA({dt:X().nonnegative(),main:jt,weather:UA(Tt).min(1),clouds:Pt.optional(),wind:Ot.optional(),visibility:X().nonnegative().optional(),pop:X().min(0).max(1).optional(),rain:_t.optional(),snow:$t.optional(),sys:nA({pod:z().optional()}).optional(),dt_txt:z().optional()}),MM=nA({cod:KE([z(),X()]),message:X().optional(),cnt:X().nonnegative().optional(),list:UA(FM).min(1),city:nA({id:X(),name:z(),coord:As,country:z().optional(),population:X().optional(),timezone:X().optional(),sunrise:X().optional(),sunset:X().optional()}).optional()}),Bs="weather_api_cache";class wM{constructor(A,B,g){if(this.baseUrl="https://api.openweathermap.org/data/2.5/weather",this.forecastUrl="https://api.openweathermap.org/data/2.5/forecast",this.cache=new Map,this.forecastCache=new Map,this.cacheTTL=720*1e3,this.forecastCacheTTL=3600*1e3,this.cacheStats={hits:0,misses:0},this.logger=SA.for("WeatherAPIClient"),this.lastKnownLocation=null,this.weatherApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.forecastApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.recentWeatherApiTimes=[],this.recentForecastApiTimes=[],this.maxRecentSamples=100,typeof A=="string"||A===void 0)this.apiKey=A??"",this.cacheTTL=(B??12)*60*1e3,this.useLocalStorage=(g??!0)&&this.isLocalStorageAvailable();else{const I=A;this.apiKey=I.apiKey??"",this.cacheTTL=I.cacheTTL??720*1e3,this.useLocalStorage=(I.useLocalStorage??!0)&&this.isLocalStorageAvailable()}this.useLocalStorage&&this.loadFromLocalStorage()}isLocalStorageAvailable(){try{const A="__localStorage_test__";return localStorage.setItem(A,A),localStorage.removeItem(A),!0}catch{return!1}}loadFromLocalStorage(){try{const A=localStorage.getItem(Bs);if(A){const B=JSON.parse(A),g=Date.now();for(const[I,E]of Object.entries(B))g-E.timestamp<this.cacheTTL&&this.cache.set(I,E)}}catch(A){this.logger.warn("Failed to load weather cache from localStorage",{error:A})}}saveToLocalStorage(){if(this.useLocalStorage)try{const A={};for(const[B,g]of this.cache.entries())A[B]=g;localStorage.setItem(Bs,JSON.stringify(A))}catch(A){this.logger.warn("Failed to save weather cache to localStorage",{error:A})}}startTiming(){const A=performance.now();return B=>{performance.now()-A}}recordWeatherApiCall(A,B){const g=Date.now();B?(this.weatherApiMetrics.successCount++,this.weatherApiMetrics.totalTime+=A,this.weatherApiMetrics.minTime=Math.min(this.weatherApiMetrics.minTime,A),this.weatherApiMetrics.maxTime=Math.max(this.weatherApiMetrics.maxTime,A),this.recentWeatherApiTimes.push(A),this.recentWeatherApiTimes.length>this.maxRecentSamples&&this.recentWeatherApiTimes.shift()):this.weatherApiMetrics.errorCount++,this.weatherApiMetrics.lastCallTimestamp=g}recordForecastApiCall(A,B){const g=Date.now();B?(this.forecastApiMetrics.successCount++,this.forecastApiMetrics.totalTime+=A,this.forecastApiMetrics.minTime=Math.min(this.forecastApiMetrics.minTime,A),this.forecastApiMetrics.maxTime=Math.max(this.forecastApiMetrics.maxTime,A),this.recentForecastApiTimes.push(A),this.recentForecastApiTimes.length>this.maxRecentSamples&&this.recentForecastApiTimes.shift()):this.forecastApiMetrics.errorCount++,this.forecastApiMetrics.lastCallTimestamp=g}calculatePercentile(A,B){if(A.length===0)return 0;const g=[...A].sort((E,i)=>E-i),I=Math.ceil(B/100*g.length)-1;return g[Math.max(0,I)]}getPerformanceStatistics(A,B){const g=A.successCount+A.errorCount,I=A.successCount>0?A.totalTime/A.successCount:0,E=g>0?A.successCount/g*100:0;return{average:Math.round(I),min:A.minTime===1/0?0:Math.round(A.minTime),max:Math.round(A.maxTime),totalCalls:g,successRate:Math.round(E*10)/10}}getWeatherApiMetrics(){return{...this.weatherApiMetrics}}getWeatherApiStatistics(){return{...this.getPerformanceStatistics(this.weatherApiMetrics,this.recentWeatherApiTimes),p95:Math.round(this.calculatePercentile(this.recentWeatherApiTimes,95)),p99:Math.round(this.calculatePercentile(this.recentWeatherApiTimes,99))}}getForecastApiMetrics(){return{...this.forecastApiMetrics}}getForecastApiStatistics(){return{...this.getPerformanceStatistics(this.forecastApiMetrics,this.recentForecastApiTimes),p95:Math.round(this.calculatePercentile(this.recentForecastApiTimes,95)),p99:Math.round(this.calculatePercentile(this.recentForecastApiTimes,99))}}resetPerformanceMetrics(){this.weatherApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.forecastApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.recentWeatherApiTimes=[],this.recentForecastApiTimes=[]}getCacheKey(A,B){const g=Math.round(A*1e4)/1e4,I=Math.round(B*1e4)/1e4;return`${g},${I}`}isCacheEntryValid(A){return Date.now()-A.timestamp<this.cacheTTL}calculateMoonPhase(A){const B=new Date("2024-01-11T11:57:00Z"),g=29.530588853*24*60*60*1e3,E=(A.getTime()-B.getTime())/g;return Math.abs(E%1)}getSolarInfo(A,B,g){const I=g??new Date;return{...this.calculateSolarInfo(A,B,I),fromApi:!1,timestamp:Date.now()}}calculateSolarInfo(A,B,g){const I=this.calculateSunriseSunset(A,B,g,!0),E=this.calculateSunriseSunset(A,B,g,!1),i=this.calculateSolarNoon(A,B,g),C=this.calculateTwilight(A,B,g,"civil",!0),a=this.calculateTwilight(A,B,g,"civil",!1),{altitude:e,azimuth:o}=this.calculateSunPosition(A,B,g),t=!isNaN(E.getTime())&&!isNaN(I.getTime())?(E.getTime()-I.getTime())/(1e3*60*60):0,s=this.determineDayStage(g,I,E,C,a);return{currentTime:g,stage:s,sunrise:I,sunset:E,solarNoon:i,civilDawn:C,civilDusk:a,sunAltitude:e,sunAzimuth:o,dayLengthHours:t}}toRadians(A){return A*(Math.PI/180)}toDegrees(A){return A*(180/Math.PI)}calculateJulianDate(A){const B=A.getUTCFullYear(),g=A.getUTCMonth()+1,I=A.getUTCDate(),E=A.getUTCHours()+A.getUTCMinutes()/60+A.getUTCSeconds()/3600;let i=Math.floor((14-g)/12),C=B+4800-i,a=g+12*i-3;return I+Math.floor((153*a+2)/5)+365*C+Math.floor(C/4)-Math.floor(C/100)+Math.floor(C/400)-32045+(E-12)/24}calculateJulianCentury(A){return(A-2451545)/36525}calculateSolarDeclination(A){const B=(280.46646+A*(36000.76983+A*3032e-7))%360,g=357.52911+A*(35999.05029-1537e-7*A),I=Math.sin(this.toRadians(g))*(1.914602-A*(.004817+14e-6*A))+Math.sin(this.toRadians(2*g))*(.019993-101e-6*A)+Math.sin(this.toRadians(3*g))*289e-6,E=B+I,C=23+(26+(21.448-A*(46.815+A*(59e-5-A*.001813)))/60)/60+.00256*Math.cos(this.toRadians(125.04-1934.136*A)),a=E-.00569-.00478*Math.sin(this.toRadians(125.04-1934.136*A));return this.toDegrees(Math.asin(Math.sin(this.toRadians(C))*Math.sin(this.toRadians(a))))}calculateEquationOfTime(A){const B=(280.46646+A*(36000.76983+A*3032e-7))%360,g=357.52911+A*(35999.05029-1537e-7*A),I=.016708634-A*(42037e-9+1267e-10*A);return Math.tan(this.toRadians(23.44/2))**2,4*this.toDegrees(Math.atan2(Math.sin(this.toRadians(2*B))*Math.cos(this.toRadians(g))*2*I-Math.sin(this.toRadians(g))*2*I+Math.sin(this.toRadians(B))*Math.cos(this.toRadians(g))*2*I*Math.cos(this.toRadians(B)),1-Math.sin(this.toRadians(B))*Math.sin(this.toRadians(B))*I*I*Math.cos(this.toRadians(g))*Math.cos(this.toRadians(g))))}calculateHourAngle(A,B,g=90.833){const I=this.toRadians(A),E=this.toRadians(B),i=this.toRadians(g),C=(Math.cos(i)-Math.sin(I)*Math.sin(E))/(Math.cos(I)*Math.cos(E));return C>1?NaN:C<-1?NaN:this.toDegrees(Math.acos(C))}calculateSunriseSunset(A,B,g,I){const E=new Date(g);E.setUTCHours(12,0,0,0);const i=this.calculateJulianDate(E),C=this.calculateJulianCentury(i),a=this.calculateSolarDeclination(C),e=this.calculateHourAngle(A,a);if(isNaN(e))return new Date(NaN);const o=this.calculateEquationOfTime(C),t=(I?-e:e)/15,G=12-B/15-o/60+t,n=Math.floor(G),h=Math.floor((G-n)*60),c=Math.floor(((G-n)*60-h)*60),d=new Date(g);return d.setUTCHours(n,h,c,0),d}calculateSolarNoon(A,B,g){const I=new Date(g);I.setUTCHours(12,0,0,0);const E=this.calculateJulianDate(I),i=this.calculateJulianCentury(E),C=this.calculateEquationOfTime(i),a=12-B/15-C/60,e=Math.floor(a),o=Math.floor((a-e)*60),t=Math.floor(((a-e)*60-o)*60),s=new Date(g);return s.setUTCHours(e,o,t,0),s}calculateTwilight(A,B,g,I,E){const C={civil:96,nautical:102,astronomical:108}[I],a=new Date(g);a.setUTCHours(12,0,0,0);const e=this.calculateJulianDate(a),o=this.calculateJulianCentury(e),t=this.calculateSolarDeclination(o),s=this.calculateHourAngle(A,t,C);if(isNaN(s))return new Date(NaN);const G=this.calculateEquationOfTime(o),n=(E?-s:s)/15,c=12-B/15-G/60+n,d=Math.floor(c),l=Math.floor((c-d)*60),F=Math.floor(((c-d)*60-l)*60),m=new Date(g);return m.setUTCHours(d,l,F,0),m}calculateSunPosition(A,B,g){const I=this.calculateJulianDate(g),E=this.calculateJulianCentury(I),i=this.calculateSolarDeclination(E),C=this.calculateEquationOfTime(E),o=(g.getUTCHours()+g.getUTCMinutes()/60+g.getUTCSeconds()/3600+B/15+C/60-12)*15,t=this.toRadians(A),s=this.toRadians(i),G=this.toRadians(o),n=Math.sin(t)*Math.sin(s)+Math.cos(t)*Math.cos(s)*Math.cos(G),h=this.toDegrees(Math.asin(n)),c=(Math.sin(s)-Math.sin(t)*n)/(Math.cos(t)*Math.cos(this.toRadians(h)));let d=this.toDegrees(Math.acos(Math.max(-1,Math.min(1,c))));return o>0&&(d=360-d),{altitude:h,azimuth:d}}determineDayStage(A,B,g,I,E){const i=A.getTime();if(isNaN(B.getTime())||isNaN(g.getTime())){const t=A.getUTCMonth();return t>=4&&t<=7?"day":"night"}const C=I&&!isNaN(I.getTime())?I.getTime():B.getTime(),a=E&&!isNaN(E.getTime())?E.getTime():g.getTime(),e=B.getTime(),o=g.getTime();return i<C||i>a?"night":i>=C&&i<e?"dawn":i>o&&i<=a?"dusk":"day"}async getWeather(A,B){if(!this.apiKey)return this.logger.warn("Weather API key not provided"),null;this.lastKnownLocation={latitude:A,longitude:B};const g=this.getCacheKey(A,B),I=this.cache.get(g);if(I&&this.isCacheEntryValid(I))return this.cacheStats.hits++,I.data;this.cacheStats.misses++;const E=performance.now();let i=!1;try{const C=`${this.baseUrl}?lat=${A}&lon=${B}&appid=${this.apiKey}&units=metric`,a=await fetch(C);if(!a.ok)throw new Error(`Weather API error: ${a.statusText}`);const e=await a.json(),o=dM.safeParse(e);if(!o.success)return this.logger.error("Weather API response validation failed",{errors:o.error.issues,response:e}),null;const t=o.data,s=Date.now()/1e3,G=s<t.sys.sunrise||s>t.sys.sunset,n={temperature:t.main.temp,humidity:t.main.humidity,pressure:t.main.pressure,weatherType:t.weather[0]?.main||"Clear",windSpeed:t.wind?.speed??0,windDirection:t.wind?.deg??0,isNight:G,moonPhase:this.calculateMoonPhase(new Date),timestamp:Date.now()};return this.cache.set(g,{data:n,timestamp:Date.now()}),this.saveToLocalStorage(),i=!0,n}catch(C){return this.logger.error("Failed to fetch weather",{error:C}),null}finally{const C=performance.now()-E;this.recordWeatherApiCall(C,i)}}invalidateCache(){this.cache.clear(),this.saveToLocalStorage()}invalidateLocation(A,B){const g=this.getCacheKey(A,B);this.cache.delete(g),this.saveToLocalStorage()}getCacheStats(){return{...this.cacheStats}}resetCacheStats(){this.cacheStats={hits:0,misses:0}}clearExpiredEntries(){let A=0;for(const[B,g]of this.cache.entries())this.isCacheEntryValid(g)||(this.cache.delete(B),A++);return A>0&&this.saveToLocalStorage(),A}getCacheSize(){return this.cache.size}getLastKnownLocation(){return this.lastKnownLocation?{...this.lastKnownLocation}:null}isForecastCacheEntryValid(A){return Date.now()-A.timestamp<this.forecastCacheTTL}getForecastCacheKey(A,B,g){const I=Math.round(A*1e4)/1e4,E=Math.round(B*1e4)/1e4;return`forecast_${I},${E}_${g}h`}async getForecast(A,B,g=24){if(!this.apiKey)return this.logger.warn("Weather API key not provided"),null;const I=Math.min(g,120),E=this.getForecastCacheKey(A,B,I),i=this.forecastCache.get(E);if(i&&this.isForecastCacheEntryValid(i))return i.data.slice(0,Math.ceil(I/3));const C=performance.now();let a=!1;try{const e=Math.ceil(I/3),o=`${this.forecastUrl}?lat=${A}&lon=${B}&appid=${this.apiKey}&units=metric&cnt=${e}`,t=await fetch(o);if(!t.ok)throw new Error(`Weather Forecast API error: ${t.statusText}`);const s=await t.json(),G=MM.safeParse(s);if(!G.success)return this.logger.error("Weather Forecast API response validation failed",{errors:G.error.issues,response:s}),null;const h=G.data.list.map(c=>({temperature:c.main.temp,humidity:c.main.humidity,pressure:c.main.pressure,weatherType:c.weather[0]?.main||"Clear",windSpeed:c.wind?.speed??0,windDirection:c.wind?.deg??0,timestamp:Date.now(),forecastTime:new Date(c.dt*1e3),probabilityOfPrecipitation:c.pop??0}));return this.forecastCache.set(E,{data:h,timestamp:Date.now()}),a=!0,h}catch(e){return this.logger.error("Failed to fetch weather forecast",{error:e}),null}finally{const e=performance.now()-C;this.recordForecastApiCall(e,a)}}async getUpcomingWeather(A,B,g=12){const I=await this.getForecast(A,B,g);if(!I||I.length===0)return null;let E=!1,i=!1,C=0,a=0;const e=[];for(const G of I)e.push(G.weatherType),(G.weatherType.toLowerCase().includes("rain")||G.weatherType.toLowerCase().includes("drizzle"))&&(E=!0,C=Math.max(C,G.probabilityOfPrecipitation)),G.weatherType.toLowerCase().includes("snow")&&(i=!0,a=Math.max(a,G.probabilityOfPrecipitation));const o={thunderstorm:5,snow:4,rain:3,drizzle:2,clouds:1,clear:0,mist:1,fog:1,haze:1,smoke:1,dust:1,sand:1,ash:1,squall:2,tornado:5};let t="Clear",s=-1;for(const G of new Set(e)){const n=o[G.toLowerCase()]??0;n>s&&(s=n,t=G)}return{willRain:E,willSnow:i,rainProbability:C,snowProbability:a,worstWeatherType:t}}detectSevereWeather(A){const B=A.weatherType.toLowerCase(),g=A.windSpeed*3.6;if(B.includes("snow")||B.includes("blizzard")){const I=B.includes("blizzard")||B.includes("heavy")||A.windSpeed>8,E=g>25;if(B.includes("blizzard")||I&&E)return{type:"Blizzard",xpBonus:.5,severity:g>50?"extreme":"high",message:"⚠️ Blizzard conditions detected! Stay safe and warm.",detectedAt:Date.now()}}if(g>118){const E=(this.lastKnownLocation?this.isTropicalRegion(this.lastKnownLocation.latitude):!1)?"Hurricane":"Typhoon";return{type:E,xpBonus:.75,severity:g>177?"extreme":"high",message:`🌀 ${E} conditions detected! Please seek shelter.`,detectedAt:Date.now()}}return B.includes("tornado")?{type:"Tornado",xpBonus:1,severity:"extreme",message:"🌪️ TORNADO WARNING! Take immediate shelter!",detectedAt:Date.now()}:B.includes("thunderstorm")&&g>60?{type:"Tornado",xpBonus:.5,severity:"high",message:"⛈️ Extreme thunderstorm with high winds! Exercise caution.",detectedAt:Date.now()}:null}isTropicalRegion(A){return Math.abs(A)<23.5}getSafetyWarning(A){switch(A.type){case"Blizzard":return A.severity==="extreme"?"🚨 EXTREME BLIZZARD: Stay indoors, avoid travel. Keep emergency supplies ready.":"⚠️ Blizzard: Dress warmly, avoid unnecessary travel. Check on neighbors.";case"Hurricane":case"Typhoon":return A.severity==="extreme"?"🚨 EXTREME CYCLONE: Seek shelter immediately! Follow evacuation orders.":"⚠️ Hurricane/Typhoon: Secure property, prepare emergency kit, follow local alerts.";case"Tornado":return"🚨 TORNADO: Take shelter in basement or interior room immediately! Stay away from windows.";default:return"⚠️ Severe weather detected. Stay informed and stay safe."}}invalidateForecastCache(){this.forecastCache.clear()}invalidateForecastLocation(A,B){for(const[g]of this.forecastCache.entries()){const I=this.getCacheKey(A,B);g.includes(I)&&this.forecastCache.delete(g)}}clearExpiredForecastEntries(){let A=0;for(const[B,g]of this.forecastCache.entries())this.isForecastCacheEntryValid(g)||(this.forecastCache.delete(B),A++);return A}}const q={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m"},ji={useColors:!0,compact:!1,showTimestamp:!0,maxFailures:5};function Oi(Q){return Q.useColors===!1?!1:typeof process<"u"&&process.stdout?.isTTY===!0}function K(Q,A,B){return B?`${A}${Q}${q.reset}`:Q}function yM(Q,A){switch(Q){case"healthy":return K("●",q.green,A);case"degraded":return K("●",q.yellow,A);case"failed":return K("●",q.red,A);default:return K("○",q.dim,A)}}function RM(Q,A){return Q?K("✓ Granted",q.green,A):K("✗ Denied",q.red,A)}function mM(Q,A){return Q?K("Available",q.green,A):K("Unavailable",q.red,A)}function Pi(Q){const A=new Date(Q),B=A.getHours().toString().padStart(2,"0"),g=A.getMinutes().toString().padStart(2,"0"),I=A.getSeconds().toString().padStart(2,"0");return`${B}:${g}:${I}`}function $g(Q){return Q<1e3?`${Q}ms`:Q<6e4?`${(Q/1e3).toFixed(1)}s`:Q<36e5?`${Math.floor(Q/6e4)}m ${Math.floor(Q%6e4/1e3)}s`:`${Math.floor(Q/36e5)}h ${Math.floor(Q%36e5/6e4)}m`}function gs(Q,A){const B=Q+A;return B===0?"N/A":`${(Q/B*100).toFixed(1)}%`}function lQ(Q,A){const B=[],g=Q.totalCalls>0;if(B.push(` Calls: ${Q.totalCalls}`),B.push(` Success: ${g?`${Q.successRate.toFixed(1)}%`:"N/A"}`),g){const I=Q.average<500?q.green:Q.average<1500?q.yellow:q.red;B.push(` Avg Time: ${K(`${Q.average}ms`,I,A)}`),B.push(` Min/Avg/Max: ${Q.min}/${Q.average}/${Q.max}ms`),B.push(` P95/P99: ${Q.p95}/${Q.p99}ms`)}else B.push(" Avg Time: N/A");return B}function KA(Q,A="─"){return A.repeat(Q)}function _i(Q,A,B){const g=Math.max(0,A-Q.length-2),I=Math.floor(g/2),E=g-I,i=KA(A),C=B?q.bright+q.cyan+Q+q.reset:Q;return`
|
|
53
|
+
`)}}function mF(Q){return typeof Q=="string"&&["humanoid","beast","undead","dragon","fiend","construct","elemental","monstrosity"].includes(Q)}function uF(Q){return typeof Q=="string"&&["common","uncommon","elite","boss"].includes(Q)}function SF(Q){return typeof Q=="string"&&["brute","archer","support"].includes(Q)}function bF(Q){return typeof Q=="string"&&["easy","medium","hard","deadly"].includes(Q)}class pF{static calculatePartyLevel(A){if(A.length===0)return 1;const B=A.map(g=>g.level||1);return da(B)}static calculatePartyStrength(A){if(A.length===0)return 0;const B=A.reduce((e,o)=>e+(o.hp?.max||10),0),g=this.getAverageAC(A),I=this.getAverageDamage(A),E=A.length,i=g/10,C=B*i,a=I*E*10;return C+a}static getXPBudget(A,B){if(A.length===0)return 0;const g=A.map(I=>I.level||1);return MI(g,B)}static getAverageAC(A){if(A.length===0)return 10;const B=A.reduce((g,I)=>g+(I.armor_class||10),0);return Math.round(B/A.length)}static getAverageHP(A){if(A.length===0)return 10;const B=A.reduce((g,I)=>g+(I.hp?.max||10),0);return Math.round(B/A.length)}static getPartySize(A){return A.length}static getAverageDamage(A){if(A.length===0)return 0;const B=A.reduce((g,I)=>g+this.estimateCharacterDamage(I),0);return Math.round(B/A.length)}static estimateCharacterDamage(A){const B=A.ability_scores,g=A.equipment?.weapons?.find(C=>C.equipped);let I,E;if(g?.damage?.dice){I=g.damage.dice;const C=g.weaponProperties?.includes("ranged")??!1,a=g.weaponProperties?.includes("finesse")??!1;E=Math.floor(((B?.[C||a?"DEX":"STR"]??10)-10)/2)}else I="1",E=Math.floor(((B?.STR??10)-10)/2);let i;try{const C=hA.parseDiceFormula(I);i=C.diceCount*((C.diceSides+1)/2)+C.modifier+E}catch{i=E+1}return Math.round(i*10)/10}static analyzeParty(A){const B=this.calculatePartyLevel(A),g=this.getPartySize(A),I=this.getAverageAC(A),E=this.getAverageHP(A),i=this.getAverageDamage(A),C=this.calculatePartyStrength(A),a=this.getXPBudget(A,"easy"),e=this.getXPBudget(A,"medium"),o=this.getXPBudget(A,"hard"),t=this.getXPBudget(A,"deadly");return{averageLevel:B,partySize:g,averageAC:I,averageHP:E,averageDamage:i,totalStrength:C,easyXP:a,mediumXP:e,hardXP:o,deadlyXP:t}}}const ZA=[];for(let Q=0;Q<256;++Q)ZA.push((Q+256).toString(16).slice(1));function YF(Q,A=0){return(ZA[Q[A+0]]+ZA[Q[A+1]]+ZA[Q[A+2]]+ZA[Q[A+3]]+"-"+ZA[Q[A+4]]+ZA[Q[A+5]]+"-"+ZA[Q[A+6]]+ZA[Q[A+7]]+"-"+ZA[Q[A+8]]+ZA[Q[A+9]]+"-"+ZA[Q[A+10]]+ZA[Q[A+11]]+ZA[Q[A+12]]+ZA[Q[A+13]]+ZA[Q[A+14]]+ZA[Q[A+15]]).toLowerCase()}let fi;const UF=new Uint8Array(16);function kF(){if(!fi){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");fi=crypto.getRandomValues.bind(crypto)}return fi(UF)}const ut={randomUUID:typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function ZF(Q,A,B){Q=Q||{};const g=Q.random??Q.rng?.()??kF();if(g.length<16)throw new Error("Random bytes length must be >= 16");return g[6]=g[6]&15|64,g[8]=g[8]&63|128,YF(g)}function WF(Q,A,B){return ut.randomUUID&&!Q?ut.randomUUID():ZF(Q)}class HF{constructor(A={}){this.options={validateAudioUrls:!1,strict:!1,audioUrlValidationTimeout:5e3,resolveImageUrls:!1,...A}}async parse(A){const B=A.name,g=A.image,I=A.creator,E=A.description,i=A.genre,C=A.tags;let a=g;g&&this.options.resolveImageUrls&&(a=await kA.resolveUrl(g));const e=A.tracks||[],o=[];for(let t=0;t<e.length;t++)try{const s=await this.parseTrack(e[t],t);if(s){const G=await this.resolveTrackUrls(s);o.push(G)}}catch(s){if(this.options.strict)throw s;console.warn(`Failed to parse track at index ${t}:`,s)}return{name:B,description:E,image:a,creator:I,genre:i,tags:C,version:A.version,playlist_type:A.playlist_type,original_playlist_tx_id:A.original_playlist_tx_id,playlist_artist:A.playlist_artist,platform:A.platform,tracks:o}}async resolveTrackUrls(A){if(!this.options.resolveImageUrls)return A;const B={...A};return B.image_url&&(B.image_url=await kA.resolveUrl(B.image_url)),B.image_thumb_url&&(B.image_thumb_url=await kA.resolveUrl(B.image_thumb_url)),B}async parseTrack(A,B){const g=QA.parseMetadata(A.metadata);if(!g&&this.options.strict)throw new Error(`Failed to parse metadata for track at index ${B}`);const I=A.chain_name,E=A.token_address,i=A.token_id,C=A.tx_id,a=A.platform,e=A.id||(I==="AR"?`AR-${C}`:`${I}-${E}-${i}`),o=A.uuid||WF(),t=QA.extractTitle(g||{}),s=QA.extractArtist(g||{}),n=typeof A.artwork_url=="string"&&A.artwork_url||typeof A.image_url=="string"&&A.image_url||null||QA.extractImageUrl(g||{}),h=QA.extractImageThumbUrl(g||{}),d=typeof A.audio_url=="string"&&A.audio_url||null||QA.extractAudioUrl(g||{}),l=QA.extractAudioUrlLossless(g||{});if(!d){if(this.options.strict)throw new Error(`No audio URL found for track: ${t||e}`);return console.warn(`Track ${e} has no audio URL - marked as Unsummonable`),null}if(this.options.validateAudioUrls&&!await this.validateAudioUrl(d)){if(this.options.strict)throw new Error(`Audio URL validation failed for track: ${t||e}`);return console.warn(`Track ${e} has invalid audio URL - marked as Unsummonable`),null}if(!t||!s||!n){if(this.options.strict)throw new Error(`Missing required fields for track: ${JSON.stringify({title:t,artist:s,imageUrl:n})}`);return null}const F=typeof g?.description=="string"?g.description:void 0,m=typeof g?.album=="string"?g.album:void 0,y=g?.duration?Number(g.duration):0,p=QA.extractGenre(g||{}),k=g?.tags||[],H=g?.bpm?Number(g.bpm):void 0,T=typeof g?.key=="string"?g.key:void 0,L=QA.convertAttributes(g?.attributes),v=Mo(g||{}),BA={id:e,uuid:o,playlist_index:B,chain_name:I,platform:a,title:t,artist:s,description:F,album:m,image_url:n,audio_url:d,audio_url_lossless:l&&l!==d?l:void 0,duration:y,genre:p,tags:Array.isArray(k)?k.map(iA=>String(iA).toLowerCase()):[],bpm:H,key:T,attributes:L||void 0,extras:v};return h&&(BA.image_thumb_url=h),typeof g?.audio_ipfs_hash=="string"?BA.audio_ipfs_hash=A.audio_ipfs_hash||g.audio_ipfs_hash:A.audio_ipfs_hash&&(BA.audio_ipfs_hash=A.audio_ipfs_hash),typeof g?.artwork_ipfs_hash=="string"?BA.artwork_ipfs_hash=A.artwork_ipfs_hash||g.artwork_ipfs_hash:A.artwork_ipfs_hash&&(BA.artwork_ipfs_hash=A.artwork_ipfs_hash),I!=="AR"?(BA.token_address=E,BA.token_id=i):C&&(BA.tx_id=C),BA}async validateAudioUrl(A){const B=new AbortController,g=setTimeout(()=>B.abort(),this.options.audioUrlValidationTimeout);try{const I=await fetch(A,{method:"HEAD",signal:B.signal});return clearTimeout(g),I.ok}catch(I){return clearTimeout(g),I instanceof Error&&I.name==="AbortError"?console.warn(`Audio URL validation timed out after ${this.options.audioUrlValidationTimeout}ms: ${A}`):console.warn(`Audio URL validation failed for ${A}:`,I),!1}}}class MB{static separateFrequencyBands(A,B){const g=A.length,I=B/2/g,E=[],i=[],C=[];for(let a=0;a<g;a++){const e=a*I,o=A[a]/255;e>=20&&e<400?E.push(o):e>=400&&e<4e3?i.push(o):e>=4e3&&e<=14e3&&C.push(o)}return{bass:E,mid:i,treble:C}}static calculateDominance(A,B){if(A.length===0)return 0;const I=A.reduce((E,i)=>E+i,0)/A.length;return B===void 0?I:I/(B/1e3)}}const sQ={targetSampleRate:8e3,fftWindowSize:32,hopSizeMs:4,hopSizeMode:{mode:"standard"},melBands:40,melBandsMode:{mode:"standard"},highPassCutoff:.4,gaussianSmoothMs:20,gaussianSmoothMode:{mode:"standard"}};class St{constructor(A={}){const B=A.hopSizeMode?QE(A.hopSizeMode):A.hopSizeMs??sQ.hopSizeMs,g=A.melBandsMode?EE(A.melBandsMode):A.melBands??sQ.melBands,I=A.gaussianSmoothMode?iE(A.gaussianSmoothMode):A.gaussianSmoothMs??sQ.gaussianSmoothMs;this.config={...sQ,...A,hopSizeMs:B,melBands:g,gaussianSmoothMs:I}}calculate(A){const B=Kg(A,this.config.targetSampleRate),g=B.targetSampleRate,I=A.duration,E=Math.round(this.config.fftWindowSize/1e3*g),i=Math.pow(2,Math.ceil(Math.log2(E))),C=Math.round(this.config.hopSizeMs/1e3*g),a=this.config.hopSizeMs/1e3,e=ui(B.data,i,C,g),o=xo(this.config.melBands,i,g),t=e.numFrames,s=[];for(let F=0;F<t;F++){const m=e.frames[F],y=new Float32Array(this.config.melBands);for(let p=0;p<this.config.melBands;p++){const k=o[p];let H=0;for(let T=0;T<k.length&&T<m.length;T++)H+=m[T]*k[T];y[p]=20*Math.log10(H+1e-10)}s.push(y)}const G=new Float32Array(t);G[0]=0;for(let F=1;F<t;F++){let m=0;for(let y=0;y<this.config.melBands;y++){const p=s[F][y]-s[F-1][y];p>0&&(m+=p)}G[F]=m}const n=1/a,h=Vo(G,this.config.highPassCutoff,n),c=mi(h,this.config.gaussianSmoothMs,n),d=Lo(c),l=new Float32Array(c.length);if(d>1e-10)for(let F=0;F<c.length;F++)l[F]=c[F]/d;else for(let F=0;F<c.length;F++)l[F]=c[F];return{envelope:l,numFrames:t,hopSizeSeconds:a,effectiveSampleRate:g,duration:I}}getConfig(){return{...this.config}}findPeaks(A,B=.5){const g=[];for(let I=1;I<A.length-1;I++)A[I]>A[I-1]&&A[I]>A[I+1]&&A[I]>=B&&g.push(I);return g}frameToTime(A,B){return A*B}timeToFrame(A,B){return Math.round(A/B)}}const fF={tempoCenter:.5,tempoWidth:1.4,minBpm:90,maxBpm:180,useOctaveResolution:!1,useTripleMeter:!1};class bt{constructor(A={}){this.config={...fF,...A}}estimateTempo(A,B){if(A.length<10)return this.getDefaultEstimate();const g=Math.round(60/this.config.minBpm/B),I=Math.round(60/this.config.maxBpm/B),E=Math.max(I,2),i=Math.min(g,Math.floor(A.length/2));if(i<=E)return this.getDefaultEstimate();const C=this.computeAutocorrelation(A,E,i),a=this.applyPerceptualWeighting(C.values,C.minLag,B);let e=this.findPeak(a,0,a.length);if(this.config.useOctaveResolution){const m=this.calculateTPS2(C.values,e),y=Math.floor(e/2);y>=1&&y<a.length&&this.calculateTPS2(C.values,y)>m&&(e=y)}if(this.config.useTripleMeter){const m=this.calculateTPS3(C.values,e),y=Math.floor(e/3);y>=1&&y<a.length&&this.calculateTPS3(C.values,y)>m&&(e=y)}const o=this.lagToBpm(e+C.minLag,B),t=o/2,s=a[e]||0,G=Math.min(e*2,a.length-1),n=a[G]||0,h=Math.max(Math.floor(e/2),0),c=a[h]||0,d=1,l=Math.min(Math.max(Math.max(n,c)/(s+.001),0),1),F=60/o;return{primaryBpm:o,secondaryBpm:t,primaryWeight:d,secondaryWeight:l,targetIntervalSeconds:F}}getConfig(){return{...this.config}}computeAutocorrelation(A,B,g){const I=g-B+1,E=new Float32Array(I);let i=0;for(let C=0;C<A.length;C++)i+=A[C];i/=A.length;for(let C=B;C<=g;C++){let a=0,e=0;for(let o=C;o<A.length;o++)a+=(A[o]-i)*(A[o-C]-i),e++;E[C-B]=e>0?a/e:0}return{values:E,minLag:B,maxLag:g}}applyPerceptualWeighting(A,B,g){const I=new Float32Array(A.length),E=this.config.tempoCenter/g,i=this.config.tempoWidth;for(let C=0;C<A.length;C++){const a=B+C;if(a<=0||E<=0){I[C]=0;continue}const e=Math.log2(a/E),o=Math.exp(-.5*Math.pow(e/i,2));I[C]=A[C]*o}return I}findPeak(A,B,g){let I=B,E=A[B];for(let i=B+1;i<g&&i<A.length;i++)A[i]>E&&(E=A[i],I=i);return I}lagToBpm(A,B){return A<=0||B<=0?120:60/(A*B)}calculateTPS2(A,B){const g=A[B]||0,I=B*2,E=I-1,i=I+1,C=A[I]||0,a=E>=0&&A[E]||0,e=i<A.length&&A[i]||0;return g+.5*C+.25*a+.25*e}calculateTPS3(A,B){const g=A[B]||0,I=B*3,E=I-1,i=I+1,C=A[I]||0,a=E>=0&&A[E]||0,e=i<A.length&&A[i]||0;return g+.33*C+.33*a+.33*e}getDefaultEstimate(){return{primaryBpm:120,secondaryBpm:60,primaryWeight:1,secondaryWeight:.5,targetIntervalSeconds:.5}}getTempoCandidates(A,B,g=5){if(A.length<10)return[{bpm:120,strength:1}];const I=Math.round(60/this.config.minBpm/B),E=Math.round(60/this.config.maxBpm/B),i=Math.max(E,2),C=Math.min(I,Math.floor(A.length/2));if(C<=i)return[{bpm:120,strength:1}];const a=this.computeAutocorrelation(A,i,C),e=this.applyPerceptualWeighting(a.values,a.minLag,B),o=[];for(let t=1;t<e.length-1;t++)if(e[t]>e[t-1]&&e[t]>e[t+1]){const s=this.lagToBpm(t+a.minLag,B);o.push({bpm:s,strength:e[t]})}return o.sort((t,s)=>s.strength-t.strength),o.slice(0,g)}}const GQ=SA.for("BeatTracker"),NF={dpAlpha:680,sensitivity:1,minPredecessorRatio:.5,maxPredecessorRatio:2};class pt{constructor(A={}){this.config={...NF,...A}}getConfig(){return{...this.config}}trackBeats(A,B,g){const I=A.length;if(I<10)return{beats:[],beatFrames:[],cumulativeScores:new Float32Array(I)};const E=Math.round(B.targetIntervalSeconds/g);if(GQ.debug("BeatTracker: Beat tracking parameters",{targetIntervalSeconds:B.targetIntervalSeconds,hopSizeSeconds:g,periodFrames:E,envelopeLength:I}),E<2||I<E*2)return{beats:[],beatFrames:[],cumulativeScores:new Float32Array(I)};const i=this.config.sensitivity,C=Math.round(this.config.dpAlpha/i),a=Math.max(10,Math.min(1e4,C));GQ.debug("Beat tracking parameters",{sensitivity:i,dpAlpha:this.config.dpAlpha,effectiveDpAlpha:a});const e=Math.max(1,Math.round(E*this.config.minPredecessorRatio)),o=Math.round(E*this.config.maxPredecessorRatio),t=new Float32Array(o-e+1);for(let F=0;F<t.length;F++){const m=e+F,y=Math.log(m/E);t[F]=-a*y*y}const s=new Int32Array(I);s.fill(-1);const G=new Float32Array(A);for(let F=o+1;F<I;F++){let m=-1/0,y=-1;for(let p=0;p<t.length;p++){const k=F-(e+p);if(k>=0&&k<I){const H=t[p]+G[k];H>m&&(m=H,y=k)}}G[F]=m+A[F],s[F]=y}GQ.debug("BeatTracker: Starting backward pass",{maxCumulativeScore:Math.max(...G),envelopeLength:I});let n=0,h=G[0];for(let F=1;F<I;F++)G[F]>h&&(h=G[F],n=F);const c=[];let d=n;for(;d>=0&&s[d]>=0;)c.unshift(d),d=s[d];return d>=0&&A[d]>0&&c.unshift(d),GQ.debug("BeatTracker: Backward pass complete",{beatsFound:c.length,bestEndFrame:n,firstBeatFrame:c[0],lastBeatFrame:c[c.length-1]}),{beats:this.convertToBeats(c,A,G,g,E),beatFrames:c,cumulativeScores:G}}convertToBeats(A,B,g,I,E){if(A.length===0)return[];const i=[];let C=0;for(let o=0;o<B.length;o++)B[o]>C&&(C=B[o]);C<=0&&(C=1);let a=0;for(let o=0;o<A.length;o++)a+=B[A[o]];const e=a/A.length;for(let o=0;o<A.length;o++){const t=A[o],s=t*I,G=Math.max(0,Math.min(1,B[t]/C)),n=B[t],h=e>0?n/e:.5,c=Math.max(0,Math.min(1,.5+.3*(h-1)));i.push({timestamp:s,beatInMeasure:0,isDownbeat:!1,measureNumber:0,intensity:G,confidence:c})}return i}trackBeatsWithOptions(A,B,g,I={}){const E=this.trackBeats(A,B,g);if(I.applyTrimming&&E.beatFrames.length>0&&this.applyDiscountedScoreTrimming(E,A.length,Math.round(B.targetIntervalSeconds/g)),I.minScoreThreshold!==void 0){const i=E.beats.filter((C,a)=>{const e=E.beatFrames[a];return E.cumulativeScores[e]>=I.minScoreThreshold});E.beats=i,E.beatFrames=E.beatFrames.filter((C,a)=>{const e=E.beatFrames[a];return E.cumulativeScores[e]>=I.minScoreThreshold})}return E}applyDiscountedScoreTrimming(A,B,g){if(A.beatFrames.length<3)return;const I=A.cumulativeScores,E=I[B-1],i=new Float32Array(B);for(let s=0;s<B;s++)i[s]=I[s]-s/B*E;let C=0,a=B-1;for(let s=0;s<B;s++)if(i[s]>0){C=s;break}for(let s=B-1;s>=0;s--)if(i[s]>0){a=s;break}const e=Math.floor(g/2),o=[],t=[];for(let s=0;s<A.beatFrames.length;s++){const G=A.beatFrames[s];G>=C-e&&G<=a+e&&(o.push(A.beats[s]),t.push(G))}A.beats=o,A.beatFrames=t}getTrackingStats(A,B){const{beats:g,beatFrames:I}=A;if(g.length===0)return{numBeats:0,avgInterval:0,stdInterval:0,avgIntensity:0,avgConfidence:0,estimatedBpm:0};const E=[];for(let t=1;t<I.length;t++)E.push((I[t]-I[t-1])*B);const i=E.length>0?E.reduce((t,s)=>t+s,0)/E.length:0,C=E.length>0?Math.sqrt(E.reduce((t,s)=>t+Math.pow(s-i,2),0)/E.length):0,a=g.reduce((t,s)=>t+s.intensity,0)/g.length,e=g.reduce((t,s)=>t+s.confidence,0)/g.length,o=i>0?60/i:0;return{numBeats:g.length,avgInterval:i,stdInterval:C,avgIntensity:a,avgConfidence:e,estimatedBpm:o}}}const Yt=SA.for("BeatMapGenerator");class tB{constructor(A={}){this.state=null;const B={...Zg,...A},g=A.hopSizeMode,I=A.melBandsMode,E=A.gaussianSmoothMode,i=g?QE(g):A.hopSizeMs??Zg.hopSizeMs,C=I?EE(I):A.melBands??Zg.melBands,a=E?iE(E):A.gaussianSmoothMs??Zg.gaussianSmoothMs;this.options={...B,hopSizeMs:i,melBands:C,gaussianSmoothMs:a,...g?{hopSizeMode:g}:{},...I?{melBandsMode:I}:{},...E?{gaussianSmoothMode:E}:{}}}getConfig(){return{...this.options}}async generateBeatMap(A,B,g,I){this.state={cancelled:!1,progress:{phase:"loading",progress:0,message:"Loading audio..."}};try{this.updateProgress("loading",0,"Loading audio...");const E=await this.fetchAndDecode(A);if(this.state.cancelled)throw new Error("Generation cancelled");return await this.generateBeatMapFromBuffer(E,B,g,I)}finally{this.state=null}}async generateBeatMapFromBuffer(A,B,g,I){this.state||(this.state={cancelled:!1,progress:{phase:"preprocessing",progress:0,message:"Starting..."}}),g&&AE(g);try{const E=A.duration;if(this.updateProgress("preprocessing",5,"Preparing audio..."),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("ose_calculation",10,"Calculating onset strength envelope..."),this.state.cancelled)throw new Error("Generation cancelled");const C=new St({targetSampleRate:8e3,fftWindowSize:32,hopSizeMs:this.options.hopSizeMs,melBands:this.options.melBands,highPassCutoff:this.options.highPassCutoff,gaussianSmoothMs:this.options.gaussianSmoothMs}).calculate(A);if(this.updateProgress("ose_calculation",35,"Onset strength envelope calculated."),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("tempo_estimation",40,"Estimating tempo..."),this.state.cancelled)throw new Error("Generation cancelled");const e=new bt({tempoCenter:this.options.tempoCenter,tempoWidth:this.options.tempoWidth,minBpm:this.options.minBpm,maxBpm:this.options.maxBpm,useOctaveResolution:this.options.useOctaveResolution}).estimateTempo(C.envelope,C.hopSizeSeconds);if(this.updateProgress("tempo_estimation",55,`Tempo estimated: ${Math.round(e.primaryBpm)} BPM`),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("beat_tracking",60,"Tracking beats..."),this.state.cancelled)throw new Error("Generation cancelled");const t=new pt({dpAlpha:this.options.dpAlpha,sensitivity:this.options.sensitivity}).trackBeats(C.envelope,e,C.hopSizeSeconds);if(this.updateProgress("beat_tracking",85,`${t.beats.length} beats detected.`),this.state.cancelled)throw new Error("Generation cancelled");if(this.updateProgress("measure_labeling",87,"Applying measure labels..."),this.state.cancelled)throw new Error("Generation cancelled");const s=g??GI;g&&BE(g,t.beats.length);const G=this.applyMeasureLabels(t.beats,s);if(this.updateProgress("measure_labeling",97,"Measure labels applied."),this.state.cancelled)throw new Error("Generation cancelled");this.updateProgress("finalizing",98,"Finalizing beat map...");let n=G;const h=this.options.filter;h>0&&(n=this.filterBeatsByGridAlignment(n,e,h));const c=n.length;n=this.applyIntensityThreshold(n),Yt.debug("BeatMapGenerator: Intensity filter applied",{beatsBefore:c,beatsAfter:n.length,beatsFiltered:c-n.length,noiseFloorThreshold:this.options.noiseFloorThreshold});const d={version:jC,algorithm:OC,minBpm:this.options.minBpm,maxBpm:this.options.maxBpm,sensitivity:this.options.sensitivity,filter:this.options.filter,noiseFloorThreshold:this.options.noiseFloorThreshold,hopSizeMs:this.options.hopSizeMs,fftSize:this.options.fftSize,dpAlpha:this.options.dpAlpha,melBands:this.options.melBands,highPassCutoff:this.options.highPassCutoff,gaussianSmoothMs:this.options.gaussianSmoothMs,tempoCenter:this.options.tempoCenter,tempoWidth:this.options.tempoWidth,useOctaveResolution:this.options.useOctaveResolution??!1,useTripleMeter:this.options.useTripleMeter??!1,generatedAt:new Date().toISOString()},l={audioId:B,duration:E,beats:n,bpm:e.primaryBpm,metadata:d,...g?{downbeatConfig:g}:{}};return this.updateProgress("complete",100,"Beat map generation complete."),I?.(this.state.progress),l}finally{this.state=null}}getProgress(){return this.state?.progress??null}cancel(){this.state&&(this.state.cancelled=!0,this.state.progress={phase:"error",progress:this.state.progress.progress,message:"Generation cancelled",error:"Cancelled by user"})}applyIntensityThreshold(A){const B=this.options.noiseFloorThreshold;return A.filter(g=>g.intensity>=B)}applyMeasureLabels(A,B){const g=this.computeMeasureOffsets(B.segments,A.length);return A.map((I,E)=>{const i=this.findActiveSegmentIndex(B.segments,E),C=B.segments[i],{downbeatBeatIndex:a,timeSignature:e}=C,{beatsPerMeasure:o}=e,t=E-a,s=(t%o+o)%o,G=s===0,h=Math.max(0,Math.floor(t/o))+g[i];return{...I,beatInMeasure:s,isDownbeat:G,measureNumber:h}})}computeMeasureOffsets(A,B){const g=[];for(let I=0;I<A.length;I++)if(I===0)g.push(0);else{const E=A[I-1],{downbeatBeatIndex:i,timeSignature:C}=E,a=C.beatsPerMeasure,o=A[I].startBeat-1-i,t=Math.max(0,Math.floor(o/a));g.push(t+1)}return g}findActiveSegmentIndex(A,B){let g=0;for(let I=0;I<A.length&&A[I].startBeat<=B;I++)g=I;return g}filterBeatsByGridAlignment(A,B,g){if(g<=0)return A;const I=60/B.primaryBpm,E=(1-g)*(I/2),i=A.filter(C=>{const e=Math.round(C.timestamp/I)*I;return Math.abs(C.timestamp-e)<=E});return Yt.debug("Filtered beats by grid alignment",{originalCount:A.length,filteredCount:i.length,threshold:g,maxDeviationMs:E*1e3}),i}updateProgress(A,B,g){this.state&&(this.state.progress={phase:A,progress:B,message:g})}async fetchAndDecode(A){const B=await fetch(A);if(!B.ok)throw new Error(`Failed to fetch audio: ${B.statusText}`);const g=await B.arrayBuffer(),I=globalThis.AudioContext||window?.AudioContext;if(!I)throw new Error("AudioContext not available in this environment");const E=new I;return await new Promise((i,C)=>{E.decodeAudioData(g,i,C)})}static toJSON(A){const B={audioId:A.audioId,duration:A.duration,beats:A.beats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),bpm:A.bpm,metadata:A.metadata};return JSON.stringify(B,null,2)}static fromJSON(A){const B=JSON.parse(A);return{audioId:B.audioId,duration:B.duration,beats:B.beats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),bpm:B.bpm,metadata:B.metadata}}static async saveToFile(A,B){if(typeof process>"u"||!process.versions?.node)throw new Error("saveToFile is only available in Node.js environment");const{writeFile:g}=await Promise.resolve().then(()=>GB),I=tB.toJSON(A);await g(B,I,"utf-8")}static async loadFromFile(A){if(typeof process>"u"||!process.versions?.node)throw new Error("loadFromFile is only available in Node.js environment");const{readFile:B}=await Promise.resolve().then(()=>GB),g=await B(A,"utf-8");return tB.fromJSON(g)}}function JF(Q){return"mergedBeats"in Q&&"detectedBeats"in Q}function qF(Q){return"subdivisionConfig"in Q&&"subdivisionMetadata"in Q}class Ut{constructor(A,B,g={},I=8){this.beatMap=A,this.audioContext=B,this.options={...vC,...g},this.rollingBpmWindowSize=I,this.normalizedBeatMap=this.createNormalizedBeatMap(A),this.state={isRunning:!1,currentTime:0,startTime:0,isPaused:!1,pauseTime:0,rafId:null,lastButtonPress:null,thresholds:this.resolveThresholds()},this.subscribers=new Set,this.scheduledBeats=this.initializeScheduledBeats()}createNormalizedBeatMap(A){if(qF(A)){let B=120;if(A.beats.length>=2){const I=A.beats[1].timestamp-A.beats[0].timestamp;I>0&&(B=60/I)}const g={version:"1.0.0",algorithm:"subdivision",minBpm:B,maxBpm:B,sensitivity:1,filter:0,noiseFloorThreshold:0,hopSizeMs:4,fftSize:2048,dpAlpha:680,melBands:40,highPassCutoff:.4,gaussianSmoothMs:20,tempoCenter:.5,tempoWidth:1.4,useOctaveResolution:!1,useTripleMeter:!1,generatedAt:new Date().toISOString()};return{audioId:A.audioId,duration:A.duration,beats:A.beats,bpm:B,metadata:g,downbeatConfig:A.downbeatConfig}}else return JF(A)?this.options.useInterpolatedBeats?{audioId:A.audioId,duration:A.duration,beats:A.mergedBeats,bpm:A.quarterNoteBpm,metadata:A.originalMetadata}:{audioId:A.audioId,duration:A.duration,beats:A.detectedBeats,bpm:A.quarterNoteBpm,metadata:A.originalMetadata}:A}initializeScheduledBeats(){return this.normalizedBeatMap.beats.map((A,B)=>({beat:A,index:B,upcomingEmitted:!1,exactEmitted:!1,passedEmitted:!1}))}getOptions(){return{...this.options}}getAccuracyThresholds(){return{...this.state.thresholds}}setDifficulty(A){A.preset!==void 0&&(this.options.difficultyPreset=A.preset),A.customThresholds!==void 0&&(this.options.customThresholds=A.customThresholds),this.state.thresholds=this.resolveThresholds()}subscribe(A){return this.subscribers.add(A),()=>{this.subscribers.delete(A)}}start(){this.state.isRunning||(this.state.isRunning=!0,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.state.isPaused=!1,this.scheduleUpdate())}stop(){this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null),this.state.isRunning=!1,this.state.isPaused=!1,this.state.pauseTime=0,this.scheduledBeats=this.initializeScheduledBeats()}pause(){!this.state.isRunning||this.state.isPaused||(this.state.isPaused=!0,this.state.pauseTime=this.getCurrentAudioTime(),this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null))}resume(){this.state.isPaused&&(this.state.isPaused=!1,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.scheduleUpdate())}seek(A){const B=Math.max(0,Math.min(A,this.normalizedBeatMap.duration));this.state.pauseTime=B,this.state.isRunning&&!this.state.isPaused&&(this.state.startTime=this.audioContext.currentTime-B),this.scheduledBeats=this.initializeScheduledBeats()}getCurrentAudioTime(){return this.state.isRunning?this.state.isPaused?this.state.pauseTime:this.audioContext.currentTime-this.state.startTime+this.getTotalLatencyCompensation():this.state.pauseTime}getTotalLatencyCompensation(){let A=0;if(this.options.compensateOutputLatency){const B=this.audioContext.outputLatency??0,g=this.audioContext.baseLatency??0;A+=B+g}return A+=this.options.userOffsetMs/1e3,A}getSyncState(){const A=this.audioContext.currentTime,B=this.getCurrentAudioTime(),g=this.audioContext.outputLatency??0,I=this.audioContext.baseLatency??0,E=A-this.state.startTime,i=B-E-this.getTotalLatencyCompensation();return{audioContextTime:A,audioElementTime:B,drift:i,isSynchronized:Math.abs(i)<=this.options.timingTolerance,outputLatency:g,baseLatency:I,userOffsetMs:this.options.userOffsetMs,totalCompensation:this.getTotalLatencyCompensation()}}scheduleUpdate(){!this.state.isRunning||this.state.isPaused||(this.state.rafId=requestAnimationFrame(()=>{this.update(),this.scheduleUpdate()}))}update(){if(!this.state.isRunning||this.state.isPaused)return;const A=this.getCurrentAudioTime(),B=this.getCurrentBpm();for(const g of this.scheduledBeats){const E=g.beat.timestamp-A;!g.upcomingEmitted&&E<=this.options.anticipationTime&&E>0&&(this.emitEvent(g,"upcoming",A,B,E),g.upcomingEmitted=!0),!g.exactEmitted&&Math.abs(E)<=this.options.timingTolerance&&(this.emitEvent(g,"exact",A,B,E),g.exactEmitted=!0),!g.passedEmitted&&E<-this.options.timingTolerance&&(this.emitEvent(g,"passed",A,B,E),g.passedEmitted=!0)}}emitEvent(A,B,g,I,E){const i={beat:A.beat,currentBpm:I,audioTime:g,timeUntilBeat:E,type:B};for(const C of this.subscribers)try{C(i)}catch(a){console.error("BeatStream callback error:",a)}}getUpcomingBeats(A){const B=this.getCurrentAudioTime(),g=[];for(const I of this.normalizedBeatMap.beats){const E=I.timestamp-B;if(E>=0&&E<=this.options.anticipationTime&&(g.push(I),g.length>=A))break}return g}getBeatAtTime(A){const B=this.options.timingTolerance;for(const g of this.normalizedBeatMap.beats)if(Math.abs(g.timestamp-A)<=B)return g;return null}getCurrentBeat(){const A=this.getCurrentAudioTime();let B=null;for(const g of this.normalizedBeatMap.beats)if(g.timestamp<=A)B=g;else break;return B}getNextBeat(){const A=this.getCurrentAudioTime();for(const B of this.normalizedBeatMap.beats)if(B.timestamp>A)return B;return null}getCurrentBpm(){const A=this.getCurrentAudioTime(),B=[];for(const a of this.normalizedBeatMap.beats)a.timestamp<=A+.1&&B.push(a);if(B.length<2)return this.normalizedBeatMap.bpm;const g=Math.min(this.rollingBpmWindowSize,B.length),I=B.slice(-g);let E=0;for(let a=1;a<I.length;a++)E+=I[a].timestamp-I[a-1].timestamp;const C=60/(E/(I.length-1));return Math.max(30,Math.min(300,C))}resolveThresholds(){return this.options.customThresholds&&Object.keys(this.options.customThresholds).length>0?{...nI(this.options.difficultyPreset||"medium"),...this.options.customThresholds}:nI(this.options.difficultyPreset||"medium")}checkButtonPress(A,B){let g=null,I=1/0;for(const G of this.normalizedBeatMap.beats){const n=A-G.timestamp,h=Math.abs(n);h<I&&(I=h,g=G)}if(!g){const G={accuracy:"miss",offset:A,matchedBeat:null,absoluteOffset:A,keyMatch:!0,pressedKey:B,requiredKey:void 0};return this.state.lastButtonPress=G,G}const E=A-g.timestamp,i=Math.abs(E);let C;const a=this.state.thresholds;i<=a.perfect?C="perfect":i<=a.great?C="great":i<=a.good?C="good":i<=a.ok?C="ok":C="miss";const e=this.options.ignoreKeyRequirements??!1,o=g.requiredKey!==void 0;let t=!0;!e&&o&&(B===void 0?(C="miss",t=!1):B!==g.requiredKey&&(C="wrongKey",t=!1));const s={accuracy:C,offset:E,matchedBeat:g,absoluteOffset:i,keyMatch:t,pressedKey:B,requiredKey:g.requiredKey};return this.state.lastButtonPress=s,s}getLastBeatAccuracy(){return this.state.lastButtonPress}isRunning(){return this.state.isRunning&&!this.state.isPaused}isPaused(){return this.state.isPaused}getCurrentTime(){return this.getCurrentAudioTime()}getDuration(){return this.normalizedBeatMap.duration}getBeatMap(){return this.beatMap}getNormalizedBeatMap(){return this.normalizedBeatMap}setBeatMap(A){this.beatMap=A,this.normalizedBeatMap=this.createNormalizedBeatMap(A),this.scheduledBeats=this.initializeScheduledBeats()}dispose(){this.stop(),this.subscribers.clear()}}const VA=SA.for("BeatInterpolator");class Pg{constructor(A={}){this.options={...KC,...A}}getConfig(){return{...this.options}}interpolate(A){const{beats:B,audioId:g,duration:I,metadata:E,bpm:i,downbeatConfig:C}=A;if(VA.debug("Starting beat interpolation",{audioId:g,detectedBeats:B.length,duration:I,originalBpm:i}),B.length===0)return VA.warn("No beats to interpolate"),this.createEmptyInterpolatedBeatMap(A);if(B.length===1)return VA.warn("Only one beat, cannot determine quarter note"),this.createSingleBeatInterpolatedBeatMap(A);const a=this.detectQuarterNote(B);VA.debug("Quarter note detected",{intervalSeconds:a.intervalSeconds,bpm:a.bpm,confidence:a.confidence,method:a.method,denseSectionCount:a.denseSectionCount});const e=this.analyzeGaps(B,a.intervalSeconds);VA.debug("Gap analysis complete",{totalGaps:e.totalGaps,halfNoteGaps:e.halfNoteGaps,anomalies:e.anomalies.length,gridAlignmentScore:e.gridAlignmentScore});const o=this.generateGrid(A,a,e);let t=this.mergeBeats(B,o,C);const s=t.filter(p=>p.source==="interpolated").length,G=s>0?t.filter(p=>p.source==="interpolated").reduce((p,k)=>p+k.confidence,0)/s:0,n=this.calculateTempoDriftRatio(t),h=this.identifyTempoClusters(B),c=h.map(p=>Math.round(p.bpm)),d=this.filterOctaveMultiples(c),l=d.length>1;let F,m;if(this.options.enableMultiTempo&&l){const p=this.runMultiTempoAnalysis(B,h,I);p&&(F=p.tempoSections,m=!0,F.length>1&&(t=this.reinterpolateBoundaryRegions(t,F,B,C)),VA.debug("Multi-tempo analysis complete",{sectionCount:F.length,tempos:F.map(k=>k.bpm)}))}const y={quarterNoteDetection:a,gapAnalysis:e,detectedBeatCount:B.length,interpolatedBeatCount:s,totalBeatCount:t.length,interpolationRatio:t.length>0?s/t.length:0,avgInterpolatedConfidence:G,tempoDriftRatio:n,detectedClusterTempos:d.length>0?d:void 0,hasMultipleTempos:l,tempoSections:F,hasMultiTempoApplied:m};return VA.debug("Interpolation complete",{detectedBeats:B.length,interpolatedBeats:s,totalBeats:t.length,interpolationRatio:y.interpolationRatio.toFixed(2),hasMultipleTempos:l,hasMultiTempoApplied:m}),{audioId:g,duration:I,detectedBeats:[...B],mergedBeats:t,quarterNoteInterval:a.intervalSeconds,quarterNoteBpm:a.bpm,quarterNoteConfidence:a.confidence,originalMetadata:E,interpolationMetadata:y,downbeatConfig:C}}detectQuarterNote(A){const B=this.identifyDenseSections(A);VA.debug("Identified dense sections",{count:B.length,totalBeats:B.reduce((s,G)=>s+G.beatCount,0)});const g=this.calculateWeightedIntervals(A,B),I=this.buildIntervalHistogram(g),E=this.findHistogramPeaks(I);if(E.length===0){const s=this.calculateAverageInterval(A);return{intervalSeconds:s,bpm:60/s,confidence:.3,histogramPeak:0,secondaryPeaks:[],method:"tempo-detector-fallback",denseSectionCount:B.length,denseSectionBeats:B.reduce((G,n)=>G+n.beatCount,0)}}const i=E[0],C=E.slice(1,4).map(s=>s.interval),a=g.reduce((s,G)=>s+G.weight,0),e=a>0?i.weight/a:0,o=B.reduce((s,G)=>s+G.beatCount,0)/A.length,t=Math.min(1,e*.6+o*.4);return{intervalSeconds:i.interval,bpm:60/i.interval,confidence:t,histogramPeak:i.weight,secondaryPeaks:C,method:"histogram",denseSectionCount:B.length,denseSectionBeats:B.reduce((s,G)=>s+G.beatCount,0)}}identifyDenseSections(A){const B=this.options.denseSectionMinBeats,g=[];if(A.length<B)return g;const I=[];for(let C=1;C<A.length;C++)I.push(A[C].timestamp-A[C-1].timestamp);let E=0;for(let C=1;C<I.length;C++){const a=I.slice(E,C+1),e=a.reduce((s,G)=>s+G,0)/a.length,o=a.reduce((s,G)=>s+Math.pow(G-e,2),0)/a.length,t=Math.pow(e*.2,2);if(o>t){const s=C-E+1;if(s>=B){const G=I.slice(E,C),n=G.reduce((c,d)=>c+d,0)/G.length,h=G.reduce((c,d)=>c+Math.pow(d-n,2),0)/G.length;g.push({startIndex:E,endIndex:C-1,beatCount:s,avgInterval:n,intervalVariance:h})}E=C}}const i=I.length-E+1;if(i>=B){const C=I.slice(E),a=C.reduce((o,t)=>o+t,0)/C.length,e=C.reduce((o,t)=>o+Math.pow(t-a,2),0)/C.length;g.push({startIndex:E,endIndex:I.length-1,beatCount:i,avgInterval:a,intervalVariance:e})}return g}identifyTempoClusters(A){const B=this.identifyDenseSections(A);if(B.length===0)return[];const g=this.options.tempoSectionThreshold,I=this.options.minClusterBeats,E=B.map(C=>({...C,bpm:60/C.avgInterval,isVerified:C.beatCount>=I}));if(E.length===1)return E[0].isVerified?E:[];E.sort((C,a)=>C.startIndex-a.startIndex);const i=[E[0]];for(let C=1;C<E.length;C++){const a=E[C],e=i[i.length-1],o=e.endIndex+1>=a.startIndex,s=1-Math.min(e.avgInterval,a.avgInterval)/Math.max(e.avgInterval,a.avgInterval)<=g;if(o&&s){const G=[];for(let c=e.startIndex;c<=a.endIndex&&c<A.length-1;c++)G.push(A[c+1].timestamp-A[c].timestamp);const n=G.reduce((c,d)=>c+d,0)/G.length,h=G.reduce((c,d)=>c+Math.pow(d-n,2),0)/G.length;i[i.length-1]={startIndex:e.startIndex,endIndex:a.endIndex,beatCount:e.beatCount+a.beatCount,avgInterval:n,intervalVariance:h,bpm:60/n,isVerified:e.beatCount+a.beatCount>=I}}else i.push(a)}return i.filter(C=>C.isVerified)}findConflictingClusters(A){if(A.length<2)return null;const B=this.options.tempoSectionThreshold,g=[];for(let I=0;I<A.length;I++)for(let E=I+1;E<A.length;E++){const i=A[I],C=A[E];if(this.isOctaveMultiple(i.bpm,C.bpm))continue;1-Math.min(i.bpm,C.bpm)/Math.max(i.bpm,C.bpm)>B&&g.push({cluster1:i,cluster2:C})}return g.length>0?g:null}isOctaveMultiple(A,B,g=.1){const I=[.5,2],E=A/B;return I.some(i=>Math.abs(E-i)<=g)}filterOctaveMultiples(A){if(A.length<=1)return A;const B=[...A].sort((I,E)=>I-E),g=[];for(const I of B)g.some(i=>this.isOctaveMultiple(I,i))||g.push(I);return g}findCrossingPoint(A,B,g){const I=this.options.tempoSectionThreshold,E=this.options.tempoAdaptationRate,i=g.slice(A.startIndex,A.endIndex+1),C=g.slice(B.startIndex,B.endIndex+1),a=g.slice(A.endIndex+1,B.startIndex),e=i[i.length-1],o=C[0];if(a.length===0){const n=(e.timestamp+o.timestamp)/2;return VA.debug("No connecting beats between clusters → automatic boundary",{cluster1Bpm:A.bpm,cluster2Bpm:B.bpm,boundaryTimestamp:n}),{hasBoundary:!0,boundaryTimestamp:n,gapRatio:1,beatsInSection1:i,beatsInSection2:C}}const t=this.interpolateForwardsWithDrift(e.timestamp,A.avgInterval,a,E,o.timestamp),s=this.interpolateBackwardsWithDrift(o.timestamp,B.avgInterval,a,E,e.timestamp),G=this.measureGapAtCrossing(t,s,A.avgInterval,B.avgInterval);if(VA.debug("Crossing point analysis",{cluster1Bpm:A.bpm,cluster2Bpm:B.bpm,connectingBeats:a.length,gapRatio:G.gapRatio.toFixed(3),threshold:I,hasBoundary:G.gapRatio>I}),G.gapRatio>I){const n=G.crossingTimestamp,{beatsInSection1:h,beatsInSection2:c}=this.assignBeatsToSections(a,A,B,n);return{hasBoundary:!0,boundaryTimestamp:n,gapRatio:G.gapRatio,beatsInSection1:[...i,...h],beatsInSection2:[...c,...C]}}else return{hasBoundary:!1,gapRatio:G.gapRatio,beatsInSection1:[...i,...a,...C],beatsInSection2:[]}}measureGapAtCrossing(A,B,g,I){const E=A.endTimestamp,i=B.endTimestamp,C=(E+i)/2,a=A.beatPositions,e=B.beatPositions;let o=E,t=1/0;for(const d of a){const l=Math.abs(d-C);l<t&&(t=l,o=d)}let s=i,G=1/0;for(const d of e){const l=Math.abs(d-C);l<G&&(G=l,s=d)}a.length===0&&(o=E),e.length===0&&(s=i);const n=Math.abs(o-s),h=(g+I)/2;return{gapRatio:h>0?n/h:0,crossingTimestamp:C,forwardsInterval:A.finalInterval,backwardsInterval:B.finalInterval}}assignBeatsToSections(A,B,g,I){const E=[],i=[];A.length>0&&A[0].timestamp>I,A[0],A[A.length-1];for(const C of A)C.timestamp<I?E.push(C):i.push(C);return VA.debug("Assigned connecting beats to sections",{totalConnectingBeats:A.length,toSection1:E.length,toSection2:i.length,boundaryTimestamp:I}),{beatsInSection1:E,beatsInSection2:i}}calculatePhaseAlignment(A,B,g,I=.1){const E=A-B,i=Math.round(E/g),C=B+i*g,a=Math.abs(A-C),e=g*I;return a<=e?1:0}runMultiTempoAnalysis(A,B,g){if(B.length<2)return null;const I=this.findConflictingClusters(B);if(!I||I.length===0)return VA.debug("No conflicting tempo clusters found"),null;VA.debug("Found conflicting tempo clusters",{conflictCount:I.length,conflicts:I.map(o=>({c1:Math.round(o.cluster1.bpm),c2:Math.round(o.cluster2.bpm)}))});const E=[...B].sort((o,t)=>o.startIndex-t.startIndex),i=[];let C=0,a=0;for(let o=0;o<E.length-1;o++){const t=E[o],s=E[o+1];if(!I.some(h=>h.cluster1===t&&h.cluster2===s||h.cluster1===s&&h.cluster2===t))continue;const n=this.findCrossingPoint(t,s,A);if(n.hasBoundary){const h=n.boundaryTimestamp;A[t.endIndex],i.push({start:C,end:h,bpm:Math.round(t.bpm*10)/10,intervalSeconds:t.avgInterval,beatCount:t.beatCount,startBeatIndex:a,endBeatIndex:t.endIndex}),C=h,a=s.startIndex}}const e=E[E.length-1];return i.push({start:C,end:g,bpm:Math.round(e.bpm*10)/10,intervalSeconds:e.avgInterval,beatCount:e.beatCount,startBeatIndex:a,endBeatIndex:e.endIndex}),i.length===1?(VA.debug("Multi-tempo analysis found no section boundaries"),null):{tempoSections:i}}reinterpolateBoundaryRegions(A,B,g,I){return VA.debug("Boundary regions identified",{sectionCount:B.length,sections:B.map(E=>({bpm:E.bpm,start:E.start.toFixed(2),end:E.end.toFixed(2)}))}),A}canApplyMultiTempo(A){const{interpolationMetadata:B}=A,g=B.hasMultipleTempos,I=B.hasMultiTempoApplied===!0;return g&&!I}calculateWeightedIntervals(A,B){const g=[];for(let I=1;I<A.length;I++){const E=A[I].timestamp-A[I-1].timestamp,i=B.some(t=>I>t.startIndex&&I<=t.endIndex),C=(A[I-1].confidence+A[I].confidence)/2,a=i?2:1;let e=1;if(I>1){const t=A[I-1].timestamp-A[I-2].timestamp;e=.5+Math.min(E,t)/Math.max(E,t)*.5}if(I<A.length-1){const t=A[I+1].timestamp-A[I].timestamp,s=Math.min(E,t)/Math.max(E,t);e=Math.max(e,.5+s*.5)}const o=C*a*e;g.push({intervalSeconds:E,weight:o,beatIndex:I,isFromDenseSection:i,confidence:C})}return g}buildIntervalHistogram(A){const B=new Map,g=.005;for(const I of A){const E=Math.round(I.intervalSeconds/g)*g,i=B.get(E)||0;B.set(E,i+I.weight)}return B}findHistogramPeaks(A){const B=[],g=Array.from(A.entries()).sort((i,C)=>i[0]-C[0]);for(let i=0;i<g.length;i++){const[C,a]=g[i],e=i>0?g[i-1][1]:0,o=i<g.length-1?g[i+1][1]:0;a>=e&&a>=o&&a>0&&B.push({interval:C,weight:a})}B.sort((i,C)=>C.weight-i.weight);const I=.05,E=[];for(const i of B)E.some(a=>Math.abs(a.interval-i.interval)<I)||E.push(i);return E}calculateAverageInterval(A){if(A.length<2)return .5;let B=0;for(let g=1;g<A.length;g++)B+=A[g].timestamp-A[g-1].timestamp;return B/(A.length-1)}analyzeGaps(A,B){const g=this.options.anomalyThreshold;let I=0;const E=[],i=[];let C=0;for(let t=1;t<A.length;t++){const G=(A[t].timestamp-A[t-1].timestamp)/B,n=Math.round(G);n>1&&i.push(n),Math.abs(G-2)<.2&&I++,(G<1-g||G>1+g&&Math.abs(G-2)>.3&&Math.abs(G-3)>.3&&Math.abs(G-4)>.3)&&E.push(t);const c=Math.round(A[t].timestamp/B)*B,d=Math.abs(A[t].timestamp-c)/B;C+=d}const a=A.length>1?C/(A.length-1):0,e=Math.max(0,1-a*2),o=i.length>0?i.reduce((t,s)=>t+s,0)/i.length:1;return{totalGaps:i.length,halfNoteGaps:I,anomalies:E,avgGapSize:o,gridAlignmentScore:e}}generateGrid(A,B,g){return this.interpolateAdaptivePhaseLocked(A,B)}interpolateForwardsWithDrift(A,B,g,I,E){const i=[];let C=B,a=A;const e=g.filter(n=>!(n.timestamp<=A||E!==void 0&&n.timestamp>E));let o=A;for(let n=0;n<e.length;n++){const h=e[n],c=h.timestamp-o,d=Math.round(c/C);if(d>0){const l=a+d*C,m=(h.timestamp-l)/d*I;C=C+m;for(let y=1;y<d;y++){const p=o+y*C;if(E!==void 0&&p>E)break;i.push(p)}}a=h.timestamp,o=h.timestamp}const t=i.length,s=A+t*B,G=i.length>0?i[i.length-1]-s:0;return{finalInterval:C,beatPositions:i,phaseError:G,endTimestamp:o}}interpolateBackwardsWithDrift(A,B,g,I,E){const i=[];let C=B,a=A;const e=g.filter(n=>!(n.timestamp>=A||E!==void 0&&n.timestamp<E)).reverse();let o=A;for(let n=0;n<e.length;n++){const h=e[n],c=o-h.timestamp,d=Math.round(c/C);if(d>0){const l=a-d*C,m=(h.timestamp-l)/d*I;C=C+m;for(let y=1;y<d;y++){const p=o-y*C;if(E!==void 0&&p<E)break;i.unshift(p)}}a=h.timestamp,o=h.timestamp}const t=i.length,s=A-t*B,G=i.length>0?i[0]-s:0;return{finalInterval:C,beatPositions:i,phaseError:G,endTimestamp:o}}interpolateAdaptivePhaseLocked(A,B){const{beats:g,duration:I}=A,E=B.intervalSeconds,i=this.options.gridSnapTolerance,C=this.options.tempoAdaptationRate,a=[],e=this.options.gridAlignmentWeight,o=this.options.anchorConfidenceWeight,t=this.options.paceConfidenceWeight;if(this.options.extrapolateStart){let n=g[0].timestamp-E;for(;n>=0;){const h=this.calculateConfidence(e,o*.5,t*B.confidence);a.unshift(this.createInterpolatedBeat(n,h,g[0].timestamp)),n-=E}}let s=g[0].timestamp,G=E;for(let n=0;n<g.length;n++){const h=g[n];if(a.push(this.createDetectedBeat(h)),n<g.length-1){const c=g[n+1],d=c.timestamp-h.timestamp,l=Math.round(d/G),F=s+l*G,m=c.timestamp-F;if(l>0){const y=m/l*C;G=G+y}for(let y=1;y<l;y++){const p=h.timestamp+y*G;if(p>=c.timestamp-i)break;const k=this.calculateConfidence(e,o*((h.confidence+c.confidence)/2),t*B.confidence);a.push(this.createInterpolatedBeat(p,k,h.timestamp))}s=c.timestamp}}if(this.options.extrapolateEnd){let n=g[g.length-1].timestamp+G;for(;n<=I;){const h=this.calculateConfidence(e,o*.5,t*B.confidence);a.push(this.createInterpolatedBeat(n,h,g[g.length-1].timestamp)),n+=G}}return a.sort((n,h)=>n.timestamp-h.timestamp),a}mergeBeats(A,B,g){const I=this.options.gridSnapTolerance,E=[],i=[...B].sort((o,t)=>o.timestamp-t.timestamp),C=[...A].sort((o,t)=>o.timestamp-t.timestamp),a=new Map;for(const o of C)a.set(o.timestamp,o);const e=new Set;for(const o of i){const t=this.findBeatNear(a,o.timestamp,I);t&&!e.has(t.timestamp)?(E.push(this.createDetectedBeat(t)),e.add(t.timestamp)):o.source==="interpolated"&&E.push(o)}for(const o of C)e.has(o.timestamp)||E.push(this.createDetectedBeat(o));return E.sort((o,t)=>o.timestamp-t.timestamp),this.reassignBeatPositions(E,g),E}reassignBeatPositions(A,B){const g=B??GI,I=this.computeMeasureOffsets(g.segments,A.length);for(let E=0;E<A.length;E++){const i=A[E],C=this.findActiveSegmentIndex(g.segments,E),a=g.segments[C],{downbeatBeatIndex:e,timeSignature:o}=a,{beatsPerMeasure:t}=o,s=E-e,G=(s%t+t)%t,n=G===0,c=Math.max(0,Math.floor(s/t))+I[C];i.beatInMeasure=G,i.isDownbeat=n,i.measureNumber=c}}computeMeasureOffsets(A,B){const g=[];for(let I=0;I<A.length;I++)if(I===0)g.push(0);else{const E=A[I-1],{downbeatBeatIndex:i,timeSignature:C}=E,a=C.beatsPerMeasure,o=A[I].startBeat-1-i,t=Math.max(0,Math.floor(o/a));g.push(t+1)}return g}findActiveSegmentIndex(A,B){let g=0;for(let I=0;I<A.length&&A[I].startBeat<=B;I++)g=I;return g}calculateConfidence(A,B,g){return Math.max(0,Math.min(1,A+B+g))}createInterpolatedBeat(A,B,g){return{timestamp:A,beatInMeasure:0,isDownbeat:!1,measureNumber:0,intensity:.5,confidence:B,source:"interpolated",distanceToAnchor:Math.abs(A-g),nearestAnchorTimestamp:g}}createDetectedBeat(A){return{...A,source:"detected",distanceToAnchor:0,nearestAnchorTimestamp:A.timestamp}}findBeatNear(A,B,g){const I=A.get(B);if(I)return I;for(const[E,i]of A)if(Math.abs(E-B)<=g)return i;return null}findNearestAnchor(A,B,g){const I=g>=0&&g<A.length?A[g]:A[0],E=g+1<A.length?A[g+1]:A[A.length-1],i=Math.abs(B-I.timestamp),C=Math.abs(B-E.timestamp);return i<=C?{timestamp:I.timestamp,confidence:I.confidence}:{timestamp:E.timestamp,confidence:E.confidence}}calculateTempoDriftRatio(A){if(A.length<3)return 1;const B=[];for(let E=1;E<A.length;E++){const i=A[E].timestamp-A[E-1].timestamp;i>0&&B.push(60/i)}if(B.length===0)return 1;const g=Math.min(...B),I=Math.max(...B);return g>0?I/g:1}createEmptyInterpolatedBeatMap(A){return{audioId:A.audioId,duration:A.duration,detectedBeats:[],mergedBeats:[],quarterNoteInterval:.5,quarterNoteBpm:120,quarterNoteConfidence:0,originalMetadata:A.metadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:.5,bpm:120,confidence:0,histogramPeak:0,secondaryPeaks:[],method:"tempo-detector-fallback",denseSectionCount:0,denseSectionBeats:0},gapAnalysis:{totalGaps:0,halfNoteGaps:0,anomalies:[],avgGapSize:1,gridAlignmentScore:0},detectedBeatCount:0,interpolatedBeatCount:0,totalBeatCount:0,interpolationRatio:0,avgInterpolatedConfidence:0,tempoDriftRatio:1,detectedClusterTempos:void 0,hasMultipleTempos:!1,tempoSections:void 0,hasMultiTempoApplied:void 0},downbeatConfig:A.downbeatConfig}}createSingleBeatInterpolatedBeatMap(A){const B=A.beats[0],g={...B,source:"detected",distanceToAnchor:0,nearestAnchorTimestamp:B.timestamp};return{audioId:A.audioId,duration:A.duration,detectedBeats:[B],mergedBeats:[g],quarterNoteInterval:.5,quarterNoteBpm:120,quarterNoteConfidence:.1,originalMetadata:A.metadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:.5,bpm:120,confidence:.1,histogramPeak:0,secondaryPeaks:[],method:"tempo-detector-fallback",denseSectionCount:0,denseSectionBeats:0},gapAnalysis:{totalGaps:0,halfNoteGaps:0,anomalies:[],avgGapSize:1,gridAlignmentScore:0},detectedBeatCount:1,interpolatedBeatCount:0,totalBeatCount:1,interpolationRatio:0,avgInterpolatedConfidence:0,tempoDriftRatio:1,detectedClusterTempos:void 0,hasMultipleTempos:!1,tempoSections:void 0,hasMultiTempoApplied:void 0},downbeatConfig:A.downbeatConfig}}static toJSON(A){const B={audioId:A.audioId,duration:A.duration,detectedBeats:A.detectedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),mergedBeats:A.mergedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey},source:g.source,distanceToAnchor:g.distanceToAnchor,nearestAnchorTimestamp:g.nearestAnchorTimestamp})),quarterNoteInterval:A.quarterNoteInterval,quarterNoteBpm:A.quarterNoteBpm,quarterNoteConfidence:A.quarterNoteConfidence,originalMetadata:A.originalMetadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:A.interpolationMetadata.quarterNoteDetection.intervalSeconds,bpm:A.interpolationMetadata.quarterNoteDetection.bpm,confidence:A.interpolationMetadata.quarterNoteDetection.confidence,histogramPeak:A.interpolationMetadata.quarterNoteDetection.histogramPeak,secondaryPeaks:A.interpolationMetadata.quarterNoteDetection.secondaryPeaks,method:A.interpolationMetadata.quarterNoteDetection.method,denseSectionCount:A.interpolationMetadata.quarterNoteDetection.denseSectionCount,denseSectionBeats:A.interpolationMetadata.quarterNoteDetection.denseSectionBeats},gapAnalysis:{totalGaps:A.interpolationMetadata.gapAnalysis.totalGaps,halfNoteGaps:A.interpolationMetadata.gapAnalysis.halfNoteGaps,anomalies:A.interpolationMetadata.gapAnalysis.anomalies,avgGapSize:A.interpolationMetadata.gapAnalysis.avgGapSize,gridAlignmentScore:A.interpolationMetadata.gapAnalysis.gridAlignmentScore},detectedBeatCount:A.interpolationMetadata.detectedBeatCount,interpolatedBeatCount:A.interpolationMetadata.interpolatedBeatCount,totalBeatCount:A.interpolationMetadata.totalBeatCount,interpolationRatio:A.interpolationMetadata.interpolationRatio,avgInterpolatedConfidence:A.interpolationMetadata.avgInterpolatedConfidence,tempoDriftRatio:A.interpolationMetadata.tempoDriftRatio,detectedClusterTempos:A.interpolationMetadata.detectedClusterTempos,hasMultipleTempos:A.interpolationMetadata.hasMultipleTempos,tempoSections:A.interpolationMetadata.tempoSections?.map(g=>({start:g.start,end:g.end,bpm:g.bpm,intervalSeconds:g.intervalSeconds,beatCount:g.beatCount,startBeatIndex:g.startBeatIndex,endBeatIndex:g.endBeatIndex})),hasMultiTempoApplied:A.interpolationMetadata.hasMultiTempoApplied},downbeatConfig:A.downbeatConfig};return JSON.stringify(B,null,2)}static fromJSON(A){const B=JSON.parse(A);return{audioId:B.audioId,duration:B.duration,detectedBeats:B.detectedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey}})),mergedBeats:B.mergedBeats.map(g=>({timestamp:g.timestamp,beatInMeasure:g.beatInMeasure,isDownbeat:g.isDownbeat,measureNumber:g.measureNumber,intensity:g.intensity,confidence:g.confidence,...g.requiredKey!==void 0&&{requiredKey:g.requiredKey},source:g.source,distanceToAnchor:g.distanceToAnchor,nearestAnchorTimestamp:g.nearestAnchorTimestamp})),quarterNoteInterval:B.quarterNoteInterval,quarterNoteBpm:B.quarterNoteBpm,quarterNoteConfidence:B.quarterNoteConfidence,originalMetadata:B.originalMetadata,interpolationMetadata:{quarterNoteDetection:{intervalSeconds:B.interpolationMetadata.quarterNoteDetection.intervalSeconds,bpm:B.interpolationMetadata.quarterNoteDetection.bpm,confidence:B.interpolationMetadata.quarterNoteDetection.confidence,histogramPeak:B.interpolationMetadata.quarterNoteDetection.histogramPeak,secondaryPeaks:B.interpolationMetadata.quarterNoteDetection.secondaryPeaks,method:B.interpolationMetadata.quarterNoteDetection.method,denseSectionCount:B.interpolationMetadata.quarterNoteDetection.denseSectionCount,denseSectionBeats:B.interpolationMetadata.quarterNoteDetection.denseSectionBeats},gapAnalysis:{totalGaps:B.interpolationMetadata.gapAnalysis.totalGaps,halfNoteGaps:B.interpolationMetadata.gapAnalysis.halfNoteGaps,anomalies:B.interpolationMetadata.gapAnalysis.anomalies,avgGapSize:B.interpolationMetadata.gapAnalysis.avgGapSize,gridAlignmentScore:B.interpolationMetadata.gapAnalysis.gridAlignmentScore},detectedBeatCount:B.interpolationMetadata.detectedBeatCount,interpolatedBeatCount:B.interpolationMetadata.interpolatedBeatCount,totalBeatCount:B.interpolationMetadata.totalBeatCount,interpolationRatio:B.interpolationMetadata.interpolationRatio,avgInterpolatedConfidence:B.interpolationMetadata.avgInterpolatedConfidence,tempoDriftRatio:B.interpolationMetadata.tempoDriftRatio,detectedClusterTempos:B.interpolationMetadata.detectedClusterTempos,hasMultipleTempos:B.interpolationMetadata.hasMultipleTempos??!1,tempoSections:B.interpolationMetadata.tempoSections?.map(g=>({start:g.start,end:g.end,bpm:g.bpm,intervalSeconds:g.intervalSeconds,beatCount:g.beatCount,startBeatIndex:g.startBeatIndex,endBeatIndex:g.endBeatIndex})),hasMultiTempoApplied:B.interpolationMetadata.hasMultiTempoApplied},downbeatConfig:B.downbeatConfig}}static async saveToFile(A,B){if(typeof process>"u"||!process.versions?.node)throw new Error("saveToFile is only available in Node.js environment");const{writeFile:g}=await Promise.resolve().then(()=>GB),I=Pg.toJSON(A);await g(B,I,"utf-8")}static async loadFromFile(A){if(typeof process>"u"||!process.versions?.node)throw new Error("loadFromFile is only available in Node.js environment");const{readFile:B}=await Promise.resolve().then(()=>GB),g=await B(A,"utf-8");return Pg.fromJSON(g)}}function nQ(Q){const{mergedBeats:A,interpolationMetadata:B,downbeatConfig:g}=Q,I=[],E=[];for(let C=0;C<A.length;C++){const a=A[C],e={timestamp:a.timestamp,beatInMeasure:a.beatInMeasure,isDownbeat:a.isDownbeat,measureNumber:a.measureNumber,intensity:a.intensity,confidence:a.confidence};I.push(e),a.source==="detected"&&E.push(C)}return{audioId:Q.audioId,duration:Q.duration,beats:I,detectedBeatIndices:E,quarterNoteInterval:Q.quarterNoteInterval,quarterNoteBpm:Q.quarterNoteBpm,downbeatConfig:g??GI,tempoSections:B.tempoSections,originalMetadata:Q.originalMetadata}}const oI=class oI{constructor(A={}){this.options={includeAdvancedMetrics:!1,sampleRate:44100,fftSize:2048,trebleBoost:1,bassBoost:1,midBoost:1,...A}}async extractSonicFingerprint(A){const B=await this.fetchAndDecode(A),g=B.duration,I=g<3;let E;I?E=[0]:E=[.05,.4,.7];const i=[];for(const d of E){const l=await this.analyzeAtPosition(B,d,I);i.push(l)}const C=this.averageFrequencyBands(i);let a=MB.calculateDominance(C.bass,380),e=MB.calculateDominance(C.mid,3600),o=MB.calculateDominance(C.treble,1e4);a=a*this.options.bassBoost,e=e*this.options.midBoost,o=o*this.options.trebleBoost;const t=a+e+o;a=a/(t||1),e=e/(t||1),o=o/(t||1);const s=this.calculateAverageAmplitude(B),G=this.calculateRMS(B),h=this.calculatePeak(B)-G,c={bass_dominance:a,mid_dominance:e,treble_dominance:o,average_amplitude:s,analysis_metadata:{duration_analyzed:I?g:g*E.length*.01,full_buffer_analyzed:I,sample_positions:E,analyzed_at:new Date().toISOString()},rms_energy:G,dynamic_range:h};if(this.options.includeAdvancedMetrics){const d=[...C.bass,...C.mid,...C.treble];c.spectral_centroid=this.calculateSpectralCentroid(d),c.spectral_rolloff=this.calculateSpectralRolloff(d),c.zero_crossing_rate=this.calculateZeroCrossingRate(B)}return c}async analyzeTimeline(A,B){const g=await this.fetchAndDecode(A),I=g.duration,E=g.sampleRate,i=[];let C=[];if(B.type==="interval"){const a=B.intervalSeconds;for(let e=0;e<I;e+=a)C.push(e)}else{const a=B.count,e=I/a;for(let o=0;o<a;o++)C.push(o*e)}for(const a of C){const e=I-a,o=Math.min(1,e);if(o<=0)break;const t=Math.floor(a*E),s=Math.floor((a+o)*E),G=this.extractAudioSegment(g,t,s),n=this.performFFT(G,this.options.fftSize),h=new Uint8Array(n.length);for(let H=0;H<n.length;H++)h[H]=Math.min(255,Math.round(n[H]));const c=MB.separateFrequencyBands(h,E),d=MB.calculateDominance(c.bass)*this.options.bassBoost,l=MB.calculateDominance(c.mid)*this.options.midBoost,F=MB.calculateDominance(c.treble)*this.options.trebleBoost,m=d+l+F||1,y=this.calculateRMS(g,t,s),p=this.calculatePeak(g,t,s),k=[...c.bass,...c.mid,...c.treble];i.push({timestamp:a,duration:o,bass:d/m,mid:l/m,treble:F/m,amplitude:y,rms_energy:y,peak:p,dynamic_range:p-y,spectral_centroid:this.calculateSpectralCentroid(k),spectral_rolloff:this.calculateSpectralRolloff(k),zero_crossing_rate:this.calculateZeroCrossingRate(g,t,s)})}return i}async generateBeatMap(A,B,g,I,E){return new tB(g).generateBeatMap(A,B,I,E)}async generateBeatMapFromBuffer(A,B,g,I,E){return new tB(g).generateBeatMapFromBuffer(A,B,I,E)}createBeatStream(A,B,g){return new Ut(A,B,g)}interpolateBeatMap(A,B){return new Pg(B).interpolate(A)}async generateBeatMapWithInterpolation(A,B,g,I,E,i){const C=await this.generateBeatMap(A,B,g,I,i);return this.interpolateBeatMap(C,E)}async generateBeatMapWithInterpolationFromBuffer(A,B,g,I,E,i){const C=await this.generateBeatMapFromBuffer(A,B,g,I,i);return this.interpolateBeatMap(C,E)}static beatMapToJSON(A){return tB.toJSON(A)}static beatMapFromJSON(A){return tB.fromJSON(A)}static async saveBeatMapToFile(A,B){return tB.saveToFile(A,B)}static async loadBeatMapFromFile(A){return tB.loadFromFile(A)}async generateRhythm(A,B,g,I,E,i){const C=await this.fetchAndDecode(A);return this.generateRhythmFromBuffer(C,B,g,I,E,i)}async generateRhythmFromBuffer(A,B,g,I,E,i){const C=await this.generateBeatMapWithInterpolationFromBuffer(A,B,I,E),a=nQ(C);return new HB(g).generate(A,a,void 0,i)}async generateRhythmLevel(A,B,g,I,E,i){const C=await this.fetchAndDecode(A);return this.generateRhythmLevelFromBuffer(C,B,g,I,E,i)}async generateRhythmLevelFromBuffer(A,B,g,I,E,i){const C=await this.generateBeatMapWithInterpolationFromBuffer(A,B,I,E),a=nQ(C);return new tQ(g).generate(A,a,i)}async generateRhythmLevelWithPreset(A,B,g,I,E,i){const C=oI.LEVEL_PRESETS[g],a={difficulty:C.difficulty,controllerMode:C.controllerMode,buttons:C.buttons};return this.generateRhythmLevel(A,B,a,I,E,i)}async generateRhythmLevelWithPresetFromBuffer(A,B,g,I,E,i){const C=oI.LEVEL_PRESETS[g],a={difficulty:C.difficulty,controllerMode:C.controllerMode,buttons:C.buttons};return this.generateRhythmLevelFromBuffer(A,B,a,I,E,i)}async fetchAndDecodeAudio(A){return this.fetchAndDecode(A)}async fetchAndDecode(A){const B=await fetch(A);if(!B.ok)throw new Error(`Failed to fetch audio: ${B.statusText}`);const g=await B.arrayBuffer(),I=globalThis.AudioContext||window?.AudioContext;if(!I)throw new Error("AudioContext not available in this environment");const E=new I;return await new Promise((i,C)=>{E.decodeAudioData(g,i,C)})}async analyzeAtPosition(A,B,g){const I=A.sampleRate,E=A.duration;let i,C;if(g)i=0,C=A.length;else{const t=E*B;i=Math.floor(t*I),C=Math.min(i+I,A.length)}const a=this.extractAudioSegment(A,i,C),e=this.performFFT(a,this.options.fftSize),o=new Uint8Array(e.length);for(let t=0;t<e.length;t++)o[t]=Math.min(255,Math.round(e[t]));return MB.separateFrequencyBands(o,I)}extractAudioSegment(A,B,g){const I=g-B,E=new Float32Array(I);for(let i=0;i<A.numberOfChannels;i++){const C=A.getChannelData(i);for(let a=0;a<I;a++)E[a]+=C[B+a]/A.numberOfChannels}return E}performFFT(A,B){const g=Math.pow(2,Math.ceil(Math.log2(Math.min(A.length,B)))),I=new Float32Array(g),E=new Float32Array(g);for(let e=0;e<Math.min(A.length,g);e++){const o=.5-.5*Math.cos(2*Math.PI*e/(g-1));I[e]=A[e]*o}this.fft(I,E);const i=new Array(g/2);for(let e=0;e<i.length;e++){const o=I[e],t=E[e];i[e]=Math.sqrt(o*o+t*t)}const C=Math.max(...i);return i.map(e=>{const o=e/(C||1);return 20*Math.log10(Math.max(o,.001))})}fft(A,B){const g=A.length;if(g<=1)return;let I=0;for(let i=0;i<g-1;i++){i<I&&([A[i],A[I]]=[A[I],A[i]],[B[i],B[I]]=[B[I],B[i]]);let C=g/2;for(;I>=C;)I-=C,C/=2;I+=C}let E=2;for(;E<=g;){const i=-2*Math.PI/E,C=Math.cos(i),a=Math.sin(i);for(let e=0;e<g;e+=E){let o=1,t=0;for(let s=0;s<E/2;s++){const G=e+s,n=e+s+E/2,h=A[n]*o-B[n]*t,c=A[n]*t+B[n]*o;A[n]=A[G]-h,B[n]=B[G]-c,A[G]+=h,B[G]+=c;const d=o*C-t*a;t=o*a+t*C,o=d}}E*=2}}averageFrequencyBands(A){if(A.length===0)return{bass:[],mid:[],treble:[]};if(A.length===1)return A[0];const B=Math.max(...A.map(i=>Math.max(i.bass.length,i.mid.length,i.treble.length))),g=[],I=[],E=[];for(let i=0;i<B;i++){const C=A.map(o=>o.bass[i]||0),a=A.map(o=>o.mid[i]||0),e=A.map(o=>o.treble[i]||0);g.push(C.reduce((o,t)=>o+t,0)/C.length),I.push(a.reduce((o,t)=>o+t,0)/a.length),E.push(e.reduce((o,t)=>o+t,0)/e.length)}return{bass:g,mid:I,treble:E}}calculateAverageAmplitude(A){let B=0,g=0;for(let I=0;I<A.numberOfChannels;I++){const E=A.getChannelData(I);for(let i=0;i<E.length;i++)B+=Math.abs(E[i]),g++}return g>0?B/g:0}calculateRMS(A,B=0,g=A.length){let I=0,E=0;for(let i=0;i<A.numberOfChannels;i++){const C=A.getChannelData(i),a=Math.min(g,C.length);for(let e=B;e<a;e++)I+=C[e]*C[e],E++}return E>0?Math.sqrt(I/E):0}calculatePeak(A,B=0,g=A.length){let I=0;for(let E=0;E<A.numberOfChannels;E++){const i=A.getChannelData(E),C=Math.min(g,i.length);for(let a=B;a<C;a++){const e=Math.abs(i[a]);e>I&&(I=e)}}return I}calculateSpectralCentroid(A){let B=0,g=0;for(let I=0;I<A.length;I++)B+=I*A[I],g+=A[I];return g>0?B/g:0}calculateSpectralRolloff(A){const g=A.reduce((E,i)=>E+i,0)*.85;let I=0;for(let E=0;E<A.length;E++)if(I+=A[E],I>=g)return E/A.length;return 1}calculateZeroCrossingRate(A,B=0,g=A.length){let I=0,E=0;for(let i=0;i<A.numberOfChannels;i++){const C=A.getChannelData(i),a=Math.min(g,C.length),e=Math.max(0,B);for(let o=Math.max(1,e+1);o<a;o++)(C[o-1]>=0&&C[o]<0||C[o-1]<0&&C[o]>=0)&&I++,E++}return E>0?I/E:0}};oI.LEVEL_PRESETS={casual:{difficulty:"easy",controllerMode:"ddr",buttons:{pitchInfluenceWeight:.3},description:"Easy difficulty, low pitch influence - for relaxed gameplay"},standard:{difficulty:"medium",controllerMode:"ddr",buttons:{pitchInfluenceWeight:.7},description:"Medium difficulty, high pitch influence - balanced experience"},challenge:{difficulty:"hard",controllerMode:"ddr",buttons:{pitchInfluenceWeight:1},description:"Hard difficulty, full pitch influence - for skilled players"},insane:{difficulty:"hard",controllerMode:"ddr",buttons:{pitchInfluenceWeight:1,consecutiveSameKeyLimit:4},description:"Maximum difficulty with strict limits - for experts only"}};let Ni=oI;const xF=["60s","70s","80s","90s","acidjazz","alternative","alternativerock","ambient","atmospheric","blues","bluesrock","bossanova","bossa","celtic","chanson","chillout","choir","classical","classicrock","club","comedy","country","cuban","dance","darkambient","darkwave","deephouse","disco","downtempo","drumandbass","dub","dubstep","easylistening","edm","electronic","electro","electropop","ethno","eurodance","experimental","folk","funk","fusion","groove","grunge","hardcore","hardrock","hiphop","house","indie","indiepop","indierock","industrial","instrumentalpop","instrumentalrock","jazz","jazzfusion","latin","lounge","metal","minimal","newage"," orchestrall","pop","popfolk","poprock","postrock","progressive","psychedelic","punkrock","rap","reggae","rnb","rock","rockandroll","ska","soul","soundtrack","synthpop","techno","trance","trip","triphop","underground","world","worldbeat","worldmusic"],Ji=["Blues---Boogie Woogie","Blues---Chicago Blues","Blues---Country Blues","Blues---Delta Blues","Blues---Electric Blues","Blues---Harmonica Blues","Blues---Jump Blues","Blues---Louisiana Blues","Blues---Modern Electric Blues","Blues---Piano Blues","Blues---Rhythm & Blues","Blues---Texas Blues","Brass & Military---Brass Band","Brass & Military---Marches","Brass & Military---Military","Children's-- - Educational","Children's-- - Nursery Rhymes","Children's-- - Story","Classical---Baroque","Classical---Choral","Classical---Classical","Classical---Contemporary","Classical---Impressionist","Classical---Medieval","Classical---Modern","Classical---Neo-Classical","Classical---Neo-Romantic","Classical---Opera","Classical---Post-Modern","Classical---Renaissance","Classical---Romantic","Electronic---Abstract","Electronic---Acid","Electronic---Acid House","Electronic---Acid Jazz","Electronic---Ambient","Electronic---Bassline","Electronic---Beatdown","Electronic---Berlin-School","Electronic---Big Beat","Electronic---Bleep","Electronic---Breakbeat","Electronic---Breakcore","Electronic---Breaks","Electronic---Broken Beat","Electronic---Chillwave","Electronic---Chiptune","Electronic---Dance-pop","Electronic---Dark Ambient","Electronic---Darkwave","Electronic---Deep House","Electronic---Deep Techno","Electronic---Disco","Electronic---Disco Polo","Electronic---Donk","Electronic---Downtempo","Electronic---Drone","Electronic---Drum n Bass","Electronic---Dub","Electronic---Dub Techno","Electronic---Dubstep","Electronic---Dungeon Synth","Electronic---EBM","Electronic---Electro","Electronic---Electro House","Electronic---Electroclash","Electronic---Euro House","Electronic---Euro-Disco","Electronic---Eurobeat","Electronic---Eurodance","Electronic---Experimental","Electronic---Freestyle","Electronic---Future Jazz","Electronic---Gabber","Electronic---Garage House","Electronic---Ghetto","Electronic---Ghetto House","Electronic---Glitch","Electronic---Goa Trance","Electronic---Grime","Electronic---Halftime","Electronic---Hands Up","Electronic---Happy Hardcore","Electronic---Hard House","Electronic---Hard Techno","Electronic---Hard Trance","Electronic---Hardcore","Electronic---Hardstyle","Electronic---Hi NRG","Electronic---Hip Hop","Electronic---Hip-House","Electronic---House","Electronic---IDM","Electronic---Illbient","Electronic---Industrial","Electronic---Italo House","Electronic---Italo-Disco","Electronic---Italodance","Electronic---Jazzdance","Electronic---Juke","Electronic---Jumpstyle","Electronic---Jungle","Electronic---Latin","Electronic---Leftfield","Electronic---Makina","Electronic---Minimal","Electronic---Minimal Techno","Electronic---Modern Classical","Electronic---Musique Concrète","Electronic---Neofolk","Electronic---New Age","Electronic---New Beat","Electronic---New Wave","Electronic---Noise","Electronic---Nu-Disco","Electronic---Power Electronics","Electronic---Progressive Breaks","Electronic---Progressive House","Electronic---Progressive Trance","Electronic---Psy-Trance","Electronic---Rhythmic Noise","Electronic---Schranz","Electronic---Sound Collage","Electronic---Speed Garage","Electronic---Speedcore","Electronic---Synth-pop","Electronic---Synthwave","Electronic---Tech House","Electronic---Tech Trance","Electronic---Techno","Electronic---Trance","Electronic---Tribal","Electronic---Tribal House","Electronic---Trip Hop","Electronic---Tropical House","Electronic---UK Garage","Electronic---Vaporwave","Folk, World, & Country---African","Folk, World, & Country---Bluegrass","Folk, World, & Country---Cajun","Folk, World, & Country---Canzone Napoletana","Folk, World, & Country---Catalan Music","Folk, World, & Country---Celtic","Folk, World, & Country---Country","Folk, World, & Country---Fado","Folk, World, & Country---Flamenco","Folk, World, & Country---Folk","Folk, World, & Country---Gospel","Folk, World, & Country---Highlife","Folk, World, & Country---Hillbilly","Folk, World, & Country---Hindustani","Folk, World, & Country---Honky Tonk","Folk, World, & Country---Indian Classical","Folk, World, & Country---Laïkó","Folk, World, & Country---Nordic","Folk, World, & Country---Pacific","Folk, World, & Country---Polka","Folk, World, & Country---Raï","Folk, World, & Country---Romani","Folk, World, & Country---Soukous","Folk, World, & Country---Séga","Folk, World, & Country---Volksmusik","Folk, World, & Country---Zouk","Folk, World, & Country---Éntekhno","Funk / Soul---Afrobeat","Funk / Soul---Boogie","Funk / Soul---Contemporary R&B","Funk / Soul---Disco","Funk / Soul---Free Funk","Funk / Soul---Funk","Funk / Soul---Gospel","Funk / Soul---Neo Soul","Funk / Soul---New Jack Swing","Funk / Soul---P.Funk","Funk / Soul---Psychedelic","Funk / Soul---Rhythm & Blues","Funk / Soul---Soul","Funk / Soul---Swingbeat","Funk / Soul---UK Street Soul","Hip Hop---Bass Music","Hip Hop---Boom Bap","Hip Hop---Bounce","Hip Hop---Britcore","Hip Hop---Cloud Rap","Hip Hop---Conscious","Hip Hop---Crunk","Hip Hop---Cut-up/DJ","Hip Hop---DJ Battle Tool","Hip Hop---Electro","Hip Hop---G-Funk","Hip Hop---Gangsta","Hip Hop---Grime","Hip Hop---Hardcore Hip-Hop","Hip Hop---Horrorcore","Hip Hop---Instrumental","Hip Hop---Jazzy Hip-Hop","Hip Hop---Miami Bass","Hip Hop---Pop Rap","Hip Hop---Ragga HipHop","Hip Hop---RnB/Swing","Hip Hop---Screw","Hip Hop---Thug Rap","Hip Hop---Trap","Hip Hop---Trip Hop","Hip Hop---Turntablism","Jazz---Afro-Cuban Jazz","Jazz---Afrobeat","Jazz---Avant-garde Jazz","Jazz---Big Band","Jazz---Bop","Jazz---Bossa Nova","Jazz---Contemporary Jazz","Jazz---Cool Jazz","Jazz---Dixieland","Jazz---Easy Listening","Jazz---Free Improvisation","Jazz---Free Jazz","Jazz---Fusion","Jazz---Gypsy Jazz","Jazz---Hard Bop","Jazz---Jazz-Funk","Jazz---Jazz-Rock","Jazz---Latin Jazz","Jazz---Modal","Jazz---Post Bop","Jazz---Ragtime","Jazz---Smooth Jazz","Jazz---Soul-Jazz","Jazz---Space-Age","Jazz---Swing","Latin---Afro-Cuban","Latin---Baião","Latin---Batucada","Latin---Beguine","Latin---Bolero","Latin---Boogaloo","Latin---Bossanova","Latin---Cha-Cha","Latin---Charanga","Latin---Compas","Latin---Cubano","Latin---Cumbia","Latin---Descarga","Latin---Forró","Latin---Guaguancó","Latin---Guajira","Latin---Guaracha","Latin---MPB","Latin---Mambo","Latin---Mariachi","Latin---Merengue","Latin---Norteño","Latin---Nueva Cancion","Latin---Pachanga","Latin---Porro","Latin---Ranchera","Latin---Reggaeton","Latin---Rumba","Latin---Salsa","Latin---Samba","Latin---Son","Latin---Son Montuno","Latin---Tango","Latin---Tejano","Latin---Vallenato","Non-Music---Audiobook","Non-Music---Comedy","Non-Music---Dialogue","Non-Music---Education","Non-Music---Field Recording","Non-Music---Interview","Non-Music---Monolog","Non-Music---Poetry","Non-Music---Political","Non-Music---Promotional","Non-Music---Radioplay","Non-Music---Religious","Non-Music---Spoken Word","Pop---Ballad","Pop---Bollywood","Pop---Bubblegum","Pop---Chanson","Pop---City Pop","Pop---Europop","Pop---Indie Pop","Pop---J-pop","Pop---K-pop","Pop---Kayōkyoku","Pop---Light Music","Pop---Music Hall","Pop---Novelty","Pop---Parody","Pop---Schlager","Pop---Vocal","Reggae---Calypso","Reggae---Dancehall","Reggae---Dub","Reggae---Lovers Rock","Reggae---Ragga","Reggae---Reggae","Reggae---Reggae-Pop","Reggae---Rocksteady","Reggae---Roots Reggae","Reggae---Ska","Reggae---Soca","Rock---AOR","Rock---Acid Rock","Rock---Acoustic","Rock---Alternative Rock","Rock---Arena Rock","Rock---Art Rock","Rock---Atmospheric Black Metal","Rock---Avantgarde","Rock---Beat","Rock---Black Metal","Rock---Blues Rock","Rock---Brit Pop","Rock---Classic Rock","Rock---Coldwave","Rock---Country Rock","Rock---Crust","Rock---Death Metal","Rock---Deathcore","Rock---Deathrock","Rock---Depressive Black Metal","Rock---Doo Wop","Rock---Doom Metal","Rock---Dream Pop","Rock---Emo","Rock---Ethereal","Rock---Experimental","Rock---Folk Metal","Rock---Folk Rock","Rock---Funeral Doom Metal","Rock---Funk Metal","Rock---Garage Rock","Rock---Glam","Rock---Goregrind","Rock---Goth Rock","Rock---Gothic Metal","Rock---Grindcore","Rock---Grunge","Rock---Hard Rock","Rock---Hardcore","Rock---Heavy Metal","Rock---Indie Rock","Rock---Industrial","Rock---Krautrock","Rock---Lo-Fi","Rock---Lounge","Rock---Math Rock","Rock---Melodic Death Metal","Rock---Melodic Hardcore","Rock---Metalcore","Rock---Mod","Rock---Neofolk","Rock---New Wave","Rock---No Wave","Rock---Noise","Rock---Noisecore","Rock---Nu Metal","Rock---Oi","Rock---Parody","Rock---Pop Punk","Rock---Pop Rock","Rock---Pornogrind","Rock---Post Rock","Rock---Post-Hardcore","Rock---Post-Metal","Rock---Post-Punk","Rock---Power Metal","Rock---Power Pop","Rock---Power Violence","Rock---Prog Rock","Rock---Progressive Metal","Rock---Psychedelic Rock","Rock---Psychobilly","Rock---Pub Rock","Rock---Punk","Rock---Rock & Roll","Rock---Rockabilly","Rock---Shoegaze","Rock---Ska","Rock---Sludge Metal","Rock---Soft Rock","Rock---Southern Rock","Rock---Space Rock","Rock---Speed Metal","Rock---Stoner Rock","Rock---Surf","Rock---Symphonic Rock","Rock---Technical Death Metal","Rock---Thrash","Rock---Twist","Rock---Viking Metal","Rock---Yé-Yé","Stage & Screen---Musical","Stage & Screen---Score","Stage & Screen---Soundtrack","Stage & Screen---Theme"],VF=["Blues","Classical","Country","Disco","Hip-Hop","Jazz","Metal","Pop","Reggae","Rock"],LF=["guitar","classical","slow","techno","strings","drums","electronic","rock","fast","piano","ambient","beat","violin","vocal","synth","female","indian","opera","male","singing","vocals","no vocals","harpsichord","loud","quiet","flute","woman","male vocal","no vocal","pop","soft","sitar","solo","man","classic","choir","voice","new age","dance","male voice","female vocal","beats","harp","cello","no voice","weird","country","metal","female voice","choral"],XF=["happy","not happy"],vF=["action","adventure","advertising","background","ballad","calm","children","christmas","commercial","cool","corporate","dark","deep","documentary","drama","dramatic","dream","emotional","energetic","epic","fast","film","fun","funny","game","groovy","happy","heavy","holiday","hopeful","inspiring","love","meditative","melancholic","melodic","motivational","movie","nature","nostalgic","party","peaceful","positive","relaxing","retro","romantic","sad","scary","science","sea","sentimental","serene","sexy","space","sport","summer","suspense","trailer","travel","upbeat","uplifting","video","war"],KF=["danceable","non-danceable"],zF=["voice","instrumental"],TF=["acoustic","electronic"];function _g(Q){return typeof Q=="object"&&Q!==null&&"embedding"in Q&&"classifier"in Q}function qi(Q){return typeof Q=="object"&&Q!==null&&"modelUrl"in Q}function hQ(Q,A){if(A)return A;const B=Q.toLowerCase();return B.includes("effnet")||B.includes("discogs")?"effnet":B.includes("vggish")?"vggish":B.includes("tempocnn")||B.includes("tempo")&&!B.includes("temple")?"tempocnn":"musicnn"}function kt(Q,A){if(A)return A;const B=Q.toLowerCase();return B.includes("jamendo")?"jamendo":B.includes("discogs400")||B.includes("discogs")?"discogs400":B.includes("tzanetakis")?"tzanetakis":B.includes("mtt_musicnn")?"mtt_musicnn":"jamendo"}function Zt(Q){switch(Q){case"jamendo":return xF;case"discogs400":return Ji;case"tzanetakis":return VF;case"mtt_musicnn":return LF;default:return Ji}}function Dg(Q){return _g(Q)?`${Q.embedding} -> ${Q.classifier}`:Q.modelUrl}function Wt(Q){if(!Q||Q.length===0)return[];const A=Q[0].length;if(A===0)return[];const B=[];for(let g=0;g<A;g++){const I=Q.reduce((E,i)=>E+(i[g]??0),0);B.push(I/Q.length)}return B}const cQ={genre:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",classifier:"https://arweave.net/ZY-GSfMe7crJUITAtHITcoLCNfNWVP1HMwywivZ_LAQ/model.json",embeddingType:"effnet",classifierType:"discogs400"},mood:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",classifier:"https://arweave.net/BUXf3AoFuIsrNDkV2hW6BhiwSVTuFllWOUQv5mu6qQ8/model.json",embeddingType:"effnet"},danceability:{modelUrl:"https://turbo-gateway.com/nX9KX1OVhEaT1dStNcsRiZKCQTWuHjAMl4MWprIFyZU/model.json",modelType:"musicnn"}},xi={discogs400:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",embeddingType:"effnet",classifier:"https://arweave.net/ZY-GSfMe7crJUITAtHITcoLCNfNWVP1HMwywivZ_LAQ/model.json",classifierType:"discogs400"},jamendo:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",embeddingType:"effnet",classifier:"https://arweave.net/MuhF5mek1BJPZLoPNY1TBTPUUBEXbVmMfAGgBp-_MyA/model.json",classifierType:"jamendo"},tzanetakis:{modelUrl:"https://arweave.net/7MQD4W5yJeUUK2tRg8TEdomew-ZY7s0K91nk35FxleM/model.json",modelType:"musicnn",genreType:"tzanetakis"},musicnn:{modelUrl:"https://arweave.net/KCZQ1geu4ymxp8axAql95FDY98VjnOzSymdkCiM9BXo/model.json",modelType:"musicnn",genreType:"mtt_musicnn"}},Vi={jamendo:{embedding:"https://arweave.net/tVO0RIu2Ly_Di5cZccw_wB3x6Vs_2KSqxhl8bdhhimE/model.json",embeddingType:"effnet",classifier:"https://arweave.net/BUXf3AoFuIsrNDkV2hW6BhiwSVTuFllWOUQv5mu6qQ8/model.json",classifierType:"jamendo"},happyMusicnn:{modelUrl:"https://arweave.net/kUIS-Xxr4k3MZ4K2gHdvMgZxK7aBYce_FPGIkGA_hjM/model.json",modelType:"musicnn"}},Li={default:{modelUrl:"https://arweave.net/nX9KX1OVhEaT1dStNcsRiZKCQTWuHjAMl4MWprIFyZU/model.json",modelType:"musicnn"}},jF={genre:Object.keys(xi),mood:Object.keys(Vi),danceability:Object.keys(Li)};function OF(Q){const A={};return Q.genre&&(A.genre=xi[Q.genre]),Q.mood&&(A.mood=Vi[Q.mood]),Q.danceability&&(A.danceability=Li[Q.danceability]),A}class PF{constructor(A={}){this.essentiaWASM=null,this.essentiaModel=null,this.extractor=null,this.initialized=!1,this.embeddingModelCache=new Map,this.classifierModelCache=new Map,this.resolvedUrlCache=new Map,this.extractors=new Map;const B=A.preset?OF(A.preset):{};this.options={models:{genre:cQ.genre,mood:cQ.mood,danceability:cQ.danceability,...B,...A.models},topN:5,threshold:.05,cacheEmbeddings:!0,enableModelCache:!0,...A},this.options.resolveUrl||(this.options.resolveUrl=kA.resolveUrl.bind(kA)),delete this.options.preset}async initializeEssentia(){if(this.initialized)return;const B=(await Promise.resolve().then(()=>xs)).EssentiaWASM;await B.ready,this.essentiaWASM=B;const g=await Promise.resolve().then(()=>nR);this.essentiaModel=g,this.extractor=new g.EssentiaTFInputExtractor(this.essentiaWASM,"musicnn");const I=new g.EssentiaTFInputExtractor(this.essentiaWASM,"vggish");this.extractors.set("vggish",I),this.initialized=!0}async resolveUrlWithCache(A){if(this.resolvedUrlCache.has(A))return this.resolvedUrlCache.get(A);if(!this.options.resolveUrl)return A;try{const B=await this.options.resolveUrl(A);return this.resolvedUrlCache.set(A,B),B!==A&&console.info("[MusicClassifier] Resolved URL to alternate gateway",{originalUrl:A,resolvedUrl:B}),B}catch(B){return console.warn("[MusicClassifier] URL resolution failed, using original URL",{url:A,error:String(B)}),this.resolvedUrlCache.set(A,A),A}}async loadModelWithRetry(A,B=3,g=1e3){if(this.options.enableModelCache)return lg.getOrFetchGraphModel(JA,A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:i=>kA.invalidateCacheForUrl(i),maxRetries:B,baseDelayMs:g});let I=null;const E=await this.resolveUrlWithCache(A);for(let i=0;i<B;i++)try{return await JA.loadGraphModel(E)}catch(C){I=C;const a=String(C).toLowerCase();if(!(a.includes("fetch")||a.includes("network")||a.includes("timeout")||a.includes("failed to fetch")||a.includes("networkerror"))||i===B-1)throw C;const o=g*Math.pow(2,i);console.warn(`[MusicClassifier] Model load failed (attempt ${i+1}/${B}), retrying in ${o}ms...`,{originalUrl:A,resolvedUrl:E,error:String(C)}),await new Promise(t=>setTimeout(t,o))}throw I}async initializeEssentiaModelWithRetry(A,B,g=3,I=1e3){for(let E=0;E<g;E++){const i=await this.resolveUrlWithCache(A),C=B==="vggish"?this.essentiaModel.TensorflowVGGish:this.essentiaModel.TensorflowMusiCNN,a=new C(JA,i);try{return await a.initialize(),a}catch(e){const o=String(e).toLowerCase();if(!(o.includes("fetch")||o.includes("network")||o.includes("timeout")||o.includes("failed to fetch")||o.includes("networkerror")||o.includes("404"))||E===g-1)throw e;kA.invalidateCacheForUrl(A);const s=I*Math.pow(2,E);console.warn(`[MusicClassifier] Essentia model init failed (attempt ${E+1}/${g}), re-resolving and retrying in ${s}ms...`,{url:A,resolvedUrl:i,error:String(e)}),await new Promise(G=>setTimeout(G,s))}}throw new Error(`[MusicClassifier] Essentia model failed to load after ${g} retries: ${A}`)}async getEmbeddingModel(A,B){if(this.embeddingModelCache.has(A))return this.embeddingModelCache.get(A);const g=hQ(A,B);let I;if(g==="effnet")I=await this.loadModelWithRetry(A);else if(g==="vggish"){if(!this.essentiaModel)throw new Error("Essentia not initialized. Call initializeEssentia() first.");if(this.options.enableModelCache){const E=await lg.getOrFetchEssentiaUrl(A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:i=>kA.invalidateCacheForUrl(i)});I=new this.essentiaModel.TensorflowVGGish(JA,E),await I.initialize()}else I=await this.initializeEssentiaModelWithRetry(A,"vggish")}else{if(!this.essentiaModel)throw new Error("Essentia not initialized. Call initializeEssentia() first.");if(this.options.enableModelCache){const E=await lg.getOrFetchEssentiaUrl(A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:i=>kA.invalidateCacheForUrl(i)});I=new this.essentiaModel.TensorflowMusiCNN(JA,E),await I.initialize()}else I=await this.initializeEssentiaModelWithRetry(A,"musicnn")}return this.options.cacheEmbeddings&&this.embeddingModelCache.set(A,I),I}clearEmbeddingCache(){for(const A of this.embeddingModelCache.values())A.dispose?A.dispose():A.terminate&&A.terminate();this.embeddingModelCache.clear()}clearClassifierCache(){for(const A of this.classifierModelCache.values())A.terminate&&A.terminate();this.classifierModelCache.clear()}clearAllCaches(){this.clearEmbeddingCache(),this.clearClassifierCache(),this.resolvedUrlCache.clear()}createEssentiaWrapper(){const A=new this.essentiaWASM.EssentiaJS(!1);return{Windowing:A.Windowing.bind(A),Spectrum:A.Spectrum.bind(A),MelBands:A.MelBands.bind(A),UnaryOperator:A.UnaryOperator.bind(A),arrayToVector:this.essentiaWASM.arrayToVector,vectorToArray:this.essentiaWASM.vectorToArray}}computeEffnetFeatures(A){if(!this.essentiaWASM)throw new Error("Essentia WASM not initialized. Call initializeEssentia() first.");const B=this.createEssentiaWrapper(),g=[],I=512,E=256,i=16e3,C=128;for(let a=0;a<=A.length-I;a+=E){const e=A.slice(a,a+I),o=B.Windowing(B.arrayToVector(e),!0,I,"hann",0,!0),t=B.Spectrum(o.frame,I),s=B.MelBands(t.spectrum,8e3,I/2,!1,0,"unit_sum",C,i,"power","slaneyMel","linear"),G=B.UnaryOperator(s.bands,1e4,1,"log10"),n=Array.from(B.vectorToArray(G.array));g.push(n)}return g}getFeaturesForArchitecture(A,B){switch(B){case"effnet":return this.computeEffnetFeatures(A);case"vggish":const g=this.extractors.get("vggish");return g?g.computeFrameWise(A,512):(console.warn("VGGish extractor not initialized, falling back to default 96-band extractor."),this.extractor.computeFrameWise(A,512));case"tempocnn":return console.warn("TempoCNN architecture requested but using default 96-band extractor. Results may be suboptimal for TempoCNN models."),this.extractor.computeFrameWise(A,512);default:return this.extractor.computeFrameWise(A,512)}}sliceAudioSignal(A,B){if(!this.options.analysisDurationSeconds&&this.options.analysisStartPosition===void 0)return A;const g=A.length,I=g/B,E=Math.min(this.options.analysisDurationSeconds??I,I),i=this.options.analysisStartPosition??.5,C=Math.floor(E*B),a=g-C,e=Math.max(0,Math.floor(i*a)),o=Math.min(e+C,g);return console.info(`[MusicClassifier] Analyzing segment: ${E.toFixed(1)}s starting at ${i} (samples ${e}-${o} of ${g}, ${(e/B).toFixed(1)}s-${(o/B).toFixed(1)}s of ${I.toFixed(1)}s)`),A.slice(e,o)}async analyze(A){try{await this.initializeEssentia();const g=await(await fetch(A)).arrayBuffer(),I=new(window.AudioContext||window.webkitAudioContext),E=await I.decodeAudioData(g),i=await this.extractor.downsampleAudioBuffer(E,I.sampleRate),C=this.sliceAudioSignal(i,I.sampleRate),a=this.extractor.computeFrameWise(C,512),e=[],o={genres:[],moods:[],mood_tags:[],vibe_metrics:{}};if(this.options.models?.genre){const t=this.options.models.genre,s=_g(t)?t.classifier:t.modelUrl;let G;_g(t)&&t.classifierType?G=t.classifierType:qi(t)&&t.genreType?G=t.genreType:G=kt(s);const n=Zt(G);o.genres=await this.runModelPrediction(t,C,n),o.primary_genre=o.genres.length>0?o.genres[0].name:"Unknown",e.push(Dg(t))}if(this.options.models?.mood){const t=this.options.models.mood,s=qi(t)?XF:vF;o.moods=await this.runModelPrediction(t,C,s),o.mood_tags=o.moods.slice(0,3).map(G=>G.name),e.push(Dg(t))}if(this.options.models?.danceability){const t=this.options.models.danceability,G=(await this.runModelPrediction(t,C,KF)).find(n=>n.name==="danceable");o.vibe_metrics.danceability=G?.confidence??0,e.push(Dg(t))}if(this.options.models?.voice){const t=this.options.models.voice,G=(await this.runModelPrediction(t,C,zF)).find(n=>n.name==="instrumental");o.vibe_metrics.instrumental_probability=G?.confidence??0,e.push(Dg(t))}if(this.options.models?.acoustic){const t=this.options.models.acoustic,G=(await this.runModelPrediction(t,C,TF)).find(n=>n.name==="electronic");o.vibe_metrics.electronic_probability=G?.confidence??0,e.push(Dg(t))}if(o.moods){const t=o.moods.find(G=>G.name==="energetic"||G.name==="upbeat"||G.name==="epic"),s=o.moods.find(G=>G.name==="happy"||G.name==="positive"||G.name==="uplifting");t&&(o.vibe_metrics.energy=t.confidence),s&&(o.vibe_metrics.valence=s.confidence)}return{genres:o.genres||[],moods:o.moods||[],primary_genre:o.primary_genre||"Unknown",mood_tags:o.mood_tags||[],vibe_metrics:o.vibe_metrics,analysis_metadata:{models_used:e,model_used:e.length>0?e[0]:void 0,frames_analyzed:a.length,duration_analyzed:E.duration,analyzed_at:new Date().toISOString()}}}catch(B){throw console.error("Music analysis failed:",B),B}}async runClassifierOnEmbeddings(A,B){const g=Wt(B);if(g.length===0)return console.warn("Empty embeddings provided to classifier"),[];const I=await this.loadModelWithRetry(A),E=JA.tensor2d([g],[1,g.length]);let i=[];try{const C=I.predict(E);i=await C.data().then(a=>Array.from(a)),C.dispose()}finally{E.dispose(),I.dispose()}return i}async predictWithTwoStepModel(A,B){const g=hQ(A.embedding,A.embeddingType),I=this.getFeaturesForArchitecture(B,g);if(I.length===0)return console.warn("No features extracted from audio signal"),[];const E=await this.getEmbeddingModel(A.embedding,A.embeddingType);let i;return g==="effnet"?i=await this.runEffnetEmbedding(E,I):g==="vggish"?i=await this.runEssentiaEmbedding(E,I):i=await this.runEssentiaEmbedding(E,I),i.length===0?(console.warn("No embeddings produced by embedding model"),[]):await this.runClassifierOnEmbeddings(A.classifier,i)}getModelInputNames(A){const B=[];try{if(A.inputs&&Array.isArray(A.inputs))for(const g of A.inputs)g&&g.name&&B.push(g.name);if(B.length===0&&A.executor){const g=A.executor;if(g._signature&&g._signature.inputs)for(const I of Object.keys(g._signature.inputs))B.push(I)}if(B.length===0&&A.inputNodes){const g=A.inputNodes;Array.isArray(g)&&B.push(...g)}if(B.length===0&&A.metadata){const g=A.metadata;if(g.signature&&g.signature.inputs)for(const I of Object.keys(g.signature.inputs))B.push(I)}if(B.length===0){const g=A;if(g.graph&&g.graph.inputs)for(const I of Object.keys(g.graph.inputs))B.push(I)}}catch(g){console.warn("[MusicClassifier] Error discovering input names:",g)}return B}async runEffnetEmbedding(A,B){const g=B[0]?.length||128,I=B.length,E=64,i=128,C=96;let a=[],e=B;if(I<C){const G=Array(g).fill(0);for(;e.length<C;)e=[...e,G]}const o=[];for(let G=0;G<E;G++){const n=G*C%e.length,h=[];for(let c=0;c<C;c++){const d=(n+c)%e.length;h.push(e[d]||Array(g).fill(0))}o.push(h)}const t=[];for(let G=0;G<E;G++)for(let n=0;n<i;n++)for(let h=0;h<C;h++)t.push(o[G][h]?.[n]??0);const s=JA.tensor3d(t,[E,i,C]);try{const G=this.getModelInputNames(A);if(G.length===0)throw new Error("Could not discover model input names");const n={};for(const F of G)F.includes("melspectrogram")||F.includes("mel")||F.includes("spectrogram")?n[F]=s:F.includes("saver")||F.includes("filename")?n[F]=JA.scalar(""):n[F]=s;const h=A.execute(n);let c;if(Array.isArray(h)?c=h[0]:h&&typeof h=="object"&&!("data"in h)?c=Object.values(h)[0]:c=h,!c||typeof c.data!="function")throw new Error("Model execution did not return a valid tensor");const d=await c.data(),l=c.shape;if(l.length===2){const F=l[1],m=[];for(let k=0;k<F;k++){let H=0;for(let T=0;T<E;T++)H+=d[T*F+k];m.push(H/E)}const y=Math.sqrt(m.reduce((k,H)=>k+H*H,0)),p=y>0?m.map(k=>k/y):m;a.push(p)}else{const F=Array.from(d.slice(0,l[l.length-1])),m=Math.sqrt(F.reduce((p,k)=>p+k*k,0)),y=m>0?F.map(p=>p/m):F;a.push(y)}c.dispose();for(const[F,m]of Object.entries(n))m!==s&&m.dispose()}finally{s.dispose()}return a}async runEssentiaEmbedding(A,B){const g=await A.predict(B,!0);return!g||g.length===0?[]:g}async runModelPrediction(A,B,g){let I;if(_g(A))I=await this.predictWithTwoStepModel(A,B);else{const E=hQ(A.modelUrl,A.modelType),i=this.getFeaturesForArchitecture(B,E);I=await this.predictWithModel(A.modelUrl,i)}return this.mapPredictions(I,g)}async predictWithModel(A,B){if(!this.essentiaModel)throw new Error("Essentia not initialized. Call initializeEssentia() first.");let g;if(this.options.enableModelCache){const C=await lg.getOrFetchEssentiaUrl(A,{resolveUrl:this.options.resolveUrl,invalidateUrlCache:a=>kA.invalidateCacheForUrl(a)});g=new this.essentiaModel.TensorflowMusiCNN(JA,C),await g.initialize()}else g=await this.initializeEssentiaModelWithRetry(A,"musicnn");const I=await g.predict(B,!0);if(!I||I.length===0)return[];const E=I[0].length,i=[];for(let C=0;C<E;C++){const a=I.reduce((e,o)=>e+o[C],0);i.push(a/I.length)}return g.terminate&&g.terminate(),i}mapPredictions(A,B){return A.map((g,I)=>({name:B[I]||`unknown_${I}`,confidence:g})).sort((g,I)=>I.confidence-g.confidence).filter(g=>g.confidence>=this.options.threshold).slice(0,this.options.topN)}}class _F{constructor(A={}){this.canvas=null,this.context=null,typeof document<"u"&&(this.canvas=document.createElement("canvas"),this.canvas.width=100,this.canvas.height=100,this.context=this.canvas.getContext("2d",{willReadFrequently:!0}))}async extractPalette(A){try{if(!this.context||!this.canvas)throw new Error("Canvas not supported in this environment");const B=await kA.resolveUrl(A),g=await this.loadImage(B);this.context.drawImage(g,0,0,100,100);const I=this.context.getImageData(0,0,100,100),E=this.getPixels(I);if(E.length===0)return this.getFallbackPalette();let i=this.kMeans(E,4);i.length<4&&(i=this.medianCut(E,4));const C=this.sortColorsByFrequency(E,i);return this.analyzePalette(C)}catch(B){return console.warn("Color extraction failed, using fallback:",B),this.getFallbackPalette()}}loadImage(A){return new Promise((B,g)=>{const I=new Image;I.crossOrigin="Anonymous",I.onload=()=>B(I),I.onerror=E=>g(E),I.src=A})}getPixels(A){const B=[];for(let g=0;g<A.data.length;g+=4){const I=A.data[g],E=A.data[g+1],i=A.data[g+2];A.data[g+3]>=128&&B.push([I,E,i])}return B}kMeans(A,B){if(A.length===0)return[];if(A.length<B)return A;let g=[];const I=new Set;for(;g.length<B&&g.length<A.length;){const E=Math.floor(Math.random()*A.length);I.has(E)||(I.add(E),g.push([...A[E]]))}for(let E=0;E<10;E++){const i=Array.from({length:B},()=>[]);for(const a of A){let e=1/0,o=0;for(let t=0;t<g.length;t++){const s=this.distance(a,g[t]);s<e&&(e=s,o=t)}i[o].push(a)}const C=[];for(let a=0;a<B;a++)if(i[a].length===0)C.push(g[a]);else{const e=i[a].reduce((o,t)=>[o[0]+t[0],o[1]+t[1],o[2]+t[2]],[0,0,0]);C.push([Math.round(e[0]/i[a].length),Math.round(e[1]/i[a].length),Math.round(e[2]/i[a].length)])}if(C.every((a,e)=>a[0]===g[e][0]&&a[1]===g[e][1]&&a[2]===g[e][2]))break;g=C}return g}medianCut(A,B){if(A.length===0)return[];const g=[],I=[{pixels:[...A],depth:0}];for(;I.length<B&&I.length>0;){let E=I[0],i=0,C=0;for(let o=0;o<I.length;o++){const t=this.getBoxRange(I[o].pixels);t>C&&(C=t,E=I[o],i=o)}if(E.pixels.length<=1)break;const a=this.sortPixelsByChannel(E.pixels,E.depth%3),e=Math.floor(a.length/2);I.splice(i,1),I.push({pixels:a.slice(0,e),depth:E.depth+1},{pixels:a.slice(e),depth:E.depth+1})}for(const E of I)if(E.pixels.length>0){const i=E.pixels.reduce((C,a)=>[C[0]+a[0],C[1]+a[1],C[2]+a[2]],[0,0,0]);g.push([Math.round(i[0]/E.pixels.length),Math.round(i[1]/E.pixels.length),Math.round(i[2]/E.pixels.length)])}return g.slice(0,B)}getBoxRange(A){if(A.length===0)return 0;let B=1/0,g=-1/0,I=1/0,E=-1/0,i=1/0,C=-1/0;for(const[a,e,o]of A)B=Math.min(B,a),g=Math.max(g,a),I=Math.min(I,e),E=Math.max(E,e),i=Math.min(i,o),C=Math.max(C,o);return Math.max(g-B,E-I,C-i)}sortPixelsByChannel(A,B){return[...A].sort((g,I)=>g[B]-I[B])}distance(A,B){return Math.sqrt(Math.pow(A[0]-B[0],2)+Math.pow(A[1]-B[1],2)+Math.pow(A[2]-B[2],2))}sortColorsByFrequency(A,B){const g=new Map;for(const I of A){let E=B[0],i=1/0;for(const a of B){const e=this.distance(I,a);e<i&&(i=e,E=a)}const C=`${E[0]},${E[1]},${E[2]}`;g.set(C,(g.get(C)||0)+1)}return B.sort((I,E)=>{const i=`${I[0]},${I[1]},${I[2]}`,C=`${E[0]},${E[1]},${E[2]}`;return(g.get(C)||0)-(g.get(i)||0)})}analyzePalette(A){const B=A.map(e=>this.rgbToHex(e[0],e[1],e[2]));if(B.length===0)return this.getFallbackPalette();const g=B[0],I=B[1],E=B[2],i=this.getBrightness(A[0]),C=this.getSaturation(A[0]),a=C<.1;return{colors:B,primary_color:g,secondary_color:I,accent_color:E,brightness:i/255,saturation:C,is_monochrome:a}}getBrightness(A){return(A[0]*299+A[1]*587+A[2]*114)/1e3}getSaturation(A){const B=Math.max(A[0],A[1],A[2]),g=Math.min(A[0],A[1],A[2]);return B===0?0:(B-g)/B}rgbToHex(A,B,g){return"#"+[A,B,g].map(I=>{const E=I.toString(16);return E.length===1?"0"+E:E}).join("").toUpperCase()}getFallbackPalette(){return{colors:["#000000","#333333","#666666"],primary_color:"#000000",secondary_color:"#333333",accent_color:"#666666",brightness:0,saturation:0,is_monochrome:!0}}}const Xi=SA.for("BeatSubdivider"),$F={tolerance:.02,defaultIntensity:.5,defaultConfidence:.7};class dg{constructor(A){this.options={...$F,...A},Xi.debug("BeatSubdivider initialized",{options:this.options})}subdivide(A,B=CE){if(Xi.debug("Starting per-beat subdivision",{audioId:A.audioId,beatCount:A.beats.length,explicitAssignments:B.beatSubdivisions.size,defaultSubdivision:B.defaultSubdivision}),oE(B),zC(B,A.beats.length),A.beats.length===0)return this.createEmptySubdividedBeatMap(A,B);const g=new Set;let I=0;const E=!!A.tempoSections&&A.tempoSections.length>1;let i=0;const C=h=>{let c=0;for(let d=h-1;d>=0&&(B.beatSubdivisions.get(d)??B.defaultSubdivision)==="triplet4";d--)c++;return c%2},a=[];for(let h=0;h<A.beats.length;h++){const c=B.beatSubdivisions.get(h)??B.defaultSubdivision;B.beatSubdivisions.has(h)&&i++,g.add(c);const d=eE(c);I=Math.max(I,d);const l=A.beats[h],F=h<A.beats.length-1?A.beats[h+1]:null,m=A.detectedBeatIndices.includes(h);if(c==="rest"||c==="half"&&l.beatInMeasure%2!==0)continue;const y=c==="dotted4"&&l.beatInMeasure%2!==0;if(c==="offbeat8"||c==="triplet4"&&C(h)===1||y||a.push({...l,isDetected:m,originalBeatIndex:h,subdivisionType:c}),F){const k=this.createInterpolatedBeatsForSubdivision(l,F,h,c,A,E,B,C(h));a.push(...k)}}const e=this.buildDetectedBeatIndices(a),o=A.beats.length,t=a.length,s=o>0?t/o:1,G={originalBeatCount:o,subdividedBeatCount:t,averageDensityMultiplier:s,explicitBeatCount:i,subdivisionsUsed:Array.from(g),hasMultipleTempos:E,maxDensity:I},n={audioId:A.audioId,duration:A.duration,beats:a,detectedBeatIndices:e,subdivisionConfig:B,downbeatConfig:A.downbeatConfig,tempoSections:A.tempoSections,subdivisionMetadata:G};return Xi.debug("Per-beat subdivision complete",{originalBeats:o,subdividedBeats:t,densityMultiplier:s,subdivisionsUsed:Array.from(g)}),n}createInterpolatedBeatsForSubdivision(A,B,g,I,E,i,C,a=0){const e=[],o=E.quarterNoteInterval,t=i?this.getQuarterNoteIntervalForTimestamp(E,A.timestamp):o;switch(I){case"quarter":break;case"half":break;case"eighth":e.push(this.createInterpolatedBeat(A,B,.5,"eighth",t,this.options));break;case"sixteenth":for(let G=.25;G<1;G+=.25)e.push(this.createInterpolatedBeat(A,B,G,"sixteenth",t,this.options));break;case"triplet8":for(const G of[1/3,2/3])e.push(this.createInterpolatedBeat(A,B,G,"triplet8",t,this.options));break;case"triplet4":{a===0?e.push(this.createInterpolatedBeat(A,B,2/3,"triplet4",t,this.options)):(C.beatSubdivisions.get(g-1)??C.defaultSubdivision)==="triplet4"&&e.push(this.createInterpolatedBeat(A,B,1/3,"triplet4",t,this.options));break}case"dotted4":A.beatInMeasure%2!==0&&e.push(this.createInterpolatedBeat(A,B,.5,"dotted4",t,this.options));break;case"dotted8":e.push(this.createInterpolatedBeat(A,B,3/4,"dotted8",t,this.options));break;case"swing":e.push(this.createInterpolatedBeat(A,B,2/3,"swing",t,this.options));break;case"offbeat8":e.push(this.createInterpolatedBeat(A,B,.5,"offbeat8",t,this.options));break;case"rest":break;default:const s=I;throw new Error(`Unknown subdivision type: ${s}`)}return e}createEmptySubdividedBeatMap(A,B){return{audioId:A.audioId,duration:A.duration,beats:[],detectedBeatIndices:[],subdivisionConfig:B,downbeatConfig:A.downbeatConfig,tempoSections:A.tempoSections,subdivisionMetadata:{originalBeatCount:0,subdividedBeatCount:0,averageDensityMultiplier:1,explicitBeatCount:0,subdivisionsUsed:[B.defaultSubdivision],hasMultipleTempos:!!A.tempoSections&&A.tempoSections.length>1,maxDensity:1}}}createInterpolatedBeat(A,B,g,I,E,i){const C=B.timestamp-A.timestamp,a=A.timestamp+C*g,e=A.beatInMeasure+g,o=(A.intensity+B.intensity)/2,t=(A.confidence+B.confidence)/2;return{timestamp:a,beatInMeasure:e,isDownbeat:!1,measureNumber:A.measureNumber,intensity:o,confidence:t,isDetected:!1,subdivisionType:I}}findClosestBeat(A,B){let g=A[0],I=Math.abs(A[0].timestamp-B);for(const E of A){const i=Math.abs(E.timestamp-B);i<I&&(I=i,g=E)}return g}calculateBeatInMeasure(A,B,g){return(A-B)/g%4}calculateMeasureNumber(A,B,g,I){const i=(A-B)/g,C=I.segments[0]?.timeSignature?.beatsPerMeasure??4;return Math.floor(i/C)}buildDetectedBeatIndices(A){const B=[];for(let g=0;g<A.length;g++)A[g].isDetected&&B.push(g);return B}getTempoSectionForTimestamp(A,B){if(!(!A||A.length===0)){for(const g of A)if(B>=g.start&&B<g.end)return g;return A[A.length-1]}}getQuarterNoteIntervalForTimestamp(A,B){const g=this.getTempoSectionForTimestamp(A.tempoSections,B);return g?g.intervalSeconds:A.quarterNoteInterval}getTempoSectionForBeatIndex(A,B){if(!(!A||A.length===0)){for(const g of A)if(B>=g.startBeatIndex&&B<=g.endBeatIndex)return g;return A[A.length-1]}}static toJSON(A){const B=Array.from(A.subdivisionConfig.beatSubdivisions.entries()),g={audioId:A.audioId,duration:A.duration,beats:A.beats.map(I=>({timestamp:I.timestamp,beatInMeasure:I.beatInMeasure,isDownbeat:I.isDownbeat,measureNumber:I.measureNumber,intensity:I.intensity,confidence:I.confidence,requiredKey:I.requiredKey,isDetected:I.isDetected,originalBeatIndex:I.originalBeatIndex,subdivisionType:I.subdivisionType})),detectedBeatIndices:A.detectedBeatIndices,subdivisionConfig:{beatSubdivisions:B,defaultSubdivision:A.subdivisionConfig.defaultSubdivision},downbeatConfig:A.downbeatConfig,tempoSections:A.tempoSections?.map(I=>({start:I.start,end:I.end,bpm:I.bpm,intervalSeconds:I.intervalSeconds,beatCount:I.beatCount,startBeatIndex:I.startBeatIndex,endBeatIndex:I.endBeatIndex})),subdivisionMetadata:{originalBeatCount:A.subdivisionMetadata.originalBeatCount,subdividedBeatCount:A.subdivisionMetadata.subdividedBeatCount,averageDensityMultiplier:A.subdivisionMetadata.averageDensityMultiplier,explicitBeatCount:A.subdivisionMetadata.explicitBeatCount,subdivisionsUsed:A.subdivisionMetadata.subdivisionsUsed,hasMultipleTempos:A.subdivisionMetadata.hasMultipleTempos,maxDensity:A.subdivisionMetadata.maxDensity}};return JSON.stringify(g)}static fromJSON(A){const B=JSON.parse(A),g=new Map(B.subdivisionConfig.beatSubdivisions||[]);return{audioId:B.audioId,duration:B.duration,beats:B.beats.map(I=>({timestamp:I.timestamp,beatInMeasure:I.beatInMeasure,isDownbeat:I.isDownbeat,measureNumber:I.measureNumber,intensity:I.intensity,confidence:I.confidence,requiredKey:I.requiredKey,isDetected:I.isDetected,originalBeatIndex:I.originalBeatIndex,subdivisionType:I.subdivisionType})),detectedBeatIndices:B.detectedBeatIndices,subdivisionConfig:{beatSubdivisions:g,defaultSubdivision:B.subdivisionConfig.defaultSubdivision},downbeatConfig:B.downbeatConfig,tempoSections:B.tempoSections?.map(I=>({start:I.start,end:I.end,bpm:I.bpm,intervalSeconds:I.intervalSeconds,beatCount:I.beatCount,startBeatIndex:I.startBeatIndex,endBeatIndex:I.endBeatIndex})),subdivisionMetadata:{originalBeatCount:B.subdivisionMetadata.originalBeatCount,subdividedBeatCount:B.subdivisionMetadata.subdividedBeatCount,averageDensityMultiplier:B.subdivisionMetadata.averageDensityMultiplier,explicitBeatCount:B.subdivisionMetadata.explicitBeatCount,subdivisionsUsed:B.subdivisionMetadata.subdivisionsUsed,hasMultipleTempos:B.subdivisionMetadata.hasMultipleTempos,maxDensity:B.subdivisionMetadata.maxDensity}}}static async saveToFile(A,B){if(typeof process>"u"||!process.versions?.node)throw new Error("saveToFile is only available in Node.js environment");const{writeFile:g}=await Promise.resolve().then(()=>GB),I=dg.toJSON(A);await g(B,I,"utf-8")}static async loadFromFile(A){if(typeof process>"u"||!process.versions?.node)throw new Error("loadFromFile is only available in Node.js environment");const{readFile:B}=await Promise.resolve().then(()=>GB),g=await B(A,"utf-8");return dg.fromJSON(g)}}class AM{constructor(A){this.recentOffsets=[],this.establishedOffset=0,this.pocketDirection="neutral",this.hotness=0,this.streakLength=0,this.hitCount=0,this.lastBpm=120,this.grooveStartTime=null,this.maxHotness=0,this.maxGrooveStreak=0,this.hotnessSamples=[],this.grooveHitCount=0,this.previousDirection="neutral",this.options={..._C,...A}}setDifficulty(A){const B=ga(A.preset,A.customPenalties);this.options.hotnessLossOnMiss=B.hotnessLossOnMiss,this.options.hotnessLossOnBreak=B.hotnessLossOnBreak}recordHit(A,B,g=0,I="perfect"){if(this.hitCount++,this.lastBpm=B,I==="miss"||I==="wrongKey")return this.recordMiss(g);const E=this.pocketDirection;this.recentOffsets.push(A),this.recentOffsets.length>this.options.averagingWindowSize&&this.recentOffsets.shift(),this.updateRunningAverage(),this.pocketDirection=this.determineDirection(this.establishedOffset);const i=this.calculatePocketWindow(B),C=Math.abs(A-this.establishedOffset),a=this.calculateConsistency(C,i),e=this.hasPocket()&&C<=i;this.hasPocket()&&(e?(this.hotness=this.hotness+this.options.hotnessGainPerHit,this.streakLength++):this.hotness=Math.max(0,this.hotness-this.options.hotnessLossOnBreak),this.maxGrooveStreak=Math.max(this.maxGrooveStreak,this.streakLength));let o;const t=E!==this.pocketDirection,s=t||this.hotness===0;if(this.hotness>0&&!s)this.grooveStartTime===null&&(this.grooveStartTime=g??this.hitCount*(60/B)),this.maxHotness=Math.max(this.maxHotness,this.hotness),this.hotnessSamples.push(this.hotness),this.grooveHitCount++;else if(s&&this.grooveStartTime!==null&&this.grooveHitCount>0){const G=g??this.hitCount*(60/B),n=this.hotnessSamples.length>0?this.hotnessSamples.reduce((h,c)=>h+c,0)/this.hotnessSamples.length:0;o={maxStreak:this.maxGrooveStreak,maxHotness:this.maxHotness,avgHotness:n,duration:G-this.grooveStartTime,totalHits:this.grooveHitCount,startTime:this.grooveStartTime,endTime:G},this.streakLength=0,this.hotness=0,this.resetGrooveStats()}return t&&(this.streakLength=0,this.hotness=0,this.resetGrooveStats()),this.previousDirection=this.pocketDirection,{pocketDirection:this.pocketDirection,establishedOffset:this.establishedOffset,consistency:a,hotness:this.hotness,tier:rI(this.hotness),streakLength:this.streakLength,inPocket:e,pocketWindow:i,endedGrooveStats:o}}recordMiss(A){const g=this.hotness>0&&this.grooveStartTime!==null&&this.grooveHitCount>0;this.hotness=Math.max(0,this.hotness-this.options.hotnessLossOnMiss),this.streakLength=0;const I=this.calculatePocketWindow(this.lastBpm);let E;if(g&&this.hotness===0){const i=A??this.hitCount*(60/this.lastBpm),C=this.hotnessSamples.length>0?this.hotnessSamples.reduce((a,e)=>a+e,0)/this.hotnessSamples.length:0;E={maxStreak:this.maxGrooveStreak,maxHotness:this.maxHotness,avgHotness:C,duration:i-this.grooveStartTime,totalHits:this.grooveHitCount,startTime:this.grooveStartTime,endTime:i},this.resetGrooveStats()}return{pocketDirection:this.pocketDirection,establishedOffset:this.establishedOffset,consistency:0,hotness:this.hotness,tier:rI(this.hotness),streakLength:this.streakLength,inPocket:!1,pocketWindow:I,endedGrooveStats:E}}getState(){const A=this.hotnessSamples.length>0?this.hotnessSamples.reduce((B,g)=>B+g,0)/this.hotnessSamples.length:0;return{pocketDirection:this.pocketDirection,establishedOffset:this.establishedOffset,hotness:this.hotness,tier:rI(this.hotness),streakLength:this.streakLength,hitCount:this.hitCount,pocketWindow:this.calculatePocketWindow(this.lastBpm),grooveStartTime:this.grooveStartTime,grooveDuration:this.grooveStartTime!==null?this.grooveHitCount*(60/this.lastBpm):0,maxHotness:this.maxHotness,avgHotness:A,grooveHitCount:this.grooveHitCount}}reset(){this.recentOffsets=[],this.establishedOffset=0,this.pocketDirection="neutral",this.hotness=0,this.streakLength=0,this.hitCount=0,this.lastBpm=120,this.resetGrooveStats()}getGrooveStats(A){if(this.grooveStartTime===null||this.grooveHitCount===0)return null;const B=A??this.hitCount*(60/this.lastBpm),g=this.hotnessSamples.length>0?this.hotnessSamples.reduce((I,E)=>I+E,0)/this.hotnessSamples.length:0;return{maxStreak:this.maxGrooveStreak,maxHotness:this.maxHotness,avgHotness:g,duration:B-this.grooveStartTime,totalHits:this.grooveHitCount,startTime:this.grooveStartTime,endTime:B}}resetGrooveStats(){this.grooveStartTime=null,this.maxHotness=0,this.maxGrooveStreak=0,this.hotnessSamples=[],this.grooveHitCount=0}hasPocket(){return this.hitCount>=this.options.minHitsForPocket}calculatePocketWindow(A){const g=PC(this.hotness)/1e3,I=120/A;return g*I}updateRunningAverage(){this.recentOffsets.length!==0&&(this.establishedOffset=this.recentOffsets.reduce((A,B)=>A+B,0)/this.recentOffsets.length)}calculateConsistency(A,B){if(!this.hasPocket())return 0;const g=A/B;return g>=1?0:1-g*g}determineDirection(A){return Math.abs(A)<this.options.neutralDeadZone?"neutral":A<0?"push":"pull"}}function BM(Q,A=CE,B){const g=nQ(Q);return new dg(B).subdivide(g,A)}function jB(Q){return"mergedBeats"in Q?Q.mergedBeats:Q.beats}function vi(Q,A){return"mergedBeats"in Q?{...Q,mergedBeats:A}:{...Q,beats:A}}function gM(Q,A,B){const g=jB(Q);if(A<0||A>=g.length)throw new Error(`beatIndex ${A} is out of bounds (beats array length: ${g.length})`);const I=g.map((E,i)=>{if(i!==A)return E;if(B===null){const{requiredKey:C,...a}=E;return a}return{...E,requiredKey:B}});return vi(Q,I)}function IM(Q,A){const B=jB(Q),g=B.length;for(const i of A)if(i.beatIndex<0||i.beatIndex>=g)throw new Error(`beatIndex ${i.beatIndex} is out of bounds (beats array length: ${g})`);const I=new Map;for(const{beatIndex:i,key:C}of A)I.set(i,C);const E=B.map((i,C)=>{const a=I.get(C);if(a===void 0)return i;if(a===null){const{requiredKey:e,...o}=i;return o}return{...i,requiredKey:a}});return vi(Q,E)}function QM(Q){const A=jB(Q),B=new Map;for(let g=0;g<A.length;g++){const I=A[g];I.requiredKey!==void 0&&B.set(g,I.requiredKey)}return B}function EM(Q){const B=jB(Q).map(g=>{if(g.requiredKey===void 0)return g;const{requiredKey:I,...E}=g;return E});return vi(Q,B)}function iM(Q){return jB(Q).some(B=>B.requiredKey!==void 0)}function CM(Q){return jB(Q).filter(B=>B.requiredKey!==void 0).length}function aM(Q){const A=jB(Q),B=new Set;for(const g of A)g.requiredKey!==void 0&&B.add(g.requiredKey);return Array.from(B).sort()}const bA=SA.for("SubdivisionPlaybackController");class eM{constructor(A,B,g={}){this.unifiedMap=A,this.audioContext=B,this.options={...TC,...g};const I={tolerance:.02,defaultIntensity:.5,defaultConfidence:.7};this.subdivider=new dg(I),this.state={isRunning:!1,isPaused:!1,currentTime:0,startTime:0,pauseTime:0,rafId:null,currentSubdivision:this.options.initialSubdivision,pendingSubdivision:null,pendingMeasureNumber:null,transitionTimestamp:null,scheduledBeats:new Map,thresholds:nI("medium")},this.subscribers=new Set,this.regenerateBeats(),bA.debug("SubdivisionPlaybackController initialized",{audioId:A.audioId,duration:A.duration,initialSubdivision:this.state.currentSubdivision,beatCount:A.beats.length})}get subdivision(){return this.state.currentSubdivision}get beatMap(){return this.unifiedMap}getOptions(){return{...this.options}}subscribe(A){return this.subscribers.add(A),()=>{this.subscribers.delete(A)}}setSubdivision(A){if(!cI(A))throw new Error(`Invalid subdivision type: ${A}`);if(this.state.currentSubdivision===A)return;const B=this.state.currentSubdivision;switch(bA.debug("Subdivision change requested",{from:B,to:A,transitionMode:this.options.transitionMode}),this.options.transitionMode){case"immediate":this.applySubdivisionChange(A,B);break;case"next-downbeat":this.state.pendingSubdivision=A,this.state.pendingMeasureNumber=null,bA.debug("Subdivision change deferred until next downbeat",{pendingType:A,transitionMode:this.options.transitionMode});break;case"next-measure":{const I=this.getCurrentBeat()?.measureNumber??null;this.state.pendingSubdivision=A,this.state.pendingMeasureNumber=I,bA.debug("Subdivision change deferred until next measure",{pendingType:A,transitionMode:this.options.transitionMode,currentMeasureNumber:I});break}default:this.applySubdivisionChange(A,B)}}setTransitionMode(A){if(!["immediate","next-downbeat","next-measure"].includes(A))throw new Error(`Invalid transition mode: ${A}`);const B=this.options.transitionMode;B!==A&&(bA.debug("Transition mode change requested",{from:B,to:A}),this.options={...this.options,transitionMode:A},A==="immediate"&&this.state.pendingSubdivision&&(bA.debug("Applying pending subdivision immediately due to mode switch",{pendingSubdivision:this.state.pendingSubdivision}),this.applySubdivisionChange(this.state.pendingSubdivision,this.state.currentSubdivision)),bA.debug("Transition mode changed",{from:B,to:A}))}applySubdivisionChange(A,B){this.state.currentSubdivision=A,this.state.pendingSubdivision=null,this.state.pendingMeasureNumber=null;const g=this.state.transitionTimestamp;if(this.state.transitionTimestamp=null,this.regenerateBeats(g),this.options.onSubdivisionChange)try{this.options.onSubdivisionChange(B,A)}catch(I){bA.error("Error in onSubdivisionChange callback",I)}bA.debug("Subdivision changed",{from:B,to:A,transitionTimestamp:g})}regenerateBeats(A=null){const B={beatSubdivisions:new Map,defaultSubdivision:this.state.currentSubdivision},g=this.subdivider.subdivide(this.unifiedMap,B),I=new Map;if(A!==null)for(const[E,i]of this.state.scheduledBeats)i.beat.timestamp<A&&I.set(E,i);this.state.scheduledBeats.clear();for(const[E,i]of I)this.state.scheduledBeats.set(E,i);for(const E of g.beats){if(A!==null&&E.timestamp<A)continue;const i=this.getBeatKey(E);this.state.scheduledBeats.has(i)||this.state.scheduledBeats.set(i,{beat:E,upcomingEmitted:!1,exactEmitted:!1,passedEmitted:!1})}bA.debug("Beats regenerated",{subdivision:this.state.currentSubdivision,beatCount:g.beats.length,preservedBeatCount:I.size,totalScheduledBeats:this.state.scheduledBeats.size,fromTimestamp:A})}getBeatKey(A){return`${A.timestamp.toFixed(6)}-${A.subdivisionType}`}play(){this.state.isRunning||(this.state.isRunning=!0,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.state.isPaused=!1,this.scheduleUpdate(),bA.debug("Playback started",{startTime:this.state.startTime,pauseTime:this.state.pauseTime}))}stop(){this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null),this.state.isRunning=!1,this.state.isPaused=!1,this.state.pauseTime=0,this.regenerateBeats(),bA.debug("Playback stopped")}pause(){!this.state.isRunning||this.state.isPaused||(this.state.isPaused=!0,this.state.pauseTime=this.getCurrentAudioTime(),this.state.rafId!==null&&(cancelAnimationFrame(this.state.rafId),this.state.rafId=null),bA.debug("Playback paused",{pauseTime:this.state.pauseTime}))}resume(){this.state.isPaused&&(this.state.isPaused=!1,this.state.startTime=this.audioContext.currentTime-this.state.pauseTime,this.scheduleUpdate(),bA.debug("Playback resumed",{startTime:this.state.startTime}))}seek(A){const B=Math.max(0,Math.min(A,this.unifiedMap.duration));this.state.pauseTime=B,this.state.isRunning&&!this.state.isPaused&&(this.state.startTime=this.audioContext.currentTime-B),this.regenerateBeats(),bA.debug("Seeked to time",{time:B})}getBeatsInRange(A,B){const g=[];for(const[,I]of this.state.scheduledBeats){const E=I.beat;E.timestamp>=A&&E.timestamp<=B&&g.push(E)}return g.sort((I,E)=>I.timestamp-E.timestamp),g}getUpcomingBeats(A){const B=this.getCurrentAudioTime(),g=[];for(const[,I]of this.state.scheduledBeats){const E=I.beat,i=E.timestamp-B;if(i>=0&&i<=this.options.anticipationTime&&(g.push(E),g.length>=A))break}return g.sort((I,E)=>I.timestamp-E.timestamp),g}getBeatAtTime(A){const B=this.options.timingTolerance;for(const[,g]of this.state.scheduledBeats)if(Math.abs(g.beat.timestamp-A)<=B)return g.beat;return null}getCurrentBeat(){const A=this.getCurrentAudioTime();let B=null;for(const[,g]of this.state.scheduledBeats)if(g.beat.timestamp<=A)B=g.beat;else break;return B}getNextBeat(){const A=this.getCurrentAudioTime(),B=Array.from(this.state.scheduledBeats.values()).map(g=>g.beat).sort((g,I)=>g.timestamp-I.timestamp);for(const g of B)if(g.timestamp>A)return g;return null}getCurrentAudioTime(){return this.state.isRunning?this.state.isPaused?this.state.pauseTime:this.audioContext.currentTime-this.state.startTime+this.getTotalLatencyCompensation():this.state.pauseTime}getTotalLatencyCompensation(){let A=0;if(this.options.compensateOutputLatency){const B=this.audioContext.outputLatency??0,g=this.audioContext.baseLatency??0;A+=B+g}return A+=this.options.userOffsetMs/1e3,A}scheduleUpdate(){!this.state.isRunning||this.state.isPaused||(this.state.rafId=requestAnimationFrame(()=>{this.update(),this.scheduleUpdate()}))}update(){if(!this.state.isRunning||this.state.isPaused)return;const A=this.getCurrentAudioTime();this.checkPendingSubdivisionChange(A);for(const B of this.state.scheduledBeats.values()){const I=B.beat.timestamp-A;!B.upcomingEmitted&&I<=this.options.anticipationTime&&I>0&&(this.emitEvent(B.beat,"upcoming",A,I),B.upcomingEmitted=!0),!B.exactEmitted&&Math.abs(I)<=this.options.timingTolerance&&(this.emitEvent(B.beat,"exact",A,I),B.exactEmitted=!0),!B.passedEmitted&&I<-this.options.timingTolerance&&(this.emitEvent(B.beat,"passed",A,I),B.passedEmitted=!0)}}checkPendingSubdivisionChange(A){if(!this.state.pendingSubdivision)return;const B=this.getCurrentBeat();if(!B)return;let g=!1;if(this.options.transitionMode==="next-downbeat")g=B.isDownbeat;else if(this.options.transitionMode==="next-measure"){const I=this.state.pendingMeasureNumber;I!==null?g=B.measureNumber>I&&Math.floor(B.beatInMeasure)===0:g=B.beatInMeasure===0}g&&(this.state.transitionTimestamp=B.timestamp,bA.debug("Applying pending subdivision change",{newSubdivision:this.state.pendingSubdivision,transitionMode:this.options.transitionMode,currentMeasureNumber:B.measureNumber,pendingMeasureNumber:this.state.pendingMeasureNumber,beatInMeasure:B.beatInMeasure,transitionTimestamp:this.state.transitionTimestamp}),this.applySubdivisionChange(this.state.pendingSubdivision,this.state.currentSubdivision))}emitEvent(A,B,g,I){const E={beat:A,currentSubdivision:this.state.currentSubdivision,audioTime:g,timeUntilBeat:I,type:B};for(const i of this.subscribers)try{i(E)}catch(C){bA.error("SubdivisionPlaybackController callback error:",C)}}isRunning(){return this.state.isRunning&&!this.state.isPaused}isPaused(){return this.state.isPaused}getCurrentTime(){return this.getCurrentAudioTime()}getDuration(){return this.unifiedMap.duration}checkButtonPress(A,B){const g=B??this.state.thresholds,I=Array.from(this.state.scheduledBeats.values()).map(o=>o.beat);let E=null,i=1/0;for(const o of I){const t=A-o.timestamp,s=Math.abs(t);s<i&&(i=s,E=o)}if(!E)return{accuracy:"miss",offset:A,matchedBeat:null,absoluteOffset:A,keyMatch:!0,pressedKey:void 0,requiredKey:void 0};const C=A-E.timestamp,a=Math.abs(C);let e;return a<=g.perfect?e="perfect":a<=g.great?e="great":a<=g.good?e="good":a<=g.ok?e="ok":e="miss",{accuracy:e,offset:C,matchedBeat:E,absoluteOffset:a,keyMatch:!0,pressedKey:void 0,requiredKey:void 0}}setBeatMap(A){this.unifiedMap=A,this.regenerateBeats(),bA.debug("Beat map updated",{audioId:A.audioId,beatCount:A.beats.length})}forceUpdate(){this.update()}getSubdividedBeatMap(){const A=[];for(const[,I]of this.state.scheduledBeats)A.push(I.beat);A.sort((I,E)=>I.timestamp-E.timestamp);const B=new Set(A.map(I=>I.subdivisionType)),g=A.filter(I=>I.isDetected).length;return{audioId:this.unifiedMap.audioId,duration:this.unifiedMap.duration,beats:A,detectedBeatIndices:[],subdivisionConfig:{beatSubdivisions:new Map,defaultSubdivision:this.state.currentSubdivision},downbeatConfig:this.unifiedMap.downbeatConfig,subdivisionMetadata:{originalBeatCount:g,subdividedBeatCount:A.length,averageDensityMultiplier:A.length/Math.max(1,this.unifiedMap.beats.length),explicitBeatCount:0,subdivisionsUsed:Array.from(B),hasMultipleTempos:!1,maxDensity:Math.max(...Array.from(B).map(I=>I==="eighth"?2:I==="sixteenth"?4:I==="triplet8"||I==="triplet4"?3:1))}}}dispose(){this.stop(),this.subscribers.clear(),this.state.scheduledBeats.clear(),bA.debug("SubdivisionPlaybackController disposed")}}const oM=[0,300,900,2700,6500,14e3,23e3,34e3,48e3,64e3,85e3,1e5,12e4,14e4,165e3,195e3,225e3,265e3,305e3,355e3],tM={stationary:1,walking:1.2,running:1.5,driving:1.3,night_time:1.25,extreme_weather:1.4,high_altitude:1.3,rhythm_game_base:1.25,rhythm_game_combo:.5,rhythm_game_groove:.5};class Ki{constructor(A){this.config={level_thresholds:A?.level_thresholds||oM,xp_per_second:A?.xp_per_second??1,xp_per_track_completion:A?.xp_per_track_completion??50,activity_bonuses:{...tM,...A?.activity_bonuses},track_mastery_threshold:A?.track_mastery_threshold??10,mastery_bonus_xp:A?.mastery_bonus_xp??100}}calculateSessionXP(A,B){let g=A.duration_seconds*this.config.xp_per_second;if(A.activity_type&&A.activity_type in this.config.activity_bonuses){const I=this.config.activity_bonuses[A.activity_type];g*=I}return A.environmental_context&&(g=this.applyEnvironmentalBonus(g,A.environmental_context)),A.gaming_context&&(g=this.applyGamingBonus(g,A.gaming_context)),A.rhythm_game_context&&(g=this.applyRhythmGameBonus(g,A.rhythm_game_context)),B&&A.duration_seconds>=B.duration*.95&&(g+=this.config.xp_per_track_completion),Math.floor(g)}applyEnvironmentalBonus(A,B){let g=1;return B.weather?.isNight&&(g*=this.config.activity_bonuses.night_time),B.weather&&(B.weather.weatherType==="Thunderstorm"&&(g*=this.config.activity_bonuses.extreme_weather),(B.weather.weatherType==="Rain"||B.weather.weatherType==="Snow")&&(g*=this.config.activity_bonuses.extreme_weather)),B.geolocation?.altitude&&B.geolocation.altitude>=2e3&&(g*=this.config.activity_bonuses.high_altitude),A*g}applyGamingBonus(A,B){if(!B.isActivelyGaming)return A;let g=1;if(g+=.25,B.currentGame){if(B.currentGame.genre){const I=B.currentGame.genre;(I.includes("Action")||I.includes("FPS"))&&(g+=.15),I.includes("RPG")&&(g+=.2),I.includes("Strategy")&&(g+=.1)}if(B.currentGame.partySize&&B.currentGame.partySize>1&&(g+=.15),B.currentGame.sessionDuration){const I=B.currentGame.sessionDuration/60;I>=1&&(g+=Math.min(.2,I*.05))}}return A*g}applyRhythmGameBonus(A,B){if(!B.isActive)return A;let g=this.config.activity_bonuses.rhythm_game_base;if(B.currentCombo>0){const I=B.currentCombo/B.maxComboCap;g+=I*this.config.activity_bonuses.rhythm_game_combo}if(B.grooveHotness>0){const I=B.grooveHotness/100;g+=I*this.config.activity_bonuses.rhythm_game_groove}return A*g}calculateTotalModifier(A,B,g){let I=1;return A&&(I*=this.calculateEnvironmentalModifier(A)),B&&B.isActivelyGaming&&(I*=this.calculateGamingModifier(B)),g&&g.isActive&&(I*=this.calculateRhythmGameModifier(g)),Math.min(I,3)}calculateEnvironmentalModifier(A){let B=1;return A.weather?.weatherType==="Thunderstorm"&&(B*=this.config.activity_bonuses.extreme_weather),A.weather?.isNight&&(B*=this.config.activity_bonuses.night_time),A.geolocation?.altitude&&A.geolocation.altitude>=2e3&&(B*=this.config.activity_bonuses.high_altitude),Math.min(B,3)}calculateGamingModifier(A){let B=1;if(B+=.25,A.currentGame){if(A.currentGame.genre){const g=A.currentGame.genre;(g.includes("Action")||g.includes("FPS"))&&(B+=.15),g.includes("RPG")&&(B+=.2),g.includes("Strategy")&&(B+=.1)}if(A.currentGame.partySize&&A.currentGame.partySize>1&&(B+=.15),A.currentGame.sessionDuration){const g=A.currentGame.sessionDuration/60;g>=1&&(B+=Math.min(.2,g*.05))}}return Math.min(B,1.75)}calculateRhythmGameModifier(A){let B=this.config.activity_bonuses.rhythm_game_base;if(A.currentCombo>0){const g=A.currentCombo/A.maxComboCap;B+=g*this.config.activity_bonuses.rhythm_game_combo}if(A.grooveHotness>0){const g=A.grooveHotness/100;B+=g*this.config.activity_bonuses.rhythm_game_groove}return Math.min(B,2.25)}getXPThresholdForLevel(A){if(A<1||A>20)throw new Error("Level must be between 1 and 20");return this.config.level_thresholds[A-1]}getXPToNextLevel(A){if(A<1||A>19)throw new Error("Current level must be between 1 and 19");const B=this.config.level_thresholds[A-1];return this.config.level_thresholds[A]-B}getLevelFromXP(A){for(let B=20;B>=1;B--)if(A>=this.config.level_thresholds[B-1])return B;return 1}isTrackMastered(A){return A>=this.config.track_mastery_threshold}getMasteryBonusXP(){return this.config.mastery_bonus_xp}getConfig(){return{...this.config}}}class sM{constructor(A){this.activeSessions=new Map,this.sessionHistory=[],this.sessionCounter=0,this.xpCalculator=A||new Ki}startSession(A,B,g){const I=`session-${this.sessionCounter++}-${A}-${Date.now()}`;return this.activeSessions.set(I,{track_uuid:A,start_time:Date.now(),track:B,environmental_context:g?.environmental_context,gaming_context:g?.gaming_context}),I}endSession(A,B,g){const I=this.activeSessions.get(A);if(!I)return null;const E=Date.now(),i=B||Math.max(1,Math.ceil((E-I.start_time)/1e3)),C={track_uuid:I.track_uuid,start_time:I.start_time,end_time:E,duration_seconds:Math.round(i),base_xp_earned:0,bonus_xp:0,activity_type:g,environmental_context:I.environmental_context,gaming_context:I.gaming_context,total_xp_earned:0};return C.base_xp_earned=Math.floor(C.duration_seconds*this.xpCalculator.getConfig().xp_per_second),C.total_xp_earned=this.xpCalculator.calculateSessionXP(C,I.track),C.bonus_xp=C.total_xp_earned-C.base_xp_earned,this.sessionHistory.push(C),this.activeSessions.delete(A),C}getActiveSession(A){return this.activeSessions.get(A)||null}getActiveSessionDuration(A){const B=this.activeSessions.get(A);return B?(Date.now()-B.start_time)/1e3:null}updateSessionContext(A,B){const g=this.activeSessions.get(A);return g?(B.environmental_context&&(g.environmental_context=B.environmental_context),B.gaming_context&&(g.gaming_context=B.gaming_context),!0):!1}getSessionHistory(){return[...this.sessionHistory]}getSessionsForTrack(A){return this.sessionHistory.filter(B=>B.track_uuid===A)}getTotalListeningTime(){return this.sessionHistory.reduce((A,B)=>A+B.duration_seconds,0)}getTotalXPEarned(){return this.sessionHistory.reduce((A,B)=>A+B.total_xp_earned,0)}getTrackListeningTime(A){return this.getSessionsForTrack(A).reduce((B,g)=>B+g.duration_seconds,0)}getTrackListenCount(A){return this.getSessionsForTrack(A).length}isTrackMastered(A,B=10){return this.getTrackListenCount(A)>=B}getSessionsInRange(A,B){return this.sessionHistory.filter(g=>g.start_time>=A&&g.end_time<=B)}getAverageSessionLength(){return this.sessionHistory.length===0?0:this.getTotalListeningTime()/this.sessionHistory.length}getLongestSession(){return this.sessionHistory.length===0?null:this.sessionHistory.reduce((A,B)=>B.duration_seconds>A.duration_seconds?B:A)}getTrackXPTotal(A){return this.getSessionsForTrack(A).reduce((B,g)=>B+g.total_xp_earned,0)}clearTrackSessions(A){const B=this.sessionHistory.length;return this.sessionHistory=this.sessionHistory.filter(g=>g.track_uuid!==A),B-this.sessionHistory.length}clearHistory(){this.sessionHistory=[]}clearActiveSessions(){this.activeSessions.clear()}getActiveSessionCount(){return this.activeSessions.size}getActiveSessionIds(){return Array.from(this.activeSessions.keys())}}const Ht=[4,8,12,16,19];class sB{static setUncappedConfig(A){this.uncappedConfig=A}static getUncappedConfig(){return this.uncappedConfig}static setStatManager(A){this.statManager=A}static getStatManager(){return this.statManager}static processLevelUp(A,B,g){const E=(A.gameMode||"standard")==="uncapped",i=E?1/0:20;if(B<1||B>i)throw new Error(`Level must be between 1 and ${i}`);const C=cB[A.class];if(!C)throw new Error(`Unknown class: ${A.class}`);const a=this.calculateHPIncrease(C.hit_die,A.ability_modifiers.CON,g),e=A.hp.max+a,o=this.getProficiencyBonus(B,E),t=o-A.proficiency_bonus,s={newLevel:B,hitPointIncrease:a,newHitPointsTotal:e,proficiencyBonusIncrease:t,newProficiencyBonus:o},G=E||Ht.includes(B);if(this.statManager&&G){const h=this.statManager.processLevelUp(A,B);h&&h.increases.length>0&&(s.abilityScoreIncreases=h.increases.map(c=>({ability:c.ability,increase:c.delta})),h.increases.length===1&&(s.abilityScoreIncrease={ability:h.increases[0].ability,increase:h.increases[0].delta}))}else Ht.includes(B)&&(s.abilityScoreIncrease={ability:"STR",increase:2});if(this.isSpellcaster(A.class)){const h=this.calculateSpellSlots(B),c={};for(const[d,l]of Object.entries(h))c[Number(d)]={total:l,used:0};s.newSpellSlots=c}const n=this.getClassFeaturesForLevel(A,A.class,B);if(n.length>0){s.classFeatures=n.map(h=>h.id),s.featureEffects=[];for(const h of n)if(h.effects&&h.effects.length>0){const c={...A,level:B},d=vg.applyFeatureEffects(c,h);d.applied&&s.featureEffects.push({featureId:h.id,featureName:h.name,effectsApplied:d.count})}}return s}static getClassFeaturesForLevel(A,B,g){const I=YA.getInstance(),E=I.getFeaturesForLevel(B,g),i={...A,level:g},C=[];for(const a of E){const e=I.validatePrerequisites(a,i);e.valid?C.push(a):(console.warn(`Feature "${a.name}" (level ${a.level}) failed prerequisite validation at level ${g}:`,e.errors),a.source==="default"&&C.push(a))}return C}static applyLevelUp(A,B){const g={...A};g.level=B.newLevel,g.hp.max=B.newHitPointsTotal,g.hp.current=Math.min(g.hp.current+B.hitPointIncrease,g.hp.max),g.proficiency_bonus=B.newProficiencyBonus;const I=g.gameMode==="uncapped"?1/0:20;if(B.abilityScoreIncreases&&B.abilityScoreIncreases.length>0)for(const E of B.abilityScoreIncreases){const i=E.ability;g.ability_scores[i]=Math.min(I,g.ability_scores[i]+E.increase);const C=g.ability_scores[i];g.ability_modifiers[i]=Math.floor((C-10)/2)}else if(B.abilityScoreIncrease){const E=B.abilityScoreIncrease.ability;g.ability_scores[E]=Math.min(I,g.ability_scores[E]+B.abilityScoreIncrease.increase);const i=g.ability_scores[E];g.ability_modifiers[E]=Math.floor((i-10)/2)}return B.newSpellSlots&&g.spells&&(g.spells.spell_slots=B.newSpellSlots),B.classFeatures&&(g.class_features=[...new Set([...g.class_features,...B.classFeatures])]),this.reapplyEquipmentEffects(g),g}static calculateHPIncrease(A,B,g){let I;return g?I=new QB(g).randomInt(1,A+1):I=Math.floor(Math.random()*A)+1,Math.max(1,I+B)}static isSpellcaster(A){return["Bard","Cleric","Druid","Paladin","Ranger","Sorcerer","Warlock","Wizard"].includes(A)}static calculateSpellSlots(A){const B={1:0};for(let g=1;g<=9;g++)B[g]=this.getSpellSlotCount(A,g);return B}static getSpellSlotCount(A,B){return B>Math.ceil(A/2)||A<1?0:A<3?B===1?2:0:A<5?B===1?3:B===2?2:0:A<7?B===1?4:B===2?3:0:A<9?B===1||B===2?4:B===3?2:0:Math.min(5,Math.ceil(A/2))}static getXPThreshold(A,B=!1){if(A<1)throw new Error("Level must be at least 1");if(!B){if(A>20)throw new Error("Standard mode only supports levels 1-20");return Jg[A]}if(this.uncappedConfig?.xpFormula)return this.uncappedConfig.xpFormula(A);if(A<=20)return Jg[A];let g=355e3;for(let I=21;I<=A;I++)g+=(I-1)*I*500;return g}static getProficiencyBonus(A,B=!1){if(!B){if(A<1||A>20)throw new Error("Standard proficiency bonus only defined for levels 1-20");return mE[A]}return this.uncappedConfig?.proficiencyBonusFormula?this.uncappedConfig.proficiencyBonusFormula(A):A<=4?2:A<=8?3:A<=12?4:A<=16?5:2+Math.floor((A-1)/4)}static calculateLevel(A,B=!1){if(!B){for(let I=20;I>=1;I--)if(A>=this.getXPThreshold(I,!1))return I;return 1}let g=1;for(;A>=this.getXPThreshold(g+1,!0);)g++;return g}static getXPToNextLevel(A,B=!1){if(!B&&A>=20)return 0;const g=this.getXPThreshold(A,B);return this.getXPThreshold(A+1,B)-g}static getProgressPercentage(A,B,g=!1){if(!g&&A>=20)return 100;const I=this.getXPThreshold(A,g),E=this.getXPThreshold(A+1,g),i=B-I,C=E-I;return Math.floor(i/C*100)}static processLevelUpWithoutStats(A,B,g){const E=(A.gameMode||"standard")==="uncapped",i=E?1/0:20;if(B<1||B>i)throw new Error(`Level must be between 1 and ${i}`);const C=cB[A.class];if(!C)throw new Error(`Unknown class: ${A.class}`);const a=this.calculateHPIncrease(C.hit_die,A.ability_modifiers.CON,g),e=A.hp.max+a,o=this.getProficiencyBonus(B,E),t=o-A.proficiency_bonus,s={newLevel:B,hitPointIncrease:a,newHitPointsTotal:e,proficiencyBonusIncrease:t,newProficiencyBonus:o};if(this.isSpellcaster(A.class)){const n=this.calculateSpellSlots(B),h={};for(const[c,d]of Object.entries(n))h[Number(c)]={total:d,used:0};s.newSpellSlots=h}const G=this.getClassFeaturesForLevel(A,A.class,B);if(G.length>0){s.classFeatures=G.map(n=>n.id),s.featureEffects=[];for(const n of G)if(n.effects&&n.effects.length>0){const h={...A,level:B},c=vg.applyFeatureEffects(h,n);c.applied&&s.featureEffects.push({featureId:n.id,featureName:n.name,effectsApplied:c.count})}}return s}static applyAutomaticBenefitsOnly(A,B){const g={...A};return g.level=B.newLevel,g.hp.max=B.newHitPointsTotal,g.hp.current=Math.min(g.hp.current+B.hitPointIncrease,g.hp.max),g.proficiency_bonus=B.newProficiencyBonus,B.newSpellSlots&&g.spells&&(g.spells.spell_slots=B.newSpellSlots),B.classFeatures&&(g.class_features=[...new Set([...g.class_features,...B.classFeatures])]),this.reapplyEquipmentEffects(g),g}static applyStatIncreasesOnly(A,B){const g={...A};g.ability_scores={...g.ability_scores},g.ability_modifiers={...g.ability_modifiers};const E=(g.gameMode||"standard")==="uncapped"?1/0:20;for(const i of B){const C=i.ability;g.ability_scores[C]=Math.min(E,g.ability_scores[C]+i.amount),g.ability_modifiers[C]=Math.floor((g.ability_scores[C]-10)/2)}return g}static reapplyEquipmentEffects(A){A.equipment&&HA.reapplyEquipmentEffects(A)}}class ft{constructor(){this.name="D&D 5e Standard"}selectIncreases(A,B,g){if(g?.forcedAbilities&&g.forcedAbilities.length>0){const I=[];for(const E of g.forcedAbilities)A.ability_scores[E]>=20||I.push({ability:E,amount:B/g.forcedAbilities.length});return I}return[]}requiresManualInput(A){return!A?.forcedAbilities||A.forcedAbilities.length===0}}class Nt{constructor(){this.name="D&D 5e Smart"}selectIncreases(A,B,g){const I=new Set(g?.excludedAbilities||[]),E=["STR","DEX","CON","INT","WIS","CHA"].filter(e=>!I.has(e)&&A.ability_scores[e]<20);if(E.length===0)return[];const C=cB[A.class]?.primary_ability||"STR";if(!I.has(C)&&A.ability_scores[C]<18&&E.includes(C)){if(B===2&&this.shouldSplit(A,C)){const e=this.findLowestStat(A,E,C);return[{ability:C,amount:1},{ability:e,amount:1}]}return[{ability:C,amount:B}]}if(B===2&&E.length>=2){const e=this.findLowestStat(A,E),o=this.findLowestStat(A,E.filter(t=>t!==e));return[{ability:e,amount:1},{ability:o,amount:1}]}return[{ability:this.findLowestStat(A,E),amount:B}]}requiresManualInput(){return!1}shouldSplit(A,B){const g=A.ability_scores[B];if(g<15)return!1;const I=Object.values(A.ability_scores);return Math.min(...I)<g-4}findLowestStat(A,B,g){let I=B[0],E=A.ability_scores[I];for(const i of B){if(i===g)continue;const C=A.ability_scores[i];C<E&&(I=i,E=C)}return I}}class Jt{constructor(){this.name="Balanced"}selectIncreases(A,B,g){const I=new Set(g?.excludedAbilities||[]),E=["STR","DEX","CON","INT","WIS","CHA"].filter(a=>!I.has(a)).filter(a=>A.ability_scores[a]<20).sort((a,e)=>A.ability_scores[a]-A.ability_scores[e]);if(E.length===0)return[];if(g?.forcedAbilities&&g.forcedAbilities.length>=2)return[{ability:g.forcedAbilities[0],amount:1},{ability:g.forcedAbilities[1],amount:1}];const i=[];let C=B;for(let a=0;a<E.length&&C>0;a++)i.push({ability:E[a],amount:1}),C--;return i}requiresManualInput(){return!1}}class qt{constructor(){this.name="Primary Only"}selectIncreases(A,B,g){const E=cB[A.class]?.primary_ability||"STR";return g?.excludedAbilities?.includes(E)?[{ability:this.findLowestStat(A,g?.excludedAbilities),amount:B}]:A.ability_scores[E]>=20?[{ability:this.findLowestStat(A,[E]),amount:B}]:[{ability:E,amount:B}]}requiresManualInput(){return!1}findLowestStat(A,B){const g=["STR","DEX","CON","INT","WIS","CHA"].filter(i=>!B?.includes(i)).filter(i=>A.ability_scores[i]<20);if(g.length===0)return"STR";let I=g[0],E=A.ability_scores[I];for(const i of g){const C=A.ability_scores[i];C<E&&(I=i,E=C)}return I}}class xt{constructor(){this.name="Random"}selectIncreases(A,B,g){const I=["STR","DEX","CON","INT","WIS","CHA"].filter(i=>!g?.excludedAbilities?.includes(i)).filter(i=>A.ability_scores[i]<20);if(I.length===0)return[];const E=[...I].sort(()=>Math.random()-.5);return B===2&&E.length>=2?[{ability:E[0],amount:1},{ability:E[1],amount:1}]:[{ability:E[0],amount:B}]}requiresManualInput(){return!1}}class Vt{constructor(){this.name="Manual"}selectIncreases(A,B,g){return[]}requiresManualInput(){return!0}}class GM{constructor(A,B){this.fn=A,this.name=B||"Custom Function"}selectIncreases(A,B,g){return this.fn(A,B,g)}requiresManualInput(){return!1}}function zi(Q){if(typeof Q!="string"&&typeof Q!="function")return Q;if(typeof Q=="function")return new GM(Q);switch(Q){case"dnD5e":return new ft;case"dnD5e_smart":return new Nt;case"balanced":return new Jt;case"primary_only":return new qt;case"random":return new xt;case"manual":return new Vt;default:const A=Q;throw new Error(`Unknown strategy type: ${A}`)}}const Lt=[4,8,12,16,19],Xt={strategy:"dnD5e",autoApply:!0};class vt{constructor(A){this.config=this.mergeWithDefaults(A),this.strategy=zi(this.config.strategy)}increaseStats(A,B,g="manual"){const I={...A},E=[],i=[];I.ability_scores={...I.ability_scores},I.ability_modifiers={...I.ability_modifiers};const a=(I.gameMode||"standard")==="uncapped"?1/0:this.config.maxStatCap??20;for(const{ability:e,amount:o}of B){const t=I.ability_scores[e],s=t+o,G=Math.min(s,a),n=G-t;n>0?(I.ability_scores[e]=G,I.ability_modifiers[e]=Math.floor((G-10)/2),E.push({ability:e,oldValue:t,newValue:G,delta:n})):s>a&&i.push({ability:e,attemptedValue:s,cappedAt:a})}return{character:I,increases:E,capped:i,source:g,timestamp:Date.now()}}decreaseStats(A,B,g="event"){const I=B.map(({ability:E,amount:i})=>({ability:E,amount:-i}));return this.increaseStats(A,I,g)}setStat(A,B,g,I="manual"){const E=A.ability_scores[B],i=g-E;return this.increaseStats(A,[{ability:B,amount:i}],I)}processLevelUp(A,B,g){const E=(A.gameMode||"standard")==="uncapped",i=this.config.statIncreaseLevels?.length?this.config.statIncreaseLevels:Lt;if(!(E||i.includes(B)))return null;const e=this.strategy.selectIncreases(A,2,g);return e.length===0?null:this.increaseStats(A,e,"level_up")}updateConfig(A){const B=this.config.strategy;this.config=this.mergeWithDefaults(A),A.strategy&&A.strategy!==B&&(this.strategy=zi(this.config.strategy))}getConfig(){return this.config}canIncrease(A,B,g=1){const I=A.ability_scores[B],i=(A.gameMode||"standard")==="uncapped"?1/0:this.config.maxStatCap??20;return I+g<=i}getStatCap(A,B){return(A.gameMode||"standard")==="uncapped"?1/0:this.config.maxStatCap??20}validateDnD5eStatSelection(A,B,g=2){const I=B.reduce((o,t)=>o+t.amount,0);if(I!==g)return{error:`Total stat increase must be ${g}, got ${I}`,reason:"invalid_amount",allowedPatterns:[`+${g} to one ability`,"+1 to two abilities"]};const E=[{count:1,amount:g},{count:2,amount:1}];if(!E.some(o=>B.length===o.count&&B.every(t=>t.amount===o.amount)))return{error:"Invalid pattern: must be +2 to one ability or +1 to two abilities",reason:"wrong_pattern",allowedPatterns:E.map(o=>o.count===1?`+${o.amount} to one ability`:"+1 to two abilities")};const C=this.getStatCap(A,"STR");for(const o of B){const t=A.ability_scores[o.ability];if(t+o.amount>C&&C!==1/0)return{error:`${o.ability} would exceed stat cap of ${C}`,reason:"exceeds_cap",allowedPatterns:[`Choose different abilities or use ${C-t} points`]}}const a=["STR","DEX","CON","INT","WIS","CHA"];for(const o of B)if(!a.includes(o.ability))return{error:`Invalid ability: ${o.ability}`,reason:"invalid_ability",allowedPatterns:a};const e=new Set;for(const o of B){if(e.has(o.ability))return{error:`Duplicate ability: ${o.ability}`,reason:"duplicate_ability",allowedPatterns:["Each ability can only be increased once"]};e.add(o.ability)}return{valid:!0}}mergeWithDefaults(A){return{maxStatCap:A?.maxStatCap??20,strategy:A?.strategy??Xt.strategy,autoApply:A?.autoApply??Xt.autoApply,statIncreaseLevels:A?.statIncreaseLevels??Lt}}}const bC=class bC{static getPlaysThreshold(A){const B=this.customThresholds.get(A);return B?.playsThreshold!==void 0&&B.playsThreshold!==null?B.playsThreshold:Math.floor(oa*Math.pow(GE,A))}static getXPThreshold(A){const B=this.customThresholds.get(A);return B?.xpThreshold!==void 0&&B.xpThreshold!==null?B.xpThreshold:Math.floor(ta*Math.pow(GE,A))}static setCustomThresholds(A,B){this.customThresholds.set(A,{...B})}static clearCustomThresholds(A){A!==void 0?this.customThresholds.delete(A):this.customThresholds.clear()}static hasCustomThresholds(A){return this.customThresholds.has(A)}static getCustomThresholds(A){return this.customThresholds.get(A)}static isMastered(A,B,g){const I=this.getPlaysThreshold(g),E=this.getXPThreshold(g);return A>=I&&B>=E}static canPrestige(A,B,g){return A>=Wg?!1:this.isMastered(B,g,A)}static isJustMastered(A,B,g,I,E){const i=this.isMastered(A,g,E),C=this.isMastered(B,I,E);return!i&&C}static calculateMasteryBonus(A){return A?ja:0}static getPrestigeInfo(A,B,g){const I=this.getPlaysThreshold(A),E=this.getXPThreshold(A),i=this.isMastered(B,g,A),C=A>=Wg;return{prestigeLevel:A,currentPlays:B,currentXP:g,playsThreshold:I,xpThreshold:E,playsProgress:Math.min(1,B/I),xpProgress:Math.min(1,g/E),isMastered:i,canPrestige:i&&!C,isMaxPrestige:C}}static toRomanNumeral(A){return ea[A]}static getNextPrestigeLevel(A){return A>=Wg?null:sa(A+1)}static createSuccessResult(A,B){const g=this.toRomanNumeral(B);return{success:!0,newPrestigeLevel:B,previousPrestigeLevel:A,message:`Successfully prestiged to level ${g}! New mastery requirements: ${this.getPlaysThreshold(B)} plays, ${this.getXPThreshold(B).toLocaleString()} XP`}}static createFailureResult(A,B){return{success:!1,newPrestigeLevel:B,previousPrestigeLevel:B,message:`Prestige failed: ${A}`}}static getAllThresholds(){const A=[];for(let B=0;B<=Wg;B++){const g=B;A.push({level:g,plays:this.getPlaysThreshold(g),xp:this.getXPThreshold(g)})}return A}};bC.customThresholds=new Map;let WA=bC;class nM{constructor(A){this.xpCalculator=new Ki,this.statManager=A,this.statManager&&sB.setStatManager(this.statManager)}addXP(A,B,g="custom"){const I={...A,xp:{...A.xp}};if(I.xp.current+=B,!this.statManager){const t=(I.gameMode||"standard")==="standard"?"dnD5e":"dnD5e_smart";this.statManager=new vt({strategy:t}),sB.setStatManager(this.statManager)}let E=!1,i;const C=[],a=I.gameMode==="uncapped",e=sB.calculateLevel(I.xp.current,a);if(e>I.level){E=!0,i=e;for(let o=I.level+1;o<=e;o++)if(this.isStatIncreaseLevel(o,a)&&this.statManager.getConfig().strategy==="dnD5e"){I.pendingStatIncreases=(I.pendingStatIncreases||0)+1;const G=sB.processLevelUpWithoutStats(I,o),n=sB.applyAutomaticBenefitsOnly(I,G),h={fromLevel:o-1,toLevel:o,hpIncrease:G.hitPointIncrease,newMaxHP:G.newHitPointsTotal,proficiencyIncrease:G.proficiencyBonusIncrease,newProficiency:G.newProficiencyBonus,statIncreases:void 0,featuresGained:G.classFeatures,newSpellSlots:G.newSpellSlots};C.push(h),I.level=o,I.hp=n.hp,I.proficiency_bonus=n.proficiency_bonus,I.class_features=n.class_features,n.spells&&(I.spells=n.spells)}else{const G={...I.ability_scores},n=[...I.class_features],h=sB.processLevelUp(I,o),c=sB.applyLevelUp(I,h),d={fromLevel:o-1,toLevel:o,hpIncrease:h.hitPointIncrease,newMaxHP:h.newHitPointsTotal,proficiencyIncrease:h.proficiencyBonusIncrease,newProficiency:h.newProficiencyBonus,statIncreases:h.abilityScoreIncreases?.map(l=>({ability:l.ability,oldValue:G[l.ability],newValue:G[l.ability]+l.increase,delta:l.increase})),featuresGained:h.classFeatures?.filter(l=>!n.includes(l)),newSpellSlots:h.newSpellSlots};C.push(d),I.level=c.level,I.hp=c.hp,I.ability_scores=c.ability_scores,I.ability_modifiers=c.ability_modifiers,I.proficiency_bonus=c.proficiency_bonus,I.class_features=c.class_features,c.spells&&(I.spells=c.spells)}}return!a&&I.level>=20?I.xp.next_level=0:I.xp.next_level=sB.getXPThreshold(I.level+1,a),{character:I,xpEarned:B,leveledUp:E,newLevel:i,levelUpDetails:C.length>0?C:void 0}}addRhythmXP(A,B,g="rhythm_game"){const I=this.addXP(A,B.finalXP,g);return{character:I.character,xpEarned:I.xpEarned,leveledUp:I.leveledUp,newLevel:I.newLevel,levelUpDetails:I.levelUpDetails}}isStatIncreaseLevel(A,B){return B?!0:[4,8,12,16,19].includes(A)}updateCharacterFromSession(A,B,g,I){const E=I?.previousListenCount??0,i=I?.previousXP??0,C=I?.prestigeLevel??A.prestige_level??0,a=this.xpCalculator.calculateSessionXP(B,g);let e=!1,o=0;if(g){const G=E+1,n=i+a;WA.isJustMastered(E,G,i,n,C)&&(e=!0,o=WA.calculateMasteryBonus(!0))}const t=a+o;return{...this.addXP(A,t,"listening"),masteredTrack:e,masteryBonusXP:o}}applyPendingStatIncrease(A,B,g){const I=A.pendingStatIncreases||0;if(I===0)throw new Error("No pending stat increases to apply");let E;if(g&&g.length>0){if(g.length>1)throw new Error("Only one secondary stat allowed for +1/+1 distribution");E=[{ability:B,amount:1},{ability:g[0],amount:1}]}else E=[{ability:B,amount:2}];const i=this.statManager.validateDnD5eStatSelection(A,E);if("error"in i)throw new Error(i.error);const C=sB.applyStatIncreasesOnly(A,E);C.pendingStatIncreases=I-1,C.pendingStatIncreases===0&&delete C.pendingStatIncreases;const a=E.map(e=>({ability:e.ability,oldValue:A.ability_scores[e.ability],newValue:C.ability_scores[e.ability],delta:e.amount}));return{character:C,statIncreases:a,remainingPending:C.pendingStatIncreases||0,timestamp:Date.now()}}hasPendingStatIncreases(A){return!!(A.pendingStatIncreases&&A.pendingStatIncreases>0)}getPendingStatIncreaseCount(A){return A.pendingStatIncreases||0}resetCharacterForPrestige(A,B,g,I,E){const i=A.prestige_level??0;if(i>=10)return WA.createFailureResult("Already at maximum prestige level",i);const C=B.getTrackListenCount(g),a=B.getTrackXPTotal(g);if(!WA.canPrestige(i,C,a)){const G=WA.getPlaysThreshold(i),n=WA.getXPThreshold(i);return C<G&&a<n?WA.createFailureResult(`Need ${G-C} more plays and ${(n-a).toLocaleString()} more XP to prestige`,i):C<G?WA.createFailureResult(`Need ${G-C} more plays to prestige (XP requirement met)`,i):WA.createFailureResult(`Need ${(n-a).toLocaleString()} more XP to prestige (plays requirement met)`,i)}const e=A.equipment?{weapons:[...A.equipment.weapons],armor:[...A.equipment.armor],items:[...A.equipment.items],totalWeight:A.equipment.totalWeight,equippedWeight:A.equipment.equippedWeight}:null;B.clearTrackSessions(g);const o=WA.getNextPrestigeLevel(i);if(o===null)return WA.createFailureResult("Cannot prestige beyond maximum level",i);const t=gQ.generate(A.seed,I,E,{level:1,gameMode:A.gameMode,forceName:A.name});return e&&(t.equipment=e),t.prestige_level=o,{...WA.createSuccessResult(i,o),character:t}}canPrestige(A,B,g){const I=A.prestige_level??0,E=B.getTrackListenCount(g),i=B.getTrackXPTotal(g);return WA.canPrestige(I,E,i)}getPrestigeInfo(A,B,g){const I=A.prestige_level??0,E=B.getTrackListenCount(g),i=B.getTrackXPTotal(g);return WA.getPrestigeInfo(I,E,i)}}class hM{constructor(A){this.sessionTotals=null,this.sessionStartTime=null,this.config=WI(A)}calculateButtonPressXP(A,B){const g=B?.comboLength??0,I=B?.grooveHotness,E=this.getBaseXP(A),i=E*this.config.xpRatio,C=this.config.combo.enabled?this.getComboMultiplier(g):1;let a=0;this.config.groove.perHitMultiplier&&I!==void 0&&I>0&&(a=I/100*this.config.groove.perHitScale);const e=Math.min(C+a,this.config.maxMultiplier),o=E*e,t=i*e,s=Math.max(0,t);return{scorePoints:E,baseXP:i,comboMultiplier:C,grooveMultiplier:a,totalMultiplier:e,finalScore:o,finalXP:s,breakdown:{accuracy:A,comboLength:g,grooveHotness:I}}}calculateComboEndBonus(A){if(!this.config.combo.endBonus.enabled||A<=0)return{comboLength:A,bonusScore:0,bonusXP:0};const B=this.config.combo.endBonus.formula?this.config.combo.endBonus.formula(A):A*2,g=B*this.config.xpRatio;return{comboLength:A,bonusScore:B,bonusXP:g}}calculateGrooveEndBonus(A){if(!this.config.groove.endBonus.enabled)return{bonusScore:0,bonusXP:0};if(A.maxStreak<2)return{bonusScore:0,bonusXP:0};const{maxStreakWeight:g,avgHotnessWeight:I,durationWeight:E}=this.config.groove.endBonus,i=A.maxStreak*g,C=A.avgHotness*I,a=A.duration*E,e=i+C+a,o=e*this.config.xpRatio;return{bonusScore:e,bonusXP:o}}getBaseXP(A){return this.config.baseXP[A]}getComboMultiplier(A){if(!this.config.combo.enabled)return 1;const B=this.config.combo.formula?this.config.combo.formula(A):this.defaultComboFormula(A);return Math.min(B,this.config.combo.cap)}defaultComboFormula(A){return 1+A/25}startSession(){this.sessionTotals=this.createEmptyTotals(),this.sessionStartTime=Date.now()}recordHit(A,B){this.sessionTotals||this.startSession();const g=this.calculateButtonPressXP(A,B);this.sessionTotals.totalScore+=g.finalScore,this.sessionTotals.totalXP+=g.finalXP;const I=B?.comboLength??0;return I>this.sessionTotals.maxCombo&&(this.sessionTotals.maxCombo=I),this.sessionTotals.accuracyDistribution[A]++,this.recalculateAccuracyPercentage(),g}getSessionTotals(){return!this.sessionTotals||!this.sessionStartTime?null:{...this.sessionTotals,duration:(Date.now()-this.sessionStartTime)/1e3}}endSession(){const A=this.getSessionTotals();return this.sessionTotals=null,this.sessionStartTime=null,A}createEmptyTotals(){return{totalScore:0,totalXP:0,maxCombo:0,accuracyDistribution:{perfect:0,great:0,good:0,ok:0,miss:0,wrongKey:0},accuracyPercentage:0,duration:0}}recalculateAccuracyPercentage(){if(!this.sessionTotals)return;const{perfect:A,great:B,good:g,ok:I,miss:E,wrongKey:i}=this.sessionTotals.accuracyDistribution,C=A+B+g+I+E+i;if(C===0){this.sessionTotals.accuracyPercentage=0;return}const a=A+B+g+I;this.sessionTotals.accuracyPercentage=a/C*100}getConfig(){return this.config}updateConfig(A){this.config=WI({...this.config,...A})}}class cM{static enchant(A,B,g,I){return uI.validateModification(g).valid?this.addModificationToEquipment(A,B,g,I):A}static applyTemplate(A,B,g,I){const C=AA.getInstance().get("equipment.templates").find(t=>t.id===g);if(!C)return A;const a={id:`template_${g}_${Date.now()}`,name:C.name||`Template: ${g}`,properties:C.properties||[],addsFeatures:C.addsFeatures,addsSkills:C.addsSkills,addsSpells:C.addsSpells,appliedAt:new Date().toISOString(),source:"template"},e=this.addModificationToEquipment(A,B,a,I),o=this.findItem(e,B);return o&&(o.templateId=g),e}static curse(A,B,g,I){return uI.validateModification(g).valid?this.addModificationToEquipment(A,B,g,I):A}static upgrade(A,B,g,I){return this.enchant(A,B,g,I)}static removeModification(A,B,g,I){const E={weapons:A.weapons.map(t=>({...t,modifications:t.modifications?[...t.modifications]:[]})),armor:A.armor.map(t=>({...t,modifications:t.modifications?[...t.modifications]:[]})),items:A.items.map(t=>({...t,modifications:t.modifications?[...t.modifications]:[]})),totalWeight:A.totalWeight,equippedWeight:A.equippedWeight},i=this.findItem(E,B);if(!i||!i.modifications)return A;const C=i.equipped,a=i.instanceId,e=this.getEquipmentData(B);C&&I&&e&&HA.unequipItem(I,B,a);const o=i.modifications.findIndex(t=>t.id===g);if(o!==-1&&i.modifications.splice(o,1),C&&I&&e){HA.equipItem(I,e,a);for(const t of i.modifications)this.applyModificationEffects(I,e,t,a)}return E}static getModificationHistory(A,B){const g=this.findItem(A,B);return!g||!g.modifications?[]:g.modifications.map(I=>({...I}))}static getCombinedEffects(A,B,g){const I=this.getEquipmentData(B);if(!I)return[];const E=I.properties?[...I.properties]:[],i=this.findItem(A,B);if(i&&i.modifications)for(const C of i.modifications)C.properties&&E.push(...C.properties);return E}static hasTemplate(A,B,g){const I=this.findItem(A,B);return I?I.templateId===g?!0:I.modifications?I.modifications.some(E=>E.source==="template"&&E.name.includes(g)):!1:!1}static getAppliedTemplates(A,B){const g=[],I=this.findItem(A,B);if(!I)return g;if(I.templateId&&g.push(I.templateId),I.modifications){for(const E of I.modifications)if(E.source==="template"){const i=E.id.match(/template_([^_]+)/);i&&g.push(i[1])}}return g}static removeAllModifications(A,B,g){const I=this.findItem(A,B);if(!I||!I.modifications||I.modifications.length===0)return A;const E=I.modifications.map(C=>C.id);let i=A;for(const C of E)i=this.removeModification(i,B,C,g);return i}static disenchant(A,B,g){const I=this.findItem(A,B);if(!I||!I.modifications)return A;const E=I.modifications.filter(C=>C.source==="enchantment"||C.source==="upgrade").map(C=>C.id);let i=A;for(const C of E)i=this.removeModification(i,B,C,g);return i}static liftCurse(A,B,g){const I=this.findItem(A,B);if(!I||!I.modifications)return A;const E=I.modifications.filter(C=>C.source==="curse").map(C=>C.id);let i=A;for(const C of E)i=this.removeModification(i,B,C,g);return i}static addModificationToEquipment(A,B,g,I){const E={weapons:A.weapons.map(o=>({...o,modifications:o.modifications?[...o.modifications]:[]})),armor:A.armor.map(o=>({...o,modifications:o.modifications?[...o.modifications]:[]})),items:A.items.map(o=>({...o,modifications:o.modifications?[...o.modifications]:[]})),totalWeight:A.totalWeight,equippedWeight:A.equippedWeight},i=this.findItem(E,B);if(!i)return A;const C=this.getEquipmentData(B);if(!C)return A;i.modifications||(i.modifications=[]),i.instanceId||(i.instanceId=`${B}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`);const a=i.equipped,e=i.instanceId;if(a&&I&&HA.unequipItem(I,B,e),i.modifications.push(g),a&&I){HA.equipItem(I,C,e);for(const o of i.modifications)this.applyModificationEffects(I,C,o,e)}return E}static findItem(A,B){return A.weapons.find(I=>I.name===B)||A.armor.find(I=>I.name===B)||A.items.find(I=>I.name===B)}static getEquipmentData(A){return AA.getInstance().get("equipment").find(I=>I.name===A)}static applyModificationEffects(A,B,g,I){const E={...B,properties:[...B.properties||[],...g.properties],grantsFeatures:g.addsFeatures,grantsSkills:g.addsSkills,grantsSpells:g.addsSpells};HA.equipItem(A,E,I)}static createModification(A,B,g,I){return{id:A,name:B,properties:g,appliedAt:new Date().toISOString(),source:I}}static createFeatureModification(A,B,g,I,E){return{id:A,name:B,properties:g,addsFeatures:I,appliedAt:new Date().toISOString(),source:E}}static createSkillModification(A,B,g,I,E){return{id:A,name:B,properties:g,addsSkills:I,appliedAt:new Date().toISOString(),source:E}}static createSpellModification(A,B,g,I,E){return{id:A,name:B,properties:g,addsSpells:I,appliedAt:new Date().toISOString(),source:E}}static generateModificationId(A="mod"){return`${A}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}static getModificationSources(A,B){const g=this.findItem(A,B);if(!g||!g.modifications)return[];const I=new Set(g.modifications.map(E=>E.source));return Array.from(I)}static countModificationsBySource(A,B){const g=this.findItem(A,B);if(!g||!g.modifications)return{};const I={};for(const E of g.modifications)I[E.source]=(I[E.source]||0)+1;return I}static countModificationsForSource(A,B,g){return this.countModificationsBySource(A,B)[g]||0}static isCursed(A,B){return this.countModificationsForSource(A,B,"curse")>0}static isEnchanted(A,B){return this.getModificationSources(A,B).some(I=>I==="enchantment"||I==="upgrade")}static getItemSummary(A,B){const g=this.findItem(A,B);return g?{name:g.name,quantity:g.quantity,equipped:g.equipped,instanceId:g.instanceId,templateId:g.templateId,modificationCount:g.modifications?.length||0,isCursed:this.isCursed(A,B),isEnchanted:this.isEnchanted(A,B),sources:this.getModificationSources(A,B),effects:this.getCombinedEffects(A,B)}:null}}const rQ={plus_one_weapon:{rarity:"uncommon",properties:[{type:"passive_modifier",target:"attack_roll",value:1,description:"+1 to attack and damage rolls",stackable:!0}],tags:["magic","enhanced","+1"]},plus_two_weapon:{rarity:"rare",properties:[{type:"passive_modifier",target:"attack_roll",value:2,description:"+2 to attack and damage rolls",stackable:!0}],tags:["magic","enhanced","+2"]},plus_three_weapon:{rarity:"very_rare",properties:[{type:"passive_modifier",target:"attack_roll",value:3,description:"+3 to attack and damage rolls",stackable:!0}],tags:["magic","enhanced","+3"]},flaming_weapon_template:{rarity:"rare",properties:[{type:"damage_bonus",target:"fire_damage",value:"1d6",description:"+1d6 fire damage",condition:{type:"on_hit",value:!0}},{type:"special_property",target:"light",value:"bright_light_20ft",description:"Sheds bright light 20ft, dim 20ft"}],tags:["magic","fire","flaming"]},frost_weapon_template:{rarity:"rare",properties:[{type:"damage_bonus",target:"cold_damage",value:"1d6",description:"+1d6 cold damage",condition:{type:"on_hit",value:!0}}],tags:["magic","cold","frost"]},shocking_weapon_template:{rarity:"rare",properties:[{type:"damage_bonus",target:"lightning_damage",value:"1d6",description:"+1d6 lightning damage",condition:{type:"on_hit",value:!0}}],tags:["magic","lightning","shocking"]},vicious_weapon_template:{rarity:"rare",properties:[{type:"passive_modifier",target:"attack_roll",value:1,description:"+1 to attack and damage rolls"},{type:"damage_bonus",target:"extra_damage_on_hit",value:"1d8",description:"Deals 1d8 extra damage, but wielder takes 1d8 necrotic",condition:{type:"on_hit",value:!0}}],tags:["magic","vicious","necrotic"]},plus_one_armor:{rarity:"rare",properties:[{type:"passive_modifier",target:"ac",value:1,description:"+1 AC bonus",stackable:!0}],tags:["magic","enhanced","+1","armor"]},plus_two_armor:{rarity:"very_rare",properties:[{type:"passive_modifier",target:"ac",value:2,description:"+2 AC bonus",stackable:!0}],tags:["magic","enhanced","+2","armor"]}};class Kt{static openBox(A,B,g){if(!A.boxContents)return{success:!1,items:[],gold:0,consumeBox:!1,error:{code:"NO_BOX_CONTENTS",message:`Box "${A.name}" has no contents defined.`}};if(g!==void 0&&A.boxContents.openRequirements){const e=this.checkRequirements(A,g);if(e)return{success:!1,items:[],gold:0,consumeBox:!1,error:e}}const I=A.boxContents,E=[];let i=0;for(const e of I.drops){const o=this.selectFromPool(e.pool,B);if(o){if(o.gold!==void 0&&o.gold>0)i+=o.gold;else if(o.itemName){const t=this.getEquipmentData(o.itemName);if(t){const s=o.quantity??1;if(t.type==="box")E.push({...t});else for(let G=0;G<s;G++)E.push(t)}else console.warn(`BoxOpener: Item "${o.itemName}" not found in equipment registry. Skipping drop.`)}}}const C=I.consumeOnOpen!==!1;let a;return g!==void 0&&I.openRequirements&&(a=I.openRequirements.map(e=>({name:e.itemName,quantity:e.quantity??1}))),{success:!0,items:E,gold:i,consumeBox:C,consumedItems:a}}static selectFromPool(A,B){if(!A||A.length===0)return;const g=A.map(I=>[I,I.weight]);return B.weightedChoice(g)}static getEquipmentData(A){return AA.getInstance().get("equipment").find(I=>I.name===A)}static isBox(A){return A.type==="box"&&A.boxContents!==void 0}static checkRequirements(A,B){if(!A.boxContents?.openRequirements)return null;const g=A.boxContents.openRequirements;for(const I of g){const E=I.quantity??1,i=B.find(C=>C.name===I.itemName);if(!i)return{code:"MISSING_ITEM",message:`Missing required item: ${I.itemName}`,requirement:I};if(i.quantity<E)return{code:"INSUFFICIENT_QUANTITY",message:`Insufficient ${I.itemName}: have ${i.quantity}, need ${E}`,requirement:I}}return null}static canOpen(A,B){return A.boxContents?.openRequirements?this.checkRequirements(A,B)===null:!0}static getRequirementsDescription(A){if(!A.boxContents?.openRequirements||A.boxContents.openRequirements.length===0)return null;const B=A.boxContents.openRequirements,g=[];for(const I of B){const E=I.quantity??1;E===1?g.push(I.itemName):g.push(`${E} ${I.itemName}`)}return`Requires: ${g.join(", ")}`}static previewContents(A){if(!A.boxContents)return{possibleItems:[],possibleGold:{min:0,max:0},totalDrops:0};const B=new Set;let g=0,I=0;for(const i of A.boxContents.drops)for(const C of i.pool)C.itemName&&B.add(C.itemName),C.gold!==void 0&&(g=Math.min(g,C.gold),I=Math.max(I,C.gold));const E={possibleItems:Array.from(B),possibleGold:{min:g,max:I},totalDrops:A.boxContents.drops.length};return A.boxContents.openRequirements&&A.boxContents.openRequirements.length>0&&(E.openRequirements=A.boxContents.openRequirements),E}}const Fg={common:0,uncommon:1,rare:2,very_rare:3,legendary:4};class rM{static spawnFromList(A,B){const I=AA.getInstance().get("equipment");let E=A;return B&&(E=B.shuffle([...A])),E.map(i=>I.find(C=>C.name===i))}static spawnByRarity(A,B,g){const i=AA.getInstance().get("equipment").filter(t=>t.rarity===A&&(t.spawnWeight??1)>0);if(i.length===0)return[];const C=Math.min(B,i.length),a=[],e=g?()=>g.random():Math.random,o=[...i];for(let t=o.length-1;t>0;t--){const s=Math.floor(e()*(t+1));[o[t],o[s]]=[o[s],o[t]]}for(let t=0;t<C;t++)a.push(o[t]);return a}static spawnByTags(A,B,g,I){const C=AA.getInstance().get("equipment").filter(t=>{if(I?.excludeZeroWeight&&(t.spawnWeight??1)<=0||I?.includeTypes&&I.includeTypes.length>0&&!I.includeTypes.includes(t.type))return!1;const s=Fg[t.rarity];return I?.minRarity&&s<Fg[I.minRarity]||I?.maxRarity&&s>Fg[I.maxRarity]?!1:t.tags&&A.some(G=>t.tags.includes(G))});if(C.length===0)return[];const a=Math.min(B,C.length),e=[],o=g?()=>g.random():Math.random;for(let t=0;t<a;t++){const s=C.map(c=>[c,c.spawnWeight??1]);let G=s.reduce((c,[,d])=>c+d,0),n=o()*G,h;for(const[c,d]of s)if(n-=d,n<=0){h=c;break}if(h){e.push(h);const c=C.indexOf(h);c>-1&&C.splice(c,1)}}return e}static spawnRandom(A,B,g){const i=AA.getInstance().get("equipment").filter(a=>{const e=a.spawnWeight??1;if(g?.excludeZeroWeight&&e<=0||g?.includeTypes&&g.includeTypes.length>0&&!g.includeTypes.includes(a.type))return!1;const o=Fg[a.rarity];return g?.minRarity&&o<Fg[g.minRarity]||g?.maxRarity&&o>Fg[g.maxRarity]?!1:e>0});if(i.length===0)return[];const C=[];for(let a=0;a<A&&i.length>0;a++){const e=i.map(h=>[h,h.spawnWeight??1]),o=e.reduce((h,[,c])=>h+c,0);let t=B.random()*o,s=0;for(let h=0;h<e.length;h++)if(t-=e[h][1],t<=0){s=h;break}const G=e[s][0];C.push(G);const n=i.indexOf(G);n>-1&&i.splice(n,1)}return C}static spawnFromTemplate(A,B){const g=AA.getInstance();let I;const E=g.get("equipment.templates");if(Array.isArray(E)?I=E.find(C=>C.id===A||C.name===A):E&&typeof E=="object"&&(I=E[A]),!I&&A in rQ&&(I=rQ[A]),!I)return null;let i;return B?i=g.get("equipment").find(a=>a.name===B):i=this.getDefaultItemForTemplate(I),i?{...i,...I,name:`${i.name} (${A.replace(/_/g," ")})`,templateId:A,properties:[...i.properties||[],...I.properties||[]],tags:[...i.tags||[],...I.tags||[]]}:null}static spawnTreasureHoard(A,B){const g=[];let I=0;const E=this.getTreasureItemCount(A,B);for(const[i,C]of Object.entries(E))for(let a=0;a<C;a++){const e=i,o=this.spawnByRarity(e,1,B);o.length>0&&(g.push(o[0]),I+=this.estimateItemValue(o[0]))}if(g.length===0){const i=this.spawnByRarity("common",1,B);i.length>0&&(g.push(i[0]),I+=this.estimateItemValue(i[0]))}return{items:g,totalValue:I,cr:A}}static addToCharacter(A,B,g=!1){A.equipment||(A.equipment={weapons:[],armor:[],items:[],totalWeight:0,equippedWeight:0});for(const I of B){const E={name:I.name,quantity:1,equipped:g,instanceId:`${I.name}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`};switch(I.type){case"weapon":A.equipment.weapons.push(E);break;case"armor":A.equipment.armor.push(E);break;case"item":case"box":A.equipment.items.push(E);break}A.equipment.totalWeight+=I.weight,g&&(A.equipment.equippedWeight+=I.weight)}return A}static openBoxForCharacter(A,B,g){if(!A.equipment?.items||A.equipment.items.findIndex(t=>t.name===B)===-1)return null;const C=AA.getInstance().get("equipment").find(t=>t.name===B);if(!C||C.type!=="box")return null;const a=A.equipment.items,e=Kt.openBox(C,g,a);if(!e.success)return{character:A,result:e};if(e.consumedItems&&e.consumedItems.length>0)for(const t of e.consumedItems)A=this.consumeItemFromCharacter(A,t.name,t.quantity).character;if(e.consumeBox&&A.equipment?.items){const t=A.equipment.items.findIndex(s=>s.name===B);t!==-1&&(A.equipment.items.splice(t,1),A.equipment.totalWeight-=C.weight)}const o=e.items;return A=this.addToCharacter(A,o,!1),{character:A,result:e}}static consumeItemFromCharacter(A,B,g){if(!A.equipment?.items)return{success:!1,character:A};const I=A.equipment.items.findIndex(o=>o.name===B);if(I===-1)return{success:!1,character:A};const E=A.equipment.items[I];if(E.quantity<g)return{success:!1,character:A};const e=AA.getInstance().get("equipment").find(o=>o.name===B)?.weight??0;return E.quantity-=g,A.equipment.totalWeight-=e*g,E.quantity<=0&&A.equipment.items.splice(I,1),{success:!0,character:A}}static getDefaultItemForTemplate(A){const g=AA.getInstance().get("equipment");let I="item";return A.tags&&(A.tags.includes("weapon")?I="weapon":A.tags.includes("armor")&&(I="armor")),g.find(E=>E.type===I)}static getTreasureItemCount(A,B){const g={},I=()=>B.random();return A<5?(g.common=Math.floor(I()*3)+1,g.uncommon=I()>.7?1:0):A<11?(g.common=Math.floor(I()*2)+1,g.uncommon=Math.floor(I()*2)+1,g.rare=I()>.6?1:0):A<17?(g.uncommon=Math.floor(I()*2)+1,g.rare=Math.floor(I()*2)+1,g.very_rare=I()>.7?1:0):(g.rare=Math.floor(I()*2)+1,g.very_rare=Math.floor(I()*2)+1,g.legendary=I()>.5?1:0),g}static estimateItemValue(A){let g={common:50,uncommon:400,rare:4e3,very_rare:4e4,legendary:2e5}[A.rarity]||50;return A.type==="weapon"?g*=1.2:A.type==="armor"&&(g*=1.5),A.weight>20&&(g*=1.1),Math.round(g)}}const Ti="geolocation_cache";class zt{constructor(A,B){if(this.cache=null,this.cacheTTL=300*1e3,this.cacheStats={hits:0,misses:0},this.logger=SA.for("GeolocationProvider"),typeof A=="number"||A===void 0)this.cacheTTL=(A??5)*60*1e3,this.useLocalStorage=(B??!0)&&this.isLocalStorageAvailable();else{const g=A;this.cacheTTL=g.cacheTTL??300*1e3,this.useLocalStorage=(g.useLocalStorage??!0)&&this.isLocalStorageAvailable()}this.useLocalStorage&&this.loadFromLocalStorage()}isLocalStorageAvailable(){try{const A="__localStorage_test__";return localStorage.setItem(A,A),localStorage.removeItem(A),!0}catch{return!1}}loadFromLocalStorage(){try{const A=localStorage.getItem(Ti);if(A){const B=JSON.parse(A),g=Date.now(),I=Object.values(B)[0];I&&g-I.timestamp<this.cacheTTL&&(this.cache=I)}}catch(A){this.logger.warn("Failed to load geolocation cache from localStorage",{error:A})}}saveToLocalStorage(){if(!(!this.useLocalStorage||!this.cache))try{const A={position:this.cache};localStorage.setItem(Ti,JSON.stringify(A))}catch(A){this.logger.warn("Failed to save geolocation cache to localStorage",{error:A})}}isCacheEntryValid(A){return A?Date.now()-A.timestamp<this.cacheTTL:!1}getCacheAge(){return this.cache?Date.now()-this.cache.timestamp:null}async getCurrentPosition(A=!1){return!A&&this.isCacheEntryValid(this.cache)?(this.cacheStats.hits++,this.cache.data):(this.cacheStats.misses++,typeof navigator>"u"||!navigator.geolocation?null:new Promise(B=>{navigator.geolocation.getCurrentPosition(g=>{const I={latitude:g.coords.latitude,longitude:g.coords.longitude,altitude:g.coords.altitude,accuracy:g.coords.accuracy,heading:g.coords.heading,speed:g.coords.speed,timestamp:g.timestamp};this.cache={data:I,timestamp:Date.now()},this.saveToLocalStorage(),B(I)},g=>{this.logger.warn("Geolocation error",{error:g.message}),B(null)},{enableHighAccuracy:!0,timeout:5e3,maximumAge:0})}))}invalidateCache(){if(this.cache=null,this.useLocalStorage)try{localStorage.removeItem(Ti)}catch(A){this.logger.warn("Failed to clear geolocation cache from localStorage",{error:A})}}getCacheStats(){return{...this.cacheStats}}resetCacheStats(){this.cacheStats={hits:0,misses:0}}isCacheExpired(){return!this.isCacheEntryValid(this.cache)}getCachedPosition(){return this.cache?.data??null}getBiome(A,B,g=null){const I=Math.abs(A),E=this.normalizeLongitude(B),i=this.isCoastal(A,B),C=i?"_coastal":"";if(g!==null&&!isNaN(g)){const a=this.getElevationBiome(g,C);if(a)return a}if(I>66.5)return`tundra${C}`;if(this.isInSwampRegion(A,E))return`swamp${C}`;if(I>=15&&I<=45&&this.isInDesertRegion(A,E))return i?"coastal_desert":"desert";if(I<=23.5){if(this.isInJungleRegion(A,E))return`jungle${C}`;if(this.isInSavannaRegion(A,E))return`savanna${C}`;if(A>0){if(E>=290&&E<=310)return`forest${C}`;if(E>=10&&E<=30)return`forest${C}`;if(E>=70&&E<=120)return`forest${C}`}else{if(E>=10&&E<=30)return`forest${C}`;if(E>=100&&E<=140)return`forest${C}`}return`forest${C}`}if(A>0&&I>=30&&I<=50&&(E>=235&&E<=290||E>=0&&E<=40||E>=110&&E<=145))return i?"coastal_urban":"urban";if(I>23.5&&I<=66.5)if(A>0){if(this.isInTaigaRegion(A,E))return`taiga${C}`;if(E>=235&&E<=290)return I>=45?`forest${C}`:`plains${C}`;if(E>=0&&E<=40)return`forest${C}`;if(E>40&&E<=180)return I>=50?`mountain${C}`:`plains${C}`}else{if(E>=280&&E<=320)return`plains${C}`;if(E>=15&&E<=40)return`plains${C}`;if(E>=110&&E<=180)return`plains${C}`}return`plains${C}`}getElevationBiome(A,B){return A>3500?`mountain${B}`:A>1500?`mountain${B}`:A<0?`valley${B}`:null}normalizeLongitude(A){let B=A%360;return B<0&&(B+=360),B}isInJungleRegion(A,B){return Math.abs(A)>15?!1:A>=-15&&A<=5&&B>=290&&B<=310||A>=-5&&A<=5&&B>=10&&B<=30||A>=-10&&A<=10&&B>=95&&B<=140}isInSwampRegion(A,B){return A>=25&&A<=26&&B>=279&&B<=281||A>=-20&&A<=-18&&B>=22&&B<=24||A>=21&&A<=22&&B>=89&&B<=90||A>=-20&&A<=-15&&B>=300&&B<=305}isInTaigaRegion(A,B){return A<50||A>70?!1:A>=50&&A<=70&&B>=230&&B<=300||A>=60&&A<=70&&B>=5&&B<=30||A>=55&&A<=70&&B>=30&&B<=180}isInSavannaRegion(A,B){return Math.abs(A)>20?!1:A>=-5&&A<=15&&B>=30&&B<=40||A>=-20&&A<=-15&&B>=15&&B<=35||A>=-25&&A<=-5&&B>=300&&B<=315||A>=-20&&A<=-10&&B>=130&&B<=135}isInDesertRegion(A,B){return A>15&&A<30&&(B>=345||B<=40)||A>15&&A<30&&B>=35&&B<=55||A>28&&A<35&&B>=35&&B<=40||A>25&&A<35&&B>=50&&B<=65||A>23&&A<30&&B>=68&&B<=75||A>35&&A<45&&B>=100&&B<=115||A<-20&&A>-30&&B>=115&&B<=145||A<-20&&A>-25&&B>=290&&B<=292||A>25&&A<35&&B>=245&&B<=250||A<-20&&A>-30&&B>=20&&B<=30}isCoastal(A,B){const g=Math.abs(A),I=this.normalizeLongitude(B);return!!(this.isInSmallIslandRegion(A,I)||this.isInNarrowLandmass(A,I)||g>60||A>30&&A<45&&I>=0&&I<=25||A>30&&A<40&&I>=25&&I<=35||A>15&&A<30&&I>=35&&I<=43||A>24&&A<30&&I>=48&&I<=55||A>41&&A<47&&I>=27&&I<=42||A>36&&A<47&&I>=46&&I<=55||A>53&&A<66&&I>=15&&I<=30||A>50&&A<60&&I>=0&&I<=10||A>15&&A<25&&I>=65&&I<=75||A>15&&A<23&&I>=80&&I<=90||A>35&&A<43&&I>=130&&I<=142||A>10&&A<25&&I>=105&&I<=122||A>20&&A<30&&I>=265&&I<=280||A>15&&A<25&&I>=275&&I<=300)}isInSmallIslandRegion(A,B){return A>=50&&A<=60&&B>=358&&B<=360||A>=50&&A<=60&&B>=0&&B<=10||A>=30&&A<=46&&B>=128&&B<=146||A>=4&&A<=22&&B>=116&&B<=127||A>=-10&&A<=6&&B>=94&&B<=142||A>=-47&&A<=-34&&B>=165&&B<=179||A>=-26&&A<=-12&&B>=43&&B<=51||A>=63&&A<=67&&B>=338&&B<=344||A>=10&&A<=23&&B>=275&&B<=300||A>=5&&A<=10&&B>=79&&B<=82||A>=18&&A<=23&&B>=204&&B<=206||A>=-18&&A<=-12&&B>=176&&B<=181}isInNarrowLandmass(A,B){return A>=7&&A<=18&&B>=275&&B<=290||A>=33&&A<=43&&B>=124&&B<=132||A>=36&&A<=47&&B>=8&&B<=19||A>=36&&A<=44&&B>=358&&B<=360||A>=36&&A<=44&&B>=0&&B<=10||A>=55&&A<=71&&B>=4&&B<=32||A>=24&&A<=31&&B>=268&&B<=272||A>=55&&A<=62&&B>=210&&B<=220||A>=50&&A<=62&&B>=156&&B<=163}}class lM{constructor(){this.logger=SA.for("MotionDetector"),this.isListening=!1,this.lastMotion=null,this.motionCallback=null,this.lastProcessedTime=0,this.minSampleIntervalMs=83,this.deltaBuffer=[],this.bufferSize=24,this.confirmedActivity="unknown",this.candidateActivity=null,this.candidateStartTime=0,this.confirmationMs=1500,this.handleMotion=A=>{if(!this.isListening||!this.motionCallback)return;const B=Date.now(),g={acceleration:{x:A.acceleration?.x??null,y:A.acceleration?.y??null,z:A.acceleration?.z??null},accelerationIncludingGravity:{x:A.accelerationIncludingGravity?.x??0,y:A.accelerationIncludingGravity?.y??0,z:A.accelerationIncludingGravity?.z??0},rotationRate:{alpha:A.rotationRate?.alpha??null,beta:A.rotationRate?.beta??null,gamma:A.rotationRate?.gamma??null},interval:A.interval,timestamp:B};if(this.lastMotion=g,B-this.lastProcessedTime<this.minSampleIntervalMs)return;this.lastProcessedTime=B;const I=g.accelerationIncludingGravity;if(I.x!=null&&I.y!=null&&I.z!=null&&!(I.x===0&&I.y===0&&I.z===0)){const E=Math.sqrt(I.x**2+I.y**2+I.z**2),i=Math.abs(E-9.8);this.updateSmoothing(i)}this.logger.debug("Motion event received",{acceleration:g.accelerationIncludingGravity,rotation:g.rotationRate,activity:this.detectActivity(g)}),this.motionCallback(g)}}startMonitoring(A){if(typeof window>"u"||!("DeviceMotionEvent"in window)){this.logger.warn("DeviceMotionEvent not supported");return}this.motionCallback=A,this.isListening=!0,this.deltaBuffer=[],this.confirmedActivity="unknown",this.candidateActivity=null,window.addEventListener("devicemotion",this.handleMotion)}stopMonitoring(){typeof window<"u"&&window.removeEventListener("devicemotion",this.handleMotion),this.isListening=!1,this.motionCallback=null,this.deltaBuffer=[],this.confirmedActivity="unknown",this.candidateActivity=null}getLastMotion(){return this.lastMotion}detectActivity(A){const B=A.accelerationIncludingGravity;if(B.x==null||B.y==null||B.z==null||B.x===0&&B.y===0&&B.z===0)return"unknown";if(this.deltaBuffer.length>=10)return this.confirmedActivity;const g=Math.sqrt(B.x**2+B.y**2+B.z**2),I=Math.abs(g-9.8);return I<.5?"stationary":I<2?"walking":I<5?"running":"driving"}updateSmoothing(A){this.deltaBuffer.push(A),this.deltaBuffer.length>this.bufferSize&&this.deltaBuffer.shift();const B=this.deltaBuffer.reduce((I,E)=>I+E,0)/this.deltaBuffer.length;let g;if(B<.5?g="stationary":B<2?g="walking":B<5?g="running":g="driving",g===this.confirmedActivity){this.candidateActivity=null;return}if(g!==this.candidateActivity){this.candidateActivity=g,this.candidateStartTime=Date.now();return}Date.now()-this.candidateStartTime>=this.confirmationMs&&(this.confirmedActivity=g,this.candidateActivity=null)}}const Tt=nA({id:X(),main:z(),description:z(),icon:z().optional()}),jt=nA({temp:X(),feels_like:X().optional(),temp_min:X().optional(),temp_max:X().optional(),pressure:X(),humidity:X().nonnegative(),sea_level:X().optional(),grnd_level:X().optional(),temp_kf:X().optional()}),Ot=nA({speed:X().nonnegative(),deg:X().min(0).max(360).optional(),gust:X().optional()}),Pt=nA({all:X().min(0).max(100)}),DM=nA({type:X().optional(),id:X().optional(),country:z().optional(),sunrise:X().nonnegative(),sunset:X().nonnegative(),pod:z().optional()}),_t=nA({"1h":X().optional(),"3h":X().optional()}),$t=nA({"1h":X().optional(),"3h":X().optional()}),As=nA({lon:X().min(-180).max(180),lat:X().min(-90).max(90)}),dM=nA({coord:As.optional(),weather:UA(Tt).min(1),base:z().optional(),main:jt,visibility:X().nonnegative().optional(),wind:Ot.optional(),rain:_t.optional(),snow:$t.optional(),clouds:Pt.optional(),dt:X().nonnegative(),sys:DM,timezone:X().optional(),id:X().optional(),name:z().optional(),cod:X().optional()}),FM=nA({dt:X().nonnegative(),main:jt,weather:UA(Tt).min(1),clouds:Pt.optional(),wind:Ot.optional(),visibility:X().nonnegative().optional(),pop:X().min(0).max(1).optional(),rain:_t.optional(),snow:$t.optional(),sys:nA({pod:z().optional()}).optional(),dt_txt:z().optional()}),MM=nA({cod:KE([z(),X()]),message:X().optional(),cnt:X().nonnegative().optional(),list:UA(FM).min(1),city:nA({id:X(),name:z(),coord:As,country:z().optional(),population:X().optional(),timezone:X().optional(),sunrise:X().optional(),sunset:X().optional()}).optional()}),Bs="weather_api_cache";class wM{constructor(A,B,g){if(this.baseUrl="https://api.openweathermap.org/data/2.5/weather",this.forecastUrl="https://api.openweathermap.org/data/2.5/forecast",this.cache=new Map,this.forecastCache=new Map,this.cacheTTL=720*1e3,this.forecastCacheTTL=3600*1e3,this.cacheStats={hits:0,misses:0},this.logger=SA.for("WeatherAPIClient"),this.lastKnownLocation=null,this.weatherApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.forecastApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.recentWeatherApiTimes=[],this.recentForecastApiTimes=[],this.maxRecentSamples=100,typeof A=="string"||A===void 0)this.apiKey=A??"",this.cacheTTL=(B??12)*60*1e3,this.useLocalStorage=(g??!0)&&this.isLocalStorageAvailable();else{const I=A;this.apiKey=I.apiKey??"",this.cacheTTL=I.cacheTTL??720*1e3,this.useLocalStorage=(I.useLocalStorage??!0)&&this.isLocalStorageAvailable()}this.useLocalStorage&&this.loadFromLocalStorage()}isLocalStorageAvailable(){try{const A="__localStorage_test__";return localStorage.setItem(A,A),localStorage.removeItem(A),!0}catch{return!1}}loadFromLocalStorage(){try{const A=localStorage.getItem(Bs);if(A){const B=JSON.parse(A),g=Date.now();for(const[I,E]of Object.entries(B))g-E.timestamp<this.cacheTTL&&this.cache.set(I,E)}}catch(A){this.logger.warn("Failed to load weather cache from localStorage",{error:A})}}saveToLocalStorage(){if(this.useLocalStorage)try{const A={};for(const[B,g]of this.cache.entries())A[B]=g;localStorage.setItem(Bs,JSON.stringify(A))}catch(A){this.logger.warn("Failed to save weather cache to localStorage",{error:A})}}startTiming(){const A=performance.now();return B=>{performance.now()-A}}recordWeatherApiCall(A,B){const g=Date.now();B?(this.weatherApiMetrics.successCount++,this.weatherApiMetrics.totalTime+=A,this.weatherApiMetrics.minTime=Math.min(this.weatherApiMetrics.minTime,A),this.weatherApiMetrics.maxTime=Math.max(this.weatherApiMetrics.maxTime,A),this.recentWeatherApiTimes.push(A),this.recentWeatherApiTimes.length>this.maxRecentSamples&&this.recentWeatherApiTimes.shift()):this.weatherApiMetrics.errorCount++,this.weatherApiMetrics.lastCallTimestamp=g}recordForecastApiCall(A,B){const g=Date.now();B?(this.forecastApiMetrics.successCount++,this.forecastApiMetrics.totalTime+=A,this.forecastApiMetrics.minTime=Math.min(this.forecastApiMetrics.minTime,A),this.forecastApiMetrics.maxTime=Math.max(this.forecastApiMetrics.maxTime,A),this.recentForecastApiTimes.push(A),this.recentForecastApiTimes.length>this.maxRecentSamples&&this.recentForecastApiTimes.shift()):this.forecastApiMetrics.errorCount++,this.forecastApiMetrics.lastCallTimestamp=g}calculatePercentile(A,B){if(A.length===0)return 0;const g=[...A].sort((E,i)=>E-i),I=Math.ceil(B/100*g.length)-1;return g[Math.max(0,I)]}getPerformanceStatistics(A,B){const g=A.successCount+A.errorCount,I=A.successCount>0?A.totalTime/A.successCount:0,E=g>0?A.successCount/g*100:0;return{average:Math.round(I),min:A.minTime===1/0?0:Math.round(A.minTime),max:Math.round(A.maxTime),totalCalls:g,successRate:Math.round(E*10)/10}}getWeatherApiMetrics(){return{...this.weatherApiMetrics}}getWeatherApiStatistics(){return{...this.getPerformanceStatistics(this.weatherApiMetrics,this.recentWeatherApiTimes),p95:Math.round(this.calculatePercentile(this.recentWeatherApiTimes,95)),p99:Math.round(this.calculatePercentile(this.recentWeatherApiTimes,99))}}getForecastApiMetrics(){return{...this.forecastApiMetrics}}getForecastApiStatistics(){return{...this.getPerformanceStatistics(this.forecastApiMetrics,this.recentForecastApiTimes),p95:Math.round(this.calculatePercentile(this.recentForecastApiTimes,95)),p99:Math.round(this.calculatePercentile(this.recentForecastApiTimes,99))}}resetPerformanceMetrics(){this.weatherApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.forecastApiMetrics={successCount:0,errorCount:0,totalTime:0,minTime:1/0,maxTime:0,lastCallTimestamp:null},this.recentWeatherApiTimes=[],this.recentForecastApiTimes=[]}getCacheKey(A,B){const g=Math.round(A*1e4)/1e4,I=Math.round(B*1e4)/1e4;return`${g},${I}`}isCacheEntryValid(A){return Date.now()-A.timestamp<this.cacheTTL}calculateMoonPhase(A){const B=new Date("2024-01-11T11:57:00Z"),g=29.530588853*24*60*60*1e3,E=(A.getTime()-B.getTime())/g;return Math.abs(E%1)}getSolarInfo(A,B,g){const I=g??new Date;return{...this.calculateSolarInfo(A,B,I),fromApi:!1,timestamp:Date.now()}}calculateSolarInfo(A,B,g){const I=this.calculateSunriseSunset(A,B,g,!0),E=this.calculateSunriseSunset(A,B,g,!1),i=this.calculateSolarNoon(A,B,g),C=this.calculateTwilight(A,B,g,"civil",!0),a=this.calculateTwilight(A,B,g,"civil",!1),{altitude:e,azimuth:o}=this.calculateSunPosition(A,B,g),t=!isNaN(E.getTime())&&!isNaN(I.getTime())?(E.getTime()-I.getTime())/(1e3*60*60):0,s=this.determineDayStage(g,I,E,C,a);return{currentTime:g,stage:s,sunrise:I,sunset:E,solarNoon:i,civilDawn:C,civilDusk:a,sunAltitude:e,sunAzimuth:o,dayLengthHours:t}}toRadians(A){return A*(Math.PI/180)}toDegrees(A){return A*(180/Math.PI)}calculateJulianDate(A){const B=A.getUTCFullYear(),g=A.getUTCMonth()+1,I=A.getUTCDate(),E=A.getUTCHours()+A.getUTCMinutes()/60+A.getUTCSeconds()/3600;let i=Math.floor((14-g)/12),C=B+4800-i,a=g+12*i-3;return I+Math.floor((153*a+2)/5)+365*C+Math.floor(C/4)-Math.floor(C/100)+Math.floor(C/400)-32045+(E-12)/24}calculateJulianCentury(A){return(A-2451545)/36525}calculateSolarDeclination(A){const B=(280.46646+A*(36000.76983+A*3032e-7))%360,g=357.52911+A*(35999.05029-1537e-7*A),I=Math.sin(this.toRadians(g))*(1.914602-A*(.004817+14e-6*A))+Math.sin(this.toRadians(2*g))*(.019993-101e-6*A)+Math.sin(this.toRadians(3*g))*289e-6,E=B+I,C=23+(26+(21.448-A*(46.815+A*(59e-5-A*.001813)))/60)/60+.00256*Math.cos(this.toRadians(125.04-1934.136*A)),a=E-.00569-.00478*Math.sin(this.toRadians(125.04-1934.136*A));return this.toDegrees(Math.asin(Math.sin(this.toRadians(C))*Math.sin(this.toRadians(a))))}calculateEquationOfTime(A){const B=(280.46646+A*(36000.76983+A*3032e-7))%360,g=357.52911+A*(35999.05029-1537e-7*A),I=.016708634-A*(42037e-9+1267e-10*A);return Math.tan(this.toRadians(23.44/2))**2,4*this.toDegrees(Math.atan2(Math.sin(this.toRadians(2*B))*Math.cos(this.toRadians(g))*2*I-Math.sin(this.toRadians(g))*2*I+Math.sin(this.toRadians(B))*Math.cos(this.toRadians(g))*2*I*Math.cos(this.toRadians(B)),1-Math.sin(this.toRadians(B))*Math.sin(this.toRadians(B))*I*I*Math.cos(this.toRadians(g))*Math.cos(this.toRadians(g))))}calculateHourAngle(A,B,g=90.833){const I=this.toRadians(A),E=this.toRadians(B),i=this.toRadians(g),C=(Math.cos(i)-Math.sin(I)*Math.sin(E))/(Math.cos(I)*Math.cos(E));return C>1?NaN:C<-1?NaN:this.toDegrees(Math.acos(C))}calculateSunriseSunset(A,B,g,I){const E=new Date(g);E.setUTCHours(12,0,0,0);const i=this.calculateJulianDate(E),C=this.calculateJulianCentury(i),a=this.calculateSolarDeclination(C),e=this.calculateHourAngle(A,a);if(isNaN(e))return new Date(NaN);const o=this.calculateEquationOfTime(C),t=(I?-e:e)/15,G=12-B/15-o/60+t,n=Math.floor(G),h=Math.floor((G-n)*60),c=Math.floor(((G-n)*60-h)*60),d=new Date(g);return d.setUTCHours(n,h,c,0),d}calculateSolarNoon(A,B,g){const I=new Date(g);I.setUTCHours(12,0,0,0);const E=this.calculateJulianDate(I),i=this.calculateJulianCentury(E),C=this.calculateEquationOfTime(i),a=12-B/15-C/60,e=Math.floor(a),o=Math.floor((a-e)*60),t=Math.floor(((a-e)*60-o)*60),s=new Date(g);return s.setUTCHours(e,o,t,0),s}calculateTwilight(A,B,g,I,E){const C={civil:96,nautical:102,astronomical:108}[I],a=new Date(g);a.setUTCHours(12,0,0,0);const e=this.calculateJulianDate(a),o=this.calculateJulianCentury(e),t=this.calculateSolarDeclination(o),s=this.calculateHourAngle(A,t,C);if(isNaN(s))return new Date(NaN);const G=this.calculateEquationOfTime(o),n=(E?-s:s)/15,c=12-B/15-G/60+n,d=Math.floor(c),l=Math.floor((c-d)*60),F=Math.floor(((c-d)*60-l)*60),m=new Date(g);return m.setUTCHours(d,l,F,0),m}calculateSunPosition(A,B,g){const I=this.calculateJulianDate(g),E=this.calculateJulianCentury(I),i=this.calculateSolarDeclination(E),C=this.calculateEquationOfTime(E),o=(g.getUTCHours()+g.getUTCMinutes()/60+g.getUTCSeconds()/3600+B/15+C/60-12)*15,t=this.toRadians(A),s=this.toRadians(i),G=this.toRadians(o),n=Math.sin(t)*Math.sin(s)+Math.cos(t)*Math.cos(s)*Math.cos(G),h=this.toDegrees(Math.asin(n)),c=(Math.sin(s)-Math.sin(t)*n)/(Math.cos(t)*Math.cos(this.toRadians(h)));let d=this.toDegrees(Math.acos(Math.max(-1,Math.min(1,c))));return o>0&&(d=360-d),{altitude:h,azimuth:d}}determineDayStage(A,B,g,I,E){const i=A.getTime();if(isNaN(B.getTime())||isNaN(g.getTime())){const t=A.getUTCMonth();return t>=4&&t<=7?"day":"night"}const C=I&&!isNaN(I.getTime())?I.getTime():B.getTime(),a=E&&!isNaN(E.getTime())?E.getTime():g.getTime(),e=B.getTime(),o=g.getTime();return i<C||i>a?"night":i>=C&&i<e?"dawn":i>o&&i<=a?"dusk":"day"}async getWeather(A,B){if(!this.apiKey)return this.logger.warn("Weather API key not provided"),null;this.lastKnownLocation={latitude:A,longitude:B};const g=this.getCacheKey(A,B),I=this.cache.get(g);if(I&&this.isCacheEntryValid(I))return this.cacheStats.hits++,I.data;this.cacheStats.misses++;const E=performance.now();let i=!1;try{const C=`${this.baseUrl}?lat=${A}&lon=${B}&appid=${this.apiKey}&units=metric`,a=await fetch(C);if(!a.ok)throw new Error(`Weather API error: ${a.statusText}`);const e=await a.json(),o=dM.safeParse(e);if(!o.success)return this.logger.error("Weather API response validation failed",{errors:o.error.issues,response:e}),null;const t=o.data,s=Date.now()/1e3,G=s<t.sys.sunrise||s>t.sys.sunset,n={temperature:t.main.temp,humidity:t.main.humidity,pressure:t.main.pressure,weatherType:t.weather[0]?.main||"Clear",windSpeed:t.wind?.speed??0,windDirection:t.wind?.deg??0,isNight:G,moonPhase:this.calculateMoonPhase(new Date),timestamp:Date.now()};return this.cache.set(g,{data:n,timestamp:Date.now()}),this.saveToLocalStorage(),i=!0,n}catch(C){return this.logger.error("Failed to fetch weather",{error:C}),null}finally{const C=performance.now()-E;this.recordWeatherApiCall(C,i)}}invalidateCache(){this.cache.clear(),this.saveToLocalStorage()}invalidateLocation(A,B){const g=this.getCacheKey(A,B);this.cache.delete(g),this.saveToLocalStorage()}getCacheStats(){return{...this.cacheStats}}resetCacheStats(){this.cacheStats={hits:0,misses:0}}clearExpiredEntries(){let A=0;for(const[B,g]of this.cache.entries())this.isCacheEntryValid(g)||(this.cache.delete(B),A++);return A>0&&this.saveToLocalStorage(),A}getCacheSize(){return this.cache.size}getLastKnownLocation(){return this.lastKnownLocation?{...this.lastKnownLocation}:null}isForecastCacheEntryValid(A){return Date.now()-A.timestamp<this.forecastCacheTTL}getForecastCacheKey(A,B,g){const I=Math.round(A*1e4)/1e4,E=Math.round(B*1e4)/1e4;return`forecast_${I},${E}_${g}h`}async getForecast(A,B,g=24){if(!this.apiKey)return this.logger.warn("Weather API key not provided"),null;const I=Math.min(g,120),E=this.getForecastCacheKey(A,B,I),i=this.forecastCache.get(E);if(i&&this.isForecastCacheEntryValid(i))return i.data.slice(0,Math.ceil(I/3));const C=performance.now();let a=!1;try{const e=Math.ceil(I/3),o=`${this.forecastUrl}?lat=${A}&lon=${B}&appid=${this.apiKey}&units=metric&cnt=${e}`,t=await fetch(o);if(!t.ok)throw new Error(`Weather Forecast API error: ${t.statusText}`);const s=await t.json(),G=MM.safeParse(s);if(!G.success)return this.logger.error("Weather Forecast API response validation failed",{errors:G.error.issues,response:s}),null;const h=G.data.list.map(c=>({temperature:c.main.temp,humidity:c.main.humidity,pressure:c.main.pressure,weatherType:c.weather[0]?.main||"Clear",windSpeed:c.wind?.speed??0,windDirection:c.wind?.deg??0,timestamp:Date.now(),forecastTime:new Date(c.dt*1e3),probabilityOfPrecipitation:c.pop??0}));return this.forecastCache.set(E,{data:h,timestamp:Date.now()}),a=!0,h}catch(e){return this.logger.error("Failed to fetch weather forecast",{error:e}),null}finally{const e=performance.now()-C;this.recordForecastApiCall(e,a)}}async getUpcomingWeather(A,B,g=12){const I=await this.getForecast(A,B,g);if(!I||I.length===0)return null;let E=!1,i=!1,C=0,a=0;const e=[];for(const G of I)e.push(G.weatherType),(G.weatherType.toLowerCase().includes("rain")||G.weatherType.toLowerCase().includes("drizzle"))&&(E=!0,C=Math.max(C,G.probabilityOfPrecipitation)),G.weatherType.toLowerCase().includes("snow")&&(i=!0,a=Math.max(a,G.probabilityOfPrecipitation));const o={thunderstorm:5,snow:4,rain:3,drizzle:2,clouds:1,clear:0,mist:1,fog:1,haze:1,smoke:1,dust:1,sand:1,ash:1,squall:2,tornado:5};let t="Clear",s=-1;for(const G of new Set(e)){const n=o[G.toLowerCase()]??0;n>s&&(s=n,t=G)}return{willRain:E,willSnow:i,rainProbability:C,snowProbability:a,worstWeatherType:t}}detectSevereWeather(A){const B=A.weatherType.toLowerCase(),g=A.windSpeed*3.6;if(B.includes("snow")||B.includes("blizzard")){const I=B.includes("blizzard")||B.includes("heavy")||A.windSpeed>8,E=g>25;if(B.includes("blizzard")||I&&E)return{type:"Blizzard",xpBonus:.5,severity:g>50?"extreme":"high",message:"⚠️ Blizzard conditions detected! Stay safe and warm.",detectedAt:Date.now()}}if(g>118){const E=(this.lastKnownLocation?this.isTropicalRegion(this.lastKnownLocation.latitude):!1)?"Hurricane":"Typhoon";return{type:E,xpBonus:.75,severity:g>177?"extreme":"high",message:`🌀 ${E} conditions detected! Please seek shelter.`,detectedAt:Date.now()}}return B.includes("tornado")?{type:"Tornado",xpBonus:1,severity:"extreme",message:"🌪️ TORNADO WARNING! Take immediate shelter!",detectedAt:Date.now()}:B.includes("thunderstorm")&&g>60?{type:"Tornado",xpBonus:.5,severity:"high",message:"⛈️ Extreme thunderstorm with high winds! Exercise caution.",detectedAt:Date.now()}:null}isTropicalRegion(A){return Math.abs(A)<23.5}getSafetyWarning(A){switch(A.type){case"Blizzard":return A.severity==="extreme"?"🚨 EXTREME BLIZZARD: Stay indoors, avoid travel. Keep emergency supplies ready.":"⚠️ Blizzard: Dress warmly, avoid unnecessary travel. Check on neighbors.";case"Hurricane":case"Typhoon":return A.severity==="extreme"?"🚨 EXTREME CYCLONE: Seek shelter immediately! Follow evacuation orders.":"⚠️ Hurricane/Typhoon: Secure property, prepare emergency kit, follow local alerts.";case"Tornado":return"🚨 TORNADO: Take shelter in basement or interior room immediately! Stay away from windows.";default:return"⚠️ Severe weather detected. Stay informed and stay safe."}}invalidateForecastCache(){this.forecastCache.clear()}invalidateForecastLocation(A,B){for(const[g]of this.forecastCache.entries()){const I=this.getCacheKey(A,B);g.includes(I)&&this.forecastCache.delete(g)}}clearExpiredForecastEntries(){let A=0;for(const[B,g]of this.forecastCache.entries())this.isForecastCacheEntryValid(g)||(this.forecastCache.delete(B),A++);return A}}const q={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m"},ji={useColors:!0,compact:!1,showTimestamp:!0,maxFailures:5};function Oi(Q){return Q.useColors===!1?!1:typeof process<"u"&&process.stdout?.isTTY===!0}function K(Q,A,B){return B?`${A}${Q}${q.reset}`:Q}function yM(Q,A){switch(Q){case"healthy":return K("●",q.green,A);case"degraded":return K("●",q.yellow,A);case"failed":return K("●",q.red,A);default:return K("○",q.dim,A)}}function RM(Q,A){return Q?K("✓ Granted",q.green,A):K("✗ Denied",q.red,A)}function mM(Q,A){return Q?K("Available",q.green,A):K("Unavailable",q.red,A)}function Pi(Q){const A=new Date(Q),B=A.getHours().toString().padStart(2,"0"),g=A.getMinutes().toString().padStart(2,"0"),I=A.getSeconds().toString().padStart(2,"0");return`${B}:${g}:${I}`}function $g(Q){return Q<1e3?`${Q}ms`:Q<6e4?`${(Q/1e3).toFixed(1)}s`:Q<36e5?`${Math.floor(Q/6e4)}m ${Math.floor(Q%6e4/1e3)}s`:`${Math.floor(Q/36e5)}h ${Math.floor(Q%36e5/6e4)}m`}function gs(Q,A){const B=Q+A;return B===0?"N/A":`${(Q/B*100).toFixed(1)}%`}function lQ(Q,A){const B=[],g=Q.totalCalls>0;if(B.push(` Calls: ${Q.totalCalls}`),B.push(` Success: ${g?`${Q.successRate.toFixed(1)}%`:"N/A"}`),g){const I=Q.average<500?q.green:Q.average<1500?q.yellow:q.red;B.push(` Avg Time: ${K(`${Q.average}ms`,I,A)}`),B.push(` Min/Avg/Max: ${Q.min}/${Q.average}/${Q.max}ms`),B.push(` P95/P99: ${Q.p95}/${Q.p99}ms`)}else B.push(" Avg Time: N/A");return B}function KA(Q,A="─"){return A.repeat(Q)}function _i(Q,A,B){const g=Math.max(0,A-Q.length-2),I=Math.floor(g/2),E=g-I,i=KA(A),C=B?q.bright+q.cyan+Q+q.reset:Q;return`
|
|
54
54
|
${i}
|
|
55
55
|
${" ".repeat(I)}${C}${" ".repeat(E)}
|
|
56
56
|
${i}
|