blacktrigram 0.7.25 → 0.7.28
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.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js +5 -0
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js +19 -0
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js +5 -0
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/data/archetypePhysicalAttributes.js +420 -0
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js +4 -0
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js +3 -0
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js +47 -0
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js +30 -0
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js +4 -0
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js +7 -0
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +25 -0
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +60 -0
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js +288 -0
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js +24 -0
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/constants/AnatomicalLimits.js +133 -1
- package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js +24 -0
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js +6 -0
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js +11 -0
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js +35 -0
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js +14 -0
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/LayoutTypes.js +32 -0
- package/lib/types/LayoutTypes.js.map +1 -1
- package/lib/types/constants/colors.js +9 -0
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js +84 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/index.js +5 -0
- package/lib/types/constants/index.js.map +1 -1
- package/lib/types/constants/layout.js +3 -0
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js +4 -0
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/physicsConstants.js +24 -0
- package/lib/types/physicsConstants.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js +9 -0
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/characterScaling.js +13 -0
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js +4 -0
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js +36 -0
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.d.ts +10 -0
- package/lib/utils/math.d.ts.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js +9 -0
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js +4 -0
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js +23 -0
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioMonitor.js","names":[],"sources":["../../src/audio/AudioMonitor.ts"],"sourcesContent":["/**\n * AudioMonitor - Performance and memory monitoring for audio system\n * Tracks memory usage, load times, and FPS impact\n */\n\nexport interface AudioMemoryStats {\n readonly totalLoadedMB: number;\n readonly assetCount: number;\n readonly estimatedSizeMB: number;\n readonly largestAssetMB: number;\n readonly averageAssetMB: number;\n}\n\nexport interface AudioPerformanceStats {\n readonly averageLoadTimeMs: number;\n readonly maxLoadTimeMs: number;\n readonly minLoadTimeMs: number;\n readonly totalLoads: number;\n readonly failedLoads: number;\n readonly averagePlaybackLatencyMs: number;\n}\n\nexport interface AudioFPSImpact {\n readonly baselineFPS: number;\n readonly currentFPS: number;\n readonly fpsDropDuringLoad: number;\n readonly fpsDropDuringPlayback: number;\n readonly impactDetected: boolean;\n}\n\nexport interface MemoryWarning {\n readonly level: \"info\" | \"warning\" | \"critical\";\n readonly message: string;\n readonly currentMB: number;\n readonly thresholdMB: number;\n readonly timestamp: number;\n}\n\nexport class AudioMonitor {\n private memoryThresholdMB = 100;\n private loadTimes: number[] = [];\n private playbackLatencies: number[] = [];\n private failedLoadCount = 0;\n private totalLoadCount = 0;\n private assetSizes: Map<string, number> = new Map();\n private warnings: MemoryWarning[] = [];\n private lastFPSCheck = 0;\n private baselineFPS = 60;\n private currentFPS = 60;\n private fpsHistory: number[] = [];\n private lastWarningTime = 0;\n private readonly WARNING_COOLDOWN_MS = 5000; // 5 seconds between similar warnings\n\n /**\n * Record a successful asset load\n */\n recordLoad(assetId: string, loadTimeMs: number, estimatedSizeMB: number): void {\n this.totalLoadCount++;\n this.loadTimes.push(loadTimeMs);\n this.assetSizes.set(assetId, estimatedSizeMB);\n\n // Keep only last 100 load times\n if (this.loadTimes.length > 100) {\n this.loadTimes.shift();\n }\n\n // Check memory threshold\n this.checkMemoryThreshold();\n }\n\n /**\n * Record a failed asset load\n */\n recordLoadFailure(assetId: string, error: Error): void {\n this.failedLoadCount++;\n this.totalLoadCount++;\n console.warn(`Load failed for ${assetId}:`, error.message);\n }\n\n /**\n * Record playback latency (time from play() call to actual playback)\n */\n recordPlaybackLatency(latencyMs: number): void {\n this.playbackLatencies.push(latencyMs);\n\n // Keep only last 100 measurements\n if (this.playbackLatencies.length > 100) {\n this.playbackLatencies.shift();\n }\n }\n\n /**\n * Update current FPS measurement\n */\n updateFPS(fps: number): void {\n this.currentFPS = fps;\n this.fpsHistory.push(fps);\n\n // Keep only last 60 measurements (1 second at 60fps)\n if (this.fpsHistory.length > 60) {\n this.fpsHistory.shift();\n }\n\n // Update baseline if we haven't checked recently\n const now = Date.now();\n if (now - this.lastFPSCheck > 5000) {\n // 5 seconds\n this.updateBaseline();\n this.lastFPSCheck = now;\n }\n }\n\n /**\n * Get current memory statistics\n */\n getMemoryStats(): AudioMemoryStats {\n const sizes = Array.from(this.assetSizes.values());\n const totalLoadedMB = sizes.reduce((sum, size) => sum + size, 0);\n const averageAssetMB = sizes.length > 0 ? totalLoadedMB / sizes.length : 0;\n const largestAssetMB = sizes.length > 0 ? Math.max(...sizes) : 0;\n\n return {\n totalLoadedMB,\n assetCount: this.assetSizes.size,\n estimatedSizeMB: totalLoadedMB,\n largestAssetMB,\n averageAssetMB,\n };\n }\n\n /**\n * Get current performance statistics\n */\n getPerformanceStats(): AudioPerformanceStats {\n const avgLoadTime =\n this.loadTimes.length > 0\n ? this.loadTimes.reduce((sum, t) => sum + t, 0) / this.loadTimes.length\n : 0;\n\n const maxLoadTime =\n this.loadTimes.length > 0 ? Math.max(...this.loadTimes) : 0;\n\n const minLoadTime =\n this.loadTimes.length > 0 ? Math.min(...this.loadTimes) : 0;\n\n const avgLatency =\n this.playbackLatencies.length > 0\n ? this.playbackLatencies.reduce((sum, l) => sum + l, 0) /\n this.playbackLatencies.length\n : 0;\n\n return {\n averageLoadTimeMs: avgLoadTime,\n maxLoadTimeMs: maxLoadTime,\n minLoadTimeMs: minLoadTime,\n totalLoads: this.totalLoadCount,\n failedLoads: this.failedLoadCount,\n averagePlaybackLatencyMs: avgLatency,\n };\n }\n\n /**\n * Get FPS impact analysis\n */\n getFPSImpact(): AudioFPSImpact {\n const avgFPS =\n this.fpsHistory.length > 0\n ? this.fpsHistory.reduce((sum, fps) => sum + fps, 0) /\n this.fpsHistory.length\n : this.currentFPS;\n\n const fpsDropDuringLoad = this.baselineFPS - avgFPS;\n const fpsDropDuringPlayback = this.baselineFPS - this.currentFPS;\n const impactDetected = fpsDropDuringLoad > 5 || fpsDropDuringPlayback > 5;\n\n return {\n baselineFPS: this.baselineFPS,\n currentFPS: this.currentFPS,\n fpsDropDuringLoad,\n fpsDropDuringPlayback,\n impactDetected,\n };\n }\n\n /**\n * Get all warnings\n */\n getWarnings(): readonly MemoryWarning[] {\n return [...this.warnings];\n }\n\n /**\n * Get recent warnings (last N)\n */\n getRecentWarnings(count: number = 10): readonly MemoryWarning[] {\n return this.warnings.slice(-count);\n }\n\n /**\n * Clear all warnings\n */\n clearWarnings(): void {\n this.warnings = [];\n }\n\n /**\n * Set memory threshold for warnings\n */\n setMemoryThreshold(thresholdMB: number): void {\n this.memoryThresholdMB = thresholdMB;\n }\n\n /**\n * Check memory threshold and create warnings with rate limiting\n */\n private checkMemoryThreshold(): void {\n const stats = this.getMemoryStats();\n const currentMB = stats.totalLoadedMB;\n const now = Date.now();\n\n // Rate limit warnings to prevent spam during rapid asset loading\n if (now - this.lastWarningTime < this.WARNING_COOLDOWN_MS) {\n return;\n }\n\n if (currentMB > this.memoryThresholdMB * 1.5) {\n // 150% threshold\n this.addWarning({\n level: \"critical\",\n message: `Audio memory usage critical: ${currentMB.toFixed(1)}MB (threshold: ${this.memoryThresholdMB}MB)`,\n currentMB,\n thresholdMB: this.memoryThresholdMB,\n timestamp: now,\n });\n this.lastWarningTime = now;\n } else if (currentMB > this.memoryThresholdMB) {\n // 100% threshold\n this.addWarning({\n level: \"warning\",\n message: `Audio memory usage high: ${currentMB.toFixed(1)}MB (threshold: ${this.memoryThresholdMB}MB)`,\n currentMB,\n thresholdMB: this.memoryThresholdMB,\n timestamp: now,\n });\n this.lastWarningTime = now;\n } else if (currentMB > this.memoryThresholdMB * 0.8) {\n // 80% threshold\n this.addWarning({\n level: \"info\",\n message: `Audio memory usage approaching threshold: ${currentMB.toFixed(1)}MB (threshold: ${this.memoryThresholdMB}MB)`,\n currentMB,\n thresholdMB: this.memoryThresholdMB,\n timestamp: now,\n });\n this.lastWarningTime = now;\n }\n }\n\n /**\n * Add a warning to the list\n */\n private addWarning(warning: MemoryWarning): void {\n this.warnings.push(warning);\n\n // Keep only last 100 warnings\n if (this.warnings.length > 100) {\n this.warnings.shift();\n }\n\n // Log critical warnings\n if (warning.level === \"critical\") {\n console.error(warning.message);\n } else if (warning.level === \"warning\") {\n console.warn(warning.message);\n }\n }\n\n /**\n * Update baseline FPS based on recent stable measurements\n */\n private updateBaseline(): void {\n if (this.fpsHistory.length < 30) {\n return; // Need more data\n }\n\n // Get average of recent stable FPS measurements\n const recent = this.fpsHistory.slice(-30);\n const avg = recent.reduce((sum, fps) => sum + fps, 0) / recent.length;\n\n // Allow baseline to adjust with dampening (slower downward adjustment)\n if (avg > this.baselineFPS) {\n this.baselineFPS = Math.min(avg, 60); // Cap at 60fps\n } else if (avg < this.baselineFPS - 5) {\n // Only adjust down if significantly lower (5fps drop)\n this.baselineFPS = Math.max(avg, 30); // Floor at 30fps\n }\n }\n\n /**\n * Reset all statistics\n */\n reset(): void {\n this.loadTimes = [];\n this.playbackLatencies = [];\n this.failedLoadCount = 0;\n this.totalLoadCount = 0;\n this.assetSizes.clear();\n this.warnings = [];\n this.fpsHistory = [];\n }\n\n /**\n * Unregister an asset (when unloaded)\n */\n unregisterAsset(assetId: string): void {\n this.assetSizes.delete(assetId);\n }\n\n /**\n * Get comprehensive report\n */\n getReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return {\n memory: this.getMemoryStats(),\n performance: this.getPerformanceStats(),\n fps: this.getFPSImpact(),\n warnings: this.getRecentWarnings(5),\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioMonitor.js","names":[],"sources":["../../src/audio/AudioMonitor.ts"],"sourcesContent":["/**\n * AudioMonitor - Performance and memory monitoring for audio system\n * Tracks memory usage, load times, and FPS impact\n */\n\nexport interface AudioMemoryStats {\n readonly totalLoadedMB: number;\n readonly assetCount: number;\n readonly estimatedSizeMB: number;\n readonly largestAssetMB: number;\n readonly averageAssetMB: number;\n}\n\nexport interface AudioPerformanceStats {\n readonly averageLoadTimeMs: number;\n readonly maxLoadTimeMs: number;\n readonly minLoadTimeMs: number;\n readonly totalLoads: number;\n readonly failedLoads: number;\n readonly averagePlaybackLatencyMs: number;\n}\n\nexport interface AudioFPSImpact {\n readonly baselineFPS: number;\n readonly currentFPS: number;\n readonly fpsDropDuringLoad: number;\n readonly fpsDropDuringPlayback: number;\n readonly impactDetected: boolean;\n}\n\nexport interface MemoryWarning {\n readonly level: \"info\" | \"warning\" | \"critical\";\n readonly message: string;\n readonly currentMB: number;\n readonly thresholdMB: number;\n readonly timestamp: number;\n}\n\nexport class AudioMonitor {\n private memoryThresholdMB = 100;\n private loadTimes: number[] = [];\n private playbackLatencies: number[] = [];\n private failedLoadCount = 0;\n private totalLoadCount = 0;\n private assetSizes: Map<string, number> = new Map();\n private warnings: MemoryWarning[] = [];\n private lastFPSCheck = 0;\n private baselineFPS = 60;\n private currentFPS = 60;\n private fpsHistory: number[] = [];\n private lastWarningTime = 0;\n private readonly WARNING_COOLDOWN_MS = 5000; // 5 seconds between similar warnings\n\n /**\n * Record a successful asset load\n */\n recordLoad(assetId: string, loadTimeMs: number, estimatedSizeMB: number): void {\n this.totalLoadCount++;\n this.loadTimes.push(loadTimeMs);\n this.assetSizes.set(assetId, estimatedSizeMB);\n\n // Keep only last 100 load times\n if (this.loadTimes.length > 100) {\n this.loadTimes.shift();\n }\n\n // Check memory threshold\n this.checkMemoryThreshold();\n }\n\n /**\n * Record a failed asset load\n */\n recordLoadFailure(assetId: string, error: Error): void {\n this.failedLoadCount++;\n this.totalLoadCount++;\n console.warn(`Load failed for ${assetId}:`, error.message);\n }\n\n /**\n * Record playback latency (time from play() call to actual playback)\n */\n recordPlaybackLatency(latencyMs: number): void {\n this.playbackLatencies.push(latencyMs);\n\n // Keep only last 100 measurements\n if (this.playbackLatencies.length > 100) {\n this.playbackLatencies.shift();\n }\n }\n\n /**\n * Update current FPS measurement\n */\n updateFPS(fps: number): void {\n this.currentFPS = fps;\n this.fpsHistory.push(fps);\n\n // Keep only last 60 measurements (1 second at 60fps)\n if (this.fpsHistory.length > 60) {\n this.fpsHistory.shift();\n }\n\n // Update baseline if we haven't checked recently\n const now = Date.now();\n if (now - this.lastFPSCheck > 5000) {\n // 5 seconds\n this.updateBaseline();\n this.lastFPSCheck = now;\n }\n }\n\n /**\n * Get current memory statistics\n */\n getMemoryStats(): AudioMemoryStats {\n const sizes = Array.from(this.assetSizes.values());\n const totalLoadedMB = sizes.reduce((sum, size) => sum + size, 0);\n const averageAssetMB = sizes.length > 0 ? totalLoadedMB / sizes.length : 0;\n const largestAssetMB = sizes.length > 0 ? Math.max(...sizes) : 0;\n\n return {\n totalLoadedMB,\n assetCount: this.assetSizes.size,\n estimatedSizeMB: totalLoadedMB,\n largestAssetMB,\n averageAssetMB,\n };\n }\n\n /**\n * Get current performance statistics\n */\n getPerformanceStats(): AudioPerformanceStats {\n const avgLoadTime =\n this.loadTimes.length > 0\n ? this.loadTimes.reduce((sum, t) => sum + t, 0) / this.loadTimes.length\n : 0;\n\n const maxLoadTime =\n this.loadTimes.length > 0 ? Math.max(...this.loadTimes) : 0;\n\n const minLoadTime =\n this.loadTimes.length > 0 ? Math.min(...this.loadTimes) : 0;\n\n const avgLatency =\n this.playbackLatencies.length > 0\n ? this.playbackLatencies.reduce((sum, l) => sum + l, 0) /\n this.playbackLatencies.length\n : 0;\n\n return {\n averageLoadTimeMs: avgLoadTime,\n maxLoadTimeMs: maxLoadTime,\n minLoadTimeMs: minLoadTime,\n totalLoads: this.totalLoadCount,\n failedLoads: this.failedLoadCount,\n averagePlaybackLatencyMs: avgLatency,\n };\n }\n\n /**\n * Get FPS impact analysis\n */\n getFPSImpact(): AudioFPSImpact {\n const avgFPS =\n this.fpsHistory.length > 0\n ? this.fpsHistory.reduce((sum, fps) => sum + fps, 0) /\n this.fpsHistory.length\n : this.currentFPS;\n\n const fpsDropDuringLoad = this.baselineFPS - avgFPS;\n const fpsDropDuringPlayback = this.baselineFPS - this.currentFPS;\n const impactDetected = fpsDropDuringLoad > 5 || fpsDropDuringPlayback > 5;\n\n return {\n baselineFPS: this.baselineFPS,\n currentFPS: this.currentFPS,\n fpsDropDuringLoad,\n fpsDropDuringPlayback,\n impactDetected,\n };\n }\n\n /**\n * Get all warnings\n */\n getWarnings(): readonly MemoryWarning[] {\n return [...this.warnings];\n }\n\n /**\n * Get recent warnings (last N)\n */\n getRecentWarnings(count: number = 10): readonly MemoryWarning[] {\n return this.warnings.slice(-count);\n }\n\n /**\n * Clear all warnings\n */\n clearWarnings(): void {\n this.warnings = [];\n }\n\n /**\n * Set memory threshold for warnings\n */\n setMemoryThreshold(thresholdMB: number): void {\n this.memoryThresholdMB = thresholdMB;\n }\n\n /**\n * Check memory threshold and create warnings with rate limiting\n */\n private checkMemoryThreshold(): void {\n const stats = this.getMemoryStats();\n const currentMB = stats.totalLoadedMB;\n const now = Date.now();\n\n // Rate limit warnings to prevent spam during rapid asset loading\n if (now - this.lastWarningTime < this.WARNING_COOLDOWN_MS) {\n return;\n }\n\n if (currentMB > this.memoryThresholdMB * 1.5) {\n // 150% threshold\n this.addWarning({\n level: \"critical\",\n message: `Audio memory usage critical: ${currentMB.toFixed(1)}MB (threshold: ${this.memoryThresholdMB}MB)`,\n currentMB,\n thresholdMB: this.memoryThresholdMB,\n timestamp: now,\n });\n this.lastWarningTime = now;\n } else if (currentMB > this.memoryThresholdMB) {\n // 100% threshold\n this.addWarning({\n level: \"warning\",\n message: `Audio memory usage high: ${currentMB.toFixed(1)}MB (threshold: ${this.memoryThresholdMB}MB)`,\n currentMB,\n thresholdMB: this.memoryThresholdMB,\n timestamp: now,\n });\n this.lastWarningTime = now;\n } else if (currentMB > this.memoryThresholdMB * 0.8) {\n // 80% threshold\n this.addWarning({\n level: \"info\",\n message: `Audio memory usage approaching threshold: ${currentMB.toFixed(1)}MB (threshold: ${this.memoryThresholdMB}MB)`,\n currentMB,\n thresholdMB: this.memoryThresholdMB,\n timestamp: now,\n });\n this.lastWarningTime = now;\n }\n }\n\n /**\n * Add a warning to the list\n */\n private addWarning(warning: MemoryWarning): void {\n this.warnings.push(warning);\n\n // Keep only last 100 warnings\n if (this.warnings.length > 100) {\n this.warnings.shift();\n }\n\n // Log critical warnings\n if (warning.level === \"critical\") {\n console.error(warning.message);\n } else if (warning.level === \"warning\") {\n console.warn(warning.message);\n }\n }\n\n /**\n * Update baseline FPS based on recent stable measurements\n */\n private updateBaseline(): void {\n if (this.fpsHistory.length < 30) {\n return; // Need more data\n }\n\n // Get average of recent stable FPS measurements\n const recent = this.fpsHistory.slice(-30);\n const avg = recent.reduce((sum, fps) => sum + fps, 0) / recent.length;\n\n // Allow baseline to adjust with dampening (slower downward adjustment)\n if (avg > this.baselineFPS) {\n this.baselineFPS = Math.min(avg, 60); // Cap at 60fps\n } else if (avg < this.baselineFPS - 5) {\n // Only adjust down if significantly lower (5fps drop)\n this.baselineFPS = Math.max(avg, 30); // Floor at 30fps\n }\n }\n\n /**\n * Reset all statistics\n */\n reset(): void {\n this.loadTimes = [];\n this.playbackLatencies = [];\n this.failedLoadCount = 0;\n this.totalLoadCount = 0;\n this.assetSizes.clear();\n this.warnings = [];\n this.fpsHistory = [];\n }\n\n /**\n * Unregister an asset (when unloaded)\n */\n unregisterAsset(assetId: string): void {\n this.assetSizes.delete(assetId);\n }\n\n /**\n * Get comprehensive report\n */\n getReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return {\n memory: this.getMemoryStats(),\n performance: this.getPerformanceStats(),\n fps: this.getFPSImpact(),\n warnings: this.getRecentWarnings(5),\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioPool.js","names":[],"sources":["../../src/audio/AudioPool.ts"],"sourcesContent":["/**\n * AudioPool - Object pooling for audio instances\n * Reduces memory allocation overhead for frequently played sounds\n */\n\nexport interface PoolStatistics {\n readonly available: number;\n readonly inUse: number;\n readonly total: number;\n readonly acquisitions: number;\n readonly releases: number;\n readonly created: number;\n}\n\nexport interface PoolConfig {\n readonly initialSize?: number;\n readonly maxSize?: number;\n readonly autoExpand?: boolean;\n}\n\n/**\n * Generic object pool implementation\n */\nexport class ObjectPool<T> {\n private available: T[] = [];\n private inUse = new Set<T>();\n private acquisitionCount = 0;\n private releaseCount = 0;\n private createdCount = 0;\n\n constructor(\n private readonly factory: () => T,\n private readonly reset: (obj: T) => void,\n private readonly config: Required<PoolConfig> = {\n initialSize: 10,\n maxSize: 100,\n autoExpand: true,\n }\n ) {\n // Pre-populate pool\n for (let i = 0; i < config.initialSize; i++) {\n this.available.push(this.createObject());\n }\n }\n\n /**\n * Acquire an object from the pool\n */\n acquire(): T | null {\n this.acquisitionCount++;\n\n let obj = this.available.pop();\n\n if (!obj) {\n // Pool is empty\n if (this.config.autoExpand && this.canExpand()) {\n obj = this.createObject();\n } else {\n console.warn(\"Pool exhausted and cannot expand\");\n return null;\n }\n }\n\n this.inUse.add(obj);\n return obj;\n }\n\n /**\n * Release an object back to the pool\n */\n release(obj: T): void {\n if (!this.inUse.has(obj)) {\n console.warn(\"Attempted to release object not in use\");\n return;\n }\n\n this.releaseCount++;\n this.reset(obj);\n this.inUse.delete(obj);\n this.available.push(obj);\n }\n\n /**\n * Release all objects back to the pool\n * Resets all in-use objects and returns them to the available pool\n */\n releaseAll(): void {\n this.inUse.forEach((obj) => {\n this.releaseCount++;\n this.reset(obj);\n this.available.push(obj);\n });\n this.inUse.clear();\n }\n\n /**\n * Get pool statistics\n */\n getStatistics(): PoolStatistics {\n return {\n available: this.available.length,\n inUse: this.inUse.size,\n total: this.available.length + this.inUse.size,\n acquisitions: this.acquisitionCount,\n releases: this.releaseCount,\n created: this.createdCount,\n };\n }\n\n /**\n * Clear the pool and dispose all objects\n */\n clear(): void {\n this.available = [];\n this.inUse.clear();\n }\n\n private createObject(): T {\n this.createdCount++;\n return this.factory();\n }\n\n private canExpand(): boolean {\n const total = this.available.length + this.inUse.size;\n return total < this.config.maxSize;\n }\n}\n\n/**\n * Specialized audio pool for HTMLAudioElement instances\n */\nexport class AudioElementPool {\n private pools: Map<string, ObjectPool<HTMLAudioElement>> = new Map();\n private defaultConfig: Required<PoolConfig> = {\n initialSize: 5,\n maxSize: 20,\n autoExpand: true,\n };\n\n /**\n * Create a pool for a specific audio asset\n */\n createPool(\n assetId: string,\n audioSrc: string,\n config?: PoolConfig\n ): ObjectPool<HTMLAudioElement> {\n const existingPool = this.pools.get(assetId);\n if (existingPool) {\n return existingPool;\n }\n\n const poolConfig = { ...this.defaultConfig, ...config };\n\n const pool = new ObjectPool<HTMLAudioElement>(\n () => {\n const audio = new Audio(audioSrc);\n audio.preload = \"auto\";\n audio.load();\n return audio;\n },\n (audio) => {\n audio.pause();\n audio.currentTime = 0;\n },\n poolConfig\n );\n\n this.pools.set(assetId, pool);\n return pool;\n }\n\n /**\n * Get pool for an asset (creates if doesn't exist)\n */\n getPool(assetId: string): ObjectPool<HTMLAudioElement> | undefined {\n return this.pools.get(assetId);\n }\n\n /**\n * Acquire audio element from pool\n */\n acquire(assetId: string): HTMLAudioElement | null {\n const pool = this.pools.get(assetId);\n if (!pool) {\n console.warn(`No pool exists for asset: ${assetId}`);\n return null;\n }\n return pool.acquire();\n }\n\n /**\n * Release audio element back to pool\n */\n release(assetId: string, audio: HTMLAudioElement): void {\n const pool = this.pools.get(assetId);\n if (!pool) {\n console.warn(`No pool exists for asset: ${assetId}`);\n return;\n }\n pool.release(audio);\n }\n\n /**\n * Release all audio elements in all pools\n */\n releaseAll(): void {\n this.pools.forEach((pool) => pool.releaseAll());\n }\n\n /**\n * Get statistics for a specific pool\n */\n getPoolStatistics(assetId: string): PoolStatistics | undefined {\n const pool = this.pools.get(assetId);\n return pool?.getStatistics();\n }\n\n /**\n * Get statistics for all pools\n */\n getAllStatistics(): Map<string, PoolStatistics> {\n const stats = new Map<string, PoolStatistics>();\n this.pools.forEach((pool, assetId) => {\n stats.set(assetId, pool.getStatistics());\n });\n return stats;\n }\n\n /**\n * Remove a pool and dispose its resources\n * @param assetId - The ID of the asset whose pool should be removed\n * @returns true if the pool existed and was removed, false if no pool was found for the asset\n */\n removePool(assetId: string): boolean {\n const pool = this.pools.get(assetId);\n if (pool) {\n pool.clear();\n this.pools.delete(assetId);\n return true;\n }\n return false;\n }\n\n /**\n * Clear all pools\n */\n clearAll(): void {\n this.pools.forEach((pool) => pool.clear());\n this.pools.clear();\n }\n\n /**\n * Check if pool exists for asset\n */\n hasPool(assetId: string): boolean {\n return this.pools.has(assetId);\n }\n\n /**\n * Get total number of pools\n */\n getPoolCount(): number {\n return this.pools.size;\n }\n}\n"],"mappings":";;;;AAuBA,IAAa,aAAb,MAA2B;CACzB,YAAyB,EAAE;CAC3B,wBAAgB,IAAI,KAAQ;CAC5B,mBAA2B;CAC3B,eAAuB;CACvB,eAAuB;CAEvB,YACE,SACA,OACA,SAAgD;EAC9C,aAAa;EACb,SAAS;EACT,YAAY;EACb,EACD;AAPiB,OAAA,UAAA;AACA,OAAA,QAAA;AACA,OAAA,SAAA;AAOjB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,aAAa,IACtC,MAAK,UAAU,KAAK,KAAK,cAAc,CAAC;;;;;CAO5C,UAAoB;AAClB,OAAK;EAEL,IAAI,MAAM,KAAK,UAAU,KAAK;AAE9B,MAAI,CAAC,IAEH,KAAI,KAAK,OAAO,cAAc,KAAK,WAAW,CAC5C,OAAM,KAAK,cAAc;OACpB;AACL,WAAQ,KAAK,mCAAmC;AAChD,UAAO;;AAIX,OAAK,MAAM,IAAI,IAAI;AACnB,SAAO;;;;;CAMT,QAAQ,KAAc;AACpB,MAAI,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE;AACxB,WAAQ,KAAK,yCAAyC;AACtD;;AAGF,OAAK;AACL,OAAK,MAAM,IAAI;AACf,OAAK,MAAM,OAAO,IAAI;AACtB,OAAK,UAAU,KAAK,IAAI;;;;;;CAO1B,aAAmB;AACjB,OAAK,MAAM,SAAS,QAAQ;AAC1B,QAAK;AACL,QAAK,MAAM,IAAI;AACf,QAAK,UAAU,KAAK,IAAI;IACxB;AACF,OAAK,MAAM,OAAO;;;;;CAMpB,gBAAgC;AAC9B,SAAO;GACL,WAAW,KAAK,UAAU;GAC1B,OAAO,KAAK,MAAM;GAClB,OAAO,KAAK,UAAU,SAAS,KAAK,MAAM;GAC1C,cAAc,KAAK;GACnB,UAAU,KAAK;GACf,SAAS,KAAK;GACf;;;;;CAMH,QAAc;AACZ,OAAK,YAAY,EAAE;AACnB,OAAK,MAAM,OAAO;;CAGpB,eAA0B;AACxB,OAAK;AACL,SAAO,KAAK,SAAS;;CAGvB,YAA6B;AAE3B,SADc,KAAK,UAAU,SAAS,KAAK,MAAM,OAClC,KAAK,OAAO;;;;;;AAO/B,IAAa,mBAAb,MAA8B;CAC5B,wBAA2D,IAAI,KAAK;CACpE,gBAA8C;EAC5C,aAAa;EACb,SAAS;EACT,YAAY;EACb;;;;CAKD,WACE,SACA,UACA,QAC8B;EAC9B,MAAM,eAAe,KAAK,MAAM,IAAI,QAAQ;AAC5C,MAAI,aACF,QAAO;EAKT,MAAM,OAAO,IAAI,iBACT;GACJ,MAAM,QAAQ,IAAI,MAAM,SAAS;AACjC,SAAM,UAAU;AAChB,SAAM,MAAM;AACZ,UAAO;MAER,UAAU;AACT,SAAM,OAAO;AACb,SAAM,cAAc;
|
|
1
|
+
{"version":3,"file":"AudioPool.js","names":[],"sources":["../../src/audio/AudioPool.ts"],"sourcesContent":["/**\n * AudioPool - Object pooling for audio instances\n * Reduces memory allocation overhead for frequently played sounds\n */\n\nexport interface PoolStatistics {\n readonly available: number;\n readonly inUse: number;\n readonly total: number;\n readonly acquisitions: number;\n readonly releases: number;\n readonly created: number;\n}\n\nexport interface PoolConfig {\n readonly initialSize?: number;\n readonly maxSize?: number;\n readonly autoExpand?: boolean;\n}\n\n/**\n * Generic object pool implementation\n */\nexport class ObjectPool<T> {\n private available: T[] = [];\n private inUse = new Set<T>();\n private acquisitionCount = 0;\n private releaseCount = 0;\n private createdCount = 0;\n\n constructor(\n private readonly factory: () => T,\n private readonly reset: (obj: T) => void,\n private readonly config: Required<PoolConfig> = {\n initialSize: 10,\n maxSize: 100,\n autoExpand: true,\n }\n ) {\n // Pre-populate pool\n for (let i = 0; i < config.initialSize; i++) {\n this.available.push(this.createObject());\n }\n }\n\n /**\n * Acquire an object from the pool\n */\n acquire(): T | null {\n this.acquisitionCount++;\n\n let obj = this.available.pop();\n\n if (!obj) {\n // Pool is empty\n if (this.config.autoExpand && this.canExpand()) {\n obj = this.createObject();\n } else {\n console.warn(\"Pool exhausted and cannot expand\");\n return null;\n }\n }\n\n this.inUse.add(obj);\n return obj;\n }\n\n /**\n * Release an object back to the pool\n */\n release(obj: T): void {\n if (!this.inUse.has(obj)) {\n console.warn(\"Attempted to release object not in use\");\n return;\n }\n\n this.releaseCount++;\n this.reset(obj);\n this.inUse.delete(obj);\n this.available.push(obj);\n }\n\n /**\n * Release all objects back to the pool\n * Resets all in-use objects and returns them to the available pool\n */\n releaseAll(): void {\n this.inUse.forEach((obj) => {\n this.releaseCount++;\n this.reset(obj);\n this.available.push(obj);\n });\n this.inUse.clear();\n }\n\n /**\n * Get pool statistics\n */\n getStatistics(): PoolStatistics {\n return {\n available: this.available.length,\n inUse: this.inUse.size,\n total: this.available.length + this.inUse.size,\n acquisitions: this.acquisitionCount,\n releases: this.releaseCount,\n created: this.createdCount,\n };\n }\n\n /**\n * Clear the pool and dispose all objects\n */\n clear(): void {\n this.available = [];\n this.inUse.clear();\n }\n\n private createObject(): T {\n this.createdCount++;\n return this.factory();\n }\n\n private canExpand(): boolean {\n const total = this.available.length + this.inUse.size;\n return total < this.config.maxSize;\n }\n}\n\n/**\n * Specialized audio pool for HTMLAudioElement instances\n */\nexport class AudioElementPool {\n private pools: Map<string, ObjectPool<HTMLAudioElement>> = new Map();\n private defaultConfig: Required<PoolConfig> = {\n initialSize: 5,\n maxSize: 20,\n autoExpand: true,\n };\n\n /**\n * Create a pool for a specific audio asset\n */\n createPool(\n assetId: string,\n audioSrc: string,\n config?: PoolConfig\n ): ObjectPool<HTMLAudioElement> {\n const existingPool = this.pools.get(assetId);\n if (existingPool) {\n return existingPool;\n }\n\n const poolConfig = { ...this.defaultConfig, ...config };\n\n const pool = new ObjectPool<HTMLAudioElement>(\n () => {\n const audio = new Audio(audioSrc);\n audio.preload = \"auto\";\n audio.load();\n return audio;\n },\n (audio) => {\n audio.pause();\n audio.currentTime = 0;\n },\n poolConfig\n );\n\n this.pools.set(assetId, pool);\n return pool;\n }\n\n /**\n * Get pool for an asset (creates if doesn't exist)\n */\n getPool(assetId: string): ObjectPool<HTMLAudioElement> | undefined {\n return this.pools.get(assetId);\n }\n\n /**\n * Acquire audio element from pool\n */\n acquire(assetId: string): HTMLAudioElement | null {\n const pool = this.pools.get(assetId);\n if (!pool) {\n console.warn(`No pool exists for asset: ${assetId}`);\n return null;\n }\n return pool.acquire();\n }\n\n /**\n * Release audio element back to pool\n */\n release(assetId: string, audio: HTMLAudioElement): void {\n const pool = this.pools.get(assetId);\n if (!pool) {\n console.warn(`No pool exists for asset: ${assetId}`);\n return;\n }\n pool.release(audio);\n }\n\n /**\n * Release all audio elements in all pools\n */\n releaseAll(): void {\n this.pools.forEach((pool) => pool.releaseAll());\n }\n\n /**\n * Get statistics for a specific pool\n */\n getPoolStatistics(assetId: string): PoolStatistics | undefined {\n const pool = this.pools.get(assetId);\n return pool?.getStatistics();\n }\n\n /**\n * Get statistics for all pools\n */\n getAllStatistics(): Map<string, PoolStatistics> {\n const stats = new Map<string, PoolStatistics>();\n this.pools.forEach((pool, assetId) => {\n stats.set(assetId, pool.getStatistics());\n });\n return stats;\n }\n\n /**\n * Remove a pool and dispose its resources\n * @param assetId - The ID of the asset whose pool should be removed\n * @returns true if the pool existed and was removed, false if no pool was found for the asset\n */\n removePool(assetId: string): boolean {\n const pool = this.pools.get(assetId);\n if (pool) {\n pool.clear();\n this.pools.delete(assetId);\n return true;\n }\n return false;\n }\n\n /**\n * Clear all pools\n */\n clearAll(): void {\n this.pools.forEach((pool) => pool.clear());\n this.pools.clear();\n }\n\n /**\n * Check if pool exists for asset\n */\n hasPool(assetId: string): boolean {\n return this.pools.has(assetId);\n }\n\n /**\n * Get total number of pools\n */\n getPoolCount(): number {\n return this.pools.size;\n }\n}\n"],"mappings":";;;;AAuBA,IAAa,aAAb,MAA2B;CACzB,YAAyB,EAAE;CAC3B,wBAAgB,IAAI,KAAQ;CAC5B,mBAA2B;CAC3B,eAAuB;CACvB,eAAuB;CAEvB,YACE,SACA,OACA,SAAgD;EAC9C,aAAa;EACb,SAAS;EACT,YAAY;EACb,EACD;AAPiB,OAAA,UAAA;AACA,OAAA,QAAA;AACA,OAAA,SAAA;AAOjB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,aAAa,IACtC,MAAK,UAAU,KAAK,KAAK,cAAc,CAAC;;;;;CAO5C,UAAoB;AAClB,OAAK;EAEL,IAAI,MAAM,KAAK,UAAU,KAAK;AAE9B,MAAI,CAAC,IAEH,KAAI,KAAK,OAAO,cAAc,KAAK,WAAW,CAC5C,OAAM,KAAK,cAAc;OACpB;AACL,WAAQ,KAAK,mCAAmC;AAChD,UAAO;;AAIX,OAAK,MAAM,IAAI,IAAI;AACnB,SAAO;;;;;CAMT,QAAQ,KAAc;AACpB,MAAI,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE;AACxB,WAAQ,KAAK,yCAAyC;AACtD;;AAGF,OAAK;AACL,OAAK,MAAM,IAAI;AACf,OAAK,MAAM,OAAO,IAAI;AACtB,OAAK,UAAU,KAAK,IAAI;;;;;;CAO1B,aAAmB;AACjB,OAAK,MAAM,SAAS,QAAQ;AAC1B,QAAK;AACL,QAAK,MAAM,IAAI;AACf,QAAK,UAAU,KAAK,IAAI;IACxB;AACF,OAAK,MAAM,OAAO;;;;;CAMpB,gBAAgC;AAC9B,SAAO;GACL,WAAW,KAAK,UAAU;GAC1B,OAAO,KAAK,MAAM;GAClB,OAAO,KAAK,UAAU,SAAS,KAAK,MAAM;GAC1C,cAAc,KAAK;GACnB,UAAU,KAAK;GACf,SAAS,KAAK;GACf;;;;;CAMH,QAAc;AACZ,OAAK,YAAY,EAAE;AACnB,OAAK,MAAM,OAAO;;CAGpB,eAA0B;AACxB,OAAK;AACL,SAAO,KAAK,SAAS;;CAGvB,YAA6B;AAE3B,SADc,KAAK,UAAU,SAAS,KAAK,MAAM,OAClC,KAAK,OAAO;;;;;;AAO/B,IAAa,mBAAb,MAA8B;CAC5B,wBAA2D,IAAI,KAAK;CACpE,gBAA8C;EAC5C,aAAa;EACb,SAAS;EACT,YAAY;EACb;;;;CAKD,WACE,SACA,UACA,QAC8B;EAC9B,MAAM,eAAe,KAAK,MAAM,IAAI,QAAQ;AAC5C,MAAI,aACF,QAAO;EAKT,MAAM,OAAO,IAAI,iBACT;GACJ,MAAM,QAAQ,IAAI,MAAM,SAAS;AACjC,SAAM,UAAU;AAChB,SAAM,MAAM;AACZ,UAAO;MAER,UAAU;AACT,SAAM,OAAO;AACb,SAAM,cAAc;KAEtB;GAbmB,GAAG,KAAK;GAAe,GAAG;GAa7C,CACD;AAED,OAAK,MAAM,IAAI,SAAS,KAAK;AAC7B,SAAO;;;;;CAMT,QAAQ,SAA2D;AACjE,SAAO,KAAK,MAAM,IAAI,QAAQ;;;;;CAMhC,QAAQ,SAA0C;EAChD,MAAM,OAAO,KAAK,MAAM,IAAI,QAAQ;AACpC,MAAI,CAAC,MAAM;AACT,WAAQ,KAAK,6BAA6B,UAAU;AACpD,UAAO;;AAET,SAAO,KAAK,SAAS;;;;;CAMvB,QAAQ,SAAiB,OAA+B;EACtD,MAAM,OAAO,KAAK,MAAM,IAAI,QAAQ;AACpC,MAAI,CAAC,MAAM;AACT,WAAQ,KAAK,6BAA6B,UAAU;AACpD;;AAEF,OAAK,QAAQ,MAAM;;;;;CAMrB,aAAmB;AACjB,OAAK,MAAM,SAAS,SAAS,KAAK,YAAY,CAAC;;;;;CAMjD,kBAAkB,SAA6C;AAE7D,SADa,KAAK,MAAM,IAAI,QACrB,EAAM,eAAe;;;;;CAM9B,mBAAgD;EAC9C,MAAM,wBAAQ,IAAI,KAA6B;AAC/C,OAAK,MAAM,SAAS,MAAM,YAAY;AACpC,SAAM,IAAI,SAAS,KAAK,eAAe,CAAC;IACxC;AACF,SAAO;;;;;;;CAQT,WAAW,SAA0B;EACnC,MAAM,OAAO,KAAK,MAAM,IAAI,QAAQ;AACpC,MAAI,MAAM;AACR,QAAK,OAAO;AACZ,QAAK,MAAM,OAAO,QAAQ;AAC1B,UAAO;;AAET,SAAO;;;;;CAMT,WAAiB;AACf,OAAK,MAAM,SAAS,SAAS,KAAK,OAAO,CAAC;AAC1C,OAAK,MAAM,OAAO;;;;;CAMpB,QAAQ,SAA0B;AAChC,SAAO,KAAK,MAAM,IAAI,QAAQ;;;;;CAMhC,eAAuB;AACrB,SAAO,KAAK,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioProvider.js","names":[],"sources":["../../src/audio/AudioProvider.tsx"],"sourcesContent":["/* eslint-disable react-refresh/only-export-components */\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { audioAssetRegistry } from \"./AudioAssetRegistry\";\nimport AudioManager from \"./AudioManager\";\nimport { AudioAsset, AudioConfig, IAudioManager } from \"./types\";\n\nexport interface AudioProviderProps {\n children: React.ReactNode;\n config?: Partial<AudioConfig>;\n manager?: IAudioManager;\n /**\n * If true, defers audio initialization until initializeAudio() is called.\n * This is useful for requiring user gesture before creating AudioContext.\n */\n deferInitialization?: boolean;\n}\n\nexport interface AudioContextValue extends IAudioManager {\n /**\n * Initialize audio manager. Must be called after user gesture if deferInitialization is true.\n */\n initializeAudio: () => Promise<void>;\n /**\n * Whether audio system has been fully initialized and is ready for use.\n * This includes both AudioContext creation (isInitialized) and asset preloading.\n * Use this property to determine if audio methods can be safely called.\n */\n isAudioReady: boolean;\n}\n\nexport const AudioContext = createContext<AudioContextValue | null>(null);\n\nexport const useAudio = (): AudioContextValue => {\n const ctx = useContext(AudioContext);\n if (!ctx) throw new Error(\"useAudio must be inside AudioProvider\");\n return ctx;\n};\n\nexport const AudioProvider: React.FC<AudioProviderProps> = ({\n children,\n config,\n manager,\n deferInitialization = false,\n}) => {\n const [audioManager] = useState<IAudioManager>(\n () => manager ?? new AudioManager(config),\n );\n const [isAudioReady, setIsAudioReady] = useState(false);\n\n // Track volume states explicitly to trigger re-renders when they change\n // Initialize with audioManager values to sync with any custom manager\n const [volumeState, setVolumeState] = useState(() => ({\n masterVolume: audioManager.masterVolume,\n sfxVolume: audioManager.sfxVolume,\n musicVolume: audioManager.musicVolume,\n muted: audioManager.muted,\n isInitialized: audioManager.isInitialized,\n }));\n\n // Update volume state whenever audioManager values change\n const syncVolumeState = useCallback(() => {\n setVolumeState({\n masterVolume: audioManager.masterVolume,\n sfxVolume: audioManager.sfxVolume,\n musicVolume: audioManager.musicVolume,\n muted: audioManager.muted,\n isInitialized: audioManager.isInitialized,\n });\n }, [audioManager]);\n\n const initializeAudio = useCallback(async () => {\n // Note: We don't check isAudioReady here to allow retry attempts\n // If initialization fails, users can retry by calling this again\n\n try {\n await audioManager.initialize(); // no args\n\n // Phase 3: Optimized preloading - only critical assets\n // 3단계: 최적화된 사전 로드 - 중요한 자산만\n // Non-critical assets will be loaded on-demand when first played\n\n // 1. Preload critical menu UI sounds (instant playback required)\n // 중요한 메뉴 UI 사운드 사전 로드 (즉시 재생 필요)\n const criticalMenuSounds = [\n audioAssetRegistry.getSFX(\"menu_hover\"),\n audioAssetRegistry.getSFX(\"menu_select\"),\n audioAssetRegistry.getSFX(\"menu_click\"),\n audioAssetRegistry.getSFX(\"menu_navigate\"),\n audioAssetRegistry.getSFX(\"menu_back\"),\n ];\n\n const menuAssets = criticalMenuSounds.filter(\n (asset) => asset !== undefined,\n ) as AudioAsset[];\n\n // 2. Preload critical combat sounds (instant playback required)\n // 중요한 전투 사운드 사전 로드 (즉시 재생 필요)\n const criticalCombatSounds = [\n audioAssetRegistry.getSFX(\"hit_light\"),\n audioAssetRegistry.getSFX(\"hit_medium\"),\n audioAssetRegistry.getSFX(\"hit_heavy\"),\n audioAssetRegistry.getSFX(\"hit_impact\"),\n audioAssetRegistry.getSFX(\"guard_block\"),\n audioAssetRegistry.getSFX(\"attack_light\"),\n audioAssetRegistry.getSFX(\"attack_whoosh\"),\n audioAssetRegistry.getSFX(\"stance_change\"),\n ];\n\n const combatAssets = criticalCombatSounds.filter(\n (asset) => asset !== undefined,\n ) as AudioAsset[];\n\n // 3. Preload essential music tracks (intro, combat, training)\n // 필수 음악 트랙 사전 로드 (인트로, 전투, 훈련)\n const introMusic = audioAssetRegistry.getMusic(\"intro_theme\");\n const combatMusic = audioAssetRegistry.getMusic(\"combat_theme\");\n const trainingMusic = audioAssetRegistry.getMusic(\"cyberpunk_fusion\");\n\n const musicAssets = [introMusic, combatMusic, trainingMusic].filter(\n (asset) => asset !== undefined,\n ) as AudioAsset[];\n\n // Combine critical assets for parallel loading\n // 병렬 로드를 위한 중요 자산 결합\n const criticalAssets = [...menuAssets, ...combatAssets, ...musicAssets];\n\n // Preload critical assets in parallel (30MB cache limit will handle this)\n // 중요 자산을 병렬로 사전 로드 (30MB 캐시 제한이 이를 처리함)\n await Promise.all(\n criticalAssets.map((a) =>\n audioManager.loadAsset(a).catch((err) => {\n console.warn(`Failed to load critical asset: ${a.id}`, err);\n }),\n ),\n );\n\n // NOTE: Non-critical assets are loaded on-demand:\n // - Archetype themes: Loaded on-demand when first requested (예: 캐릭터 선택 화면 또는 전투 화면에서 필요할 때)\n // - Other combat sounds: Loaded when first used in combat\n // - Placeholder assets: Loaded as fallbacks when needed\n // - Philosophy screen music: Loaded on-demand when philosophy screen opens\n // This reduces initial memory footprint while ensuring core gameplay music is ready\n\n setIsAudioReady(true);\n } catch (error) {\n console.error(\"Failed to initialize audio manager:\", error);\n // Continue without audio - silent mode fallback\n setIsAudioReady(true); // Mark as ready even in fallback mode\n }\n }, [audioManager]); // Removed isAudioReady to prevent unnecessary callback recreation\n\n // Auto-initialize if not deferred\n useEffect(() => {\n if (!deferInitialization) {\n initializeAudio();\n }\n }, [deferInitialization, initializeAudio]);\n\n const contextValue = React.useMemo<AudioContextValue>(() => {\n // Wrap methods that change volume state to also sync React state\n const wrappedSetVolume = (\n type: \"master\" | \"sfx\" | \"music\" | \"voice\",\n volume: number,\n ) => {\n audioManager.setVolume(type, volume);\n syncVolumeState();\n };\n\n const wrappedMute = () => {\n audioManager.mute();\n syncVolumeState();\n };\n\n const wrappedUnmute = () => {\n audioManager.unmute();\n syncVolumeState();\n };\n\n // Create a dynamic wrapper that accesses getter properties on-demand\n // This ensures components always get current values\n return {\n // Explicitly bind all IAudioManager methods\n initialize: audioManager.initialize.bind(audioManager),\n loadAsset: audioManager.loadAsset.bind(audioManager),\n playSFX: audioManager.playSFX.bind(audioManager),\n playSoundEffect: audioManager.playSoundEffect.bind(audioManager),\n playMusic: audioManager.playMusic.bind(audioManager),\n stopMusic: audioManager.stopMusic.bind(audioManager),\n setVolume: wrappedSetVolume,\n mute: wrappedMute,\n unmute: wrappedUnmute,\n fadeIn: audioManager.fadeIn.bind(audioManager),\n fadeOut: audioManager.fadeOut.bind(audioManager),\n playKoreanTechniqueSound:\n audioManager.playKoreanTechniqueSound.bind(audioManager),\n playTrigramStanceSound:\n audioManager.playTrigramStanceSound.bind(audioManager),\n playVitalPointHitSound:\n audioManager.playVitalPointHitSound.bind(audioManager),\n playDojiangAmbience: audioManager.playDojiangAmbience.bind(audioManager),\n\n // Use tracked state values for reactivity (triggers re-renders)\n get isInitialized() {\n return volumeState.isInitialized;\n },\n get masterVolume() {\n return volumeState.masterVolume;\n },\n get sfxVolume() {\n return volumeState.sfxVolume;\n },\n get musicVolume() {\n return volumeState.musicVolume;\n },\n get muted() {\n return volumeState.muted;\n },\n\n // AudioProvider-specific properties\n initializeAudio,\n isAudioReady,\n };\n }, [\n audioManager,\n initializeAudio,\n isAudioReady,\n syncVolumeState,\n volumeState,\n ]);\n\n return (\n <AudioContext.Provider value={contextValue}>\n {children}\n </AudioContext.Provider>\n );\n};\n"],"mappings":";;;;;AAoCA,IAAa,eAAe,cAAwC,KAAK;AAEzE,IAAa,iBAAoC;CAC/C,MAAM,MAAM,WAAW,aAAa;AACpC,KAAI,CAAC,IAAK,OAAM,IAAI,MAAM,wCAAwC;AAClE,QAAO;;AAGT,IAAa,iBAA+C,EAC1D,UACA,QACA,SACA,sBAAsB,YAClB;CACJ,MAAM,CAAC,gBAAgB,eACf,WAAW,IAAI,aAAa,OAAO,CAC1C;CACD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CAIvD,MAAM,CAAC,aAAa,kBAAkB,gBAAgB;EACpD,cAAc,aAAa;EAC3B,WAAW,aAAa;EACxB,aAAa,aAAa;EAC1B,OAAO,aAAa;EACpB,eAAe,aAAa;EAC7B,EAAE;CAGH,MAAM,kBAAkB,kBAAkB;AACxC,iBAAe;GACb,cAAc,aAAa;GAC3B,WAAW,aAAa;GACxB,aAAa,aAAa;GAC1B,OAAO,aAAa;GACpB,eAAe,aAAa;GAC7B,CAAC;IACD,CAAC,aAAa,CAAC;CAElB,MAAM,kBAAkB,YAAY,YAAY;AAI9C,MAAI;AACF,SAAM,aAAa,YAAY;GAgB/B,MAAM,aARqB;IACzB,mBAAmB,OAAO,aAAa;IACvC,mBAAmB,OAAO,cAAc;IACxC,mBAAmB,OAAO,aAAa;IACvC,mBAAmB,OAAO,gBAAgB;IAC1C,mBAAmB,OAAO,YAAY;IACvC,CAEqC,QACnC,UAAU,UAAU,KAAA,EACtB;GAeD,MAAM,eAXuB;IAC3B,mBAAmB,OAAO,YAAY;IACtC,mBAAmB,OAAO,aAAa;IACvC,mBAAmB,OAAO,YAAY;IACtC,mBAAmB,OAAO,aAAa;IACvC,mBAAmB,OAAO,cAAc;IACxC,mBAAmB,OAAO,eAAe;IACzC,mBAAmB,OAAO,gBAAgB;IAC1C,mBAAmB,OAAO,gBAAgB;IAC3C,CAEyC,QACvC,UAAU,UAAU,KAAA,EACtB;GAQD,MAAM,cAAc;IAJD,mBAAmB,SAAS,cAAc;IACzC,mBAAmB,SAAS,eAAe;IACzC,mBAAmB,SAAS,mBAAmB;IAET,CAAC,QAC1D,UAAU,UAAU,KAAA,EACtB;GAID,MAAM,iBAAiB;IAAC,GAAG;IAAY,GAAG;IAAc,GAAG;IAAY;AAIvE,SAAM,QAAQ,IACZ,eAAe,KAAK,MAClB,aAAa,UAAU,EAAE,CAAC,OAAO,QAAQ;AACvC,YAAQ,KAAK,kCAAkC,EAAE,MAAM,IAAI;KAC3D,CACH,CACF;AASD,mBAAgB,KAAK;WACd,OAAO;AACd,WAAQ,MAAM,uCAAuC,MAAM;AAE3D,mBAAgB,KAAK;;IAEtB,CAAC,aAAa,CAAC;AAGlB,iBAAgB;AACd,MAAI,CAAC,oBACH,kBAAiB;IAElB,CAAC,qBAAqB,gBAAgB,CAAC;CAE1C,MAAM,eAAe,MAAM,cAAiC;EAE1D,MAAM,oBACJ,MACA,WACG;AACH,gBAAa,UAAU,MAAM,OAAO;AACpC,oBAAiB;;EAGnB,MAAM,oBAAoB;AACxB,gBAAa,MAAM;AACnB,oBAAiB;;EAGnB,MAAM,sBAAsB;AAC1B,gBAAa,QAAQ;AACrB,oBAAiB;;AAKnB,SAAO;GAEL,YAAY,aAAa,WAAW,KAAK,aAAa;GACtD,WAAW,aAAa,UAAU,KAAK,aAAa;GACpD,SAAS,aAAa,QAAQ,KAAK,aAAa;GAChD,iBAAiB,aAAa,gBAAgB,KAAK,aAAa;GAChE,WAAW,aAAa,UAAU,KAAK,aAAa;GACpD,WAAW,aAAa,UAAU,KAAK,aAAa;GACpD,WAAW;GACX,MAAM;GACN,QAAQ;GACR,QAAQ,aAAa,OAAO,KAAK,aAAa;GAC9C,SAAS,aAAa,QAAQ,KAAK,aAAa;GAChD,0BACE,aAAa,yBAAyB,KAAK,aAAa;GAC1D,wBACE,aAAa,uBAAuB,KAAK,aAAa;GACxD,wBACE,aAAa,uBAAuB,KAAK,aAAa;GACxD,qBAAqB,aAAa,oBAAoB,KAAK,aAAa;GAGxE,IAAI,gBAAgB;AAClB,WAAO,YAAY;;GAErB,IAAI,eAAe;AACjB,WAAO,YAAY;;GAErB,IAAI,YAAY;AACd,WAAO,YAAY;;GAErB,IAAI,cAAc;AAChB,WAAO,YAAY;;GAErB,IAAI,QAAQ;AACV,WAAO,YAAY;;GAIrB;GACA;GACD;IACA;EACD;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,QACE,oBAAC,aAAa,UAAd;EAAuB,OAAO;EAC3B;EACqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"AudioProvider.js","names":[],"sources":["../../src/audio/AudioProvider.tsx"],"sourcesContent":["/* eslint-disable react-refresh/only-export-components */\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { audioAssetRegistry } from \"./AudioAssetRegistry\";\nimport AudioManager from \"./AudioManager\";\nimport { AudioAsset, AudioConfig, IAudioManager } from \"./types\";\n\nexport interface AudioProviderProps {\n children: React.ReactNode;\n config?: Partial<AudioConfig>;\n manager?: IAudioManager;\n /**\n * If true, defers audio initialization until initializeAudio() is called.\n * This is useful for requiring user gesture before creating AudioContext.\n */\n deferInitialization?: boolean;\n}\n\nexport interface AudioContextValue extends IAudioManager {\n /**\n * Initialize audio manager. Must be called after user gesture if deferInitialization is true.\n */\n initializeAudio: () => Promise<void>;\n /**\n * Whether audio system has been fully initialized and is ready for use.\n * This includes both AudioContext creation (isInitialized) and asset preloading.\n * Use this property to determine if audio methods can be safely called.\n */\n isAudioReady: boolean;\n}\n\nexport const AudioContext = createContext<AudioContextValue | null>(null);\n\nexport const useAudio = (): AudioContextValue => {\n const ctx = useContext(AudioContext);\n if (!ctx) throw new Error(\"useAudio must be inside AudioProvider\");\n return ctx;\n};\n\nexport const AudioProvider: React.FC<AudioProviderProps> = ({\n children,\n config,\n manager,\n deferInitialization = false,\n}) => {\n const [audioManager] = useState<IAudioManager>(\n () => manager ?? new AudioManager(config),\n );\n const [isAudioReady, setIsAudioReady] = useState(false);\n\n // Track volume states explicitly to trigger re-renders when they change\n // Initialize with audioManager values to sync with any custom manager\n const [volumeState, setVolumeState] = useState(() => ({\n masterVolume: audioManager.masterVolume,\n sfxVolume: audioManager.sfxVolume,\n musicVolume: audioManager.musicVolume,\n muted: audioManager.muted,\n isInitialized: audioManager.isInitialized,\n }));\n\n // Update volume state whenever audioManager values change\n const syncVolumeState = useCallback(() => {\n setVolumeState({\n masterVolume: audioManager.masterVolume,\n sfxVolume: audioManager.sfxVolume,\n musicVolume: audioManager.musicVolume,\n muted: audioManager.muted,\n isInitialized: audioManager.isInitialized,\n });\n }, [audioManager]);\n\n const initializeAudio = useCallback(async () => {\n // Note: We don't check isAudioReady here to allow retry attempts\n // If initialization fails, users can retry by calling this again\n\n try {\n await audioManager.initialize(); // no args\n\n // Phase 3: Optimized preloading - only critical assets\n // 3단계: 최적화된 사전 로드 - 중요한 자산만\n // Non-critical assets will be loaded on-demand when first played\n\n // 1. Preload critical menu UI sounds (instant playback required)\n // 중요한 메뉴 UI 사운드 사전 로드 (즉시 재생 필요)\n const criticalMenuSounds = [\n audioAssetRegistry.getSFX(\"menu_hover\"),\n audioAssetRegistry.getSFX(\"menu_select\"),\n audioAssetRegistry.getSFX(\"menu_click\"),\n audioAssetRegistry.getSFX(\"menu_navigate\"),\n audioAssetRegistry.getSFX(\"menu_back\"),\n ];\n\n const menuAssets = criticalMenuSounds.filter(\n (asset) => asset !== undefined,\n ) as AudioAsset[];\n\n // 2. Preload critical combat sounds (instant playback required)\n // 중요한 전투 사운드 사전 로드 (즉시 재생 필요)\n const criticalCombatSounds = [\n audioAssetRegistry.getSFX(\"hit_light\"),\n audioAssetRegistry.getSFX(\"hit_medium\"),\n audioAssetRegistry.getSFX(\"hit_heavy\"),\n audioAssetRegistry.getSFX(\"hit_impact\"),\n audioAssetRegistry.getSFX(\"guard_block\"),\n audioAssetRegistry.getSFX(\"attack_light\"),\n audioAssetRegistry.getSFX(\"attack_whoosh\"),\n audioAssetRegistry.getSFX(\"stance_change\"),\n ];\n\n const combatAssets = criticalCombatSounds.filter(\n (asset) => asset !== undefined,\n ) as AudioAsset[];\n\n // 3. Preload essential music tracks (intro, combat, training)\n // 필수 음악 트랙 사전 로드 (인트로, 전투, 훈련)\n const introMusic = audioAssetRegistry.getMusic(\"intro_theme\");\n const combatMusic = audioAssetRegistry.getMusic(\"combat_theme\");\n const trainingMusic = audioAssetRegistry.getMusic(\"cyberpunk_fusion\");\n\n const musicAssets = [introMusic, combatMusic, trainingMusic].filter(\n (asset) => asset !== undefined,\n ) as AudioAsset[];\n\n // Combine critical assets for parallel loading\n // 병렬 로드를 위한 중요 자산 결합\n const criticalAssets = [...menuAssets, ...combatAssets, ...musicAssets];\n\n // Preload critical assets in parallel (30MB cache limit will handle this)\n // 중요 자산을 병렬로 사전 로드 (30MB 캐시 제한이 이를 처리함)\n await Promise.all(\n criticalAssets.map((a) =>\n audioManager.loadAsset(a).catch((err) => {\n console.warn(`Failed to load critical asset: ${a.id}`, err);\n }),\n ),\n );\n\n // NOTE: Non-critical assets are loaded on-demand:\n // - Archetype themes: Loaded on-demand when first requested (예: 캐릭터 선택 화면 또는 전투 화면에서 필요할 때)\n // - Other combat sounds: Loaded when first used in combat\n // - Placeholder assets: Loaded as fallbacks when needed\n // - Philosophy screen music: Loaded on-demand when philosophy screen opens\n // This reduces initial memory footprint while ensuring core gameplay music is ready\n\n setIsAudioReady(true);\n } catch (error) {\n console.error(\"Failed to initialize audio manager:\", error);\n // Continue without audio - silent mode fallback\n setIsAudioReady(true); // Mark as ready even in fallback mode\n }\n }, [audioManager]); // Removed isAudioReady to prevent unnecessary callback recreation\n\n // Auto-initialize if not deferred\n useEffect(() => {\n if (!deferInitialization) {\n initializeAudio();\n }\n }, [deferInitialization, initializeAudio]);\n\n const contextValue = React.useMemo<AudioContextValue>(() => {\n // Wrap methods that change volume state to also sync React state\n const wrappedSetVolume = (\n type: \"master\" | \"sfx\" | \"music\" | \"voice\",\n volume: number,\n ) => {\n audioManager.setVolume(type, volume);\n syncVolumeState();\n };\n\n const wrappedMute = () => {\n audioManager.mute();\n syncVolumeState();\n };\n\n const wrappedUnmute = () => {\n audioManager.unmute();\n syncVolumeState();\n };\n\n // Create a dynamic wrapper that accesses getter properties on-demand\n // This ensures components always get current values\n return {\n // Explicitly bind all IAudioManager methods\n initialize: audioManager.initialize.bind(audioManager),\n loadAsset: audioManager.loadAsset.bind(audioManager),\n playSFX: audioManager.playSFX.bind(audioManager),\n playSoundEffect: audioManager.playSoundEffect.bind(audioManager),\n playMusic: audioManager.playMusic.bind(audioManager),\n stopMusic: audioManager.stopMusic.bind(audioManager),\n setVolume: wrappedSetVolume,\n mute: wrappedMute,\n unmute: wrappedUnmute,\n fadeIn: audioManager.fadeIn.bind(audioManager),\n fadeOut: audioManager.fadeOut.bind(audioManager),\n playKoreanTechniqueSound:\n audioManager.playKoreanTechniqueSound.bind(audioManager),\n playTrigramStanceSound:\n audioManager.playTrigramStanceSound.bind(audioManager),\n playVitalPointHitSound:\n audioManager.playVitalPointHitSound.bind(audioManager),\n playDojiangAmbience: audioManager.playDojiangAmbience.bind(audioManager),\n\n // Use tracked state values for reactivity (triggers re-renders)\n get isInitialized() {\n return volumeState.isInitialized;\n },\n get masterVolume() {\n return volumeState.masterVolume;\n },\n get sfxVolume() {\n return volumeState.sfxVolume;\n },\n get musicVolume() {\n return volumeState.musicVolume;\n },\n get muted() {\n return volumeState.muted;\n },\n\n // AudioProvider-specific properties\n initializeAudio,\n isAudioReady,\n };\n }, [\n audioManager,\n initializeAudio,\n isAudioReady,\n syncVolumeState,\n volumeState,\n ]);\n\n return (\n <AudioContext.Provider value={contextValue}>\n {children}\n </AudioContext.Provider>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioUtils.js","names":[],"sources":["../../src/audio/AudioUtils.ts"],"sourcesContent":["/**\n * Audio utility functions for Korean martial arts game\n */\n\nimport type { AudioFormat } from \"./types\";\n\nexport const AUDIO_FORMATS: readonly AudioFormat[] = [\n \"audio/webm\",\n \"audio/mp3\",\n \"audio/wav\",\n \"audio/ogg\",\n] as const;\n\n/**\n * Select the best available audio format based on browser support\n */\nexport function selectAudioFormat(\n availableFormats: readonly AudioFormat[],\n preferredFormats: readonly AudioFormat[] = AUDIO_FORMATS\n): AudioFormat | null {\n // Check each preferred format against available formats\n for (const preferred of preferredFormats) {\n if (availableFormats.includes(preferred) && canPlayType(preferred)) {\n return preferred;\n }\n }\n\n // If no preferred format is available, return the first available that can be played\n for (const format of availableFormats) {\n if (canPlayType(format)) {\n return format;\n }\n }\n\n return null;\n}\n\n/**\n * Get preferred audio URLs based on format selection\n * Returns an array of URLs to try in order of preference\n */\nexport function getPreferredFormat(\n availableFormats: readonly AudioFormat[],\n basePath: string\n): string[] {\n const selectedFormat = selectAudioFormat(availableFormats);\n\n if (!selectedFormat) {\n // Fallback to first format if none can be determined\n const fallbackFormat = availableFormats[0];\n if (fallbackFormat) {\n const extension = fallbackFormat.replace(\"audio/\", \"\");\n return [`${basePath}.${extension}`];\n }\n return [];\n }\n\n const extension = selectedFormat.replace(\"audio/\", \"\");\n return [`${basePath}.${extension}`];\n}\n\n/**\n * Check if the browser can play a specific audio type\n */\nexport function canPlayType(format: AudioFormat): boolean {\n if (typeof Audio === \"undefined\") {\n // In test environment, mock basic support\n return [\"audio/mp3\", \"audio/wav\"].includes(format);\n }\n\n const audio = new Audio();\n const support = audio.canPlayType(format);\n return support === \"probably\" || support === \"maybe\";\n}\n\n/**\n * Clamp volume to valid range (0-1)\n */\nexport function clampVolume(volume: number): number {\n return Math.max(0, Math.min(1, volume));\n}\n\n/**\n * Get metadata for the best available format\n */\nexport function getBestFormatMetadata(\n availableFormats: readonly AudioFormat[]\n) {\n const selectedFormat = selectAudioFormat(availableFormats);\n return {\n format: selectedFormat,\n supported: selectedFormat ? canPlayType(selectedFormat) : false,\n quality: getFormatQuality(selectedFormat),\n };\n}\n\nfunction getFormatQuality(\n format: AudioFormat | null\n): \"high\" | \"medium\" | \"low\" {\n if (!format) return \"low\";\n\n switch (format) {\n case \"audio/wav\":\n return \"high\";\n case \"audio/webm\":\n case \"audio/ogg\":\n return \"medium\";\n case \"audio/mp3\":\n default:\n return \"medium\";\n }\n}\n\nexport class AudioUtils {\n /**\n * Check if browser can play audio type\n */\n static canPlayType(mimeType: string): boolean {\n const audio = new Audio();\n const canPlay = audio.canPlayType(mimeType);\n return canPlay === \"probably\" || canPlay === \"maybe\";\n }\n\n /**\n * Select best audio format from available options\n */\n static selectAudioFormat(\n available: readonly AudioFormat[],\n preferred: readonly AudioFormat[] = [\n \"audio/mp3\",\n \"audio/wav\",\n \"audio/ogg\",\n \"audio/webm\",\n ]\n ): AudioFormat | null {\n // Convert to mutable arrays for processing\n const availableFormats: AudioFormat[] = [...available];\n const preferredFormats: AudioFormat[] = [...preferred];\n\n // Find first preferred format that is both available and supported\n for (const format of preferredFormats) {\n if (availableFormats.includes(format) && this.canPlayType(format)) {\n return format;\n }\n }\n\n // Fallback: find any available format that is supported\n for (const format of availableFormats) {\n if (this.canPlayType(format)) {\n return format;\n }\n }\n\n return null;\n }\n\n /**\n * Get best format metadata\n */\n static getBestFormatMetadata(formats: readonly AudioFormat[]): {\n format: AudioFormat | null;\n supported: boolean;\n confidence: string;\n } {\n const selectedFormat = this.selectAudioFormat(formats);\n\n if (!selectedFormat) {\n return {\n format: null,\n supported: false,\n confidence: \"\",\n };\n }\n\n const audio = new Audio();\n const confidence = audio.canPlayType(selectedFormat);\n\n return {\n format: selectedFormat,\n supported: this.canPlayType(selectedFormat),\n confidence,\n };\n }\n}\n\n/**\n * Detect supported audio formats in the current browser\n */\nexport function detectSupportedFormats(): AudioFormat[] {\n const audio = new Audio();\n const formats: AudioFormat[] = [];\n\n if (audio.canPlayType(\"audio/mp3\")) {\n formats.push(\"audio/mp3\");\n }\n\n if (audio.canPlayType(\"audio/wav\")) {\n formats.push(\"audio/wav\");\n }\n\n if (audio.canPlayType(\"audio/ogg\")) {\n formats.push(\"audio/ogg\");\n }\n\n if (audio.canPlayType(\"audio/webm\")) {\n formats.push(\"audio/webm\");\n }\n\n return formats;\n}\n\n/**\n * Create and configure an audio element\n */\nexport function createAudioElement(\n url: string,\n volume: number = 1.0\n): HTMLAudioElement {\n const audio = new Audio(url);\n audio.volume = volume;\n audio.preload = \"auto\";\n return audio;\n}\n\n/**\n * Validate audio URL format\n */\nexport function validateAudioUrl(url: string): boolean {\n return typeof url === \"string\" && url.length > 0;\n}\n\n/**\n * Normalize volume to range [0, 1]\n */\nexport function normalizeVolume(volume: number): number {\n return Math.max(0, Math.min(1, volume));\n}\n\n/**\n * Format audio duration from seconds to mm:ss\n */\nexport function formatDuration(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.floor(seconds % 60);\n return `${minutes}:${remainingSeconds.toString().padStart(2, \"0\")}`;\n}\n\n/**\n * Check if the Audio API is supported\n */\nexport function isAudioSupported(): boolean {\n return typeof Audio !== \"undefined\";\n}\n\n/**\n * Get the optimal audio format from supported formats\n */\nexport function getOptimalFormat(\n supportedFormats: AudioFormat[]\n): AudioFormat | null {\n const preferredOrder: AudioFormat[] = [\n \"audio/webm\",\n \"audio/mp3\",\n \"audio/wav\",\n \"audio/ogg\",\n ];\n\n for (const format of preferredOrder) {\n if (supportedFormats.includes(format)) {\n return format;\n }\n }\n\n return supportedFormats.length > 0 ? supportedFormats[0] : null;\n}\n"],"mappings":";AAMA,IAAa,gBAAwC;CACnD;CACA;CACA;CACA;CACD;;;;AAKD,SAAgB,kBACd,kBACA,mBAA2C,eACvB;AAEpB,MAAK,MAAM,aAAa,iBACtB,KAAI,iBAAiB,SAAS,UAAU,IAAI,YAAY,UAAU,CAChE,QAAO;AAKX,MAAK,MAAM,UAAU,iBACnB,KAAI,YAAY,OAAO,CACrB,QAAO;AAIX,QAAO;;;;;;AAOT,SAAgB,mBACd,kBACA,UACU;CACV,MAAM,iBAAiB,kBAAkB,iBAAiB;AAE1D,KAAI,CAAC,gBAAgB;EAEnB,MAAM,iBAAiB,iBAAiB;AACxC,MAAI,eAEF,QAAO,CAAC,GAAG,SAAS,GADF,eAAe,QAAQ,UAAU,
|
|
1
|
+
{"version":3,"file":"AudioUtils.js","names":[],"sources":["../../src/audio/AudioUtils.ts"],"sourcesContent":["/**\n * Audio utility functions for Korean martial arts game\n */\n\nimport type { AudioFormat } from \"./types\";\n\nexport const AUDIO_FORMATS: readonly AudioFormat[] = [\n \"audio/webm\",\n \"audio/mp3\",\n \"audio/wav\",\n \"audio/ogg\",\n] as const;\n\n/**\n * Select the best available audio format based on browser support\n */\nexport function selectAudioFormat(\n availableFormats: readonly AudioFormat[],\n preferredFormats: readonly AudioFormat[] = AUDIO_FORMATS\n): AudioFormat | null {\n // Check each preferred format against available formats\n for (const preferred of preferredFormats) {\n if (availableFormats.includes(preferred) && canPlayType(preferred)) {\n return preferred;\n }\n }\n\n // If no preferred format is available, return the first available that can be played\n for (const format of availableFormats) {\n if (canPlayType(format)) {\n return format;\n }\n }\n\n return null;\n}\n\n/**\n * Get preferred audio URLs based on format selection\n * Returns an array of URLs to try in order of preference\n */\nexport function getPreferredFormat(\n availableFormats: readonly AudioFormat[],\n basePath: string\n): string[] {\n const selectedFormat = selectAudioFormat(availableFormats);\n\n if (!selectedFormat) {\n // Fallback to first format if none can be determined\n const fallbackFormat = availableFormats[0];\n if (fallbackFormat) {\n const extension = fallbackFormat.replace(\"audio/\", \"\");\n return [`${basePath}.${extension}`];\n }\n return [];\n }\n\n const extension = selectedFormat.replace(\"audio/\", \"\");\n return [`${basePath}.${extension}`];\n}\n\n/**\n * Check if the browser can play a specific audio type\n */\nexport function canPlayType(format: AudioFormat): boolean {\n if (typeof Audio === \"undefined\") {\n // In test environment, mock basic support\n return [\"audio/mp3\", \"audio/wav\"].includes(format);\n }\n\n const audio = new Audio();\n const support = audio.canPlayType(format);\n return support === \"probably\" || support === \"maybe\";\n}\n\n/**\n * Clamp volume to valid range (0-1)\n */\nexport function clampVolume(volume: number): number {\n return Math.max(0, Math.min(1, volume));\n}\n\n/**\n * Get metadata for the best available format\n */\nexport function getBestFormatMetadata(\n availableFormats: readonly AudioFormat[]\n) {\n const selectedFormat = selectAudioFormat(availableFormats);\n return {\n format: selectedFormat,\n supported: selectedFormat ? canPlayType(selectedFormat) : false,\n quality: getFormatQuality(selectedFormat),\n };\n}\n\nfunction getFormatQuality(\n format: AudioFormat | null\n): \"high\" | \"medium\" | \"low\" {\n if (!format) return \"low\";\n\n switch (format) {\n case \"audio/wav\":\n return \"high\";\n case \"audio/webm\":\n case \"audio/ogg\":\n return \"medium\";\n case \"audio/mp3\":\n default:\n return \"medium\";\n }\n}\n\nexport class AudioUtils {\n /**\n * Check if browser can play audio type\n */\n static canPlayType(mimeType: string): boolean {\n const audio = new Audio();\n const canPlay = audio.canPlayType(mimeType);\n return canPlay === \"probably\" || canPlay === \"maybe\";\n }\n\n /**\n * Select best audio format from available options\n */\n static selectAudioFormat(\n available: readonly AudioFormat[],\n preferred: readonly AudioFormat[] = [\n \"audio/mp3\",\n \"audio/wav\",\n \"audio/ogg\",\n \"audio/webm\",\n ]\n ): AudioFormat | null {\n // Convert to mutable arrays for processing\n const availableFormats: AudioFormat[] = [...available];\n const preferredFormats: AudioFormat[] = [...preferred];\n\n // Find first preferred format that is both available and supported\n for (const format of preferredFormats) {\n if (availableFormats.includes(format) && this.canPlayType(format)) {\n return format;\n }\n }\n\n // Fallback: find any available format that is supported\n for (const format of availableFormats) {\n if (this.canPlayType(format)) {\n return format;\n }\n }\n\n return null;\n }\n\n /**\n * Get best format metadata\n */\n static getBestFormatMetadata(formats: readonly AudioFormat[]): {\n format: AudioFormat | null;\n supported: boolean;\n confidence: string;\n } {\n const selectedFormat = this.selectAudioFormat(formats);\n\n if (!selectedFormat) {\n return {\n format: null,\n supported: false,\n confidence: \"\",\n };\n }\n\n const audio = new Audio();\n const confidence = audio.canPlayType(selectedFormat);\n\n return {\n format: selectedFormat,\n supported: this.canPlayType(selectedFormat),\n confidence,\n };\n }\n}\n\n/**\n * Detect supported audio formats in the current browser\n */\nexport function detectSupportedFormats(): AudioFormat[] {\n const audio = new Audio();\n const formats: AudioFormat[] = [];\n\n if (audio.canPlayType(\"audio/mp3\")) {\n formats.push(\"audio/mp3\");\n }\n\n if (audio.canPlayType(\"audio/wav\")) {\n formats.push(\"audio/wav\");\n }\n\n if (audio.canPlayType(\"audio/ogg\")) {\n formats.push(\"audio/ogg\");\n }\n\n if (audio.canPlayType(\"audio/webm\")) {\n formats.push(\"audio/webm\");\n }\n\n return formats;\n}\n\n/**\n * Create and configure an audio element\n */\nexport function createAudioElement(\n url: string,\n volume: number = 1.0\n): HTMLAudioElement {\n const audio = new Audio(url);\n audio.volume = volume;\n audio.preload = \"auto\";\n return audio;\n}\n\n/**\n * Validate audio URL format\n */\nexport function validateAudioUrl(url: string): boolean {\n return typeof url === \"string\" && url.length > 0;\n}\n\n/**\n * Normalize volume to range [0, 1]\n */\nexport function normalizeVolume(volume: number): number {\n return Math.max(0, Math.min(1, volume));\n}\n\n/**\n * Format audio duration from seconds to mm:ss\n */\nexport function formatDuration(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.floor(seconds % 60);\n return `${minutes}:${remainingSeconds.toString().padStart(2, \"0\")}`;\n}\n\n/**\n * Check if the Audio API is supported\n */\nexport function isAudioSupported(): boolean {\n return typeof Audio !== \"undefined\";\n}\n\n/**\n * Get the optimal audio format from supported formats\n */\nexport function getOptimalFormat(\n supportedFormats: AudioFormat[]\n): AudioFormat | null {\n const preferredOrder: AudioFormat[] = [\n \"audio/webm\",\n \"audio/mp3\",\n \"audio/wav\",\n \"audio/ogg\",\n ];\n\n for (const format of preferredOrder) {\n if (supportedFormats.includes(format)) {\n return format;\n }\n }\n\n return supportedFormats.length > 0 ? supportedFormats[0] : null;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BoneImpactAudioMap.js","names":[],"sources":["../../src/audio/BoneImpactAudioMap.ts"],"sourcesContent":["/**\n * Body Region Sound Mapping for Black Trigram\n * Maps body regions and impact intensities to existing audio assets\n *\n * Uses only existing sound files - no new audio creation needed:\n * - hit_flesh_* for soft tissue impacts\n * - hit_light_* for minor bone contact\n * - hit_medium_* for solid bone impacts\n * - hit_heavy_* for devastating bone damage\n * - hit_critical_* for fracture-level and vital point strikes\n * - body_realistic_sound_* as ambient bone/flesh mixing\n */\n\nimport { AudioBodyRegion, ImpactIntensity } from \"./types\";\n\n/**\n * Body region to sound ID mapping\n * Returns base sound ID without variant number\n */\nexport const BODY_REGION_SOUND_MAP: Record<\n AudioBodyRegion,\n Record<ImpactIntensity, string>\n> = {\n head: {\n // Head strikes: skull thud, skull crack sounds\n light: \"hit_light\", // Glancing temple/jaw hits\n medium: \"hit_medium\", // Solid jaw/temple strikes\n heavy: \"hit_heavy\", // Devastating skull impacts\n critical: \"hit_critical\", // Vital point (temple, back of neck)\n fracture: \"hit_critical\", // Skull fracture sounds (severe)\n },\n torso: {\n // Torso strikes: rib impact, rib crack, internal organ thuds\n light: \"hit_light\", // Light rib contact\n medium: \"hit_medium\", // Solid rib/sternum impact\n heavy: \"hit_heavy\", // Rib-breaking force, liver strikes\n critical: \"hit_critical\", // Solar plexus, heart vital points\n fracture: \"hit_critical\", // Multiple rib fractures, internal damage\n },\n arms: {\n // Arm/limb strikes: limb bone thud, joint crack\n light: \"hit_flesh\", // Muscle strikes, glancing blows\n medium: \"hit_medium\", // Solid elbow/forearm bone contact\n heavy: \"hit_heavy\", // Joint destruction (shoulder, elbow, wrist)\n critical: \"hit_critical\", // Nerve strikes, joint breaks\n fracture: \"hit_critical\", // Complete arm bone fracture\n },\n legs: {\n // Leg strikes: knee cap, shin bone, ankle impacts\n light: \"hit_flesh\", // Thigh muscle strikes\n medium: \"hit_medium\", // Shin/knee bone impacts\n heavy: \"hit_heavy\", // Knee destruction, ankle breaks\n critical: \"hit_critical\", // Vital leg nerve strikes\n fracture: \"hit_critical\", // Femur/tibia fractures\n },\n soft_tissue: {\n // Soft tissue: muscle thud, flesh impact (no bone contact)\n light: \"hit_flesh\", // Light muscle contact\n medium: \"hit_flesh\", // Solid muscle compression\n heavy: \"body_realistic_sound\", // Deep muscle trauma\n critical: \"hit_critical\", // Soft vital points (throat, groin)\n fracture: \"hit_critical\", // Severe soft tissue damage\n },\n};\n\n/**\n * Number of audio variants per sound type\n * Used for random selection in combat\n */\nexport const SOUND_VARIANT_COUNTS: Record<string, number> = {\n hit_flesh: 4,\n hit_light: 4,\n hit_medium: 4,\n hit_heavy: 4,\n hit_critical: 4,\n body_realistic_sound: 1, // Only 1 variant available\n};\n\n/**\n * Volume multipliers based on impact intensity\n * Higher damage = louder sounds (as per requirements)\n */\nexport const IMPACT_VOLUME_MULTIPLIERS: Record<ImpactIntensity, number> = {\n light: 0.7, // -30% volume\n medium: 0.85, // -15% volume\n heavy: 1.0, // Normal volume\n critical: 1.15, // +15% volume\n fracture: 1.3, // +30% volume (bone-breaking audio)\n};\n\n/**\n * Get sound ID for a bone impact event\n * @param region - Body region struck\n * @param intensity - Impact intensity level\n * @param randomize - Whether to add random variant (default: true)\n * @returns Sound ID to play (e.g., \"hit_critical_3\")\n */\nexport function getBoneImpactSoundId(\n region: AudioBodyRegion,\n intensity: ImpactIntensity,\n randomize: boolean = true\n): string {\n const baseSoundId = BODY_REGION_SOUND_MAP[region][intensity];\n\n if (!randomize) {\n return baseSoundId;\n }\n\n // Get variant count for this sound type\n const variantCount = SOUND_VARIANT_COUNTS[baseSoundId] ?? 1;\n\n if (variantCount === 1) {\n return baseSoundId;\n }\n\n // Random variant selection (1 to variantCount)\n const variant = Math.floor(Math.random() * variantCount) + 1;\n return `${baseSoundId}_${variant}`;\n}\n\n/**\n * Calculate impact intensity from damage amount\n * @param damage - Damage dealt in attack\n * @param remainingHealth - Target's remaining health (for fracture detection)\n * @param isVitalPoint - Whether strike hit a vital point\n * @returns Impact intensity level\n */\nexport function calculateImpactIntensity(\n damage: number,\n remainingHealth?: number,\n isVitalPoint?: boolean\n): ImpactIntensity {\n // Vital point strikes are always critical (highest priority)\n if (isVitalPoint) {\n return \"critical\";\n }\n\n // Fracture detection: health below 30% + high damage\n // Note: Assumes maxHealth = 100 (standard across codebase)\n if (\n remainingHealth !== undefined &&\n remainingHealth < 30 &&\n damage >= 20\n ) {\n return \"fracture\";\n }\n\n // Intensity based on damage amount\n if (damage >= 40) return \"critical\";\n if (damage >= 25) return \"heavy\";\n if (damage >= 10) return \"medium\";\n return \"light\";\n}\n\n/**\n * Body region detection thresholds\n * Y-axis thresholds for vertical body regions\n */\nconst REGION_DETECTION_THRESHOLDS = {\n HEAD_MIN: 0.75, // Top 25% of body is head\n TORSO_MIN: 0.25, // Middle 50% is torso (or arms)\n ARMS_X_MIN: 0.3, // Side hits (X > 0.3) in torso range are arms\n} as const;\n\n/**\n * Detect body region from 3D hit coordinates\n * Uses Y-axis (vertical) and X-axis (horizontal) to determine region\n *\n * @param hitPosition - 3D position where strike landed\n * @param characterHeight - Total height of character model (default: 2.0)\n * @returns Body region struck\n */\nexport function detectAudioBodyRegion(\n hitPosition: { x: number; y: number; z?: number },\n characterHeight: number = 2.0\n): AudioBodyRegion {\n const { x, y } = hitPosition;\n const normalizedY = y / characterHeight; // Normalize to 0-1 range\n\n // Head region: top 25% of body (HEAD_MIN - 1.0)\n if (normalizedY >= REGION_DETECTION_THRESHOLDS.HEAD_MIN) {\n return \"head\";\n }\n\n // Torso region: middle 50% of body (TORSO_MIN - HEAD_MIN)\n if (normalizedY >= REGION_DETECTION_THRESHOLDS.TORSO_MIN) {\n // Check horizontal position for arms\n const absX = Math.abs(x);\n if (absX > REGION_DETECTION_THRESHOLDS.ARMS_X_MIN) {\n // Hits on the sides are likely arms\n return \"arms\";\n }\n return \"torso\";\n }\n\n // Legs region: bottom 25% of body (0 - TORSO_MIN)\n return \"legs\";\n}\n\n/**\n * Get volume multiplier for impact intensity\n * @param intensity - Impact intensity level\n * @returns Volume multiplier (0.7 to 1.3)\n */\nexport function getImpactVolumeMultiplier(intensity: ImpactIntensity): number {\n return IMPACT_VOLUME_MULTIPLIERS[intensity];\n}\n"],"mappings":";;;;;AAmBA,IAAa,wBAGT;CACF,MAAM;EAEJ,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,OAAO;EAEL,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,MAAM;EAEJ,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,MAAM;EAEJ,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,aAAa;EAEX,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACF;;;;;AAMD,IAAa,uBAA+C;CAC1D,WAAW;CACX,WAAW;CACX,YAAY;CACZ,WAAW;CACX,cAAc;CACd,sBAAsB;CACvB;;;;;AAMD,IAAa,4BAA6D;CACxE,OAAO;CACP,QAAQ;CACR,OAAO;CACP,UAAU;CACV,UAAU;CACX;;;;;;;;AASD,SAAgB,qBACd,QACA,WACA,YAAqB,MACb;CACR,MAAM,cAAc,sBAAsB,QAAQ;AAElD,KAAI,CAAC,UACH,QAAO;CAIT,MAAM,eAAe,qBAAqB,gBAAgB;AAE1D,KAAI,iBAAiB,EACnB,QAAO;AAKT,QAAO,GAAG,YAAY,GADN,KAAK,MAAM,KAAK,QAAQ,GAAG,aAAa,GAAG;;;;;;;;;AAW7D,SAAgB,yBACd,QACA,iBACA,cACiB;AAEjB,KAAI,aACF,QAAO;AAKT,KACE,oBAAoB,KAAA,KACpB,kBAAkB,MAClB,UAAU,GAEV,QAAO;AAIT,KAAI,UAAU,GAAI,QAAO;AACzB,KAAI,UAAU,GAAI,QAAO;AACzB,KAAI,UAAU,GAAI,QAAO;AACzB,QAAO;;;;;;AAOT,IAAM,8BAA8B;CAClC,UAAU;CACV,WAAW;CACX,YAAY;CACb;;;;;;;;;AAUD,SAAgB,sBACd,aACA,kBAA0B,GACT;CACjB,MAAM,EAAE,GAAG,MAAM;CACjB,MAAM,cAAc,IAAI;AAGxB,KAAI,eAAe,4BAA4B,SAC7C,QAAO;AAIT,KAAI,eAAe,4BAA4B,WAAW;AAGxD,MADa,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"BoneImpactAudioMap.js","names":[],"sources":["../../src/audio/BoneImpactAudioMap.ts"],"sourcesContent":["/**\n * Body Region Sound Mapping for Black Trigram\n * Maps body regions and impact intensities to existing audio assets\n *\n * Uses only existing sound files - no new audio creation needed:\n * - hit_flesh_* for soft tissue impacts\n * - hit_light_* for minor bone contact\n * - hit_medium_* for solid bone impacts\n * - hit_heavy_* for devastating bone damage\n * - hit_critical_* for fracture-level and vital point strikes\n * - body_realistic_sound_* as ambient bone/flesh mixing\n */\n\nimport { AudioBodyRegion, ImpactIntensity } from \"./types\";\n\n/**\n * Body region to sound ID mapping\n * Returns base sound ID without variant number\n */\nexport const BODY_REGION_SOUND_MAP: Record<\n AudioBodyRegion,\n Record<ImpactIntensity, string>\n> = {\n head: {\n // Head strikes: skull thud, skull crack sounds\n light: \"hit_light\", // Glancing temple/jaw hits\n medium: \"hit_medium\", // Solid jaw/temple strikes\n heavy: \"hit_heavy\", // Devastating skull impacts\n critical: \"hit_critical\", // Vital point (temple, back of neck)\n fracture: \"hit_critical\", // Skull fracture sounds (severe)\n },\n torso: {\n // Torso strikes: rib impact, rib crack, internal organ thuds\n light: \"hit_light\", // Light rib contact\n medium: \"hit_medium\", // Solid rib/sternum impact\n heavy: \"hit_heavy\", // Rib-breaking force, liver strikes\n critical: \"hit_critical\", // Solar plexus, heart vital points\n fracture: \"hit_critical\", // Multiple rib fractures, internal damage\n },\n arms: {\n // Arm/limb strikes: limb bone thud, joint crack\n light: \"hit_flesh\", // Muscle strikes, glancing blows\n medium: \"hit_medium\", // Solid elbow/forearm bone contact\n heavy: \"hit_heavy\", // Joint destruction (shoulder, elbow, wrist)\n critical: \"hit_critical\", // Nerve strikes, joint breaks\n fracture: \"hit_critical\", // Complete arm bone fracture\n },\n legs: {\n // Leg strikes: knee cap, shin bone, ankle impacts\n light: \"hit_flesh\", // Thigh muscle strikes\n medium: \"hit_medium\", // Shin/knee bone impacts\n heavy: \"hit_heavy\", // Knee destruction, ankle breaks\n critical: \"hit_critical\", // Vital leg nerve strikes\n fracture: \"hit_critical\", // Femur/tibia fractures\n },\n soft_tissue: {\n // Soft tissue: muscle thud, flesh impact (no bone contact)\n light: \"hit_flesh\", // Light muscle contact\n medium: \"hit_flesh\", // Solid muscle compression\n heavy: \"body_realistic_sound\", // Deep muscle trauma\n critical: \"hit_critical\", // Soft vital points (throat, groin)\n fracture: \"hit_critical\", // Severe soft tissue damage\n },\n};\n\n/**\n * Number of audio variants per sound type\n * Used for random selection in combat\n */\nexport const SOUND_VARIANT_COUNTS: Record<string, number> = {\n hit_flesh: 4,\n hit_light: 4,\n hit_medium: 4,\n hit_heavy: 4,\n hit_critical: 4,\n body_realistic_sound: 1, // Only 1 variant available\n};\n\n/**\n * Volume multipliers based on impact intensity\n * Higher damage = louder sounds (as per requirements)\n */\nexport const IMPACT_VOLUME_MULTIPLIERS: Record<ImpactIntensity, number> = {\n light: 0.7, // -30% volume\n medium: 0.85, // -15% volume\n heavy: 1.0, // Normal volume\n critical: 1.15, // +15% volume\n fracture: 1.3, // +30% volume (bone-breaking audio)\n};\n\n/**\n * Get sound ID for a bone impact event\n * @param region - Body region struck\n * @param intensity - Impact intensity level\n * @param randomize - Whether to add random variant (default: true)\n * @returns Sound ID to play (e.g., \"hit_critical_3\")\n */\nexport function getBoneImpactSoundId(\n region: AudioBodyRegion,\n intensity: ImpactIntensity,\n randomize: boolean = true\n): string {\n const baseSoundId = BODY_REGION_SOUND_MAP[region][intensity];\n\n if (!randomize) {\n return baseSoundId;\n }\n\n // Get variant count for this sound type\n const variantCount = SOUND_VARIANT_COUNTS[baseSoundId] ?? 1;\n\n if (variantCount === 1) {\n return baseSoundId;\n }\n\n // Random variant selection (1 to variantCount)\n const variant = Math.floor(Math.random() * variantCount) + 1;\n return `${baseSoundId}_${variant}`;\n}\n\n/**\n * Calculate impact intensity from damage amount\n * @param damage - Damage dealt in attack\n * @param remainingHealth - Target's remaining health (for fracture detection)\n * @param isVitalPoint - Whether strike hit a vital point\n * @returns Impact intensity level\n */\nexport function calculateImpactIntensity(\n damage: number,\n remainingHealth?: number,\n isVitalPoint?: boolean\n): ImpactIntensity {\n // Vital point strikes are always critical (highest priority)\n if (isVitalPoint) {\n return \"critical\";\n }\n\n // Fracture detection: health below 30% + high damage\n // Note: Assumes maxHealth = 100 (standard across codebase)\n if (\n remainingHealth !== undefined &&\n remainingHealth < 30 &&\n damage >= 20\n ) {\n return \"fracture\";\n }\n\n // Intensity based on damage amount\n if (damage >= 40) return \"critical\";\n if (damage >= 25) return \"heavy\";\n if (damage >= 10) return \"medium\";\n return \"light\";\n}\n\n/**\n * Body region detection thresholds\n * Y-axis thresholds for vertical body regions\n */\nconst REGION_DETECTION_THRESHOLDS = {\n HEAD_MIN: 0.75, // Top 25% of body is head\n TORSO_MIN: 0.25, // Middle 50% is torso (or arms)\n ARMS_X_MIN: 0.3, // Side hits (X > 0.3) in torso range are arms\n} as const;\n\n/**\n * Detect body region from 3D hit coordinates\n * Uses Y-axis (vertical) and X-axis (horizontal) to determine region\n *\n * @param hitPosition - 3D position where strike landed\n * @param characterHeight - Total height of character model (default: 2.0)\n * @returns Body region struck\n */\nexport function detectAudioBodyRegion(\n hitPosition: { x: number; y: number; z?: number },\n characterHeight: number = 2.0\n): AudioBodyRegion {\n const { x, y } = hitPosition;\n const normalizedY = y / characterHeight; // Normalize to 0-1 range\n\n // Head region: top 25% of body (HEAD_MIN - 1.0)\n if (normalizedY >= REGION_DETECTION_THRESHOLDS.HEAD_MIN) {\n return \"head\";\n }\n\n // Torso region: middle 50% of body (TORSO_MIN - HEAD_MIN)\n if (normalizedY >= REGION_DETECTION_THRESHOLDS.TORSO_MIN) {\n // Check horizontal position for arms\n const absX = Math.abs(x);\n if (absX > REGION_DETECTION_THRESHOLDS.ARMS_X_MIN) {\n // Hits on the sides are likely arms\n return \"arms\";\n }\n return \"torso\";\n }\n\n // Legs region: bottom 25% of body (0 - TORSO_MIN)\n return \"legs\";\n}\n\n/**\n * Get volume multiplier for impact intensity\n * @param intensity - Impact intensity level\n * @returns Volume multiplier (0.7 to 1.3)\n */\nexport function getImpactVolumeMultiplier(intensity: ImpactIntensity): number {\n return IMPACT_VOLUME_MULTIPLIERS[intensity];\n}\n"],"mappings":";;;;;AAmBA,IAAa,wBAGT;CACF,MAAM;EAEJ,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,OAAO;EAEL,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,MAAM;EAEJ,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,MAAM;EAEJ,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACD,aAAa;EAEX,OAAO;EACP,QAAQ;EACR,OAAO;EACP,UAAU;EACV,UAAU;EACX;CACF;;;;;AAMD,IAAa,uBAA+C;CAC1D,WAAW;CACX,WAAW;CACX,YAAY;CACZ,WAAW;CACX,cAAc;CACd,sBAAsB;CACvB;;;;;AAMD,IAAa,4BAA6D;CACxE,OAAO;CACP,QAAQ;CACR,OAAO;CACP,UAAU;CACV,UAAU;CACX;;;;;;;;AASD,SAAgB,qBACd,QACA,WACA,YAAqB,MACb;CACR,MAAM,cAAc,sBAAsB,QAAQ;AAElD,KAAI,CAAC,UACH,QAAO;CAIT,MAAM,eAAe,qBAAqB,gBAAgB;AAE1D,KAAI,iBAAiB,EACnB,QAAO;AAKT,QAAO,GAAG,YAAY,GADN,KAAK,MAAM,KAAK,QAAQ,GAAG,aAAa,GAAG;;;;;;;;;AAW7D,SAAgB,yBACd,QACA,iBACA,cACiB;AAEjB,KAAI,aACF,QAAO;AAKT,KACE,oBAAoB,KAAA,KACpB,kBAAkB,MAClB,UAAU,GAEV,QAAO;AAIT,KAAI,UAAU,GAAI,QAAO;AACzB,KAAI,UAAU,GAAI,QAAO;AACzB,KAAI,UAAU,GAAI,QAAO;AACzB,QAAO;;;;;;AAOT,IAAM,8BAA8B;CAClC,UAAU;CACV,WAAW;CACX,YAAY;CACb;;;;;;;;;AAUD,SAAgB,sBACd,aACA,kBAA0B,GACT;CACjB,MAAM,EAAE,GAAG,MAAM;CACjB,MAAM,cAAc,IAAI;AAGxB,KAAI,eAAe,4BAA4B,SAC7C,QAAO;AAIT,KAAI,eAAe,4BAA4B,WAAW;AAGxD,MADa,KAAK,IAAI,EAClB,GAAO,4BAA4B,WAErC,QAAO;AAET,SAAO;;AAIT,QAAO;;;;;;;AAQT,SAAgB,0BAA0B,WAAoC;AAC5E,QAAO,0BAA0B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VariantSelector.js","names":[],"sources":["../../src/audio/VariantSelector.ts"],"sourcesContent":["/**\n * Audio Variant Selector for Korean martial arts context\n * Selects appropriate audio variants based on combat context and archetype\n */\n\nimport type { PlayerArchetype, TrigramStance } from \"../types/common\";\nimport { MusicTrack, MusicTrackId, SoundEffect, SoundEffectId } from \"./types\";\n\nexport interface AudioVariantContext {\n readonly archetype?: PlayerArchetype;\n readonly stance?: TrigramStance;\n readonly intensity?: \"light\" | \"medium\" | \"heavy\" | \"critical\";\n readonly combatPhase?: \"intro\" | \"combat\" | \"victory\" | \"defeat\";\n readonly damageAmount?: number;\n readonly isVitalPoint?: boolean;\n readonly comboCount?: number;\n}\n\nexport class VariantSelector {\n /**\n * Select the best sound variant for the given context\n */\n static selectSoundVariant(\n baseSound: SoundEffect,\n archetype: PlayerArchetype,\n stance?: TrigramStance\n ): SoundEffect {\n if (!baseSound.variations || baseSound.variations.length === 0) {\n return baseSound;\n }\n\n // Select variant based on archetype\n let variantIndex: number;\n switch (archetype) {\n case \"musa\": // Traditional warrior - use original\n variantIndex = 0;\n break;\n case \"amsalja\": // Assassin - use quieter variants\n variantIndex = Math.min(1, baseSound.variations.length - 1);\n break;\n case \"hacker\": // Cyber warrior - use electronic variants\n variantIndex = Math.min(2, baseSound.variations.length - 1);\n break;\n case \"jeongbo_yowon\": // Intelligence - use subtle variants\n variantIndex = Math.min(3, baseSound.variations.length - 1);\n break;\n case \"jojik_pokryeokbae\": // Crime - use aggressive variants\n variantIndex = Math.min(4, baseSound.variations.length - 1);\n break;\n default:\n variantIndex = 0;\n }\n\n // Modify based on stance if provided\n if (stance) {\n const stanceModifier = this.getStanceModifier(stance);\n variantIndex =\n (variantIndex + stanceModifier) % baseSound.variations.length;\n }\n\n const selectedVariant = baseSound.variations[variantIndex];\n\n return {\n ...baseSound,\n url: selectedVariant,\n id: `${baseSound.id}_${archetype}_${stance ?? \"default\"}`,\n };\n }\n\n /**\n * Select the best music variant for the given context\n */\n static selectMusicVariant(\n baseMusic: MusicTrack,\n archetype: PlayerArchetype,\n intensity: number = 0.5\n ): MusicTrack {\n if (!baseMusic.variations || baseMusic.variations.length === 0) {\n return baseMusic;\n }\n\n // Select based on archetype and intensity\n const archetypeWeight = this.getArchetypeWeight(archetype);\n const intensityWeight = Math.floor(intensity * 3); // 0-2 range\n\n const variantIndex =\n (archetypeWeight + intensityWeight) % baseMusic.variations.length;\n const selectedVariant = baseMusic.variations[variantIndex];\n\n return {\n ...baseMusic,\n url: selectedVariant,\n id: `${baseMusic.id}_${archetype}_intensity${intensityWeight}`,\n volume: (baseMusic.volume ?? 0.7) * (0.8 + intensity * 0.4), // Fix: Handle undefined volume\n };\n }\n\n /**\n * Get attack sound based on damage and archetype\n */\n static getAttackSound(context: AudioVariantContext): SoundEffectId {\n const { damageAmount = 0, intensity } = context;\n\n // Determine intensity from damage if not provided\n let soundIntensity = intensity;\n if (!soundIntensity) {\n if (damageAmount > 50) soundIntensity = \"critical\";\n else if (damageAmount > 30) soundIntensity = \"heavy\";\n else if (damageAmount > 15) soundIntensity = \"medium\";\n else soundIntensity = \"light\";\n }\n\n // Map intensity to sound ID\n switch (soundIntensity) {\n case \"critical\":\n return context.isVitalPoint ? \"critical_hit\" : \"attack_critical\";\n case \"heavy\":\n return \"attack_heavy\";\n case \"medium\":\n return \"attack_medium\";\n case \"light\":\n default:\n return \"attack_light\";\n }\n }\n\n /**\n * Get hit sound based on damage and vital point status\n */\n static getHitSound(context: AudioVariantContext): SoundEffectId {\n const { damageAmount = 0, isVitalPoint = false, intensity } = context;\n\n if (isVitalPoint) {\n return \"critical_hit\";\n }\n\n // Determine intensity from damage if not provided\n let soundIntensity = intensity;\n if (!soundIntensity) {\n if (damageAmount > 50) soundIntensity = \"critical\";\n else if (damageAmount > 30) soundIntensity = \"heavy\";\n else if (damageAmount > 15) soundIntensity = \"medium\";\n else soundIntensity = \"light\";\n }\n\n switch (soundIntensity) {\n case \"critical\":\n return \"hit_critical\";\n case \"heavy\":\n return \"hit_heavy\";\n case \"medium\":\n return \"hit_medium\";\n case \"light\":\n default:\n return \"hit_light\";\n }\n }\n\n /**\n * Get appropriate music for combat phase and archetype\n */\n static getCombatMusic(context: AudioVariantContext): MusicTrackId {\n const { combatPhase = \"combat\" } = context;\n\n switch (combatPhase) {\n case \"intro\":\n return \"intro_theme\";\n case \"victory\":\n return \"victory_theme\";\n case \"defeat\":\n return \"ambient_dojang\"; // Somber ambient for defeat\n case \"combat\":\n default:\n // Could vary by archetype in full implementation\n return \"combat_theme\";\n }\n }\n\n /**\n * Get stance modifier for audio selection\n */\n private static getStanceModifier(stance: TrigramStance): number {\n const stanceMap: Record<TrigramStance, number> = {\n geon: 0, // Heaven - original\n tae: 1, // Lake - fluid\n li: 2, // Fire - intense\n jin: 3, // Thunder - explosive\n son: 4, // Wind - swift\n gam: 5, // Water - flowing\n gan: 6, // Mountain - stable\n gon: 7, // Earth - grounded\n };\n return stanceMap[stance] || 0;\n }\n\n /**\n * Get archetype weight for variant selection\n */\n private static getArchetypeWeight(archetype: PlayerArchetype): number {\n const archetypeMap: Record<PlayerArchetype, number> = {\n musa: 0, // Traditional\n amsalja: 1, // Stealth\n hacker: 2, // Tech\n jeongbo_yowon: 3, // Intelligence\n jojik_pokryeokbae: 4, // Aggressive\n };\n return archetypeMap[archetype] || 0;\n }\n\n /**\n * Select context-appropriate audio variant\n */\n public static selectByContext(\n baseAssetId: string,\n context: {\n archetype: PlayerArchetype;\n stance?: TrigramStance;\n intensity?: number;\n }\n ): { soundId?: string; musicId?: string } {\n // Return string IDs instead of typed IDs\n return {\n soundId: `${baseAssetId}_${context.archetype}`,\n musicId: `${baseAssetId}_${context.archetype}`,\n };\n }\n\n /**\n * Select the best music variant for the given context\n */\n public static selectMusicVariantForArchetype(\n baseMusic: MusicTrack,\n archetype: PlayerArchetype,\n intensity: number = 1.0\n ): string {\n // Fix: Check for variations property existence\n if (\n !(\"variations\" in baseMusic) ||\n !baseMusic.variations ||\n baseMusic.variations.length === 0\n ) {\n console.warn(`No variations available for music track: ${baseMusic.id}`);\n return baseMusic.url;\n }\n\n const archetypeWeight = this.getArchetypeWeight(archetype);\n const intensityWeight = Math.floor(intensity * 10);\n\n const variantIndex =\n (archetypeWeight + intensityWeight) % baseMusic.variations.length;\n const selectedVariant = baseMusic.variations[variantIndex];\n\n console.log(\n `Selected variant for ${archetype} at intensity ${intensity}: ${selectedVariant}`\n );\n\n return selectedVariant;\n }\n\n /**\n * Get random variant from available options\n */\n public static selectRandomVariant(\n baseAsset: MusicTrack | SoundEffect\n ): string {\n // Fix: Type guard to check for variations\n const hasVariations = (\n asset: MusicTrack | SoundEffect\n ): asset is SoundEffect => {\n return (\n \"variations\" in asset &&\n Array.isArray(asset.variations) &&\n asset.variations.length > 0\n );\n };\n\n if (!hasVariations(baseAsset)) {\n return baseAsset.url;\n }\n\n // TypeScript knows baseAsset.variations exists due to earlier check\n const variations = baseAsset.variations;\n if (!variations || variations.length === 0) {\n return baseAsset.url;\n }\n const randomIndex = Math.floor(Math.random() * variations.length);\n const selectedVariant = variations[randomIndex];\n\n console.log(`Random variant selected: ${selectedVariant}`);\n return selectedVariant;\n }\n}\n\nexport default VariantSelector;\n\n// Export convenience functions\nexport function selectCombatSound(context: AudioVariantContext): SoundEffectId {\n return VariantSelector.getAttackSound(context);\n}\n\nexport function selectHitSound(context: AudioVariantContext): SoundEffectId {\n return VariantSelector.getHitSound(context);\n}\n\nexport function selectCombatMusic(context: AudioVariantContext): MusicTrackId {\n return VariantSelector.getCombatMusic(context);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VariantSelector.js","names":[],"sources":["../../src/audio/VariantSelector.ts"],"sourcesContent":["/**\n * Audio Variant Selector for Korean martial arts context\n * Selects appropriate audio variants based on combat context and archetype\n */\n\nimport type { PlayerArchetype, TrigramStance } from \"../types/common\";\nimport { MusicTrack, MusicTrackId, SoundEffect, SoundEffectId } from \"./types\";\n\nexport interface AudioVariantContext {\n readonly archetype?: PlayerArchetype;\n readonly stance?: TrigramStance;\n readonly intensity?: \"light\" | \"medium\" | \"heavy\" | \"critical\";\n readonly combatPhase?: \"intro\" | \"combat\" | \"victory\" | \"defeat\";\n readonly damageAmount?: number;\n readonly isVitalPoint?: boolean;\n readonly comboCount?: number;\n}\n\nexport class VariantSelector {\n /**\n * Select the best sound variant for the given context\n */\n static selectSoundVariant(\n baseSound: SoundEffect,\n archetype: PlayerArchetype,\n stance?: TrigramStance\n ): SoundEffect {\n if (!baseSound.variations || baseSound.variations.length === 0) {\n return baseSound;\n }\n\n // Select variant based on archetype\n let variantIndex: number;\n switch (archetype) {\n case \"musa\": // Traditional warrior - use original\n variantIndex = 0;\n break;\n case \"amsalja\": // Assassin - use quieter variants\n variantIndex = Math.min(1, baseSound.variations.length - 1);\n break;\n case \"hacker\": // Cyber warrior - use electronic variants\n variantIndex = Math.min(2, baseSound.variations.length - 1);\n break;\n case \"jeongbo_yowon\": // Intelligence - use subtle variants\n variantIndex = Math.min(3, baseSound.variations.length - 1);\n break;\n case \"jojik_pokryeokbae\": // Crime - use aggressive variants\n variantIndex = Math.min(4, baseSound.variations.length - 1);\n break;\n default:\n variantIndex = 0;\n }\n\n // Modify based on stance if provided\n if (stance) {\n const stanceModifier = this.getStanceModifier(stance);\n variantIndex =\n (variantIndex + stanceModifier) % baseSound.variations.length;\n }\n\n const selectedVariant = baseSound.variations[variantIndex];\n\n return {\n ...baseSound,\n url: selectedVariant,\n id: `${baseSound.id}_${archetype}_${stance ?? \"default\"}`,\n };\n }\n\n /**\n * Select the best music variant for the given context\n */\n static selectMusicVariant(\n baseMusic: MusicTrack,\n archetype: PlayerArchetype,\n intensity: number = 0.5\n ): MusicTrack {\n if (!baseMusic.variations || baseMusic.variations.length === 0) {\n return baseMusic;\n }\n\n // Select based on archetype and intensity\n const archetypeWeight = this.getArchetypeWeight(archetype);\n const intensityWeight = Math.floor(intensity * 3); // 0-2 range\n\n const variantIndex =\n (archetypeWeight + intensityWeight) % baseMusic.variations.length;\n const selectedVariant = baseMusic.variations[variantIndex];\n\n return {\n ...baseMusic,\n url: selectedVariant,\n id: `${baseMusic.id}_${archetype}_intensity${intensityWeight}`,\n volume: (baseMusic.volume ?? 0.7) * (0.8 + intensity * 0.4), // Fix: Handle undefined volume\n };\n }\n\n /**\n * Get attack sound based on damage and archetype\n */\n static getAttackSound(context: AudioVariantContext): SoundEffectId {\n const { damageAmount = 0, intensity } = context;\n\n // Determine intensity from damage if not provided\n let soundIntensity = intensity;\n if (!soundIntensity) {\n if (damageAmount > 50) soundIntensity = \"critical\";\n else if (damageAmount > 30) soundIntensity = \"heavy\";\n else if (damageAmount > 15) soundIntensity = \"medium\";\n else soundIntensity = \"light\";\n }\n\n // Map intensity to sound ID\n switch (soundIntensity) {\n case \"critical\":\n return context.isVitalPoint ? \"critical_hit\" : \"attack_critical\";\n case \"heavy\":\n return \"attack_heavy\";\n case \"medium\":\n return \"attack_medium\";\n case \"light\":\n default:\n return \"attack_light\";\n }\n }\n\n /**\n * Get hit sound based on damage and vital point status\n */\n static getHitSound(context: AudioVariantContext): SoundEffectId {\n const { damageAmount = 0, isVitalPoint = false, intensity } = context;\n\n if (isVitalPoint) {\n return \"critical_hit\";\n }\n\n // Determine intensity from damage if not provided\n let soundIntensity = intensity;\n if (!soundIntensity) {\n if (damageAmount > 50) soundIntensity = \"critical\";\n else if (damageAmount > 30) soundIntensity = \"heavy\";\n else if (damageAmount > 15) soundIntensity = \"medium\";\n else soundIntensity = \"light\";\n }\n\n switch (soundIntensity) {\n case \"critical\":\n return \"hit_critical\";\n case \"heavy\":\n return \"hit_heavy\";\n case \"medium\":\n return \"hit_medium\";\n case \"light\":\n default:\n return \"hit_light\";\n }\n }\n\n /**\n * Get appropriate music for combat phase and archetype\n */\n static getCombatMusic(context: AudioVariantContext): MusicTrackId {\n const { combatPhase = \"combat\" } = context;\n\n switch (combatPhase) {\n case \"intro\":\n return \"intro_theme\";\n case \"victory\":\n return \"victory_theme\";\n case \"defeat\":\n return \"ambient_dojang\"; // Somber ambient for defeat\n case \"combat\":\n default:\n // Could vary by archetype in full implementation\n return \"combat_theme\";\n }\n }\n\n /**\n * Get stance modifier for audio selection\n */\n private static getStanceModifier(stance: TrigramStance): number {\n const stanceMap: Record<TrigramStance, number> = {\n geon: 0, // Heaven - original\n tae: 1, // Lake - fluid\n li: 2, // Fire - intense\n jin: 3, // Thunder - explosive\n son: 4, // Wind - swift\n gam: 5, // Water - flowing\n gan: 6, // Mountain - stable\n gon: 7, // Earth - grounded\n };\n return stanceMap[stance] || 0;\n }\n\n /**\n * Get archetype weight for variant selection\n */\n private static getArchetypeWeight(archetype: PlayerArchetype): number {\n const archetypeMap: Record<PlayerArchetype, number> = {\n musa: 0, // Traditional\n amsalja: 1, // Stealth\n hacker: 2, // Tech\n jeongbo_yowon: 3, // Intelligence\n jojik_pokryeokbae: 4, // Aggressive\n };\n return archetypeMap[archetype] || 0;\n }\n\n /**\n * Select context-appropriate audio variant\n */\n public static selectByContext(\n baseAssetId: string,\n context: {\n archetype: PlayerArchetype;\n stance?: TrigramStance;\n intensity?: number;\n }\n ): { soundId?: string; musicId?: string } {\n // Return string IDs instead of typed IDs\n return {\n soundId: `${baseAssetId}_${context.archetype}`,\n musicId: `${baseAssetId}_${context.archetype}`,\n };\n }\n\n /**\n * Select the best music variant for the given context\n */\n public static selectMusicVariantForArchetype(\n baseMusic: MusicTrack,\n archetype: PlayerArchetype,\n intensity: number = 1.0\n ): string {\n // Fix: Check for variations property existence\n if (\n !(\"variations\" in baseMusic) ||\n !baseMusic.variations ||\n baseMusic.variations.length === 0\n ) {\n console.warn(`No variations available for music track: ${baseMusic.id}`);\n return baseMusic.url;\n }\n\n const archetypeWeight = this.getArchetypeWeight(archetype);\n const intensityWeight = Math.floor(intensity * 10);\n\n const variantIndex =\n (archetypeWeight + intensityWeight) % baseMusic.variations.length;\n const selectedVariant = baseMusic.variations[variantIndex];\n\n console.log(\n `Selected variant for ${archetype} at intensity ${intensity}: ${selectedVariant}`\n );\n\n return selectedVariant;\n }\n\n /**\n * Get random variant from available options\n */\n public static selectRandomVariant(\n baseAsset: MusicTrack | SoundEffect\n ): string {\n // Fix: Type guard to check for variations\n const hasVariations = (\n asset: MusicTrack | SoundEffect\n ): asset is SoundEffect => {\n return (\n \"variations\" in asset &&\n Array.isArray(asset.variations) &&\n asset.variations.length > 0\n );\n };\n\n if (!hasVariations(baseAsset)) {\n return baseAsset.url;\n }\n\n // TypeScript knows baseAsset.variations exists due to earlier check\n const variations = baseAsset.variations;\n if (!variations || variations.length === 0) {\n return baseAsset.url;\n }\n const randomIndex = Math.floor(Math.random() * variations.length);\n const selectedVariant = variations[randomIndex];\n\n console.log(`Random variant selected: ${selectedVariant}`);\n return selectedVariant;\n }\n}\n\nexport default VariantSelector;\n\n// Export convenience functions\nexport function selectCombatSound(context: AudioVariantContext): SoundEffectId {\n return VariantSelector.getAttackSound(context);\n}\n\nexport function selectHitSound(context: AudioVariantContext): SoundEffectId {\n return VariantSelector.getHitSound(context);\n}\n\nexport function selectCombatMusic(context: AudioVariantContext): MusicTrackId {\n return VariantSelector.getCombatMusic(context);\n}\n"],"mappings":"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"}
|