@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.
Files changed (264) hide show
  1. package/README.md +17 -11
  2. package/lib/client.js +85 -35
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +41 -36
  5. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
  6. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
  7. package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
  8. package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
  9. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
  10. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
  11. package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
  12. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
  13. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
  14. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
  15. package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
  16. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
  17. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
  18. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
  19. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
  20. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  21. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
  22. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
  23. package/lib/index.js +16 -7
  24. package/lib/index.js.map +2 -2
  25. package/lib/novel-to-game/README.md +98 -44
  26. package/lib/novel-to-game/README_ZH.md +100 -46
  27. package/lib/novel-to-game/VERSION +1 -1
  28. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
  29. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
  30. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
  31. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
  32. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
  33. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
  34. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
  35. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
  36. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
  37. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
  38. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
  39. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
  40. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
  41. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
  42. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
  43. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
  44. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
  45. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
  46. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
  47. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
  57. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
  58. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
  59. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
  60. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
  61. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
  62. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
  63. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
  64. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
  65. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
  66. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
  91. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
  92. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
  93. package/lib/novel-to-game/manifest.json +303 -78
  94. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
  95. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
  96. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
  97. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
  98. package/lib/types/client/drama-production-view.d.ts +11 -0
  99. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  100. package/lib/types/client/file-activity.d.ts +1 -1
  101. package/lib/types/client/file-activity.d.ts.map +1 -1
  102. package/lib/types/client/production-prompts.d.ts +7 -1
  103. package/lib/types/client/production-prompts.d.ts.map +1 -1
  104. package/lib/types/client/production-runtime.d.ts +7 -2
  105. package/lib/types/client/production-runtime.d.ts.map +1 -1
  106. package/lib/types/drama-adapters.d.ts.map +1 -1
  107. package/lib/types/skill-provider.d.ts.map +1 -1
  108. package/lib/types/video-project.d.ts.map +1 -1
  109. package/lib/video-recap/CHANGELOG.md +108 -1
  110. package/lib/video-recap/README.en.md +18 -14
  111. package/lib/video-recap/README.md +18 -14
  112. package/lib/video-recap/manifest.json +297 -321
  113. package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
  114. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
  115. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
  116. package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
  117. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
  118. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
  119. package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
  120. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
  121. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
  122. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
  123. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
  124. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
  125. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
  126. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
  127. package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
  128. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
  129. package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
  130. package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
  131. package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
  132. package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
  133. package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
  134. package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
  135. package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
  136. package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
  137. package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
  138. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
  139. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
  140. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
  141. package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
  142. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
  143. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
  144. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
  145. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
  146. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
  147. package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
  148. package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
  149. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
  150. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
  151. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
  152. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
  153. package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
  154. package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
  155. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
  156. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
  157. package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
  158. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
  159. package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
  160. package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
  161. package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
  162. package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
  163. package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
  164. package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
  165. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
  166. package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
  167. package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
  168. package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
  169. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
  170. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
  171. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
  172. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
  173. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
  174. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
  175. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
  176. package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
  177. package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
  178. package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
  179. package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
  180. package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
  181. package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
  182. package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
  183. package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
  184. package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
  185. package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
  186. package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
  187. package/lib/video-recap/skills/video-script/SKILL.md +9 -7
  188. package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
  189. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
  190. package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
  191. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
  192. package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
  193. package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
  194. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
  195. package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
  196. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
  197. package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
  198. package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
  199. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
  200. package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
  201. package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
  202. package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
  203. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
  204. package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
  205. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
  206. package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
  207. package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
  208. package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
  209. package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
  210. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
  211. package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
  212. package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
  213. package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
  214. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
  215. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
  216. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
  217. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
  218. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
  219. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
  220. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
  221. package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
  222. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
  223. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
  224. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
  225. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
  226. package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
  227. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
  228. package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
  229. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
  230. package/package.json +45 -45
  231. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  232. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  233. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  234. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
  235. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
  236. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
  237. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
  238. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
  239. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
  240. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
  241. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
  242. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
  243. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
  244. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
  245. package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
  246. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
  247. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
  248. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
  249. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
  250. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
  251. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
  252. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
  253. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
  254. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
  255. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
  256. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
  257. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
  258. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
  259. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
  260. package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
  261. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
  262. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
  263. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
  264. /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
