|
@@ -0,0 +1,171 @@
|
|
|
+import { defineComponent, onMounted, onUnmounted, reactive, ref, } from "vue";
|
|
|
+import state, { gotoNext } from "/src/state";
|
|
|
+import { IPostMessage } from "/src/utils/native-message";
|
|
|
+import { api_cloudFollowTime, api_cloudToggleFollow } from "/src/helpers/communication";
|
|
|
+import { storeData } from "/src/store";
|
|
|
+import { Snackbar } from "@varlet/ui";
|
|
|
+
|
|
|
+export const followData = reactive({
|
|
|
+ list: [] as any, // 频率列表
|
|
|
+ index: 0,
|
|
|
+ start: false,
|
|
|
+ rendered: false,
|
|
|
+});
|
|
|
+
|
|
|
+/** 点击跟练模式 */
|
|
|
+export const toggleFollow = (notCancel = true) => {
|
|
|
+ state.modeType = state.modeType === "follow" ? "practise" : "follow";
|
|
|
+ // 取消跟练
|
|
|
+ if (!notCancel) {
|
|
|
+ followData.start = false;
|
|
|
+ openToggleRecord(false);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const noteFrequency = ref(0);
|
|
|
+const audioFrequency = ref(0);
|
|
|
+const followTime = ref(0);
|
|
|
+// 切换录音
|
|
|
+const openToggleRecord = async (open: boolean = true) => {
|
|
|
+ await api_cloudToggleFollow(open ? "start" : "end");
|
|
|
+ // 记录跟练时长
|
|
|
+ if (open) {
|
|
|
+ followTime.value = Date.now();
|
|
|
+ } else {
|
|
|
+ const playTime = Date.now() - followTime.value;
|
|
|
+ if (followTime.value !== 0 && playTime > 0) {
|
|
|
+ followTime.value = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 清除音符状态
|
|
|
+const onClear = () => {
|
|
|
+ state.times.forEach((item: any) => {
|
|
|
+ const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!;
|
|
|
+ if (note) {
|
|
|
+ note.classList.remove("error");
|
|
|
+ note.classList.remove("success");
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/** 开始跟练 */
|
|
|
+export const handleFollowStart = async () => {
|
|
|
+ if (!storeData.isApp){
|
|
|
+ Snackbar({
|
|
|
+ content: '请在APP端使用',
|
|
|
+ type: 'warning',
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ onClear();
|
|
|
+ followData.start = true;
|
|
|
+ followData.index = 0;
|
|
|
+ followData.list = [];
|
|
|
+ gotoNext(state.times[0]);
|
|
|
+ await openToggleRecord(true);
|
|
|
+ setStep()
|
|
|
+};
|
|
|
+/** 结束跟练 */
|
|
|
+export const handleFollowEnd = () => {
|
|
|
+ followData.start = false;
|
|
|
+ openToggleRecord(false);
|
|
|
+ followData.index = 0;
|
|
|
+ gotoNext(state.times[0]);
|
|
|
+};
|
|
|
+
|
|
|
+// 下一个
|
|
|
+const next = () => {
|
|
|
+ gotoNext(state.times[followData.index]);
|
|
|
+};
|
|
|
+
|
|
|
+// 获取当前音符
|
|
|
+const getNoteIndex = (): any => {
|
|
|
+ const item = state.times[followData.index];
|
|
|
+ if (item.frequency <= 0) {
|
|
|
+ followData.index = followData.index + 1;
|
|
|
+ next();
|
|
|
+ return getNoteIndex();
|
|
|
+ }
|
|
|
+ noteFrequency.value = item.frequency;
|
|
|
+ // state.fixedKey = item.realKey;
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ min: item.frequency - item.prevFrequency * 0.1,
|
|
|
+ max: item.frequency + item.nextFrequency * 0.1,
|
|
|
+ };
|
|
|
+};
|
|
|
+let checking = false;
|
|
|
+/** 录音回调 */
|
|
|
+const onFollowTime = (evt?: IPostMessage) => {
|
|
|
+ const frequency: number = evt?.content?.frequency;
|
|
|
+ audioFrequency.value = frequency;
|
|
|
+ // console.log("🚀 ~ frequency:", frequency);
|
|
|
+ followData.list.push(frequency);
|
|
|
+};
|
|
|
+
|
|
|
+/** 在渲染前后计算光标应该走到的音符 */
|
|
|
+const setStep = () => {
|
|
|
+ let startTime = Date.now();
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ const endTime = Date.now();
|
|
|
+ // 渲染时间大于16.6,就会让页面卡顿, 如果渲染时间大与16.6就下一个渲染帧去计算
|
|
|
+ if (endTime - startTime < 16.6) {
|
|
|
+ if (!followData.start) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ checked();
|
|
|
+ }
|
|
|
+ setStep();
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const checked = () => {
|
|
|
+ if (checking) return;
|
|
|
+ checking = true;
|
|
|
+ const item = getNoteIndex();
|
|
|
+ // console.log("🚀 ~ item:", item)
|
|
|
+ for (let i = 0; i < followData.list.length; i++) {
|
|
|
+ const frequency = followData.list[i];
|
|
|
+ if (frequency > item.min && frequency < item.max) {
|
|
|
+ // console.log(item.min, frequency, item.max);
|
|
|
+ next();
|
|
|
+ followData.index += 1;
|
|
|
+ followData.list = followData.list.slice(i + 1);
|
|
|
+ setColor(item, true);
|
|
|
+ checking = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setColor(item);
|
|
|
+ checking = false;
|
|
|
+};
|
|
|
+const setColor = (item: any, isRight = false) => {
|
|
|
+ const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!;
|
|
|
+ if (note) {
|
|
|
+ if (isRight) {
|
|
|
+ note.classList.remove("error");
|
|
|
+ note.classList.add("success");
|
|
|
+ } else {
|
|
|
+ note.classList.remove("success");
|
|
|
+ note.classList.add("error");
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export default defineComponent({
|
|
|
+ name: "follow",
|
|
|
+ setup() {
|
|
|
+ onMounted(() => {
|
|
|
+ api_cloudFollowTime(onFollowTime);
|
|
|
+ console.log("进入跟练模式");
|
|
|
+ });
|
|
|
+ onUnmounted(() => {
|
|
|
+ api_cloudFollowTime(onFollowTime, false);
|
|
|
+ onClear();
|
|
|
+ console.log("退出跟练模式");
|
|
|
+ });
|
|
|
+ return () => <div></div>;
|
|
|
+ },
|
|
|
+});
|