|
@@ -42,6 +42,7 @@ export const metronomeData = reactive({
|
|
|
cursorTips: '' as string, // 光标模式提示文字
|
|
|
followAudioIndex: 1, // 当前的拍数
|
|
|
totalNumerator: 2, // 总拍数
|
|
|
+ firstBeatTypeArr:[] as number[] // 第一小节的节拍
|
|
|
});
|
|
|
|
|
|
watch(
|
|
@@ -331,58 +332,27 @@ class Metronome {
|
|
|
const m = {
|
|
|
measureNumberXML: measureNumberXML,
|
|
|
measureNumberIndex: measureListIndex,
|
|
|
+ CompoundTempo: note?.noteElement?.sourceMeasure?.CompoundTempo || "",
|
|
|
numerator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.numerator || 0,
|
|
|
+ denominator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.denominator || 0,
|
|
|
start: startTime,
|
|
|
end: noteEndTime,
|
|
|
time: noteEndTime - startTime,
|
|
|
stave_x: note?.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.stave?.x || 0,
|
|
|
- end_x: note?.stave?.end_x || 0 || 0,
|
|
|
+ end_x: note?.stave?.end_x || 0,
|
|
|
stepList: [] as number[],
|
|
|
svgs: [] as any[],
|
|
|
isRestFlag: note.isRestFlag,
|
|
|
};
|
|
|
- // 2.统计小节的拍数
|
|
|
- // 3.统计小节的时长, 开始时间,结束时间
|
|
|
- // console.log(measureNumberXML,note.measures, times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex))
|
|
|
- if ([121].includes(state.subjectId)) {
|
|
|
- const _measures = times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex);
|
|
|
- note.measures = _measures;
|
|
|
- m.start = note.measures[0].time;
|
|
|
- m.end = note.measures[note.measures.length - 1].endtime;
|
|
|
- m.time = note.measures[note.measures.length - 1].endtime - note.measures[0].time;
|
|
|
- try {
|
|
|
- const tickables = note.noteElement.sourceMeasure.verticalMeasureList.reduce((arr: any[], value: any) => {
|
|
|
- arr.push(...value.vfVoices["1"].tickables);
|
|
|
- return arr;
|
|
|
- }, []);
|
|
|
- const xList: any[] = [];
|
|
|
- m.svgs = tickables
|
|
|
- .map((n: any) => {
|
|
|
- const x = n.getBoundingBox().x;
|
|
|
- if (!xList.includes(x) && n.duration !== "w") {
|
|
|
- xList.push(x);
|
|
|
- n._start_x = x;
|
|
|
- return n;
|
|
|
- }
|
|
|
- })
|
|
|
- .filter(Boolean)
|
|
|
- .sort((a: any, b: any) => a._start_x - b._start_x);
|
|
|
- // console.log(measureNumberXML, m.svgs)
|
|
|
- } catch (error) {
|
|
|
- console.log(error);
|
|
|
- }
|
|
|
- m.stepList = calculateMutilpleMetroStep(note.measures, m);
|
|
|
- } else {
|
|
|
- /**
|
|
|
- * bug:#9877
|
|
|
- * 多分轨合并显示,不同分轨的音符数量可能不同
|
|
|
- */
|
|
|
- let measureArr = note.measures;
|
|
|
- if (state.isCombineRender) {
|
|
|
- measureArr = measureArr.filter((item: any) => item.MeasureNumberXML === m.measureNumberXML)
|
|
|
- }
|
|
|
- m.stepList = calculateMetroStep(measureArr, m);
|
|
|
+ /**
|
|
|
+ * bug:#9877
|
|
|
+ * 多分轨合并显示,不同分轨的音符数量可能不同
|
|
|
+ */
|
|
|
+ let measureArr = note.measures;
|
|
|
+ if (state.isCombineRender) {
|
|
|
+ measureArr = measureArr.filter((item: any) => item.MeasureNumberXML === m.measureNumberXML)
|
|
|
}
|
|
|
+ m.stepList = calculateMetroStep(measureArr, m);
|
|
|
measures.push(m);
|
|
|
xmlNumber = measureNumberXML;
|
|
|
}
|
|
@@ -403,30 +373,57 @@ class Metronome {
|
|
|
try {
|
|
|
for (let i = 0; i < measures.length; i++) {
|
|
|
const measure = measures[i];
|
|
|
- const noteStep = measure.time / measure.numerator;
|
|
|
- // console.log("🚀 ~ measure.measureNumberXML",measure.measureNumberXML, noteStep)
|
|
|
- const WIDTH = [121].includes(state.subjectId) ? 95 : 100;
|
|
|
- const widthStep = WIDTH / (measure.numerator + 1);
|
|
|
+ // 87拍和45拍要根据小节返回的CompoundTempo特殊处理
|
|
|
+ const beatTypeArr = getBeatTypeArr(measure.numerator, measure.denominator, measure.CompoundTempo)
|
|
|
+ const CompoundTempoArr = beatTypeArr.map((beatType:number) => {
|
|
|
+ return Math.abs(beatType*measure.numerator)
|
|
|
+ })
|
|
|
+ if(i===0){
|
|
|
+ metronomeData.firstBeatTypeArr = beatTypeArr
|
|
|
+ }
|
|
|
metroMeasure[i] = [] as number[];
|
|
|
+ // 根据有几个拍子,划分成几份
|
|
|
+ const widthStep = 100 / (beatTypeArr.length+1);
|
|
|
+ // 当前拍子的组合数(2+3+2,3+2)中的数字
|
|
|
+ let beatNum = 0;
|
|
|
// console.log('stepList', [...measure.stepList], measure.measureNumberXML)
|
|
|
- for (let j = 0; j < measure.numerator; j++) {
|
|
|
- const time = noteStep * j + measure.start;
|
|
|
+ for (let j = 0; j < beatTypeArr.length; j++) {
|
|
|
+ // 累加
|
|
|
+ const beatMuit = Array(j).fill("").reduce((num:number,v:any,i:number) => {
|
|
|
+ return num += Math.abs(beatTypeArr[i])
|
|
|
+ }, 0) || 0
|
|
|
+ const time = measure.time * beatMuit + measure.start;
|
|
|
metroList.push(time);
|
|
|
let left = "";
|
|
|
- if (measure.stepList[j]) {
|
|
|
- left = measure.stepList[j] + "px";
|
|
|
+ // 当前拍子数对应的节拍位置索引
|
|
|
+ let currentIdx = 0;
|
|
|
+ if (j == 0) {
|
|
|
+ currentIdx = 0
|
|
|
+ } else {
|
|
|
+ beatNum += CompoundTempoArr[j-1];
|
|
|
+ currentIdx = beatNum
|
|
|
+ }
|
|
|
+ // 如果是87拍,并且是3+2+2的组合,第二拍的节拍指针需要定位到第四个音符的位置
|
|
|
+ // if (measure.numerator === 7 && measure.denominator === 8 && measure.CompoundTempo === '3+2+2' && j === 1) {
|
|
|
+ // currentIdx += 1;
|
|
|
+ // }
|
|
|
+ if (measure.stepList[currentIdx]) {
|
|
|
+ left = measure.stepList[currentIdx] + "px";
|
|
|
} else {
|
|
|
const preLeft = measure.stepList[j - 1];
|
|
|
- left = !preLeft ? `${widthStep}%` : preLeft.toString().indexOf("%") > -1 ? `${preLeft} + ${widthStep}%` : `${preLeft}px + ${widthStep}%`;
|
|
|
+ left = !preLeft || preLeft.toString().indexOf("%") > -1 ? `${widthStep*(j+1)}%` : `${preLeft}px + ${widthStep}%`;
|
|
|
measure.stepList[j] = left;
|
|
|
- }
|
|
|
+ }
|
|
|
metroMeasure[i].push({
|
|
|
+ isTick: beatTypeArr[j] < 0,
|
|
|
index: j,
|
|
|
time,
|
|
|
// left: (measure.stepList[j] ? measure.stepList[j] + 'px' : (j + 1) * widthStep + '%'),
|
|
|
left: left?.indexOf("%") > -1 ? `calc(${left})` : left,
|
|
|
measureNumberXML: measure.measureNumberXML,
|
|
|
isRestFlag: measure.isRestFlag,
|
|
|
+ stepList: measure.stepList,
|
|
|
+ isPercent: left?.indexOf("%") > -1, // 是否是根据小节宽度等分
|
|
|
});
|
|
|
}
|
|
|
}
|
|
@@ -442,6 +439,63 @@ class Metronome {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/** 获取节拍类型数组 */
|
|
|
+export function getBeatTypeArr(numerator?:number, denominator?:number, CompoundTempo?:string){
|
|
|
+ const speedBeatUnit = state.speedBeatUnit
|
|
|
+ const Numerator = numerator || state.osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Numerator || 4
|
|
|
+ const Denominator = denominator || state.osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Denominator || 4
|
|
|
+ let loopArr = []
|
|
|
+ // 规则 负数代表重声,正数代表轻声
|
|
|
+ switch (`${Numerator}/${Denominator}`) {
|
|
|
+ case "2/2":
|
|
|
+ loopArr = [-1/2, 1/2]
|
|
|
+ break;
|
|
|
+ case "3/2":
|
|
|
+ loopArr = [-1/3, 1/3, 1/3]
|
|
|
+ break;
|
|
|
+ case "5/4":
|
|
|
+ //5/4拍根据谱面的CompoundTempo来特殊处理
|
|
|
+ if(CompoundTempo==="2+3"){
|
|
|
+ loopArr = [-1/5, 1/5, -1/5, 1/5, 1/5]
|
|
|
+ }else{
|
|
|
+ loopArr = [-1/5, 1/5, 1/5, -1/5, 1/5]
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case "3/8":
|
|
|
+ // 3/8拍 速度为浮点4分音符时候特殊处理
|
|
|
+ if(speedBeatUnit==="1/4."){
|
|
|
+ loopArr = [-1/1]
|
|
|
+ }else{
|
|
|
+ loopArr = [-1/3, 1/3, 1/3]
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case "6/8":
|
|
|
+ loopArr = [-1/2, 1/2]
|
|
|
+ break;
|
|
|
+ case "7/8":
|
|
|
+ //7/8拍根据谱面的CompoundTempo来特殊处理
|
|
|
+ if(CompoundTempo==="2+2+3"){
|
|
|
+ loopArr = [-2/7, 2/7, 3/7]
|
|
|
+ }else if(CompoundTempo==="2+3+2"){
|
|
|
+ loopArr = [-2/7, 3/7, 2/7]
|
|
|
+ }else{
|
|
|
+ loopArr = [-3/7, 2/7, 2/7]
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case "9/8":
|
|
|
+ loopArr = [-3/9, 3/9, 3/9]
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ loopArr.push(-1/Numerator);
|
|
|
+ for (let i = 1; i < Numerator; i++) {
|
|
|
+ loopArr.push(1/Numerator);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // console.log(loopArr, "loopArr")
|
|
|
+ return loopArr
|
|
|
+}
|
|
|
+
|
|
|
// 计算拍子的时值
|
|
|
function calculateMetroStep(arr: any[], m: any): number[] {
|
|
|
const measureLength = arr.reduce((total: number, item: any) => {
|