index.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { Popup } from "@varlet/ui";
  2. import "@varlet/ui/es/popup/style/index";
  3. import { defineComponent, onBeforeUnmount, onMounted, onUnmounted, reactive, watch } from "vue";
  4. import {
  5. connectWebsocket,
  6. evaluatingData,
  7. handleEndBegin,
  8. handleEndSoundCheck,
  9. handlePerformDetection,
  10. handleStartBegin,
  11. } from "/src/view/evaluating";
  12. import Earphone from "./earphone";
  13. import styles from "./index.module.less";
  14. import SoundEffect from "./sound-effect";
  15. import state from "/src/state";
  16. import { storeData } from "/src/store";
  17. import { browser } from "/src/utils";
  18. import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic";
  19. import { Icon } from "vant";
  20. import _event, { EventEnum } from "/src/helpers/eventemitter";
  21. // frequency 频率, amplitude 振幅, decibels 分贝
  22. type TCriteria = "frequency" | "amplitude" | "decibels";
  23. export default defineComponent({
  24. name: "evaluat-model",
  25. setup() {
  26. /**
  27. * 木管(长笛 萨克斯 单簧管)乐器一级的2、3、6测评要放原音音频
  28. * 铜管乐器一级的1a,1b,5,6测评要放原音音频
  29. */
  30. const getMusicMode = () => {
  31. const muguan = [2, 4, 5, 6];
  32. const tongguan = [12, 13, 14, 15, 17];
  33. if (muguan.includes(state.subjectId) && (state.examSongName || "").search(/[^\u0000-\u00FF](1-2|1-3|1-6)/gi) > -1) {
  34. return "music";
  35. }
  36. if (tongguan.includes(state.subjectId) && (state.examSongName || "").search(/[^\u0000-\u00FF](1-1-1|1-1-2|1-5|1-6)/gi) > -1) {
  37. return "music";
  38. }
  39. if ([23, 113, 121].includes(state.subjectId)) {
  40. return "music";
  41. }
  42. return "background";
  43. };
  44. const browserInfo = browser();
  45. /** 是否是节奏练习 */
  46. const isRhythmicExercises = () => {
  47. const examSongName = state.examSongName || "";
  48. return examSongName.indexOf("节奏练习") > -1;
  49. };
  50. /** 获取评测标准 */
  51. const getEvaluationCriteria = () => {
  52. let criteria: TCriteria = "frequency";
  53. // 声部打击乐
  54. if ([23, 113, 121].includes(state.subjectId)) {
  55. criteria = "amplitude";
  56. } else if (isRhythmicExercises()) {
  57. // 分类为节奏练习
  58. criteria = "decibels";
  59. }
  60. return criteria;
  61. };
  62. /** 生成评测曲谱数据 */
  63. const formatTimes = () => {
  64. let ListenMode = false;
  65. let dontEvaluatingMode = false;
  66. let skip = false;
  67. const datas = [];
  68. for (let index = 0; index < state.times.length; index++) {
  69. const item = state.times[index];
  70. const note = getNoteByMeasuresSlursStart(item);
  71. const rate = state.speed / state.originSpeed;
  72. const difftime = item.difftime;
  73. const start = difftime + (item.sourceRelativeTime || item.relativeTime);
  74. const end = difftime + (item.sourceRelaEndtime || item.relaEndtime);
  75. const isStaccato = note.noteElement.voiceEntry.isStaccato();
  76. const noteRate = isStaccato ? 0.5 : 1;
  77. if (note.formatLyricsEntries.contains("Play") || note.formatLyricsEntries.contains("Play...")) {
  78. ListenMode = false;
  79. }
  80. if (note.formatLyricsEntries.contains("Listen")) {
  81. ListenMode = true;
  82. }
  83. if (note.formatLyricsEntries.contains("纯律结束")) {
  84. dontEvaluatingMode = false;
  85. }
  86. if (note.formatLyricsEntries.contains("纯律")) {
  87. dontEvaluatingMode = true;
  88. }
  89. const nextNote = state.times[index + 1];
  90. // console.log("noteinfo", note.noteElement.isRestFlag && !!note.stave && !!nextNote)
  91. if (skip && (note.stave || !item.noteElement.isRestFlag || (nextNote && !nextNote.noteElement.isRestFlag))) {
  92. skip = false;
  93. }
  94. if (note.noteElement.isRestFlag && !!note.stave && !!nextNote && nextNote.noteElement.isRestFlag) {
  95. skip = true;
  96. }
  97. // console.log(note.measureOpenIndex, item.measureOpenIndex, note);
  98. // console.log("skip", skip)
  99. const data = {
  100. timeStamp: (start * 1000) / rate,
  101. duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
  102. frequency: item.frequency,
  103. nextFrequency: item.nextFrequency,
  104. prevFrequency: item.prevFrequency,
  105. // 重复的情况index会自然累加,render的index是谱面渲染的index
  106. measureIndex: note.measureOpenIndex,
  107. measureRenderIndex: item.measureListIndex,
  108. dontEvaluating: ListenMode || dontEvaluatingMode,
  109. musicalNotesIndex: item.i,
  110. denominator: note.noteElement?.Length.denominator,
  111. };
  112. datas.push(data);
  113. }
  114. return datas;
  115. };
  116. /** 连接websocket */
  117. const handleConnect = async () => {
  118. const behaviorId = localStorage.getItem("behaviorId") || undefined;
  119. const rate = state.speed / state.originSpeed;
  120. const content = {
  121. musicXmlInfos: formatTimes(),
  122. id: state.examSongId,
  123. subjectId: state.subjectId,
  124. detailId: state.detailId,
  125. examSongId: state.examSongId,
  126. xmlUrl: state.xmlUrl,
  127. partIndex: state.partIndex,
  128. behaviorId,
  129. tenantId: storeData.user.tenantId,
  130. platform: browserInfo.ios ? "IOS" : browserInfo.android ? "ANDROID" : "WEB",
  131. clientId: storeData.platformType === "STUDENT" ? "student" : storeData.platformType === "TEACHER" ? "teacher" : "education",
  132. speed: state.speed,
  133. heardLevel: state.setting.evaluationDifficulty,
  134. beatLength: Math.round((state.fixtime * 1000) / rate),
  135. campId: sessionStorage.getItem("campId") || "",
  136. evaluationCriteria: getEvaluationCriteria(),
  137. };
  138. const result = await connectWebsocket(content);
  139. state.playSource = getMusicMode();
  140. };
  141. /** 评测返回 */
  142. const handleResult = (result: any) => {
  143. console.log('评测返回2', result.body)
  144. }
  145. onMounted(() => {
  146. handlePerformDetection();
  147. _event.on(EventEnum.sendResultScore, handleResult)
  148. });
  149. onBeforeUnmount(() => {
  150. _event.off(EventEnum.sendResultScore)
  151. })
  152. watch(
  153. () => evaluatingData.checkEnd,
  154. () => {
  155. if (evaluatingData.checkEnd) {
  156. console.log("检测结束,连接websocket");
  157. handleConnect();
  158. }
  159. }
  160. );
  161. return () => (
  162. <div>
  163. {evaluatingData.websocketState && (
  164. <>
  165. {!evaluatingData.startBegin && (
  166. <div class={styles.btn} onClick={handleStartBegin}>
  167. 开始演奏
  168. </div>
  169. )}
  170. {evaluatingData.startBegin && (
  171. <div class={[styles.btn, styles.endBtn]} onClick={() => handleEndBegin(false)}>
  172. <Icon name="success" />
  173. <span>结束演奏</span>
  174. </div>
  175. )}
  176. </>
  177. )}
  178. <Popup closeOnClickOverlay={false} defaultStyle={false} v-model:show={evaluatingData.earphoneMode}>
  179. <Earphone
  180. onClose={() => {
  181. evaluatingData.earphoneMode = false;
  182. handlePerformDetection();
  183. }}
  184. />
  185. </Popup>
  186. <Popup closeOnClickOverlay={false} defaultStyle={false} v-model:show={evaluatingData.soundEffectMode}>
  187. <SoundEffect
  188. onClose={(value: any) => {
  189. evaluatingData.soundEffectMode = false;
  190. if (value) {
  191. state.setting.soundEffect = false;
  192. }
  193. handleEndSoundCheck();
  194. handlePerformDetection();
  195. }}
  196. />
  197. </Popup>
  198. </div>
  199. );
  200. },
  201. });