index.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import { defineComponent, onMounted, onUnmounted, reactive, ref } from "vue";
  2. import state, { gotoNext, resetPlaybackToStart } from "/src/state";
  3. import { IPostMessage } from "/src/utils/native-message";
  4. import { api_cloudFollowTime, api_cloudToggleFollow } from "/src/helpers/communication";
  5. import { storeData } from "/src/store";
  6. import { audioRecorder } from "./audioRecorder";
  7. import { handleStartTick } from "/src/view/tick";
  8. export const followData = reactive({
  9. list: [] as any, // 频率列表
  10. index: 0,
  11. start: false,
  12. rendered: false,
  13. /** 麦克风权限 */
  14. earphone: false,
  15. });
  16. /** 点击跟练模式 */
  17. export const toggleFollow = (notCancel = true) => {
  18. state.modeType = state.modeType === "follow" ? "practise" : "follow";
  19. // 取消跟练
  20. if (!notCancel) {
  21. followData.start = false;
  22. openToggleRecord(false);
  23. }
  24. };
  25. const noteFrequency = ref(0);
  26. const audioFrequency = ref(0);
  27. const followTime = ref(0);
  28. // 切换录音
  29. const openToggleRecord = async (open: boolean = true) => {
  30. api_cloudToggleFollow(open ? "start" : "end");
  31. // 记录跟练时长
  32. if (open) {
  33. followTime.value = Date.now();
  34. } else {
  35. const playTime = Date.now() - followTime.value;
  36. if (followTime.value !== 0 && playTime > 0) {
  37. followTime.value = 0;
  38. }
  39. }
  40. if (!storeData.isApp) {
  41. const openState = await audioRecorder?.toggleRecord(open);
  42. // 开启录音失败
  43. if (!openState && followData.start) {
  44. followData.earphone = true;
  45. followData.start = false;
  46. }
  47. }
  48. };
  49. // 清除音符状态
  50. const onClear = () => {
  51. state.times.forEach((item: any) => {
  52. const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!;
  53. if (note) {
  54. note.classList.remove("follow-up", "follow-down", "follow-error", "follow-success");
  55. }
  56. const _note: HTMLElement = document.getElementById(`vf-${item.id}`)!;
  57. if (_note) {
  58. _note.classList.remove("follow-up", "follow-down");
  59. }
  60. });
  61. };
  62. /** 开始跟练 */
  63. export const handleFollowStart = async () => {
  64. // 跟练模式开始前,增加播放系统节拍器
  65. const tickend = await handleStartTick();
  66. // console.log("🚀 ~ tickend:", tickend)
  67. // 节拍器返回false, 取消播放
  68. if (!tickend) {
  69. return false;
  70. }
  71. onClear();
  72. followData.start = true;
  73. followData.index = 0;
  74. followData.list = [];
  75. resetPlaybackToStart();
  76. openToggleRecord(true);
  77. getNoteIndex();
  78. };
  79. /** 结束跟练 */
  80. export const handleFollowEnd = () => {
  81. followData.start = false;
  82. openToggleRecord(false);
  83. followData.index = 0;
  84. console.log("结束");
  85. };
  86. // 下一个
  87. const next = () => {
  88. gotoNext(state.times[followData.index]);
  89. };
  90. // 获取当前音符
  91. const getNoteIndex = (): any => {
  92. const item = state.times[followData.index];
  93. if (item.frequency <= 0) {
  94. followData.index = followData.index + 1;
  95. next();
  96. return getNoteIndex();
  97. }
  98. noteFrequency.value = item.frequency;
  99. // state.fixedKey = item.realKey;
  100. return {
  101. id: item.id,
  102. min: item.frequency - (item.frequency - item.prevFrequency) * 0.5,
  103. max: item.frequency + (item.nextFrequency - item.frequency) * 0.5,
  104. duration: item.duration,
  105. baseFrequency: item.frequency,
  106. };
  107. };
  108. let checking = false;
  109. /** 录音回调 */
  110. const onFollowTime = (evt?: IPostMessage) => {
  111. const frequency: number = evt?.content?.frequency;
  112. // 没有开始, 不处理
  113. if (!followData.start) return;
  114. // console.log('频率', frequency)
  115. if (frequency > 0) {
  116. audioFrequency.value = frequency;
  117. // data.list.push(frequency)
  118. checked();
  119. }
  120. };
  121. let startTime = 0;
  122. const checked = () => {
  123. if (checking) return;
  124. checking = true;
  125. const item = getNoteIndex();
  126. // 降噪处理, 频率低于 当前音的50% 时, 不处理
  127. if (audioFrequency.value < item.baseFrequency * 0.5) {
  128. checking = false;
  129. return;
  130. }
  131. if (audioFrequency.value >= item.min && audioFrequency.value <= item.max) {
  132. if (startTime === 0) {
  133. startTime = Date.now();
  134. } else {
  135. const playTime = (Date.now() - startTime) / 1000;
  136. // console.log('时长', playTime, item.duration / 2)
  137. if (playTime >= item.duration * 0.6) {
  138. startTime = 0;
  139. followData.index = followData.index + 1;
  140. setColor(item, "", true);
  141. next();
  142. checking = false;
  143. return;
  144. }
  145. }
  146. // console.log("频率对了", item.min, audioFrequency.value, item.max, item.duration);
  147. }
  148. // console.log("频率不对", item.min, audioFrequency.value, item.max, item.baseFrequency);
  149. setColor(item, audioFrequency.value > item.baseFrequency ? "follow-up" : "follow-down");
  150. checking = false;
  151. };
  152. const setColor = (item: any, state: "follow-up" | "follow-down" | "", isRight = false) => {
  153. const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!;
  154. if (note) {
  155. note.classList.remove("follow-up", "follow-down", "follow-error", "follow-success");
  156. if (isRight) {
  157. note.classList.add("follow-success");
  158. } else {
  159. note.classList.add("follow-error", state);
  160. }
  161. }
  162. const _note: HTMLElement = document.getElementById(`vf-${item.id}`)!;
  163. if (_note) {
  164. _note.classList.remove("follow-up", "follow-down");
  165. state && _note.classList.add(state);
  166. }
  167. };
  168. export default defineComponent({
  169. name: "follow",
  170. setup() {
  171. onMounted(async () => {
  172. /** 如果是PC端 */
  173. if (!storeData.isApp) {
  174. const canRecorder = await audioRecorder.checkSupport();
  175. if (canRecorder) {
  176. audioRecorder.init();
  177. audioRecorder.progress = (frequency: number) => {
  178. onFollowTime({ api: "", content: { frequency } });
  179. };
  180. } else {
  181. followData.earphone = true;
  182. }
  183. } else {
  184. api_cloudFollowTime(onFollowTime);
  185. }
  186. console.log("进入跟练模式");
  187. });
  188. onUnmounted(() => {
  189. api_cloudFollowTime(onFollowTime, false);
  190. resetPlaybackToStart();
  191. onClear();
  192. openToggleRecord(false);
  193. console.log("退出跟练模式");
  194. });
  195. return () => <div></div>;
  196. },
  197. });