@oh-story/dsh 0.1.12 → 0.1.13
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/README.md +17 -11
- package/lib/client.js +85 -35
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +41 -36
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
- package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
- package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
- package/lib/index.js +16 -7
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/README.md +98 -44
- package/lib/novel-to-game/README_ZH.md +100 -46
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
- package/lib/novel-to-game/manifest.json +303 -78
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
- package/lib/types/client/drama-production-view.d.ts +11 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +1 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +7 -2
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +108 -1
- package/lib/video-recap/README.en.md +18 -14
- package/lib/video-recap/README.md +18 -14
- package/lib/video-recap/manifest.json +297 -321
- package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
- package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
- package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
- package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
- package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
- package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
- package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
- package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
- package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
- package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
- package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
- package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
- package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
- package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
- package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
- package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
- package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
- package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
- package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
- package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
- package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
- package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
- package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
- package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
- package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
- package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
- package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
- package/lib/video-recap/skills/video-script/SKILL.md +9 -7
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
- package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
- package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
- package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
- package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
- package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
- package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
- package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
- package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
- package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
- package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
- package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
- package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
- package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
- package/package.json +45 -45
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
- /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
|
@@ -1,76 +1,31 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const PENTA = [0, 2, 4, 7, 9]; // 宫 商 角 徵 羽
|
|
8
|
-
const BASE = 196; // G3,偏冷
|
|
9
|
-
|
|
10
|
-
function freq(degree, octave = 0) {
|
|
1
|
+
// 程序化丝竹:Web Audio 合成,零素材。五声音阶;笛箫(正弦+揉音+气声)与琵琶古筝(拨弦)。
|
|
2
|
+
// AudioContext 不可用时整体静默,不影响游戏。
|
|
3
|
+
const PENTA = [0, 2, 4, 7, 9];
|
|
4
|
+
const BASE = 220;
|
|
5
|
+
const freq = (degree, octave = 0) => {
|
|
11
6
|
const idx = ((degree % 5) + 5) % 5;
|
|
12
|
-
|
|
13
|
-
return BASE * Math.pow(2, octave + extra + PENTA[idx] / 12);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// 散板/慢板乐句:[音级(null=休止), 八度, 拍数]。无强拍循环,留白多。
|
|
17
|
-
const SCENES = {
|
|
18
|
-
title: {
|
|
19
|
-
bpm: 50, vol: 0.10, voices: ['di'],
|
|
20
|
-
melody: [[0, 0, 3], [2, 0, 2], [null, 0, 1], [4, 0, 3], [2, 0, 1.5], [null, 0, 2], [1, 0, 2], [0, 0, 4], [null, 0, 3]],
|
|
21
|
-
bass: [],
|
|
22
|
-
},
|
|
23
|
-
act1: {
|
|
24
|
-
bpm: 60, vol: 0.07, voices: ['pluck'],
|
|
25
|
-
melody: [[0, 0, 1], [null, 0, 1], [2, 0, 0.5], [null, 0, 1.5], [4, 0, 1], [null, 0, 2], [2, 0, 0.5], [1, 0, 0.5], [null, 0, 2], [0, 0, 1], [null, 0, 3]],
|
|
26
|
-
bass: [0],
|
|
27
|
-
},
|
|
28
|
-
act2: {
|
|
29
|
-
bpm: 66, vol: 0.08, voices: ['pluck', 'di'],
|
|
30
|
-
melody: [[0, 0, 1], [2, 0, 0.5], [4, 0, 1], [7, 0, 1.5], [4, 0, 0.5], [2, 0, 1], [null, 0, 0.5], [4, 0, 1], [7, 0, 1], [9, 0, 2], [7, 0, 1], [4, 0, 1], [null, 0, 1], [2, 0, 2]],
|
|
31
|
-
melody2: [[0, -1, 2], [null, 0, 1], [4, -1, 2], [null, 0, 1], [0, -1, 3], [null, 0, 2]],
|
|
32
|
-
bass: [0, 3],
|
|
33
|
-
},
|
|
34
|
-
act3: {
|
|
35
|
-
bpm: 46, vol: 0.07, voices: ['di'],
|
|
36
|
-
melody: [[4, -1, 2], [null, 0, 2], [2, -1, 1.5], [null, 0, 3], [0, -1, 3], [null, 0, 4]],
|
|
37
|
-
bass: [],
|
|
38
|
-
},
|
|
39
|
-
act4: {
|
|
40
|
-
bpm: 58, vol: 0.085, voices: ['pluck', 'di'],
|
|
41
|
-
melody: [[0, -1, 1], [4, -1, 1], [7, 0, 1.5], [null, 0, .5], [9, 0, 2], [7, 0, 1], [4, 0, 1], [2, 0, 2], [null, 0, 1], [0, 0, 3]],
|
|
42
|
-
melody2: [[0, -1, 2], [2, -1, 2], [4, -1, 2], [7, -1, 3], [null, 0, 2]],
|
|
43
|
-
bass: [0, 4],
|
|
44
|
-
},
|
|
45
|
-
ending_liyanei: { // 向上收束的完整乐句
|
|
46
|
-
bpm: 54, vol: 0.10, voices: ['di'],
|
|
47
|
-
melody: [[0, 0, 1.5], [2, 0, 1], [4, 0, 1.5], [7, 0, 2], [9, 0, 3], [null, 0, 2], [7, 0, 1.5], [9, 0, 4], [null, 0, 4]],
|
|
48
|
-
bass: [0],
|
|
49
|
-
},
|
|
50
|
-
ending_liuluo: { // 乐句不完整,中途停住
|
|
51
|
-
bpm: 50, vol: 0.09, voices: ['di'],
|
|
52
|
-
melody: [[0, 0, 2], [2, 0, 1.5], [1, 0, 1], [null, 0, 12]],
|
|
53
|
-
bass: [],
|
|
54
|
-
},
|
|
55
|
-
ending_other: {
|
|
56
|
-
bpm: 50, vol: 0.09, voices: ['di'],
|
|
57
|
-
melody: [[4, 0, 2], [2, 0, 1.5], [0, 0, 3], [null, 0, 4], [2, -1, 3], [null, 0, 5]],
|
|
58
|
-
bass: [],
|
|
59
|
-
},
|
|
7
|
+
return BASE * 2 ** (octave + Math.floor(degree / 5) + PENTA[idx] / 12);
|
|
60
8
|
};
|
|
61
9
|
|
|
62
|
-
|
|
10
|
+
// 乐句:[音级|null, 八度, 拍];lead=主声部,pad=低声部,bass=每小节根音
|
|
11
|
+
const TRACKS = {
|
|
12
|
+
title: { bpm: 56, lead: 'di', melody: [[2, 0, 2], [4, 0, 1], [5, 0, 1.5], [4, 0, 0.5], [2, 0, 2], [null, 0, 1], [1, 0, 1], [2, 0, 1], [4, 0, 3], [null, 0, 1], [5, 0, 1], [7, 0, 1], [5, 0, 1], [4, 0, 1], [2, 0, 4], [null, 0, 2]], pad: [[0, -1, 8], [3, -1, 8], [4, -1, 8], [0, -1, 8]] },
|
|
13
|
+
daily: { bpm: 84, lead: 'pluck', melody: [[0, 0, 1], [2, 0, 0.5], [4, 0, 0.5], [5, 0, 1], [4, 0, 1], [2, 0, 1], [4, 0, 0.5], [2, 0, 0.5], [1, 0, 1], [0, 0, 1], [null, 0, 1], [2, 0, 1], [4, 0, 1], [5, 0, 0.5], [7, 0, 0.5], [5, 0, 1], [4, 0, 2], [null, 0, 1]], pad: [[0, -1, 4], [4, -1, 4], [3, -1, 4], [0, -1, 4]] },
|
|
14
|
+
garden: { bpm: 92, lead: 'pluck', melody: [[4, 0, 0.5], [5, 0, 0.5], [7, 0, 1], [5, 0, 0.5], [4, 0, 0.5], [2, 0, 1], [4, 0, 1], [7, 0, 0.5], [9, 0, 0.5], [7, 0, 1], [5, 0, 1], [4, 0, 2], [2, 0, 0.5], [4, 0, 0.5], [5, 0, 1], [4, 0, 1], [2, 0, 1], [0, 0, 2]], pad: [[0, -1, 4], [2, -1, 4], [3, -1, 4], [0, -1, 4]], echo: 'di' },
|
|
15
|
+
tender: { bpm: 60, lead: 'di', melody: [[4, 0, 2], [2, 0, 1], [4, 0, 1], [5, 0, 2], [4, 0, 2], [2, 0, 1.5], [1, 0, 0.5], [0, 0, 3], [null, 0, 1], [2, 0, 1], [4, 0, 1], [7, 0, 2], [5, 0, 1], [4, 0, 1], [2, 0, 4]], pad: [[0, -1, 8], [3, -1, 8], [1, -1, 8], [0, -1, 8]], echo: 'pluck' },
|
|
16
|
+
tension: { bpm: 72, lead: 'pluck', melody: [[0, -1, 0.5], [0, -1, 0.5], [3, -1, 1], [null, 0, 0.5], [2, -1, 0.5], [1, -1, 1], [0, -1, 1], [null, 0, 1], [0, -1, 0.5], [0, -1, 0.5], [4, -1, 1], [3, -1, 1], [1, -1, 2], [null, 0, 1]], pad: [[0, -2, 4], [1, -2, 4]] },
|
|
17
|
+
night: { bpm: 50, lead: 'di', melody: [[2, 0, 3], [null, 0, 1], [1, 0, 2], [0, 0, 2], [null, 0, 2], [4, -1, 3], [null, 0, 1], [2, 0, 2], [1, 0, 4], [null, 0, 2]], pad: [[0, -1, 8], [4, -2, 8]], watch: true },
|
|
18
|
+
sad: { bpm: 48, lead: 'di', melody: [[4, 0, 2], [2, 0, 2], [1, 0, 2], [0, 0, 3], [null, 0, 1], [1, 0, 1], [0, 0, 1], [4, -1, 4], [null, 0, 2]], pad: [[0, -1, 8], [3, -2, 8]] },
|
|
19
|
+
fate: { bpm: 44, lead: 'di', melody: [[0, 0, 4], [null, 0, 2], [4, -1, 3], [null, 0, 1], [2, -1, 4], [null, 0, 4]], pad: [[0, -2, 16]] },
|
|
20
|
+
};
|
|
63
21
|
|
|
64
22
|
class AudioEngine {
|
|
65
23
|
constructor() {
|
|
66
24
|
this.ctx = null;
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
71
|
-
this.muted = localStorage.getItem(MUTE_KEY) === '1';
|
|
72
|
-
this._noiseBuf = null;
|
|
73
|
-
this._clearing = false;
|
|
25
|
+
this.track = null;
|
|
26
|
+
this.timer = null;
|
|
27
|
+
this.bgmVol = 0.6;
|
|
28
|
+
this.seVol = 0.7;
|
|
74
29
|
}
|
|
75
30
|
|
|
76
31
|
unlock() {
|
|
@@ -79,253 +34,154 @@ class AudioEngine {
|
|
|
79
34
|
const AC = window.AudioContext || window.webkitAudioContext;
|
|
80
35
|
if (!AC) return;
|
|
81
36
|
this.ctx = new AC();
|
|
82
|
-
this.master = this.ctx.createGain();
|
|
83
|
-
this.master.gain.value = this.muted ? 0 : 0.5;
|
|
84
|
-
this.master.connect(this.ctx.destination);
|
|
85
37
|
this.bgmGain = this.ctx.createGain();
|
|
86
|
-
this.
|
|
87
|
-
this.bgmGain.connect(this.
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
38
|
+
this.seGain = this.ctx.createGain();
|
|
39
|
+
this.bgmGain.connect(this.ctx.destination);
|
|
40
|
+
this.seGain.connect(this.ctx.destination);
|
|
41
|
+
this.setVolumes(this.bgmVol * 100, this.seVol * 100);
|
|
42
|
+
const pending = this.wanted;
|
|
43
|
+
this.wanted = null;
|
|
44
|
+
if (pending) this.play(pending);
|
|
91
45
|
}
|
|
92
46
|
if (this.ctx.state === 'suspended') this.ctx.resume();
|
|
93
47
|
} catch { /* 静默降级 */ }
|
|
94
48
|
}
|
|
95
49
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.
|
|
101
|
-
|
|
102
|
-
if (this.master) this.master.gain.setTargetAtTime(m ? 0 : 0.5, this.ctx.currentTime, 0.05);
|
|
50
|
+
setVolumes(bgm, se) {
|
|
51
|
+
this.bgmVol = bgm / 100;
|
|
52
|
+
this.seVol = se / 100;
|
|
53
|
+
if (!this.ctx) return;
|
|
54
|
+
this.bgmGain.gain.setTargetAtTime(this.bgmVol * 0.32, this.ctx.currentTime, 0.1);
|
|
55
|
+
this.seGain.gain.setTargetAtTime(this.seVol * 0.5, this.ctx.currentTime, 0.05);
|
|
103
56
|
}
|
|
104
|
-
toggleMuted() { this.setMuted(!this.muted); return this.muted; }
|
|
105
57
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const cfg = SCENES[scene];
|
|
58
|
+
play(id) {
|
|
59
|
+
if (!this.ctx) { this.wanted = id; return; }
|
|
60
|
+
if (this.track?.id === id) return;
|
|
61
|
+
this.stop();
|
|
62
|
+
const cfg = TRACKS[id];
|
|
112
63
|
if (!cfg) return;
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.
|
|
118
|
-
this.
|
|
64
|
+
const beat = 60 / cfg.bpm;
|
|
65
|
+
const length = cfg.melody.reduce((n, note) => n + note[2], 0) * beat;
|
|
66
|
+
// 每首曲子单独一路增益,换曲时淡出旧的那一路,不让两段旋律叠在一起
|
|
67
|
+
const out = this.ctx.createGain();
|
|
68
|
+
out.connect(this.bgmGain);
|
|
69
|
+
this.track = { id, out, length, events: this.events(cfg, beat, length), idx: 0, loop: this.ctx.currentTime + 0.15 };
|
|
70
|
+
this.timer = setInterval(() => this.schedule(), 300);
|
|
71
|
+
this.schedule();
|
|
119
72
|
}
|
|
120
73
|
|
|
121
|
-
|
|
122
|
-
|
|
74
|
+
stop() {
|
|
75
|
+
clearInterval(this.timer);
|
|
76
|
+
const t = this.track;
|
|
77
|
+
this.track = null;
|
|
78
|
+
if (!t || !this.ctx) return;
|
|
79
|
+
t.out.gain.setTargetAtTime(0, this.ctx.currentTime, 0.15);
|
|
80
|
+
setTimeout(() => t.out.disconnect(), 600);
|
|
123
81
|
}
|
|
124
82
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
83
|
+
// 一轮乐句展开成按时间排好的音符表;调度时一次只订一个音,前瞻 1.5 秒
|
|
84
|
+
events(cfg, beat, length) {
|
|
85
|
+
const list = [];
|
|
86
|
+
let t = 0;
|
|
87
|
+
for (const [deg, oct, beats] of cfg.melody) {
|
|
88
|
+
if (deg !== null) {
|
|
89
|
+
list.push({ at: t, kind: cfg.lead, f: freq(deg, oct), dur: beats * beat, vol: 0.11 });
|
|
90
|
+
if (cfg.echo) list.push({ at: t + beat * 0.5, kind: cfg.echo, f: freq(deg, oct + 1), dur: Math.min(beats * beat, 0.8), vol: 0.03 });
|
|
91
|
+
}
|
|
92
|
+
t += beats * beat;
|
|
93
|
+
}
|
|
94
|
+
let p = 0;
|
|
95
|
+
for (const [deg, oct, beats] of cfg.pad ?? []) {
|
|
96
|
+
if (p >= length) break;
|
|
97
|
+
list.push({ at: p, kind: 'pad', f: freq(deg, oct), dur: Math.min(beats * beat, 2.4), vol: 0.06 });
|
|
98
|
+
p += beats * beat;
|
|
131
99
|
}
|
|
100
|
+
if (cfg.watch) list.push({ at: length * 0.5, kind: 'block', f: 380, dur: 0.08, vol: 0.05 }, { at: length * 0.5 + 0.6, kind: 'block', f: 340, dur: 0.08, vol: 0.04 });
|
|
101
|
+
return list.sort((a, b) => a.at - b.at);
|
|
132
102
|
}
|
|
133
103
|
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
104
|
+
schedule() {
|
|
105
|
+
const t = this.track;
|
|
106
|
+
if (!t || !this.ctx || !t.events.length) return;
|
|
107
|
+
const now = this.ctx.currentTime;
|
|
108
|
+
// 后台标签页回来时,不补订已经过去的音,免得一齐炸响
|
|
109
|
+
if (t.loop + t.events[t.idx].at < now) t.loop = now + 0.05 - t.events[t.idx].at;
|
|
110
|
+
while (t.loop + t.events[t.idx].at < now + 1.5) {
|
|
111
|
+
const e = t.events[t.idx];
|
|
112
|
+
const at = t.loop + e.at;
|
|
113
|
+
if (e.kind === 'pad') this.pluck(e.f, at, e.dur, e.vol, t.out);
|
|
114
|
+
else if (e.kind === 'block') this.block(at, e.f, e.dur, e.vol, t.out);
|
|
115
|
+
else this.voice(e.kind, e.f, at, e.dur, e.vol, t.out);
|
|
116
|
+
t.idx += 1;
|
|
117
|
+
if (t.idx >= t.events.length) { t.idx = 0; t.loop += t.length; }
|
|
140
118
|
}
|
|
141
|
-
if (cfg.melody2) {
|
|
142
|
-
let t2 = t0;
|
|
143
|
-
for (const [deg, oct, beats] of cfg.melody2) {
|
|
144
|
-
if (deg !== null) this._voice('di', freq(deg, oct), t2, beats * beatLen, cfg.vol * 0.7);
|
|
145
|
-
t2 += beats * beatLen;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const perBeat = st.loopLen / Math.max(1, cfg.bass.length);
|
|
149
|
-
cfg.bass.forEach((deg, i) => {
|
|
150
|
-
this._voice('pluckLow', freq(deg, -1), t0 + i * perBeat, perBeat, cfg.vol * 0.6);
|
|
151
|
-
});
|
|
152
119
|
}
|
|
153
120
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
else if (kind === 'pluck') this._pluck(f, t, Math.min(dur, 0.9), vol);
|
|
158
|
-
else this._pluck(f, t, Math.min(dur, 1.2), vol * 0.8);
|
|
121
|
+
voice(kind, f, t, dur, vol, dest) {
|
|
122
|
+
if (kind === 'di') this.di(f, t, dur, vol, dest);
|
|
123
|
+
else this.pluck(f, t, Math.min(dur, 1.1), vol * 0.9, dest);
|
|
159
124
|
}
|
|
160
125
|
|
|
161
|
-
|
|
162
|
-
if (!this.ctx) return;
|
|
126
|
+
di(f, t, dur, vol, dest = this.bgmGain) {
|
|
163
127
|
try {
|
|
164
128
|
const o = this.ctx.createOscillator();
|
|
165
129
|
const g = this.ctx.createGain();
|
|
166
|
-
o.type = 'sine';
|
|
167
|
-
o.frequency.value = f;
|
|
168
|
-
// 揉音
|
|
169
130
|
const lfo = this.ctx.createOscillator();
|
|
170
131
|
const lg = this.ctx.createGain();
|
|
171
|
-
|
|
172
|
-
|
|
132
|
+
o.type = 'sine';
|
|
133
|
+
o.frequency.value = f;
|
|
134
|
+
lfo.frequency.value = 5;
|
|
135
|
+
lg.gain.value = f * 0.005;
|
|
173
136
|
lfo.connect(lg).connect(o.frequency);
|
|
174
|
-
const atk = Math.min(0.
|
|
137
|
+
const atk = Math.min(0.15, dur * 0.3);
|
|
175
138
|
g.gain.setValueAtTime(0, t);
|
|
176
139
|
g.gain.linearRampToValueAtTime(vol, t + atk);
|
|
177
|
-
g.gain.setValueAtTime(vol * 0.
|
|
140
|
+
g.gain.setValueAtTime(vol * 0.8, t + Math.max(atk, dur - 0.2));
|
|
178
141
|
g.gain.linearRampToValueAtTime(0.0001, t + dur);
|
|
179
|
-
o.connect(g).connect(
|
|
142
|
+
o.connect(g).connect(dest);
|
|
180
143
|
o.start(t); o.stop(t + dur + 0.05);
|
|
181
144
|
lfo.start(t); lfo.stop(t + dur + 0.05);
|
|
182
|
-
// 轻噪气声
|
|
183
|
-
this._noise(t, Math.min(dur, 0.4), vol * 0.15, this.bgmGain, f * 4, 6);
|
|
184
145
|
} catch { /* ignore */ }
|
|
185
146
|
}
|
|
186
147
|
|
|
187
|
-
|
|
188
|
-
if (!this.ctx) return;
|
|
148
|
+
pluck(f, t, dur, vol, dest) {
|
|
189
149
|
try {
|
|
190
|
-
for (const [mult, v] of [[1, 1], [2, 0.
|
|
150
|
+
for (const [mult, v] of [[1, 1], [2, 0.35], [3, 0.12]]) {
|
|
191
151
|
const o = this.ctx.createOscillator();
|
|
192
152
|
const g = this.ctx.createGain();
|
|
193
153
|
o.type = 'triangle';
|
|
194
154
|
o.frequency.value = f * mult;
|
|
195
155
|
g.gain.setValueAtTime(vol * v, t);
|
|
196
156
|
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
|
|
197
|
-
o.connect(g).connect(
|
|
157
|
+
o.connect(g).connect(dest);
|
|
198
158
|
o.start(t); o.stop(t + dur + 0.05);
|
|
199
159
|
}
|
|
200
160
|
} catch { /* ignore */ }
|
|
201
161
|
}
|
|
202
162
|
|
|
203
|
-
|
|
204
|
-
if (!this.ctx) return;
|
|
205
|
-
try {
|
|
206
|
-
if (!this._noiseBuf) {
|
|
207
|
-
const len = this.ctx.sampleRate * 0.5;
|
|
208
|
-
this._noiseBuf = this.ctx.createBuffer(1, len, this.ctx.sampleRate);
|
|
209
|
-
const d = this._noiseBuf.getChannelData(0);
|
|
210
|
-
for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1;
|
|
211
|
-
}
|
|
212
|
-
const src = this.ctx.createBufferSource();
|
|
213
|
-
src.buffer = this._noiseBuf;
|
|
214
|
-
const f = this.ctx.createBiquadFilter();
|
|
215
|
-
f.type = 'bandpass';
|
|
216
|
-
f.frequency.value = filterFreq;
|
|
217
|
-
f.Q.value = q;
|
|
218
|
-
const g = this.ctx.createGain();
|
|
219
|
-
g.gain.setValueAtTime(vol, t);
|
|
220
|
-
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
|
|
221
|
-
src.connect(f).connect(g).connect(dest);
|
|
222
|
-
src.start(t); src.stop(t + dur + 0.02);
|
|
223
|
-
} catch { /* ignore */ }
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
_block(t, f, dur, vol, dest) { // 木鱼/云板基件
|
|
227
|
-
this._tone(f, t, dur, 'sine', vol, dest, 0.002, dur * 0.6);
|
|
228
|
-
this._noise(t, Math.min(0.04, dur), vol * 0.5, dest, f * 3, 4);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
_tone(f, t, dur, wave, vol, dest, attack = 0.005, release = 0.06) {
|
|
232
|
-
if (!this.ctx) return;
|
|
163
|
+
block(t, f, dur, vol, dest) {
|
|
233
164
|
try {
|
|
234
165
|
const o = this.ctx.createOscillator();
|
|
235
166
|
const g = this.ctx.createGain();
|
|
236
|
-
o.type = wave;
|
|
237
167
|
o.frequency.value = f;
|
|
238
|
-
g.gain.setValueAtTime(
|
|
239
|
-
g.gain.
|
|
240
|
-
g.gain.setValueAtTime(vol * 0.8, t + Math.max(attack, dur - release));
|
|
241
|
-
g.gain.linearRampToValueAtTime(0.0001, t + dur);
|
|
168
|
+
g.gain.setValueAtTime(vol, t);
|
|
169
|
+
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
|
|
242
170
|
o.connect(g).connect(dest);
|
|
243
|
-
o.start(t); o.stop(t + dur + 0.
|
|
171
|
+
o.start(t); o.stop(t + dur + 0.02);
|
|
244
172
|
} catch { /* ignore */ }
|
|
245
173
|
}
|
|
246
174
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
playClear(onDone) {
|
|
250
|
-
if (!this.ctx) { onDone?.(); return; }
|
|
251
|
-
this._clearing = true;
|
|
252
|
-
this.stopBGM();
|
|
253
|
-
const t = this.ctx.currentTime;
|
|
254
|
-
const g = this.bgmGain.gain;
|
|
255
|
-
g.cancelScheduledValues(t);
|
|
256
|
-
g.setValueAtTime(g.value, t);
|
|
257
|
-
g.linearRampToValueAtTime(0.0001, t + 2.0); // 抽空
|
|
258
|
-
// 只剩一个衰减的低音
|
|
259
|
-
this._tone(freq(0, -2), t + 0.3, 2.2, 'sine', 0.12, this.master, 0.02, 1.8);
|
|
260
|
-
// 静默 3.4 秒后归还声场
|
|
261
|
-
setTimeout(() => {
|
|
262
|
-
if (!this.ctx) return;
|
|
263
|
-
this.bgmGain.gain.setTargetAtTime(0.30, this.ctx.currentTime, 0.5);
|
|
264
|
-
this._clearing = false;
|
|
265
|
-
onDone?.();
|
|
266
|
-
}, 2000 + 3400);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// ---------- SFX ----------
|
|
270
|
-
sfx(name, opt = {}) {
|
|
271
|
-
if (!this.ctx || this.muted) return;
|
|
175
|
+
se(name) {
|
|
176
|
+
if (!this.ctx) return;
|
|
272
177
|
const t = this.ctx.currentTime + 0.01;
|
|
273
|
-
const out = this.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
case 'an': // 墨:低而闷的木质轻响,几乎听不见
|
|
281
|
-
this._block(t, 320, 0.06, 0.045, out);
|
|
282
|
-
break;
|
|
283
|
-
case 'wind': // 朱:压抑的擦弦/气声,不安
|
|
284
|
-
this._noise(t, 0.35, 0.09, out, 480, 3);
|
|
285
|
-
this._tone(233, t, 0.3, 'sawtooth', 0.03, out, 0.12, 0.12);
|
|
286
|
-
break;
|
|
287
|
-
case 'submit': // 云板一记
|
|
288
|
-
this._tone(1180, t, 0.1, 'sine', 0.2, out, 0.001, 0.06);
|
|
289
|
-
this._noise(t, 0.05, 0.12, out, 2600, 4);
|
|
290
|
-
break;
|
|
291
|
-
case 'plank': // 木牌翻动
|
|
292
|
-
this._block(t, 640, 0.05, 0.14, out);
|
|
293
|
-
this._block(t + 0.06, 520, 0.05, 0.11, out);
|
|
294
|
-
break;
|
|
295
|
-
case 'paper': // 纸张轻响
|
|
296
|
-
this._noise(t, 0.12, 0.08, out, 3200, 1.2);
|
|
297
|
-
break;
|
|
298
|
-
case 'wang': // 闷实的印章落纸
|
|
299
|
-
this._block(t, 210, 0.1, 0.2, out);
|
|
300
|
-
this._noise(t, 0.07, 0.1, out, 700, 1.5);
|
|
301
|
-
break;
|
|
302
|
-
case 'mou': { // 极轻的连续摩擦,进度越高越明显
|
|
303
|
-
const p = Math.min(1, (opt.progress ?? 30) / 100);
|
|
304
|
-
this._noise(t, 0.3, 0.03 + p * 0.05, out, 900 + p * 600, 5);
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
case 'faluo': { // 沉重的堂木+余响
|
|
308
|
-
this._tone(140, t, 0.4, 'sine', 0.3, out, 0.002, 0.3);
|
|
309
|
-
this._block(t, 260, 0.12, 0.26, out);
|
|
310
|
-
this._noise(t + 0.1, 0.6, 0.08, out, 300, 1);
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
case 'watch': // 远处更漏,两下
|
|
314
|
-
this._block(t, 380, 0.09, 0.07, out);
|
|
315
|
-
this._block(t + 0.5, 340, 0.09, 0.055, out);
|
|
316
|
-
break;
|
|
317
|
-
case 'qing': { // 结局定格:悠长的磬
|
|
318
|
-
this._tone(1047, t, 2.8, 'sine', 0.14, out, 0.004, 2.4);
|
|
319
|
-
this._tone(1568, t, 2.2, 'sine', 0.05, out, 0.004, 1.8);
|
|
320
|
-
this._tone(524, t, 3.0, 'sine', 0.06, out, 0.004, 2.6);
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
case 'click':
|
|
324
|
-
this._block(t, 760, 0.04, 0.1, out);
|
|
325
|
-
break;
|
|
326
|
-
default:
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
178
|
+
const out = this.seGain;
|
|
179
|
+
if (name === 'click') this.block(t, 900, 0.05, 0.12, out);
|
|
180
|
+
else if (name === 'page') { this.block(t, 1400, 0.03, 0.05, out); this.block(t + 0.04, 1100, 0.03, 0.04, out); }
|
|
181
|
+
else if (name === 'choice') { this.pluck(freq(4, 1), t, 0.6, 0.18, out); this.pluck(freq(7, 1), t + 0.08, 0.7, 0.14, out); }
|
|
182
|
+
else if (name === 'chapter') { this.pluck(freq(0, 0), t, 1.6, 0.2, out); this.pluck(freq(4, 0), t + 0.12, 1.6, 0.16, out); this.pluck(freq(7, 0), t + 0.24, 2, 0.14, out); }
|
|
183
|
+
else if (name === 'ending') { for (const [d, o, dt] of [[0, 1, 0], [4, 1, 0.02], [0, 0, 0.04]]) this.di(freq(d, o), t + dt, 3, 0.12, out); }
|
|
184
|
+
else if (name === 'save') { this.pluck(freq(2, 1), t, 0.4, 0.15, out); this.pluck(freq(4, 1), t + 0.07, 0.5, 0.12, out); }
|
|
329
185
|
}
|
|
330
186
|
}
|
|
331
187
|
|