@@ -1,76 +1,31 @@
1
- // 音频:Web Audio 程序化合成,零素材零依赖。
2
- // 明代市井家宅——克制、室内、偏冷。五声音阶(宫商角徵羽),丝竹音色:
3
- // 笛/箫(正弦+轻噪气声+揉音)、琵琶/古筝(快起音快衰减拨弦)、云板/木鱼(短促打击)。
4
- // 功能三色通感:明账=清亮金属音,暗账=几乎听不见的闷木音,风声=不安的擦弦音。
5
- // 一切方法在 AudioContext 不可用时静默降级,绝不影响游戏运行;删掉本模块玩法不变。
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
- const extra = Math.floor(degree / 5);
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
- const MUTE_KEY = 'jpm_mute';
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.master = null;
68
- this.bgmGain = null;
69
- this.sfxGain = null;
70
- this.bgm = null;
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.bgmGain.gain.value = 0.30; // BGM 明显低于音效
87
- this.bgmGain.connect(this.master);
88
- this.sfxGain = this.ctx.createGain();
89
- this.sfxGain.gain.value = 0.55;
90
- this.sfxGain.connect(this.master);
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
- get ready() { return !!this.ctx; }
97
- get currentScene() { return this.bgm?.scene ?? null; }
98
-
99
- setMuted(m) {
100
- this.muted = m;
101
- localStorage.setItem(MUTE_KEY, m ? '1' : '0');
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
- // ---------- BGM ----------
107
- playBGM(scene) {
108
- if (!this.ctx || this._clearing) return;
109
- if (this.bgm?.scene === scene) return;
110
- this.stopBGM();
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 beatLen = 60 / cfg.bpm;
114
- const total = cfg.melody.reduce((a, n) => a + n[2], 0);
115
- const st = { scene, cfg, beatLen, next: this.ctx.currentTime + 0.1, loopLen: total * beatLen };
116
- st.timer = setInterval(() => this._schedule(st), 400);
117
- this._schedule(st);
118
- this.bgm = st;
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
- stopBGM() {
122
- if (this.bgm) { clearInterval(this.bgm.timer); this.bgm = null; }
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
- _schedule(st) {
126
- if (!this.ctx) return;
127
- const horizon = this.ctx.currentTime + 1.2;
128
- while (st.next < horizon) {
129
- this._loopOnce(st, st.next);
130
- st.next += st.loopLen;
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
- _loopOnce(st, t0) {
135
- const { cfg, beatLen } = st;
136
- let t = t0;
137
- for (const [deg, oct, beats] of cfg.melody) {
138
- if (deg !== null) this._voice(cfg.voices[0], freq(deg, oct), t, beats * beatLen, cfg.vol);
139
- t += beats * beatLen;
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
- // 丝竹声部:di=笛/箫(气声+揉音), pluck=琵琶/古筝(拨弦), pluckLow=低音拨弦
155
- _voice(kind, f, t, dur, vol) {
156
- if (kind === 'di') this._di(f, t, dur, vol);
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
- _di(f, t, dur, vol) {
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
- lfo.frequency.value = 4.5;
172
- lg.gain.value = f * 0.006;
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.18, dur * 0.3);
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.85, t + Math.max(atk, dur - 0.25));
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(this.bgmGain);
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
- _pluck(f, t, dur, vol) {
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.4]]) {
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(this.bgmGain);
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
- _noise(t, dur, vol, dest, filterFreq = 1200, q = 1) {
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(0, t);
239
- g.gain.linearRampToValueAtTime(vol, t + attack);
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.05);
171
+ o.start(t); o.stop(t + dur + 0.02);
244
172
  } catch { /* ignore */ }
245
173
  }
246
174
 
247
- // ---------- 第79回:两秒内抽空,静默3-4秒 ----------
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.sfxGain;
274
- switch (name) {
275
- case 'ming': // 金:清亮金属泛音,短促上扬
276
- this._tone(1568, t, 0.14, 'sine', 0.16, out, 0.001, 0.08);
277
- this._tone(2093, t + 0.05, 0.16, 'sine', 0.12, out, 0.001, 0.1);
278
- this._tone(2637, t + 0.09, 0.12, 'sine', 0.06, out, 0.001, 0.08);
279
- break;
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