blacktrigram 0.7.38 → 0.7.40
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/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.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/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- 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.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.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/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.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/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.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/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.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/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.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/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.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/BaseButton.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/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.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/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.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/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.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/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- 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/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.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.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/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- 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.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.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/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- 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/FacialExpressions.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.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.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.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- 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/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.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.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/KoreanCulture.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/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.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/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.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.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * 캐시에서 절대 제거되지 않아야 하는 중요한 자산\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // 레이스 컨디션 방지를 위해 현재 로드 중인 음악 자산 추적\n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB 제한 및 중요 자산으로 AudioCache 초기화\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // 캐시 먼저 확인 - 캐시된 자산의 즉시 재생\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // 캐시 관리를 위한 크기 추정\n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // 크기 추적과 함께 LRU 캐시에 추가\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * 바이트 단위의 오디오 자산 크기 추정\n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // 온디맨드 로딩: 음악이 캐시에 없으면 먼저 로드\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // 레이스 컨디션 방지를 위해 이미 로드 중인지 확인\n // JavaScript의 싱글 스레드 특성으로 원자적 check-and-add 작업 보장\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // 진행 중인 로드가 완료될 때까지 대기\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // 로드 시작 전에 로딩 중으로 표시 (싱글 스레드 JS에서 원자적 작업)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // 완료 시 항상 로딩 세트에서 제거\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * 음악 자산 로드가 완료될 때까지 대기\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU 캐시에서 제거\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU 캐시 메트릭을 포함한 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":";;;;;AAmBA,IAAM,0BAA0B;;;;;AAMhC,IAAM,wBAAwB;CAE5B;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,IAAa,eAAb,MAAmD;CACjD,gBAAgC;CAChC,eAA+B;CAC/B,aAA6B;CAC7B,SAA0B;CAC1B,qBAA4C;CAC5C,gBAAiC;CACjC,eAAgD;CAChD,6BAAoD,IAAI,KAAK;CAC7D,iBAAkC;CAIlC,+BAAoC,IAAI,KAAK;CAG7C;CACA;CACA;CACA;CACA,iBAAsC,IAAI,IAAI;EAC5C;EACA;EACA;EACD,CAAC;CAEF,YAAY,QAA+B;AACzC,MAAI,QAAQ;AACV,QAAK,gBAAgB,OAAO,gBAAgB;AAC5C,QAAK,eAAe,OAAO,eAAe;AAC1C,QAAK,aAAa,OAAO,aAAa;;AAIxC,OAAK,cAAc,IAAI,kBAAkB;AACzC,OAAK,YAAY,IAAI,kBAAkB;AACvC,OAAK,UAAU,IAAI,cAAc;EAIjC,MAAM,cAAgC;GACpC,cAAc,KAAK,OAAO;GAC1B,gBAAgB,CAAC,GAAG,sBAAsB;GAC1C,OAAO,OAAO,YAAY,eAAA,QAAA,IAAA,aAA0C;GACrE;AACD,OAAK,aAAa,IAAI,WAAW,YAAY;;;;;;;CAQ/C,mBAA2B,IAAY,OAA+B;AACpE,MAAI,CAAC,MAAM,OACT,OAAM,iBACJ,eACM;AACJ,QAAK,UAAU,QAAQ,IAAI,MAAM;KAEnC,EAAE,MAAM,MAAM,CACf;MAED,MAAK,UAAU,QAAQ,IAAI,MAAM;;CAKrC,IAAI,gBAAyB;AAC3B,SAAO,KAAK;;CAGd,IAAI,eAAwB;AAC1B,SAAO,KAAK;;CAGd,IAAI,oBAAmC;AACrC,SAAO,KAAK;;CAGd,IAAI,eAAuB;AACzB,SAAO,KAAK;;CAGd,IAAI,YAAoB;AACtB,SAAO,KAAK;;CAGd,IAAI,cAAsB;AACxB,SAAO,KAAK;;CAGd,IAAI,QAAiB;AACnB,SAAO,KAAK;;CAGd,MAAM,WAAW,QAAqC;AACpD,MAAI;AAMF,QAHE,WAAW,gBACV,WACE,qBACkB;AACvB,QAAK,iBAAiB;AACtB,QAAK,gBAAgB;AAErB,OAAI,QAAQ;AACV,SAAK,gBAAgB,OAAO,gBAAgB,KAAK;AACjD,SAAK,eAAe,OAAO,eAAe,KAAK;AAC/C,SAAK,aAAa,OAAO,aAAa,KAAK;;AAI7C,OAAI,QAAQ,uBAAuB;IACjC,MAAM,oBACJ,OAAO,wBAAwB;AACjC,SAAK,QAAQ,mBAAmB,kBAAkB;;WAE7C,OAAO;AACd,WAAQ,KACN,4DACA,MACD;AACD,QAAK,iBAAiB;AACtB,QAAK,gBAAgB;;;CAIzB,MAAM,UAAU,OAAmB,SAAsC;EACvE,MAAM,YAAY,YAAY,KAAK;AAEnC,MAAI;AAIF,OADoB,KAAK,WAAW,IAAI,MAAM,GAC1C;QAEa,KAAK,WAAW,IAAI,MAAM,GACrC,CACF;;GAMJ,MAAM,SACJ,OAAO,YAAY,eAAA,QAAA,IAAA,aAA0C;GAC/D,MAAM,cAA2B;IAC/B,SAAS,SAAS,MAAM;IACxB,YAAY,SAAS,IAAI;IACzB,YAAY,SAAS,KAAK;IAC1B,GAAG;IACJ;GAGD,MAAM,SAAS,MAAM,KAAK,YAAY,UAAU,OAAO,YAAY;AAEnE,OAAI,OAAO,WAAW,OAAO,OAAO;AAClC,WAAO,MAAM,SAAS,MAAM,UAAU;AACtC,SAAK,WAAW,IAAI,MAAM,IAAI,OAAO,MAAM;IAI3C,MAAM,gBAAgB,KAAK,kBAAkB,MAAM;AAInD,SAAK,WAAW,IAAI,MAAM,IAAI,OAAO,cAAc;IAGnD,MAAM,WAAW,YAAY,KAAK,GAAG;AACrC,SAAK,QAAQ,WAAW,MAAM,IAAI,UAAU,iBAAiB,OAAO,MAAM;AAG1E,QAAI,KAAK,eAAe,IAAI,MAAM,GAAG,CACnC,MAAK,UAAU,WAAW,MAAM,IAAI,MAAM,KAAK;KAC7C,aAAa;KACb,SAAS;KACT,YAAY;KACb,CAAC;UAEC;IAEL,MAAM,QAAQ,OAAO,yBAAS,IAAI,MAAM,qBAAqB;AAC7D,SAAK,QAAQ,kBAAkB,MAAM,IAAI,MAAM;AAG/C,QAAI,OAAO,OAAO;AAChB,UAAK,WAAW,IAAI,MAAM,IAAI,OAAO,MAAM;KAG3C,MAAM,gBAAgB,KAAK,kBAAkB,MAAM;AACnD,UAAK,WAAW,IAAI,MAAM,IAAI,OAAO,cAAc;;;WAGhD,OAAO;GACd,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,WAAQ,KAAK,8BAA8B,MAAM,GAAG,IAAI,IAAI;AAC5D,QAAK,QAAQ,kBAAkB,MAAM,IAAI,IAAI;;;;;;;;;CAUjD,kBAA0B,QAA4B;AAGpD,SAAO,0BAA0B,OAAO;;CAG1C,MAAM,gBAAgB,IAAkC;AACtD,MAAI,KAAK,OAAQ;EAEjB,MAAM,gBAAgB,YAAY,KAAK;AAEvC,MAAI;AAGF,QAAK,WAAW,IAAI,GAAG;AAGvB,OAAI,KAAK,eAAe,IAAI,GAAG,IAAI,KAAK,UAAU,QAAQ,GAAG,EAAE;IAC7D,MAAM,QAAQ,KAAK,UAAU,QAAQ,GAAG;AACxC,QAAI,OAAO;AACT,WAAM,SAAS,KAAK,aAAa,KAAK;AACtC,WAAM,MAAM,MAAM;AAGlB,UAAK,mBAAmB,IAAI,MAAM;KAElC,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,UAAK,QAAQ,sBAAsB,QAAQ;AAC3C;;;GAKJ,MAAM,QAAQ,KAAK,WAAW,IAAI,GAAG;AACrC,OAAI,OAAO;AACT,UAAM,cAAc;AACpB,UAAM,SAAS,KAAK,aAAa,KAAK;AACtC,UAAM,MAAM,MAAM;IAElB,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,SAAK,QAAQ,sBAAsB,QAAQ;;WAEtC,OAAO;AACd,WAAQ,KAAK,+BAA+B,GAAG,IAAI,MAAM;;;CAK7D,MAAM,QAAQ,IAAmB,QAAgC;AAC/D,MAAI,KAAK,OAAQ;EAEjB,MAAM,gBAAgB,YAAY,KAAK;AAEvC,MAAI;AAGF,QAAK,WAAW,IAAI,GAAG;AAGvB,OAAI,KAAK,eAAe,IAAI,GAAG,IAAI,KAAK,UAAU,QAAQ,GAAG,EAAE;IAC7D,MAAM,QAAQ,KAAK,UAAU,QAAQ,GAAG;AACxC,QAAI,OAAO;AACT,WAAM,UAAU,UAAU,KAAK,cAAc,KAAK;AAClD,WAAM,MAAM,MAAM;AAGlB,UAAK,mBAAmB,IAAI,MAAM;KAElC,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,UAAK,QAAQ,sBAAsB,QAAQ;AAC3C;;;GAKJ,MAAM,QAAQ,KAAK,WAAW,IAAI,GAAG;AACrC,OAAI,OAAO;AACT,UAAM,cAAc;AACpB,UAAM,UAAU,UAAU,KAAK,cAAc,KAAK;AAClD,UAAM,MAAM,MAAM;IAElB,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,SAAK,QAAQ,sBAAsB,QAAQ;;WAEtC,OAAO;AACd,WAAQ,KAAK,+BAA+B,GAAG,IAAI,MAAM;;;CAI7D,MAAM,UAAU,IAAkB,QAAgC;AAChE,MAAI,KAAK,OAAQ;AAEjB,OAAK,WAAW;AAIhB,OAAK,WAAW,IAAI,GAAG;EAEvB,IAAI,QAAQ,KAAK,WAAW,IAAI,GAAG;AAInC,MAAI,CAAC,MAKH,KAAI,KAAK,aAAa,IAAI,GAAG,EAAE;AAM7B,SAAM,KAAK,iBAAiB,GAAG;AAC/B,WAAQ,KAAK,WAAW,IAAI,GAAG;SAC1B;AAGL,QAAK,aAAa,IAAI,GAAG;AAMzB,OAAI;IACF,MAAM,EAAE,uBAAuB,MAAM,OAAO;IAC5C,MAAM,aAAa,mBAAmB,SAAS,GAAG;AAElD,QAAI,WACF,KAAI;AACF,WAAM,KAAK,UAAU,WAAW;AAChC,aAAQ,KAAK,WAAW,IAAI,GAAG;aAKxB,OAAO;AACd,aAAQ,KAAK,8BAA8B,GAAG,cAAc,MAAM;AAClE;;SAEG;AACL,aAAQ,KAAK,sCAAsC,KAAK;AACxD;;aAEM;AAGR,SAAK,aAAa,OAAO,GAAG;;;AAKlC,MAAI,MACF,KAAI;AACF,SAAM,cAAc;AACpB,SAAM,UAAU,UAAU,KAAK,gBAAgB,KAAK;AACpD,SAAM,OAAO;AACb,QAAK,eAAe;AACpB,QAAK,qBAAqB;AAC1B,SAAM,MAAM,MAAM;WAKX,OAAO;AACd,WAAQ,KAAK,wBAAwB,GAAG,IAAI,MAAM;;;;;;;;;CAWxD,MAAc,iBAAiB,IAAY,YAAoB,KAAqB;EAClF,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,gBAAgB;AAEtB,SAAO,KAAK,aAAa,IAAI,GAAG,EAAE;AAChC,OAAI,KAAK,KAAK,GAAG,YAAY,WAAW;AACtC,YAAQ,KAAK,6CAA6C,GAAG,WAAW;AACxE;;AAEF,SAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,cAAc,CAAC;;;CAIpE,YAAkB;AAChB,MAAI,KAAK,cAAc;AACrB,QAAK,aAAa,OAAO;AACzB,QAAK,aAAa,cAAc;AAChC,QAAK,eAAe;AACpB,QAAK,qBAAqB;;;CAI9B,UAAgB;AACd,OAAK,WAAW;AAChB,OAAK,WAAW,SAAS,UAAU;AACjC,OAAI,CAAC,MAAM,QAAQ;AACjB,UAAM,OAAO;AACb,UAAM,cAAc;;IAEtB;;CAGJ,UAAU,MAA4C,QAAsB;EAC1E,MAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC;AAEtD,UAAQ,MAAR;GACE,KAAK;AACH,SAAK,gBAAgB;AACrB;GACF,KAAK;AACH,SAAK,aAAa;AAClB;GACF,KAAK;AACH,SAAK,eAAe;AACpB,QAAI,KAAK,aACP,MAAK,aAAa,SAAS,KAAK,eAAe,KAAK;AAEtD;GACF,KAAK,QAEH;;;CAIN,OAAa;AACX,OAAK,SAAS;AACd,MAAI,KAAK,aACP,MAAK,aAAa,SAAS;;CAI/B,SAAe;AACb,OAAK,SAAS;AACd,MAAI,KAAK,aACP,MAAK,aAAa,SAAS,KAAK,eAAe,KAAK;;CAIxD,MAAM,QAAQ,WAAmB,KAAqB;AACpD,MAAI,CAAC,KAAK,aAAc;EAExB,MAAM,eAAe,KAAK;AAC1B,SAAO,IAAI,SAAS,YAAY;GAE9B,MAAM,WADc,aAAa,UACD,WAAW;GAE3C,MAAM,eAAe,kBAAkB;AACrC,QAAI,KAAK,gBAAgB,KAAK,aAAa,SAAS,EAClD,MAAK,aAAa,SAAS,KAAK,IAC9B,GACA,KAAK,aAAa,SAAS,SAC5B;SACI;AACL,mBAAc,aAAa;AAC3B,UAAK,WAAW;AAChB,cAAS;;MAEV,GAAG;IACN;;CAGJ,MAAM,OAAO,SAAuB,WAAmB,KAAqB;AAC1E,QAAM,KAAK,UAAU,SAAS,EAAE;AAEhC,MAAI,CAAC,KAAK,aAAc;AAExB,SAAO,IAAI,SAAS,YAAY;GAC9B,MAAM,eAAe,KAAK,eAAe,KAAK;GAC9C,MAAM,WAAW,gBAAgB,WAAW;GAE5C,MAAM,eAAe,kBAAkB;AACrC,QAAI,KAAK,gBAAgB,KAAK,aAAa,SAAS,aAClD,MAAK,aAAa,SAAS,KAAK,IAC9B,cACA,KAAK,aAAa,SAAS,SAC5B;SACI;AACL,mBAAc,aAAa;AAC3B,cAAS;;MAEV,GAAG;IACN;;CAGJ,MAAM,UACJ,aACA,WACA,WAAmB,KACJ;AAGf,QADuB,KAAK,QAAQ,SAC9B;AACN,QAAM,KAAK,OAAO,WAAW,SAAS;AACtC,UAAQ,IAAI,mBAAmB,YAAY,MAAM,YAAY;;CAG/D,kBAAyD;AACvD,SAAO,IAAI,IAAI,KAAK,WAAW;;CAIjC,MAAM,UAAU,IAA2B;AACzC,SAAO,KAAK,gBAAgB,GAAG;;CAGjC,MAAM,yBACJ,aACA,WACe;EACf,MAAM,UAAU,GAAG,UAAU,GAAG;AAChC,SAAO,KAAK,gBAAgB,QAAQ;;CAGtC,MAAM,uBAAuB,QAA+B;EAC1D,MAAM,UAAU,UAAU;AAC1B,SAAO,KAAK,gBAAgB,QAAQ;;CAGtC,MAAM,uBAAuB,UAAiC;EAC5D,MAAM,UAAU,eAAe;AAC/B,SAAO,KAAK,gBAAgB,QAAQ;;CAGtC,MAAM,sBAAqC;AACzC,SAAO,KAAK,UAAU,kBAAkB;;CAI1C,kBAA0B;AACxB,SAAO,KAAK;;CAGd,iBAAyB;AACvB,SAAO,KAAK;;CAGd,eAAuB;AACrB,SAAO,KAAK;;CAGd,IAAI,cAAuB;AACzB,SAAO,KAAK;;;;;;;;;CAYd,MAAM,gBACJ,QACA,SACA,YACe;EACf,MAAM,UAAU,MAAM,KAAK,YAAY,UACrC,QACA,UACC,aAAa;AACZ,gBAAa,SAAS,QAAQ,SAAS,MAAM;IAEhD;AAGD,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;GACvC,MAAM,SAAS,QAAQ;GACvB,MAAM,QAAQ,OAAO;AAErB,OAAI,OAAO,WAAW,OAAO,OAAO;AAClC,WAAO,MAAM,SAAS,MAAM,UAAU;AACtC,SAAK,WAAW,IAAI,MAAM,IAAI,OAAO,MAAM;AAG3C,QAAI,KAAK,eAAe,IAAI,MAAM,GAAG,CACnC,MAAK,UAAU,WAAW,MAAM,IAAI,MAAM,KAAK;KAC7C,aAAa;KACb,SAAS;KACT,YAAY;KACb,CAAC;;;;;;;;;CAWV,YAAY,SAA0B;EAEpC,MAAM,QAAQ,KAAK,WAAW,IAAI,QAAQ;AAC1C,MAAI,OAAO;AACT,SAAM,OAAO;AACb,SAAM,MAAM;AACZ,QAAK,WAAW,OAAO,QAAQ;;EAKjC,MAAM,mBAAmB,KAAK,WAAW,OAAO,QAAQ;EAGxD,MAAM,WAAW,KAAK,YAAY,YAAY,QAAQ;AAGtD,MAAI,KAAK,UAAU,QAAQ,QAAQ,CACjC,MAAK,UAAU,WAAW,QAAQ;AAIpC,OAAK,QAAQ,gBAAgB,QAAQ;AAErC,SAAO,YAAY,UAAU,KAAA,KAAa;;;;;;;;CAS5C,gBAaE;AACA,SAAO;GACL,UAAU,KAAK,WAAW,UAAU;GACpC,YAAY,KAAK,WAAW;GAC5B,WAAW,KAAK,UAAU,kBAAkB;GAC7C;;;;;;CAOH,iBAAmC;AACjC,SAAO,KAAK,QAAQ,gBAAgB;;;;;;CAOtC,sBAA6C;AAC3C,SAAO,KAAK,QAAQ,qBAAqB;;;;;;CAO3C,eAA+B;AAC7B,SAAO,KAAK,QAAQ,cAAc;;;;;;CAOpC,UAAU,KAAmB;AAC3B,OAAK,QAAQ,UAAU,IAAI;;;;;;CAO7B,sBAKE;AACA,SAAO,KAAK,QAAQ,WAAW;;;;;;CAOjC,oBAA8C;AAC5C,SAAO,KAAK,QAAQ,aAAa;;;;;CAMnC,sBAA4B;AAC1B,OAAK,QAAQ,eAAe;;;;;;;CAQ9B,kBACE,SAC0D;AAC1D,MAAI,QACF,QAAO,KAAK,UAAU,kBAAkB,QAAQ;AAElD,SAAO,KAAK,UAAU,kBAAkB;;;;;;CAO1C,sBAIE;AACA,SAAO,KAAK,YAAY,eAAe"}
|
|
1
|
+
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * 캐시에서 절대 제거되지 않아야 하는 중요한 자산\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // 레이스 컨디션 방지를 위해 현재 로드 중인 음악 자산 추적\n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB 제한 및 중요 자산으로 AudioCache 초기화\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // 캐시 먼저 확인 - 캐시된 자산의 즉시 재생\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // 캐시 관리를 위한 크기 추정\n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // 크기 추적과 함께 LRU 캐시에 추가\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * 바이트 단위의 오디오 자산 크기 추정\n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // 온디맨드 로딩: 음악이 캐시에 없으면 먼저 로드\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // 레이스 컨디션 방지를 위해 이미 로드 중인지 확인\n // JavaScript의 싱글 스레드 특성으로 원자적 check-and-add 작업 보장\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // 진행 중인 로드가 완료될 때까지 대기\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // 로드 시작 전에 로딩 중으로 표시 (싱글 스레드 JS에서 원자적 작업)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // 완료 시 항상 로딩 세트에서 제거\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * 음악 자산 로드가 완료될 때까지 대기\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU 캐시에서 제거\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU 캐시 메트릭을 포함한 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":"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"}
|
|
@@ -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;
|
|
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;EAPiB,KAAA,UAAA;EACA,KAAA,QAAA;EACA,KAAA,SAAA;EAOjB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,aAAa,KACtC,KAAK,UAAU,KAAK,KAAK,cAAc,CAAC;;;;;CAO5C,UAAoB;EAClB,KAAK;EAEL,IAAI,MAAM,KAAK,UAAU,KAAK;EAE9B,IAAI,CAAC,KAEH,IAAI,KAAK,OAAO,cAAc,KAAK,WAAW,EAC5C,MAAM,KAAK,cAAc;OACpB;GACL,QAAQ,KAAK,mCAAmC;GAChD,OAAO;;EAIX,KAAK,MAAM,IAAI,IAAI;EACnB,OAAO;;;;;CAMT,QAAQ,KAAc;EACpB,IAAI,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE;GACxB,QAAQ,KAAK,yCAAyC;GACtD;;EAGF,KAAK;EACL,KAAK,MAAM,IAAI;EACf,KAAK,MAAM,OAAO,IAAI;EACtB,KAAK,UAAU,KAAK,IAAI;;;;;;CAO1B,aAAmB;EACjB,KAAK,MAAM,SAAS,QAAQ;GAC1B,KAAK;GACL,KAAK,MAAM,IAAI;GACf,KAAK,UAAU,KAAK,IAAI;IACxB;EACF,KAAK,MAAM,OAAO;;;;;CAMpB,gBAAgC;EAC9B,OAAO;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;EACZ,KAAK,YAAY,EAAE;EACnB,KAAK,MAAM,OAAO;;CAGpB,eAA0B;EACxB,KAAK;EACL,OAAO,KAAK,SAAS;;CAGvB,YAA6B;EAE3B,OADc,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;EAC5C,IAAI,cACF,OAAO;EAKT,MAAM,OAAO,IAAI,iBACT;GACJ,MAAM,QAAQ,IAAI,MAAM,SAAS;GACjC,MAAM,UAAU;GAChB,MAAM,MAAM;GACZ,OAAO;MAER,UAAU;GACT,MAAM,OAAO;GACb,MAAM,cAAc;KAEtB;GAbmB,GAAG,KAAK;GAAe,GAAG;GAa7C,CACD;EAED,KAAK,MAAM,IAAI,SAAS,KAAK;EAC7B,OAAO;;;;;CAMT,QAAQ,SAA2D;EACjE,OAAO,KAAK,MAAM,IAAI,QAAQ;;;;;CAMhC,QAAQ,SAA0C;EAChD,MAAM,OAAO,KAAK,MAAM,IAAI,QAAQ;EACpC,IAAI,CAAC,MAAM;GACT,QAAQ,KAAK,6BAA6B,UAAU;GACpD,OAAO;;EAET,OAAO,KAAK,SAAS;;;;;CAMvB,QAAQ,SAAiB,OAA+B;EACtD,MAAM,OAAO,KAAK,MAAM,IAAI,QAAQ;EACpC,IAAI,CAAC,MAAM;GACT,QAAQ,KAAK,6BAA6B,UAAU;GACpD;;EAEF,KAAK,QAAQ,MAAM;;;;;CAMrB,aAAmB;EACjB,KAAK,MAAM,SAAS,SAAS,KAAK,YAAY,CAAC;;;;;CAMjD,kBAAkB,SAA6C;EAE7D,OADa,KAAK,MAAM,IAAI,QACrB,EAAM,eAAe;;;;;CAM9B,mBAAgD;EAC9C,MAAM,wBAAQ,IAAI,KAA6B;EAC/C,KAAK,MAAM,SAAS,MAAM,YAAY;GACpC,MAAM,IAAI,SAAS,KAAK,eAAe,CAAC;IACxC;EACF,OAAO;;;;;;;CAQT,WAAW,SAA0B;EACnC,MAAM,OAAO,KAAK,MAAM,IAAI,QAAQ;EACpC,IAAI,MAAM;GACR,KAAK,OAAO;GACZ,KAAK,MAAM,OAAO,QAAQ;GAC1B,OAAO;;EAET,OAAO;;;;;CAMT,WAAiB;EACf,KAAK,MAAM,SAAS,SAAS,KAAK,OAAO,CAAC;EAC1C,KAAK,MAAM,OAAO;;;;;CAMpB,QAAQ,SAA0B;EAChC,OAAO,KAAK,MAAM,IAAI,QAAQ;;;;;CAMhC,eAAuB;EACrB,OAAO,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":"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"}
|
|
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;
|
|
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;
|
|
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;CAElD,IAAI,CAAC,WACH,OAAO;CAIT,MAAM,eAAe,qBAAqB,gBAAgB;CAE1D,IAAI,iBAAiB,GACnB,OAAO;CAKT,OAAO,GAAG,YAAY,GADN,KAAK,MAAM,KAAK,QAAQ,GAAG,aAAa,GAAG;;;;;;;;;AAW7D,SAAgB,yBACd,QACA,iBACA,cACiB;CAEjB,IAAI,cACF,OAAO;CAKT,IACE,oBAAoB,KAAA,KACpB,kBAAkB,MAClB,UAAU,IAEV,OAAO;CAIT,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,UAAU,IAAI,OAAO;CACzB,OAAO;;;;;;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;CAGxB,IAAI,eAAe,4BAA4B,UAC7C,OAAO;CAIT,IAAI,eAAe,4BAA4B,WAAW;EAGxD,IADa,KAAK,IAAI,EAClB,GAAO,4BAA4B,YAErC,OAAO;EAET,OAAO;;CAIT,OAAO;;;;;;;AAQT,SAAgB,0BAA0B,WAAoC;CAC5E,OAAO,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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"}
|
package/lib/audio/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/audio/types.ts"],"sourcesContent":["// filepath: /workspaces/blacktrigram/src/audio/types.ts\n/**\n * Type definitions for audio types\n * Auto-generated by type migration script\n */\n\nimport { KoreanText, PlayerArchetype, TrigramStance } from \"@/types\";\n\n// Export AudioCategory enum (only one declaration)\nexport enum AudioCategory {\n SFX = \"sfx\",\n MUSIC = \"music\",\n VOICE = \"voice\",\n UI = \"ui\",\n // Remove AMBIENT since it's causing errors\n}\n\n// Audio context for spatial audio\nexport interface AudioContext3D {\n readonly position: { x: number; y: number; z?: number };\n readonly velocity?: { x: number; y: number; z?: number };\n readonly orientation?: { x: number; y: number; z?: number };\n readonly maxDistance?: number;\n readonly rolloffFactor?: number;\n}\n\n// Combat audio mapping\n// Add missing type exports\nexport type SoundEffectId = string;\nexport type MusicTrackId = string;\nexport type VoiceLineId = string;\n\n// Audio format type (string literals only, not enum)\nexport type AudioFormat =\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/webm\";\n\n// Audio effect definitions\nexport interface AudioEffect {\n readonly type: \"reverb\" | \"delay\" | \"distortion\" | \"filter\" | \"compressor\";\n readonly parameters: Record<string, number>;\n readonly enabled: boolean;\n}\n\n// Audio mixer channel\nexport interface AudioChannel {\n readonly id: string;\n readonly category: AudioCategory;\n readonly volume: number;\n readonly muted: boolean;\n readonly effects: readonly AudioEffect[];\n readonly connectedSources: readonly string[];\n}\n\n// Audio platform capabilities\nexport interface AudioCapabilities {\n readonly supportsWebAudio: boolean;\n readonly supportsHowler: boolean;\n readonly maxSources: number;\n readonly formats: readonly string[];\n readonly spatialAudio: boolean;\n readonly realTimeEffects: boolean;\n}\n\nexport interface IAudioManager {\n readonly isInitialized: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n\n initialize(config?: AudioConfig): Promise<void>;\n loadAsset(asset: AudioAsset): Promise<void>; // ✅ Added missing method\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void;\n playMusic(trackId: string): Promise<void>;\n playSoundEffect(soundId: string): Promise<void>;\n playSFX(soundId: string, volume?: number): Promise<void>; // ✅ Added missing playSFX method\n stopMusic(): void;\n mute(): void;\n unmute(): void;\n fadeIn(trackId: string, duration?: number): Promise<void>; // ✅ Added for combat audio\n fadeOut(duration?: number): Promise<void>; // ✅ Added for combat audio\n playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void>;\n playTrigramStanceSound(stance: string): Promise<void>;\n playVitalPointHitSound(severity: string): Promise<void>;\n playDojiangAmbience(): Promise<void>;\n}\n\nexport interface AudioAsset {\n readonly id: string;\n readonly name?: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n readonly volume?: number;\n readonly category?: string;\n}\n\nexport interface MusicTrack extends AudioAsset {\n readonly type: \"music\";\n readonly title?: KoreanText;\n readonly artist?: string;\n readonly album?: string;\n readonly bpm?: number;\n readonly loop?: boolean;\n readonly fadeInTime?: number;\n readonly fadeOutTime?: number;\n readonly variations?: readonly string[];\n readonly category: \"music\" | \"voice\";\n}\n\nexport interface SoundEffect extends AudioAsset {\n readonly type: \"sound\";\n readonly pitch?: number;\n readonly variations?: readonly string[];\n readonly category: \"sfx\" | \"ui\";\n}\n\nexport interface VoiceLine extends AudioAsset {\n readonly type: \"voice\";\n readonly text: KoreanText;\n readonly archetype?: PlayerArchetype;\n readonly emotion?:\n | \"neutral\"\n | \"aggressive\"\n | \"defensive\"\n | \"victorious\"\n | \"defeated\";\n category?: AudioCategory;\n volume?: number;\n}\n\nexport interface AudioConfig {\n readonly enableSpatialAudio: boolean;\n readonly maxSimultaneousSounds: number;\n readonly audioFormats: readonly string[];\n readonly fadeTransitionTime: number;\n readonly defaultVolume?: number;\n masterVolume?: number;\n musicVolume?: number;\n sfxVolume?: number;\n}\n\nexport interface AudioEvent {\n readonly type: \"play\" | \"stop\" | \"pause\" | \"resume\" | \"volume\" | \"fade\";\n readonly assetId: string;\n readonly volume?: number;\n readonly delay?: number;\n readonly fadeTime?: number;\n readonly loop?: boolean;\n readonly priority?: number;\n}\n\nexport interface CombatAudioMap {\n readonly attacks: Record<string, SoundEffectId>;\n readonly impacts: Record<string, SoundEffectId>;\n readonly stances: Record<TrigramStance, string>;\n readonly environments: Record<string, SoundEffectId>;\n readonly ui: Record<string, SoundEffectId>;\n}\n\nexport interface AudioState {\n readonly isPlaying: boolean;\n readonly isPaused: boolean;\n readonly currentTime: number;\n readonly duration: number;\n readonly volume: number;\n readonly loop: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n readonly currentMusicTrack: string | null;\n readonly isInitialized: boolean;\n readonly fallbackMode: boolean;\n}\n\nexport interface AudioPlaybackOptions {\n readonly volume?: number;\n readonly loop?: boolean;\n readonly fadeIn?: number;\n readonly fadeOut?: number;\n readonly delay?: number;\n readonly startTime?: number;\n readonly endTime?: number;\n readonly rate?: number;\n}\n\nexport interface ProceduralSoundConfig {\n readonly frequency: number;\n readonly duration: number;\n readonly type: \"sine\" | \"square\" | \"sawtooth\" | \"triangle\" | \"noise\";\n readonly attack?: number;\n readonly decay?: number;\n readonly sustain?: number;\n readonly release?: number;\n readonly volume?: number;\n}\n\nexport interface CombatAudioEvent {\n readonly type: \"attack\" | \"hit\" | \"block\" | \"dodge\" | \"stance_change\";\n readonly technique?: string;\n readonly stance?: string;\n readonly damage?: number;\n readonly critical?: boolean;\n}\n\n/**\n * Audio-specific body regions for impact sound mapping\n * Maps to Korean martial arts vital point locations\n */\nexport type AudioBodyRegion =\n | \"head\" // 두부 (Head/Skull): temple, jaw, neck\n | \"torso\" // 몸통 (Torso): ribs, sternum, solar plexus, organs\n | \"arms\" // 팔 (Arms): shoulder, elbow, forearm, wrist\n | \"legs\" // 다리 (Legs): hip, knee, shin, ankle\n | \"soft_tissue\"; // 연조직 (Soft tissue): muscle, flesh, non-bone areas\n\n/**\n * Impact intensity levels for bone/flesh contact\n * Determines audio selection and volume variation\n */\nexport type ImpactIntensity =\n | \"light\" // 경타 (Light): Glancing blows, minimal damage\n | \"medium\" // 중타 (Medium): Solid contact, moderate damage\n | \"heavy\" // 강타 (Heavy): Devastating strikes, severe damage\n | \"critical\" // 급소타 (Critical): Vital point precision strikes\n | \"fracture\"; // 골절 (Fracture): Bone-breaking force, <30% health\n\n/**\n * Bone impact audio event with body region and intensity\n * Used for anatomically accurate combat sound feedback\n */\nexport interface BoneImpactEvent {\n readonly region: AudioBodyRegion;\n readonly intensity: ImpactIntensity;\n readonly vitalPoint?: boolean; // True if hitting a vital point\n readonly remainingHealth?: number; // For fracture detection (<30%)\n}\n\nexport interface AudioLoadingState {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n readonly errors: readonly string[];\n}\nexport interface AudioSystemInterface {\n playSFX: (id: SoundEffectId, options?: AudioPlaybackOptions) => void;\n playMusic: (id: MusicTrackId, options?: AudioPlaybackOptions) => void;\n stopMusic: (id?: MusicTrackId, fadeOutDuration?: number) => void;\n setVolume: (type: \"master\" | \"sfx\" | \"music\", volume: number) => void;\n loadAudioAsset: (asset: AudioAsset) => Promise<void>;\n isMusicPlaying: (id?: MusicTrackId) => boolean;\n}\n\nexport interface AudioManagerInterface extends IAudioManager {}\n"],"mappings":";AASA,IAAY,gBAAL,yBAAA,eAAA;
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/audio/types.ts"],"sourcesContent":["// filepath: /workspaces/blacktrigram/src/audio/types.ts\n/**\n * Type definitions for audio types\n * Auto-generated by type migration script\n */\n\nimport { KoreanText, PlayerArchetype, TrigramStance } from \"@/types\";\n\n// Export AudioCategory enum (only one declaration)\nexport enum AudioCategory {\n SFX = \"sfx\",\n MUSIC = \"music\",\n VOICE = \"voice\",\n UI = \"ui\",\n // Remove AMBIENT since it's causing errors\n}\n\n// Audio context for spatial audio\nexport interface AudioContext3D {\n readonly position: { x: number; y: number; z?: number };\n readonly velocity?: { x: number; y: number; z?: number };\n readonly orientation?: { x: number; y: number; z?: number };\n readonly maxDistance?: number;\n readonly rolloffFactor?: number;\n}\n\n// Combat audio mapping\n// Add missing type exports\nexport type SoundEffectId = string;\nexport type MusicTrackId = string;\nexport type VoiceLineId = string;\n\n// Audio format type (string literals only, not enum)\nexport type AudioFormat =\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/webm\";\n\n// Audio effect definitions\nexport interface AudioEffect {\n readonly type: \"reverb\" | \"delay\" | \"distortion\" | \"filter\" | \"compressor\";\n readonly parameters: Record<string, number>;\n readonly enabled: boolean;\n}\n\n// Audio mixer channel\nexport interface AudioChannel {\n readonly id: string;\n readonly category: AudioCategory;\n readonly volume: number;\n readonly muted: boolean;\n readonly effects: readonly AudioEffect[];\n readonly connectedSources: readonly string[];\n}\n\n// Audio platform capabilities\nexport interface AudioCapabilities {\n readonly supportsWebAudio: boolean;\n readonly supportsHowler: boolean;\n readonly maxSources: number;\n readonly formats: readonly string[];\n readonly spatialAudio: boolean;\n readonly realTimeEffects: boolean;\n}\n\nexport interface IAudioManager {\n readonly isInitialized: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n\n initialize(config?: AudioConfig): Promise<void>;\n loadAsset(asset: AudioAsset): Promise<void>; // ✅ Added missing method\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void;\n playMusic(trackId: string): Promise<void>;\n playSoundEffect(soundId: string): Promise<void>;\n playSFX(soundId: string, volume?: number): Promise<void>; // ✅ Added missing playSFX method\n stopMusic(): void;\n mute(): void;\n unmute(): void;\n fadeIn(trackId: string, duration?: number): Promise<void>; // ✅ Added for combat audio\n fadeOut(duration?: number): Promise<void>; // ✅ Added for combat audio\n playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void>;\n playTrigramStanceSound(stance: string): Promise<void>;\n playVitalPointHitSound(severity: string): Promise<void>;\n playDojiangAmbience(): Promise<void>;\n}\n\nexport interface AudioAsset {\n readonly id: string;\n readonly name?: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n readonly volume?: number;\n readonly category?: string;\n}\n\nexport interface MusicTrack extends AudioAsset {\n readonly type: \"music\";\n readonly title?: KoreanText;\n readonly artist?: string;\n readonly album?: string;\n readonly bpm?: number;\n readonly loop?: boolean;\n readonly fadeInTime?: number;\n readonly fadeOutTime?: number;\n readonly variations?: readonly string[];\n readonly category: \"music\" | \"voice\";\n}\n\nexport interface SoundEffect extends AudioAsset {\n readonly type: \"sound\";\n readonly pitch?: number;\n readonly variations?: readonly string[];\n readonly category: \"sfx\" | \"ui\";\n}\n\nexport interface VoiceLine extends AudioAsset {\n readonly type: \"voice\";\n readonly text: KoreanText;\n readonly archetype?: PlayerArchetype;\n readonly emotion?:\n | \"neutral\"\n | \"aggressive\"\n | \"defensive\"\n | \"victorious\"\n | \"defeated\";\n category?: AudioCategory;\n volume?: number;\n}\n\nexport interface AudioConfig {\n readonly enableSpatialAudio: boolean;\n readonly maxSimultaneousSounds: number;\n readonly audioFormats: readonly string[];\n readonly fadeTransitionTime: number;\n readonly defaultVolume?: number;\n masterVolume?: number;\n musicVolume?: number;\n sfxVolume?: number;\n}\n\nexport interface AudioEvent {\n readonly type: \"play\" | \"stop\" | \"pause\" | \"resume\" | \"volume\" | \"fade\";\n readonly assetId: string;\n readonly volume?: number;\n readonly delay?: number;\n readonly fadeTime?: number;\n readonly loop?: boolean;\n readonly priority?: number;\n}\n\nexport interface CombatAudioMap {\n readonly attacks: Record<string, SoundEffectId>;\n readonly impacts: Record<string, SoundEffectId>;\n readonly stances: Record<TrigramStance, string>;\n readonly environments: Record<string, SoundEffectId>;\n readonly ui: Record<string, SoundEffectId>;\n}\n\nexport interface AudioState {\n readonly isPlaying: boolean;\n readonly isPaused: boolean;\n readonly currentTime: number;\n readonly duration: number;\n readonly volume: number;\n readonly loop: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n readonly currentMusicTrack: string | null;\n readonly isInitialized: boolean;\n readonly fallbackMode: boolean;\n}\n\nexport interface AudioPlaybackOptions {\n readonly volume?: number;\n readonly loop?: boolean;\n readonly fadeIn?: number;\n readonly fadeOut?: number;\n readonly delay?: number;\n readonly startTime?: number;\n readonly endTime?: number;\n readonly rate?: number;\n}\n\nexport interface ProceduralSoundConfig {\n readonly frequency: number;\n readonly duration: number;\n readonly type: \"sine\" | \"square\" | \"sawtooth\" | \"triangle\" | \"noise\";\n readonly attack?: number;\n readonly decay?: number;\n readonly sustain?: number;\n readonly release?: number;\n readonly volume?: number;\n}\n\nexport interface CombatAudioEvent {\n readonly type: \"attack\" | \"hit\" | \"block\" | \"dodge\" | \"stance_change\";\n readonly technique?: string;\n readonly stance?: string;\n readonly damage?: number;\n readonly critical?: boolean;\n}\n\n/**\n * Audio-specific body regions for impact sound mapping\n * Maps to Korean martial arts vital point locations\n */\nexport type AudioBodyRegion =\n | \"head\" // 두부 (Head/Skull): temple, jaw, neck\n | \"torso\" // 몸통 (Torso): ribs, sternum, solar plexus, organs\n | \"arms\" // 팔 (Arms): shoulder, elbow, forearm, wrist\n | \"legs\" // 다리 (Legs): hip, knee, shin, ankle\n | \"soft_tissue\"; // 연조직 (Soft tissue): muscle, flesh, non-bone areas\n\n/**\n * Impact intensity levels for bone/flesh contact\n * Determines audio selection and volume variation\n */\nexport type ImpactIntensity =\n | \"light\" // 경타 (Light): Glancing blows, minimal damage\n | \"medium\" // 중타 (Medium): Solid contact, moderate damage\n | \"heavy\" // 강타 (Heavy): Devastating strikes, severe damage\n | \"critical\" // 급소타 (Critical): Vital point precision strikes\n | \"fracture\"; // 골절 (Fracture): Bone-breaking force, <30% health\n\n/**\n * Bone impact audio event with body region and intensity\n * Used for anatomically accurate combat sound feedback\n */\nexport interface BoneImpactEvent {\n readonly region: AudioBodyRegion;\n readonly intensity: ImpactIntensity;\n readonly vitalPoint?: boolean; // True if hitting a vital point\n readonly remainingHealth?: number; // For fracture detection (<30%)\n}\n\nexport interface AudioLoadingState {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n readonly errors: readonly string[];\n}\nexport interface AudioSystemInterface {\n playSFX: (id: SoundEffectId, options?: AudioPlaybackOptions) => void;\n playMusic: (id: MusicTrackId, options?: AudioPlaybackOptions) => void;\n stopMusic: (id?: MusicTrackId, fadeOutDuration?: number) => void;\n setVolume: (type: \"master\" | \"sfx\" | \"music\", volume: number) => void;\n loadAudioAsset: (asset: AudioAsset) => Promise<void>;\n isMusicPlaying: (id?: MusicTrackId) => boolean;\n}\n\nexport interface AudioManagerInterface extends IAudioManager {}\n"],"mappings":";AASA,IAAY,gBAAL,yBAAA,eAAA;CACL,cAAA,SAAM;CACN,cAAA,WAAQ;CACR,cAAA,WAAQ;CACR,cAAA,QAAK;;KAEN"}
|