|  | @@ -7,13 +7,14 @@ import state from "/src/state"
 | 
	
		
			
				|  |  |  import "./index.less"
 | 
	
		
			
				|  |  |  import Bird from "./bird"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -type pointsPosType = { x: number; y: number; MeasureNumberXML: number }[]
 | 
	
		
			
				|  |  | +type pointsPosType = { x: number; y: number; MeasureNumberXML: number; noteId: number }[]
 | 
	
		
			
				|  |  |  type smoothAnimationType = {
 | 
	
		
			
				|  |  |     isShow: Ref<boolean>
 | 
	
		
			
				|  |  |     canvasDom: null | HTMLCanvasElement
 | 
	
		
			
				|  |  |     canvasCtx: null | undefined | CanvasRenderingContext2D
 | 
	
		
			
				|  |  |     canvasDomWith: number
 | 
	
		
			
				|  |  |     canvasDomHeight: number
 | 
	
		
			
				|  |  | +   canvasSmoothDom: null | HTMLCanvasElement
 | 
	
		
			
				|  |  |     smoothAnimationBoxDom: null | HTMLElement
 | 
	
		
			
				|  |  |     smoothBotDom: null | HTMLElement
 | 
	
		
			
				|  |  |     osmdCanvasPageDom: null | HTMLElement
 | 
	
	
		
			
				|  | @@ -21,19 +22,22 @@ type smoothAnimationType = {
 | 
	
		
			
				|  |  |     osdmScrollDomWith: number
 | 
	
		
			
				|  |  |     osdmScrollDomOffsetLeft: number
 | 
	
		
			
				|  |  |     selectionBoxDom: null | HTMLElement
 | 
	
		
			
				|  |  | +   batePos: pointsPosType
 | 
	
		
			
				|  |  |     pointsPos: pointsPosType
 | 
	
		
			
				|  |  |     translateXNum: number
 | 
	
		
			
				|  |  |     aveSpeed: number
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const _numberOfSegments = 58 // 中间切割线的个数
 | 
	
		
			
				|  |  | +const _numberOfSegments = 60 // 中间切割线的个数
 | 
	
		
			
				|  |  | +const _canvasDomHeight = 60 // canvans 高度
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  export const smoothAnimationState = {
 | 
	
		
			
				|  |  |     isShow: ref(false), // 是否显示
 | 
	
		
			
				|  |  |     canvasDom: null,
 | 
	
		
			
				|  |  |     canvasCtx: null,
 | 
	
		
			
				|  |  |     canvasDomWith: 0,
 | 
	
		
			
				|  |  | -   canvasDomHeight: 80,
 | 
	
		
			
				|  |  | +   canvasDomHeight: _canvasDomHeight,
 | 
	
		
			
				|  |  | +   canvasSmoothDom: null,
 | 
	
		
			
				|  |  |     smoothAnimationBoxDom: null,
 | 
	
		
			
				|  |  |     smoothBotDom: null,
 | 
	
		
			
				|  |  |     osmdCanvasPageDom: null,
 | 
	
	
		
			
				|  | @@ -41,7 +45,8 @@ export const smoothAnimationState = {
 | 
	
		
			
				|  |  |     osdmScrollDomWith: 0,
 | 
	
		
			
				|  |  |     osdmScrollDomOffsetLeft: 0,
 | 
	
		
			
				|  |  |     selectionBoxDom: null,
 | 
	
		
			
				|  |  | -   pointsPos: [], // 计算之后的点坐标数组
 | 
	
		
			
				|  |  | +   batePos: [], // times 直接转换的数组
 | 
	
		
			
				|  |  | +   pointsPos: [], // 筛选之后的点坐标数组
 | 
	
		
			
				|  |  |     translateXNum: 0, // 当前谱面的translateX的距离   谱面的位置信息 由translateX和scrollLeft的偏移一起决定
 | 
	
		
			
				|  |  |     aveSpeed: 0 // 谱面的一帧的平均速度
 | 
	
		
			
				|  |  |  } as smoothAnimationType
 | 
	
	
		
			
				|  | @@ -63,8 +68,12 @@ export function initSmoothAnimation() {
 | 
	
		
			
				|  |  |     createSmoothAnimation()
 | 
	
		
			
				|  |  |     // 初始化动画数据
 | 
	
		
			
				|  |  |     const batePos = getPointsPosByBatePos()
 | 
	
		
			
				|  |  | -   console.log(batePos, "batePos")
 | 
	
		
			
				|  |  | -   smoothAnimationState.pointsPos = createSmoothCurvePoints(batePos, undefined, undefined, _numberOfSegments)
 | 
	
		
			
				|  |  | +   smoothAnimationState.batePos = batePos
 | 
	
		
			
				|  |  | +   const batePos1 = dataFilter([...batePos])
 | 
	
		
			
				|  |  | +   const batePos2 = createSmoothCurvePoints(batePos1, _numberOfSegments)
 | 
	
		
			
				|  |  | +   smoothAnimationState.pointsPos = batePos2
 | 
	
		
			
				|  |  | +   // 初始化旋律线
 | 
	
		
			
				|  |  | +   initCanvasSmooth()
 | 
	
		
			
				|  |  |     // 谱面的平均速度(因为可能有反复的情况所以实际距离要加上反复的距离)
 | 
	
		
			
				|  |  |     const canvasDomPath = batePos.reduce((path, item, index, arr) => {
 | 
	
		
			
				|  |  |        if (index !== 0) {
 | 
	
	
		
			
				|  | @@ -88,6 +97,30 @@ export function initSmoothAnimation() {
 | 
	
		
			
				|  |  |     console.log(smoothAnimationState, "一行谱小鸟数据")
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// 排序
 | 
	
		
			
				|  |  | +function dataFilter(batePos: pointsPosType) {
 | 
	
		
			
				|  |  | +   // 去掉反复跳房子的数据
 | 
	
		
			
				|  |  | +   const filterData = batePos.filter((item, index, array) => {
 | 
	
		
			
				|  |  | +      return array.findIndex(i => i.noteId === item.noteId) === index
 | 
	
		
			
				|  |  | +   })
 | 
	
		
			
				|  |  | +   // 先按 音符排序
 | 
	
		
			
				|  |  | +   const sortedData = filterData.sort((a, b) => a.noteId - b.noteId)
 | 
	
		
			
				|  |  | +   // 再按 小节排序
 | 
	
		
			
				|  |  | +   return sortedData.sort((a, b) => a.MeasureNumberXML - b.MeasureNumberXML)
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +//根据 activeInde和进度 查找转换之后的activeInde
 | 
	
		
			
				|  |  | +function dataFindIndex(activeIndex: number, progress: number) {
 | 
	
		
			
				|  |  | +   // 百分比转为当前的index 距离个数
 | 
	
		
			
				|  |  | +   const progressCalcIndex = Math.round(progress * _numberOfSegments)
 | 
	
		
			
				|  |  | +   const { noteId, MeasureNumberXML } = smoothAnimationState.batePos[activeIndex]
 | 
	
		
			
				|  |  | +   return (
 | 
	
		
			
				|  |  | +      smoothAnimationState.pointsPos.findIndex(item => {
 | 
	
		
			
				|  |  | +         return item.noteId === noteId && item.MeasureNumberXML === MeasureNumberXML
 | 
	
		
			
				|  |  | +      }) + progressCalcIndex
 | 
	
		
			
				|  |  | +   )
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * 销毁
 | 
	
		
			
				|  |  |   */
 | 
	
	
		
			
				|  | @@ -99,7 +132,8 @@ export function destroySmoothAnimation() {
 | 
	
		
			
				|  |  |        canvasDom: null,
 | 
	
		
			
				|  |  |        canvasCtx: null,
 | 
	
		
			
				|  |  |        canvasDomWith: 0,
 | 
	
		
			
				|  |  | -      canvasDomHeight: 80,
 | 
	
		
			
				|  |  | +      canvasDomHeight: _canvasDomHeight,
 | 
	
		
			
				|  |  | +      canvasSmoothDom: null,
 | 
	
		
			
				|  |  |        smoothAnimationBoxDom: null,
 | 
	
		
			
				|  |  |        smoothBotDom: null,
 | 
	
		
			
				|  |  |        osmdCanvasPageDom: null,
 | 
	
	
		
			
				|  | @@ -107,6 +141,7 @@ export function destroySmoothAnimation() {
 | 
	
		
			
				|  |  |        osdmScrollDomWith: 0,
 | 
	
		
			
				|  |  |        osdmScrollDomOffsetLeft: 0,
 | 
	
		
			
				|  |  |        selectionBoxDom: null,
 | 
	
		
			
				|  |  | +      batePos: [],
 | 
	
		
			
				|  |  |        pointsPos: [],
 | 
	
		
			
				|  |  |        translateXNum: 0,
 | 
	
		
			
				|  |  |        aveSpeed: 0
 | 
	
	
		
			
				|  | @@ -125,12 +160,7 @@ export function moveSmoothAnimationByPlayTime(time?: number) {
 | 
	
		
			
				|  |  |     if (currentTime <= state.fixtime) return
 | 
	
		
			
				|  |  |     if (currentTime > state.times.last()?.endtime) return
 | 
	
		
			
				|  |  |     // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
 | 
	
		
			
				|  |  | -   let nextIndex = state.activeNoteIndex + 1
 | 
	
		
			
				|  |  | -   let nextBBox = state.times[nextIndex]?.bbox
 | 
	
		
			
				|  |  | -   while (!nextBBox && nextIndex < state.times.length) {
 | 
	
		
			
				|  |  | -      nextIndex += 1
 | 
	
		
			
				|  |  | -      nextBBox = state.times[nextIndex]?.bbox
 | 
	
		
			
				|  |  | -   }
 | 
	
		
			
				|  |  | +   const nextIndex = state.activeNoteIndex + 1
 | 
	
		
			
				|  |  |     // 当前的音符和下一个音符之间的时值 (当是最后一个音符的时候,下一个音符的时间取当前音符的endtime)
 | 
	
		
			
				|  |  |     const noteDuration =
 | 
	
		
			
				|  |  |        (nextIndex > state.times.length - 1 ? state.times[state.activeNoteIndex]?.endtime : state.times[nextIndex].time) -
 | 
	
	
		
			
				|  | @@ -155,21 +185,19 @@ export function moveSmoothAnimation(progress: number, activeIndex: number, isMov
 | 
	
		
			
				|  |  |     // if (!smoothAnimationState.isShow.value) {
 | 
	
		
			
				|  |  |     //    return
 | 
	
		
			
				|  |  |     // }
 | 
	
		
			
				|  |  | -   // 计算 下一个音符index 在pointsPos 中的距离
 | 
	
		
			
				|  |  | -   const nextPointsIndex = (activeIndex + 1) * (_numberOfSegments + 1) - 1
 | 
	
		
			
				|  |  | -   // 百分比转为当前的index 距离个数
 | 
	
		
			
				|  |  | -   const progressCalcIndex = Math.round(progress * _numberOfSegments)
 | 
	
		
			
				|  |  | -   // // 当前的index
 | 
	
		
			
				|  |  | -   let nowIndex = nextPointsIndex - _numberOfSegments + progressCalcIndex
 | 
	
		
			
				|  |  | +   const nowIndex = dataFindIndex(activeIndex, progress)
 | 
	
		
			
				|  |  |     const nowPointsPos = smoothAnimationState.pointsPos[nowIndex]
 | 
	
		
			
				|  |  | -   smoothAnimationState.canvasCtx?.clearRect(0, 0, smoothAnimationState.canvasDomWith, smoothAnimationState.canvasDomHeight)
 | 
	
		
			
				|  |  | +   // 当x的值为null和undefinedde的时候 错误 不走下面的方法
 | 
	
		
			
				|  |  | +   if (!(nowPointsPos?.x != null)) {
 | 
	
		
			
				|  |  | +      console.error(nowPointsPos?.x, "nowPointsPos", nowIndex, activeIndex)
 | 
	
		
			
				|  |  | +      return
 | 
	
		
			
				|  |  | +   }
 | 
	
		
			
				|  |  |     // 移动
 | 
	
		
			
				|  |  |     smoothAnimationMove(
 | 
	
		
			
				|  |  |        {
 | 
	
		
			
				|  |  | -         x: nowPointsPos.x - 18,
 | 
	
		
			
				|  |  | +         x: nowPointsPos.x - 18, //鸟的大小
 | 
	
		
			
				|  |  |           y: nowPointsPos.y - 23
 | 
	
		
			
				|  |  |        },
 | 
	
		
			
				|  |  | -      smoothAnimationState.pointsPos,
 | 
	
		
			
				|  |  |        smoothAnimationState.pointsPos.slice(0, nowIndex)
 | 
	
		
			
				|  |  |     )
 | 
	
		
			
				|  |  |     // 当移动到屏幕最右边时候 就不进行移动了    存在移动到屏幕最右边时候  有反复的情况需要屏幕移动。所以这里注释掉了
 | 
	
	
		
			
				|  | @@ -187,7 +215,7 @@ export function moveSmoothAnimation(progress: number, activeIndex: number, isMov
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  function move_osmd(nowPointsPos: pointsPosType[0]) {
 | 
	
		
			
				|  |  |     // 评测移动太快看不到前面小节的分数,评测改成0.5倍速移动谱面
 | 
	
		
			
				|  |  | -   const speed = (state.modeType === 'evaluating' ? smoothAnimationState.aveSpeed * 0.5 : smoothAnimationState.aveSpeed) * (state.speed / 60)
 | 
	
		
			
				|  |  | +   const speed = (state.modeType === "evaluating" ? smoothAnimationState.aveSpeed * 0.5 : smoothAnimationState.aveSpeed) * (state.speed / 60)
 | 
	
		
			
				|  |  |     // 视口宽度
 | 
	
		
			
				|  |  |     const clientWidth = smoothAnimationState.osdmScrollDomWith
 | 
	
		
			
				|  |  |     const clientMidWidth = clientWidth / 2
 | 
	
	
		
			
				|  | @@ -258,17 +286,11 @@ export function moveTranslateXNum(translateXNum: number) {
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * 进度条和块移动方法
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -function smoothAnimationMove(pos: { x: number; y: number }, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
 | 
	
		
			
				|  |  | +function smoothAnimationMove(pos: { x: number; y: number }, progresspointsPos: pointsPosType) {
 | 
	
		
			
				|  |  |     smoothAnimationState.smoothBotDom && (smoothAnimationState.smoothBotDom.style.transform = `translate(${pos.x}px, ${pos.y}px)`)
 | 
	
		
			
				|  |  | -   smoothAnimationState.canvasCtx && drawSmoothCurve(smoothAnimationState.canvasCtx, pointsPos, progresspointsPos)
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * 计算视口宽度
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -export function calcClientWidth() {
 | 
	
		
			
				|  |  | -   smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom?.offsetWidth || 0
 | 
	
		
			
				|  |  | -   smoothAnimationState.osdmScrollDomOffsetLeft = smoothAnimationState.osdmScrollDom?.getBoundingClientRect().left || 0
 | 
	
		
			
				|  |  | +   smoothAnimationState.canvasCtx && drawSmoothCurveProgress(smoothAnimationState.canvasCtx, progresspointsPos, "#FFC121")
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * 创建dom
 | 
	
		
			
				|  |  |   */
 | 
	
	
		
			
				|  | @@ -298,7 +320,11 @@ function createSmoothAnimation() {
 | 
	
		
			
				|  |  |     smoothAnimationState.canvasDomWith = osmdCanvasPageDom?.offsetWidth || 0
 | 
	
		
			
				|  |  |     smoothCanvasDom.width = smoothAnimationState.canvasDomWith
 | 
	
		
			
				|  |  |     smoothCanvasDom.height = smoothAnimationState.canvasDomHeight
 | 
	
		
			
				|  |  | -   smoothAnimationState.canvasCtx = smoothCanvasDom.getContext("2d")
 | 
	
		
			
				|  |  | +   const ctx = smoothCanvasDom.getContext("2d")!
 | 
	
		
			
				|  |  | +   smoothAnimationState.canvasCtx = ctx
 | 
	
		
			
				|  |  | +   ctx.imageSmoothingEnabled = true
 | 
	
		
			
				|  |  | +   ctx.lineCap = "round"
 | 
	
		
			
				|  |  | +   ctx.lineJoin = "round"
 | 
	
		
			
				|  |  |     // bot
 | 
	
		
			
				|  |  |     const smoothBotDom = document.createElement("div")
 | 
	
		
			
				|  |  |     smoothBotDom.className = "smoothBot"
 | 
	
	
		
			
				|  | @@ -313,30 +339,40 @@ function createSmoothAnimation() {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  | + * 计算视口宽度
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +export function calcClientWidth() {
 | 
	
		
			
				|  |  | +   smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom?.offsetWidth || 0
 | 
	
		
			
				|  |  | +   smoothAnimationState.osdmScrollDomOffsetLeft = smoothAnimationState.osdmScrollDom?.getBoundingClientRect().left || 0
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  |   * 根据音符获取坐标
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  function getPointsPosByBatePos(): pointsPosType {
 | 
	
		
			
				|  |  | -   let totalAvInde = 0
 | 
	
		
			
				|  |  | -   // 取平均值
 | 
	
		
			
				|  |  | -   const totalAv =
 | 
	
		
			
				|  |  | -      state.times.reduce((total, item) => {
 | 
	
		
			
				|  |  | -         if (item.frequency !== -1) {
 | 
	
		
			
				|  |  | -            // -1 为休止符
 | 
	
		
			
				|  |  | -            total += item.frequency
 | 
	
		
			
				|  |  | -            totalAvInde++
 | 
	
		
			
				|  |  | -         }
 | 
	
		
			
				|  |  | -         return total
 | 
	
		
			
				|  |  | -      }, 0) / totalAvInde
 | 
	
		
			
				|  |  | -   const pointsPos = state.times.reduce((posArr: any[], item) => {
 | 
	
		
			
				|  |  | -      // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
 | 
	
		
			
				|  |  | -      if (item.bbox) {
 | 
	
		
			
				|  |  | +   // 得到音符频率数据
 | 
	
		
			
				|  |  | +   const frequencyData = state.times.map(item => {
 | 
	
		
			
				|  |  | +      return !item.frequency || item.frequency === -1 ? 0 : item.frequency
 | 
	
		
			
				|  |  | +   })
 | 
	
		
			
				|  |  | +   // 线性频率数据
 | 
	
		
			
				|  |  | +   const frequencyLineData = quantileScale(frequencyData, 8, _canvasDomHeight - 8) // 最小值和最大值
 | 
	
		
			
				|  |  | +   const pointsPos = state.times.reduce((posArr: any[], item, index) => {
 | 
	
		
			
				|  |  | +      // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox)
 | 
	
		
			
				|  |  | +      if (item.bbox?.x != null && item.noteId != null) {
 | 
	
		
			
				|  |  |           posArr.push({
 | 
	
		
			
				|  |  | +            noteId: item.noteId,
 | 
	
		
			
				|  |  |              MeasureNumberXML: item.MeasureNumberXML,
 | 
	
		
			
				|  |  |              x: item.bbox.x,
 | 
	
		
			
				|  |  | -            // 当为休止符的时候 取最下面的位置*0.9,确保能显示完整
 | 
	
		
			
				|  |  | -            y:
 | 
	
		
			
				|  |  | -               smoothAnimationState.canvasDomHeight / 2 -
 | 
	
		
			
				|  |  | -               ((((item.frequency === -1 ? 2 * totalAv * 0.1 : item.frequency) - totalAv) / totalAv) * smoothAnimationState.canvasDomHeight) / 2
 | 
	
		
			
				|  |  | +            y: _canvasDomHeight - frequencyLineData[index]
 | 
	
		
			
				|  |  | +         })
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +         // 连续休止小节 noteId 可能为 null,所以这里取上一个音符的id
 | 
	
		
			
				|  |  | +         posArr.push({
 | 
	
		
			
				|  |  | +            // 这里当第一个音符noteId为null,找不到前一个noteId,所以兼容一下
 | 
	
		
			
				|  |  | +            noteId: item.noteId != null ? item.noteId : (posArr[posArr.length - 1]?.noteId != null ? posArr[posArr.length - 1]?.noteId : -1) + 0.01, // 这里+0.01 是制造一个假id
 | 
	
		
			
				|  |  | +            MeasureNumberXML: item.MeasureNumberXML,
 | 
	
		
			
				|  |  | +            x: item.bbox?.x != null ? item.bbox.x : posArr[posArr.length - 1]?.x || 10,
 | 
	
		
			
				|  |  | +            y: _canvasDomHeight - frequencyLineData[index]
 | 
	
		
			
				|  |  |           })
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        return posArr
 | 
	
	
		
			
				|  | @@ -345,156 +381,86 @@ function getPointsPosByBatePos(): pointsPosType {
 | 
	
		
			
				|  |  |     const extendPoint = {
 | 
	
		
			
				|  |  |        ...pointsPos[pointsPos.length - 1]
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  | -   extendPoint.MeasureNumberXML++
 | 
	
		
			
				|  |  | +   extendPoint.MeasureNumberXML += 100 // 防止MeasureNumberXML重复
 | 
	
		
			
				|  |  | +   extendPoint.noteId += 100 // 防止noteId重复
 | 
	
		
			
				|  |  |     // 当总长度减30小于最后一个音符时候,取最后一个音符加15
 | 
	
		
			
				|  |  |     extendPoint.x = smoothAnimationState.canvasDomWith - 30 > extendPoint.x ? smoothAnimationState.canvasDomWith - 30 : extendPoint.x + 15
 | 
	
		
			
				|  |  |     pointsPos.push(extendPoint)
 | 
	
		
			
				|  |  |     return pointsPos
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// 数据平滑算法
 | 
	
		
			
				|  |  | +function quantileScale(data: number[], minRange = 0, maxRange = _canvasDomHeight) {
 | 
	
		
			
				|  |  | +   const sortedData = [...data].sort((a, b) => a - b)
 | 
	
		
			
				|  |  | +   return data.map(value => {
 | 
	
		
			
				|  |  | +      const rank = sortedData.indexOf(value) / (sortedData.length - 1)
 | 
	
		
			
				|  |  | +      const scaledValue = rank * (maxRange - minRange) + minRange
 | 
	
		
			
				|  |  | +      return Math.max(minRange, Math.min(scaledValue, maxRange))
 | 
	
		
			
				|  |  | +   })
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * 使用传入的曲线的顶点坐标创建平滑曲线的顶点。
 | 
	
		
			
				|  |  |   * @param  {Array}   points  曲线顶点坐标数组,
 | 
	
		
			
				|  |  | - * @param  {Float}   tension 密集程度,默认为 0.5
 | 
	
		
			
				|  |  | - * @param  {Boolean} closed  是否创建闭合曲线,默认为 false
 | 
	
		
			
				|  |  |   * @param  {Int}     numberOfSegments 平滑曲线 2 个顶点间的线段数,默认为 20
 | 
	
		
			
				|  |  |   * @return {Array}   平滑曲线的顶点坐标数组
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -function createSmoothCurvePoints(pointsPos: pointsPosType, tension?: number, closed?: boolean, numberOfSegments?: number) {
 | 
	
		
			
				|  |  | -   if (pointsPos.length <= 2) {
 | 
	
		
			
				|  |  | -      return pointsPos
 | 
	
		
			
				|  |  | +function createSmoothCurvePoints(points: pointsPosType, numSegments: number) {
 | 
	
		
			
				|  |  | +   if (points.length <= 2) {
 | 
	
		
			
				|  |  | +      return points
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  | -   tension = tension ? tension : 0.5
 | 
	
		
			
				|  |  | -   closed = closed ? true : false
 | 
	
		
			
				|  |  | -   numberOfSegments = numberOfSegments ? numberOfSegments : 20
 | 
	
		
			
				|  |  | -   let ps = pointsPos.slice(0),
 | 
	
		
			
				|  |  | -      result = [],
 | 
	
		
			
				|  |  | -      x,
 | 
	
		
			
				|  |  | -      y,
 | 
	
		
			
				|  |  | -      t1x,
 | 
	
		
			
				|  |  | -      t2x,
 | 
	
		
			
				|  |  | -      t1y,
 | 
	
		
			
				|  |  | -      t2y,
 | 
	
		
			
				|  |  | -      c1,
 | 
	
		
			
				|  |  | -      c2,
 | 
	
		
			
				|  |  | -      c3,
 | 
	
		
			
				|  |  | -      c4,
 | 
	
		
			
				|  |  | -      st,
 | 
	
		
			
				|  |  | -      t,
 | 
	
		
			
				|  |  | -      i
 | 
	
		
			
				|  |  | -   if (closed) {
 | 
	
		
			
				|  |  | -      ps.unshift(pointsPos[pointsPos.length - 1])
 | 
	
		
			
				|  |  | -      ps.unshift(pointsPos[pointsPos.length - 1])
 | 
	
		
			
				|  |  | -      ps.push(pointsPos[0])
 | 
	
		
			
				|  |  | -   } else {
 | 
	
		
			
				|  |  | -      ps.unshift(pointsPos[0])
 | 
	
		
			
				|  |  | -      ps.push(pointsPos[pointsPos.length - 1])
 | 
	
		
			
				|  |  | -   }
 | 
	
		
			
				|  |  | -   for (i = 1; i < ps.length - 2; i += 1) {
 | 
	
		
			
				|  |  | -      //console.log(ps[i + 1].MeasureNumberXML, ps[i - 1].MeasureNumberXML, ps[i + 2].MeasureNumberXML, ps[i].MeasureNumberXML)
 | 
	
		
			
				|  |  | -      t1x = (ps[i + 1].x - ps[i - 1].x) * tension
 | 
	
		
			
				|  |  | -      t2x = (ps[i + 2].x - ps[i].x) * tension
 | 
	
		
			
				|  |  | -      t1y = (ps[i + 1].y - ps[i - 1].y) * tension
 | 
	
		
			
				|  |  | -      t2y = (ps[i + 2].y - ps[i].y) * tension
 | 
	
		
			
				|  |  | -      // 当中途出现反复 刚开始反复时候 53 52 22 52  (22)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
 | 
	
		
			
				|  |  | -      if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML > 1) {
 | 
	
		
			
				|  |  | -         const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
 | 
	
		
			
				|  |  | -         //在当前值的情况下 向前一位
 | 
	
		
			
				|  |  | -         let index = ps.findIndex(item => {
 | 
	
		
			
				|  |  | -            return nowNumberXML === item.MeasureNumberXML
 | 
	
		
			
				|  |  | -         })
 | 
	
		
			
				|  |  | -         // 查询不到index时候取当前值
 | 
	
		
			
				|  |  | -         index === -1 && (index = i + 1)
 | 
	
		
			
				|  |  | -         t2x = (ps[index].x - ps[i].x) * tension
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      // 当中途出现反复 结束反复时候 22 53 22 22  (53)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
 | 
	
		
			
				|  |  | -      if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML > 1) {
 | 
	
		
			
				|  |  | -         //在当前值的情况下 向后一位
 | 
	
		
			
				|  |  | -         const nowNumberXML = ps[i].MeasureNumberXML - 1
 | 
	
		
			
				|  |  | -         let index = ps.findIndex((item, index) => {
 | 
	
		
			
				|  |  | -            return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
 | 
	
		
			
				|  |  | -         })
 | 
	
		
			
				|  |  | -         // 查询不到index时候取当前值
 | 
	
		
			
				|  |  | -         index === -1 && (index = i)
 | 
	
		
			
				|  |  | -         t1x = (ps[i + 1].x - ps[index].x) * tension
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      // 当中途出现跳房子 刚开始跳房子时候 35 35 54 35  (54)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
 | 
	
		
			
				|  |  | -      if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML < -1) {
 | 
	
		
			
				|  |  | -         const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
 | 
	
		
			
				|  |  | -         //在当前值的情况下 向前一位
 | 
	
		
			
				|  |  | -         let index = ps.findIndex(item => {
 | 
	
		
			
				|  |  | -            return nowNumberXML === item.MeasureNumberXML
 | 
	
		
			
				|  |  | -         })
 | 
	
		
			
				|  |  | -         // 查询不到index时候取当前值
 | 
	
		
			
				|  |  | -         index === -1 && (index = i + 1)
 | 
	
		
			
				|  |  | -         t2x = (ps[index].x - ps[i].x) * tension
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      // 当中途出现跳房子 结束跳房子时候 54 35 54 54  (35)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
 | 
	
		
			
				|  |  | -      if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML < -1) {
 | 
	
		
			
				|  |  | -         const nowNumberXML = ps[i].MeasureNumberXML - 1
 | 
	
		
			
				|  |  | -         let index = ps.findIndex((item, index) => {
 | 
	
		
			
				|  |  | -            return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
 | 
	
		
			
				|  |  | -         })
 | 
	
		
			
				|  |  | -         // 查询不到index时候取当前值
 | 
	
		
			
				|  |  | -         index === -1 && (index = i)
 | 
	
		
			
				|  |  | -         t1x = (ps[i + 1].x - ps[index].x) * tension
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      const nowMeasureNumberXML = pointsPos[i - 1].MeasureNumberXML
 | 
	
		
			
				|  |  | -      const nextMeasureNumberXML = pointsPos[i].MeasureNumberXML
 | 
	
		
			
				|  |  | -      for (t = 0; t <= numberOfSegments; t++) {
 | 
	
		
			
				|  |  | -         // 小于1时候是反复   大于1是跳房子  不画曲线  停留
 | 
	
		
			
				|  |  | -         if (nextMeasureNumberXML - nowMeasureNumberXML < 0 || nextMeasureNumberXML - nowMeasureNumberXML > 1) {
 | 
	
		
			
				|  |  | -            //console.log(x, y)
 | 
	
		
			
				|  |  | -            result.push({
 | 
	
		
			
				|  |  | -               x: x as number,
 | 
	
		
			
				|  |  | -               y: y as number,
 | 
	
		
			
				|  |  | -               MeasureNumberXML: nowMeasureNumberXML
 | 
	
		
			
				|  |  | -            })
 | 
	
		
			
				|  |  | -            continue
 | 
	
		
			
				|  |  | -         }
 | 
	
		
			
				|  |  | -         st = t / numberOfSegments
 | 
	
		
			
				|  |  | -         c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1
 | 
	
		
			
				|  |  | -         c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2)
 | 
	
		
			
				|  |  | -         c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st
 | 
	
		
			
				|  |  | -         c4 = Math.pow(st, 3) - Math.pow(st, 2)
 | 
	
		
			
				|  |  | -         x = c1 * ps[i].x + c2 * ps[i + 1].x + c3 * t1x + c4 * t2x
 | 
	
		
			
				|  |  | -         y = c1 * ps[i].y + c2 * ps[i + 1].y + c3 * t1y + c4 * t2y
 | 
	
		
			
				|  |  | -         //console.log(x, y)
 | 
	
		
			
				|  |  | -         result.push({
 | 
	
		
			
				|  |  | -            x,
 | 
	
		
			
				|  |  | -            y,
 | 
	
		
			
				|  |  | -            MeasureNumberXML: t === numberOfSegments ? nextMeasureNumberXML : nowMeasureNumberXML
 | 
	
		
			
				|  |  | -         })
 | 
	
		
			
				|  |  | +   const curvePoints = []
 | 
	
		
			
				|  |  | +   for (let i = 0; i < points.length - 1; i++) {
 | 
	
		
			
				|  |  | +      const p0 = i > 0 ? points[i - 1] : points[i]
 | 
	
		
			
				|  |  | +      const p1 = points[i]
 | 
	
		
			
				|  |  | +      const p2 = points[i + 1]
 | 
	
		
			
				|  |  | +      const p3 = i !== points.length - 2 ? points[i + 2] : points[i + 1]
 | 
	
		
			
				|  |  | +      for (let j = 0; j < numSegments; j++) {
 | 
	
		
			
				|  |  | +         const t = j / numSegments
 | 
	
		
			
				|  |  | +         const t2 = t * t
 | 
	
		
			
				|  |  | +         const t3 = t2 * t
 | 
	
		
			
				|  |  | +         const x = 0.5 * (2 * p1.x + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3)
 | 
	
		
			
				|  |  | +         const y = 0.5 * (2 * p1.y + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3)
 | 
	
		
			
				|  |  | +         curvePoints.push({ x, y, MeasureNumberXML: p1.MeasureNumberXML, noteId: p1.noteId })
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  | -   return result
 | 
	
		
			
				|  |  | +   return curvePoints
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/** 初始化一条完整的旋律线dom */
 | 
	
		
			
				|  |  | +function initCanvasSmooth() {
 | 
	
		
			
				|  |  | +   const smoothDom = document.createElement("canvas")
 | 
	
		
			
				|  |  | +   smoothDom.width = smoothAnimationState.canvasDomWith
 | 
	
		
			
				|  |  | +   smoothDom.height = smoothAnimationState.canvasDomHeight
 | 
	
		
			
				|  |  | +   const smoothDomCtx = smoothDom.getContext("2d")!
 | 
	
		
			
				|  |  | +   smoothDomCtx.imageSmoothingEnabled = true
 | 
	
		
			
				|  |  | +   smoothDomCtx.lineCap = "round"
 | 
	
		
			
				|  |  | +   smoothDomCtx.lineJoin = "round"
 | 
	
		
			
				|  |  | +   // 根据坐标花线
 | 
	
		
			
				|  |  | +   drawLines(smoothDomCtx, smoothAnimationState.pointsPos, "rgba(255,255,255,0.6)")
 | 
	
		
			
				|  |  | +   smoothAnimationState.canvasSmoothDom = smoothDom
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * 根据进度画线
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +function drawSmoothCurveProgress(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
 | 
	
		
			
				|  |  | +   context.clearRect(0, 0, smoothAnimationState.canvasDomWith, smoothAnimationState.canvasDomHeight)
 | 
	
		
			
				|  |  | +   smoothAnimationState.canvasSmoothDom && context.drawImage(smoothAnimationState.canvasSmoothDom, 0, 0)
 | 
	
		
			
				|  |  | +   drawLines(context, pointsPos, color)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * 根据坐标划线
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -function drawSmoothCurve(context: CanvasRenderingContext2D, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
 | 
	
		
			
				|  |  | +function drawLines(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
 | 
	
		
			
				|  |  | +   if (pointsPos.length === 0) return
 | 
	
		
			
				|  |  |     context.lineWidth = 2
 | 
	
		
			
				|  |  | -   context.lineJoin = "round" // 优化锯齿
 | 
	
		
			
				|  |  | -   context.lineCap = "round" // 优化锯齿
 | 
	
		
			
				|  |  | -   context.strokeStyle = "rgba(255,255,255,0.6)"
 | 
	
		
			
				|  |  | -   drawLines(context, pointsPos)
 | 
	
		
			
				|  |  | -   if (progresspointsPos?.length) {
 | 
	
		
			
				|  |  | -      context.strokeStyle = "#FFC121"
 | 
	
		
			
				|  |  | -      drawLines(context, progresspointsPos)
 | 
	
		
			
				|  |  | -   }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -function drawLines(context: CanvasRenderingContext2D, points: pointsPosType) {
 | 
	
		
			
				|  |  | +   context.strokeStyle = color
 | 
	
		
			
				|  |  |     context.beginPath()
 | 
	
		
			
				|  |  | -   context.moveTo(points[0].x, points[0].y)
 | 
	
		
			
				|  |  | -   for (let i = 1; i < points.length - 1; i++) {
 | 
	
		
			
				|  |  | -      if (Math.abs(points[i].MeasureNumberXML - points[i - 1].MeasureNumberXML) > 1) {
 | 
	
		
			
				|  |  | -         // 取消反复和跳房子连线
 | 
	
		
			
				|  |  | -         context.stroke()
 | 
	
		
			
				|  |  | -         context.beginPath()
 | 
	
		
			
				|  |  | -         context.moveTo(points[i + 1].x, points[i + 1].y)
 | 
	
		
			
				|  |  | -         continue
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      context.lineTo(points[i].x, points[i].y)
 | 
	
		
			
				|  |  | +   context.moveTo(pointsPos[0].x, pointsPos[0].y)
 | 
	
		
			
				|  |  | +   for (let i = 1; i < pointsPos.length; i++) {
 | 
	
		
			
				|  |  | +      context.lineTo(pointsPos[i].x, pointsPos[i].y)
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     context.stroke()
 | 
	
		
			
				|  |  |  }
 |