Browse Source

管乐迷12280

liushengqiang 1 year ago
parent
commit
cb907e7ac0

+ 210 - 0
src/constant/instruments.ts

@@ -0,0 +1,210 @@
+const instruments: any = {
+	"Acoustic Grand Piano": "大钢琴",
+	"Bright Acoustic Piano": "明亮的钢琴",
+	"Electric Grand Piano": "电钢琴",
+	"Rhodes Piano": "柔和的电钢琴",
+	"Chorused Piano": "加合唱效果的电钢琴",
+	Harpsichord: "羽管键琴",
+	Clavichord: "科拉维科特琴",
+	Celesta: "钢片琴",
+	Glockenspiel: "钢片琴",
+	"Music box": "八音盒",
+	Vibraphone: "颤音琴",
+	Marimba: "马林巴",
+	Xylophone: "木琴",
+	"Tubular Bells": "管钟",
+	Dulcimer: "大扬琴",
+	"Hammond Organ": "击杆风琴",
+	"Percussive Organ": "打击式风琴",
+	"Rock Organ": "摇滚风琴",
+	"Church Organ": "教堂风琴",
+	"Reed Organ": "簧管风琴",
+	Accordian: "手风琴",
+	Harmonica: "口琴",
+	"Tango Accordian": "探戈手风琴",
+	"Acoustic Guitar": "钢弦吉他",
+	"Electric Guitar": "闷音电吉他",
+	"Overdriven Guitar": "加驱动效果的电吉他",
+	"Distortion Guitar": "加失真效果的电吉他",
+	"Guitar Harmonics": "吉他和音",
+	"Acoustic Bass": "大贝司",
+	"Electric Bass": "电贝司",
+	"Fretless Bass": "无品贝司",
+	"Slap Bass": "掌击",
+	"Synth Bass": "电子合成",
+	Violin: "小提琴",
+	Viola: "中提琴",
+	Cello: "大提琴",
+	Contrabass: "低音大提琴",
+	"Tremolo Strings": "弦乐群颤音音色",
+	"Pizzicato Strings": "弦乐群拨弦音色",
+	"Orchestral Harp": "竖琴",
+	Timpani: "定音鼓",
+	"String Ensemble": "弦乐合奏音色",
+	"Synth Strings": "合成弦乐合奏音色",
+	"Choir Aahs": "人声合唱",
+	"Voice Oohs": "人声",
+	"Synth Voice": "合成人声",
+	"Orchestra Hit": "管弦乐敲击齐奏",
+	Trumpet: "小号",
+	Trombone: "长号",
+	Tuba: "大号",
+	"Muted Trumpet": "加弱音器小号",
+	"French Horn": "法国号",
+	"Brass Section": "铜管组",
+	"Synth Brass": "合成铜管音色",
+	"Soprano Sax": "高音萨克斯管",
+	"Alto Sax": "中音萨克斯管",
+	"Tenor Sax": "次中音萨克斯管",
+	"Baritone Sax": "低音萨克斯管",
+	Oboe: "双簧管",
+	"English Horn": "英国管",
+	Bassoon: "巴松",
+	Clarinet: "单簧管",
+	"Soprano Saxophone": "高音萨克斯管",
+	"Alto Saxophone": "中音萨克斯管",
+	"Tenor Saxophone": "次中音萨克斯管",
+	"Baritone Saxophone": "低音萨克斯管",
+	Piccolo: "短笛",
+	Flute: "长笛",
+	Recorder: "竖笛",
+	"Soprano Recorder": "高音竖笛",
+	"Pan Flute": "排箫",
+	"Bottle Blow": "瓶木管",
+	Whistle: "口哨声",
+	Ocarina: "陶笛",
+	Lead: "合成主音",
+	"Lead lead": "合成主音",
+	"Pad age": "合成音色",
+	Pad: "合成音色",
+	FX: "合成效果  科幻",
+	Sitar: "西塔尔",
+	Banjo: "班卓琴",
+	Shamisen: "三昧线",
+	Koto: "十三弦筝",
+	Kalimba: "卡林巴",
+	Bagpipe: "风笛",
+	Fiddle: "民族提琴",
+	Shanai: "山奈",
+	"Tinkle Bell": "叮当铃",
+	Agogos: "阿戈戈铃",
+	"Steel Drums": "钢鼓",
+	"Taiko Drum": "太鼓",
+	"Melodic Toms": "嗵嗵鼓",
+	"Synth Drums": "合成鼓",
+	"Reverse Cymbals": "反向镲",
+	"Agogo Bells": "阿戈戈铃",
+	"Taiko Drums": "太鼓",
+	Bongos: "邦戈鼓",
+	"Bongo Bell": "邦戈铃",
+	Congas: "康加鼓",
+	Guiro: "刮壶",
+	"Guitar Fret Noise": "吉他换把杂音",
+	"Breath Noise": "呼吸声",
+	Seashore: "海浪声",
+	"Bird Tweet": "鸟鸣",
+	"Telephone Ring": "电话铃",
+	Helicopter: "直升机",
+	Applause: "鼓掌声",
+	Gunshot: "枪声",
+	"Acoustic Bass Drum": "大鼓",
+	"Bass Drum": "大鼓",
+	"Side Drum": "小鼓鼓边",
+	"Acoustic Snare": "小鼓",
+	"Hand Claps": "拍手",
+	"Electric Snare": "小鼓",
+	"Low Floor Tom": "低音嗵鼓",
+	"Closed Hi-Hat": "闭合踩镲",
+	"High Floor Tom": "高音落地嗵鼓",
+	"Pedal Hi-Hat": "脚踏踩镲",
+	"Low Tom": "低音嗵鼓",
+	"Open Hi-Hat": "开音踩镲",
+	"Low-Mid Tom": "中低音嗵鼓",
+	"Hi Mid Tom": "高音鼓",
+	"Crash Cymbals": "对镲",
+	"High Tom": "高音嗵鼓",
+	"Ride Cymbals": "叮叮镲",
+	"Chinese Cymbals": "中国镲",
+	"Ride Bell": "圆铃",
+	Tambourine: "铃鼓",
+	"Splash Cymbal": "溅音镲",
+	Cowbell: "牛铃",
+	"Crash Cymbal": "强音钹",
+	"Vibra-Slap": "颤音器",
+	"Ride Cymbal": "打点钹",
+	"Hi Bongo": "高音邦戈鼓",
+	"Low Bongo": "低音邦戈鼓",
+	"Mute Hi Conga": "弱音高音康加鼓",
+	"Open Hi Conga": "强音高音康加鼓",
+	"Low Conga": "低音康加鼓",
+	"High Timbale": "高音天巴鼓",
+	"Low Timbale": "低音天巴鼓",
+	"High Agogo": "高音阿戈戈铃",
+	"Low Agogo": "低音阿戈戈铃",
+	Cabasa: "卡巴萨",
+	Maracas: "沙锤",
+	"Short Whistle": "短口哨",
+	"Long Whistle": "长口哨",
+	"Short Guiro": "短刮壶",
+	"Long Guiro": "长刮壶",
+	Claves: "响棒",
+	"Hi Wood Block": "高音木鱼",
+	"Low Wood Block": "低音木鱼",
+	"Mute Triangle": "弱音三角铁",
+	"Open Triangle": "强音三角铁",
+	"Drum Set": "架子鼓",
+	"Hulusi flute": "葫芦丝",
+	Melodica: "口风琴",
+	"Snare Drum": "小军鼓",
+	Cymbal: "镲",
+	Cymbals: "镲",
+	"Horn in F": "圆号",
+	Triangle: "三角铁",
+	Vibrato: "颤音琴",
+	"Suspend Cymbals": "吊镲",
+	"Suspended Cymbals": "吊镲",
+	"Tom-Toms": "嗵嗵鼓",
+	Bell: "铃铛",
+	Bells: "铃铛",
+	"Alto Clarinet": "中音单簧管",
+	"Bass Clarinet": "低音单簧管",
+	Cornet: "短号",
+	Euphonium: "上低音号",
+	"crash cymbals": "对镲",
+	Castanets: "响板",
+	Shaker: "沙锤",
+	"Mark tree": "音树",
+	Chimes: "管钟",
+	"Mark Tree": "音树",
+	"Tom-toms": "嗵嗵鼓",
+	"Hi-Hat": "踩镲",
+	"Sleigh Bells": "雪橇铃",
+	Flexatone: "弹音器",
+	"Brake drum": "闸鼓",
+	Gong: "锣",
+	"concert tom": "音乐会嗵嗵鼓",
+	"brake drum": "车轮鼓",
+	"finger cymbal": "指钹",
+	"ride cymbal": "叮叮镲",
+	"Concert Toms": "音乐会嗵嗵鼓",
+	Vibraslap: "弹音器",
+	"Wood Blocks": "木鱼",
+	"Temple Blocks": "木鱼",
+	"Wood Block": "木鱼",
+	"Field Drum": "军鼓",
+	"Quad-Toms": "筒鼓",
+	Quads: "筒鼓",
+	"Drums set": "架子鼓",
+	"High Bongo": "邦戈",
+	Timbales: "天巴鼓",
+};
+/** 获取分轨名称 */
+export const getInstrumentName = (name = '') => {
+  name = name.toLocaleLowerCase().replaceAll(' ', '')
+  for(let key in instruments){
+    const _key = key.toLocaleLowerCase().replaceAll(' ', '')
+    if (_key.includes(name) || name.includes(_key)){
+      return instruments[key]
+    }
+  }
+};

+ 216 - 234
src/helpers/calcSpeed.ts

@@ -1,56 +1,56 @@
-import { OpenSheetMusicDisplay, SourceMeasure } from '../../accompany/osmd-extended/src'
+import { OpenSheetMusicDisplay, SourceMeasure } from "/osmd-extended/src";
 
 export const noteDuration = {
-  '1/2': 2,
-  w: 1,
-  h: 0.5,
-  q: 0.25,
-  '8': 0.125,
-  '16': 0.0625,
-  '32': 0.03125,
-  '64': 0.015625,
-  '128': 0.0078125,
-}
+	"1/2": 2,
+	w: 1,
+	h: 0.5,
+	q: 0.25,
+	"8": 0.125,
+	"16": 0.0625,
+	"32": 0.03125,
+	"64": 0.015625,
+	"128": 0.0078125,
+};
 
 export type GradualChange = {
-  resetXmlNoteIndex: number
-  startXmlNoteIndex: number
-  endXmlNoteIndex: number
-  startWord: string
-  startMeasureListIndex: number
-  endMeasureListIndex: number
-  resetMeasureListIndex: number
-}
+	resetXmlNoteIndex: number;
+	startXmlNoteIndex: number;
+	endXmlNoteIndex: number;
+	startWord: string;
+	startMeasureListIndex: number;
+	endMeasureListIndex: number;
+	resetMeasureListIndex: number;
+};
 
 export const speedInfo: { [key in string]: number } = {
-  'rall.': 1.333333333,
-  'poco rit.': 1.333333333,
-  'rit.': 1.333333333,
-  'molto rit.': 1.333333333,
-  'molto rall': 1.333333333,
-  'molto': 1.333333333,
-  lentando: 1.333333333,
-  allargando: 1.333333333,
-  morendo: 1.333333333,
-  'accel.': 0.8,
-  calando: 2,
-  'poco accel.': 0.8,
-  'gradually slowing': 1.333333333,
-  'slowing': 1.333333333,
-  'slow': 1.333333333,
-  'slowly': 1.333333333,
-  faster: 1.333333333,
-}
+	"rall.": 1.333333333,
+	"poco rit.": 1.333333333,
+	"rit.": 1.333333333,
+	"molto rit.": 1.333333333,
+	"molto rall": 1.333333333,
+	molto: 1.333333333,
+	lentando: 1.333333333,
+	allargando: 1.333333333,
+	morendo: 1.333333333,
+	"accel.": 0.8,
+	calando: 2,
+	"poco accel.": 0.8,
+	"gradually slowing": 1.333333333,
+	slowing: 1.333333333,
+	slow: 1.333333333,
+	slowly: 1.333333333,
+	faster: 1.333333333,
+};
 
 /**
  * 计算渐变速度
  */
-export const calcGradual = () => {}
+export const calcGradual = () => {};
 
 /**
  * 2022年9月14日版本 计算渐变速度,此方法不兼容之前的选择范围。
  */
-export const calcGradual2 = () => {}
+export const calcGradual2 = () => {};
 
 /**
  * 获取指定元素下一个Note元素
@@ -58,44 +58,44 @@ export const calcGradual2 = () => {}
  * @param selectors 选择器
  */
 const getNextNote = (ele: Element, selectors: string) => {
-  let index = 0
-  const parentEle = ele.closest(selectors)
-  let pointer = parentEle
-  const measure = parentEle?.closest('measure')
-  let siblingNote: Element | null | undefined = null
-  // 查找到相邻的第一个note元素
-  while (!siblingNote && index < (measure?.childNodes.length || 50)) {
-    index++
-    if (pointer?.nextElementSibling?.tagName === 'note') {
-      siblingNote = pointer?.nextElementSibling
-    }
-    pointer = pointer?.nextElementSibling!
-  }
-  return siblingNote
-}
+	let index = 0;
+	const parentEle = ele.closest(selectors);
+	let pointer = parentEle;
+	const measure = parentEle?.closest("measure");
+	let siblingNote: Element | null | undefined = null;
+	// 查找到相邻的第一个note元素
+	while (!siblingNote && index < (measure?.childNodes.length || 50)) {
+		index++;
+		if (pointer?.nextElementSibling?.tagName === "note") {
+			siblingNote = pointer?.nextElementSibling;
+		}
+		pointer = pointer?.nextElementSibling!;
+	}
+	return siblingNote;
+};
 
 export type GradualElement = {
-  ele: Element
-  index: number
-  noteInMeasureIndex: number
-  textContent: string
-  measureIndex: number
-  type: 'words' | 'metronome'
-  allDuration: number
-  leftDuration: number
-}
+	ele: Element;
+	index: number;
+	noteInMeasureIndex: number;
+	textContent: string;
+	measureIndex: number;
+	type: "words" | "metronome";
+	allDuration: number;
+	leftDuration: number;
+};
 
-export type GradualNote = GradualItem[]
+export type GradualNote = GradualItem[];
 
 export type GradualItem = {
-  start: number
-  measureIndex: number
-  noteInMeasureIndex: number
-  allDuration: number
-  leftDuration: number
-  type: string
-  closedMeasureIndex: number
-}
+	start: number;
+	measureIndex: number;
+	noteInMeasureIndex: number;
+	allDuration: number;
+	leftDuration: number;
+	type: string;
+	closedMeasureIndex: number;
+};
 
 // export type GradualItem = {
 //   start: number
@@ -113,173 +113,155 @@ export type GradualItem = {
  * @param xml 始终按照第一分谱进行减慢速度的计算
  */
 export const getGradualLengthByXml = (xml: string) => {
-  // const firstPartXml = onlyVisible(xml, 0)
-  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
-  const measures = Array.from(xmlParse.querySelectorAll('measure'))
-  const notes = Array.from(xmlParse.querySelectorAll('note'))
-  const words = Array.from(xmlParse.querySelectorAll('words'))
-  const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))
+	// const firstPartXml = onlyVisible(xml, 0)
+	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const measures = Array.from(xmlParse.querySelectorAll("measure"));
+	const notes = Array.from(xmlParse.querySelectorAll("note"));
+	const words = Array.from(xmlParse.querySelectorAll("words"));
+	const metronomes = Array.from(xmlParse.querySelectorAll("metronome"));
 
-  const eles: GradualElement[] = []
+	const eles: GradualElement[] = [];
 
-  for (const ele of [...words, ...metronomes]) {
-    const note = getNextNote(ele, 'direction')
-    // console.log(ele, note)
-    if (note) {
-      const measure = note?.closest('measure')!
-      const measureNotes = Array.from(measure.querySelectorAll('note'))
+	for (const ele of [...words, ...metronomes]) {
+		const note = getNextNote(ele, "direction");
+		// console.log(ele, note)
+		if (note) {
+			const measure = note?.closest("measure")!;
+			const measureNotes = Array.from(measure.querySelectorAll("note"));
 
-      const noteInMeasureIndex = Array.from(measure.childNodes)
-        .filter((item) => item.nodeName === 'note')
-        .findIndex((item) => item === note)
+			const noteInMeasureIndex = Array.from(measure.childNodes)
+				.filter((item) => item.nodeName === "note")
+				.findIndex((item) => item === note);
 
-      let allDuration = 0
-      let leftDuration = 0
-      for (let i = 0; i < measureNotes.length; i++) {
-        const n = measureNotes[i]
-        const duration = +(n.querySelector('duration')?.textContent || '0')
-        allDuration += duration
-        if (i < noteInMeasureIndex) {
-          leftDuration = allDuration
-        }
-      }
-      eles.push({
-        ele,
-        index: notes.indexOf(note!),
-        noteInMeasureIndex,
-        textContent: ele.textContent!,
-        measureIndex: measures.indexOf(measure!),
-        type: ele.tagName as GradualElement['type'],
-        allDuration,
-        leftDuration,
-      })
-    }
-  }
+			let allDuration = 0;
+			let leftDuration = 0;
+			for (let i = 0; i < measureNotes.length; i++) {
+				const n = measureNotes[i];
+				const duration = +(n.querySelector("duration")?.textContent || "0");
+				allDuration += duration;
+				if (i < noteInMeasureIndex) {
+					leftDuration = allDuration;
+				}
+			}
+			eles.push({
+				ele,
+				index: notes.indexOf(note!),
+				noteInMeasureIndex,
+				textContent: ele.textContent!,
+				measureIndex: measures.indexOf(measure!),
+				type: ele.tagName as GradualElement["type"],
+				allDuration,
+				leftDuration,
+			});
+		}
+	}
 
-  // 结尾处手动插入一个音符节点
-  eles.push({
-    ele: notes[notes.length - 1],
-    index: notes.length,
-    noteInMeasureIndex: 0,
-    textContent: '',
-    type: 'metronome',
-    allDuration: 1,
-    leftDuration: 1,
-    measureIndex: measures.length,
-  })
+	// 结尾处手动插入一个音符节点
+	eles.push({
+		ele: notes[notes.length - 1],
+		index: notes.length,
+		noteInMeasureIndex: 0,
+		textContent: "",
+		type: "metronome",
+		allDuration: 1,
+		leftDuration: 1,
+		measureIndex: measures.length,
+	});
 
-  const gradualNotes: GradualNote[] = []
-  eles.sort((a, b) => a.index - b.index)
-  const keys = Object.keys(speedInfo).map(w => w.toLocaleLowerCase())
-  for (const ele of eles) {
-    // 是否是同时也是关闭标签
-  let isLastNoteAndNotClosed = false
-    let closed = 0
-    const textContent = ele.textContent?.toLocaleLowerCase().trim()
-    if (ele === eles[eles.length - 1]) {
-      if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
-        isLastNoteAndNotClosed = true
-      }
-    }
-    const isKeyWork = keys.find(k => {
-      const ks = k.split(' ')
-      return textContent && ks.includes(textContent)
-    })
-    if (
-      ele.type === "metronome" ||
-      (ele.type === "words" && (textContent.startsWith("a tempo") || isKeyWork) || isLastNoteAndNotClosed)
-    ) {
-      const indexOf = gradualNotes.findIndex((item) => item.length === 1)
-      if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
-        closed = -1
-        gradualNotes[indexOf][1] = {
-          start: ele.index,
-          measureIndex: ele.measureIndex,
-          closedMeasureIndex: ele.measureIndex,
-          noteInMeasureIndex: ele.noteInMeasureIndex,
-          allDuration: ele.allDuration,
-          leftDuration: ele.leftDuration,
-          type: textContent,
-        }
-      }
-    }
-    if (ele.type === 'words' && isKeyWork) {
-      gradualNotes.push([
-        {
-          start: ele.index,
-          measureIndex: ele.measureIndex,
-          closedMeasureIndex: ele.measureIndex + closed,
-          noteInMeasureIndex: ele.noteInMeasureIndex,
-          allDuration: ele.allDuration,
-          leftDuration: ele.leftDuration,
-          type: textContent,
-        },
-      ])
-    }
-  }
-  return gradualNotes
-}
+	const gradualNotes: GradualNote[] = [];
+	eles.sort((a, b) => a.index - b.index);
+	const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase());
+	for (const ele of eles) {
+		// 是否是同时也是关闭标签
+		let isLastNoteAndNotClosed = false;
+		let closed = 0;
+		const textContent = ele.textContent?.toLocaleLowerCase().trim();
+		if (ele === eles[eles.length - 1]) {
+			if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
+				isLastNoteAndNotClosed = true;
+			}
+		}
+		const isKeyWork = keys.find((k) => {
+			const ks = k.split(" ");
+			return textContent && ks.includes(textContent);
+		});
+		if (ele.type === "metronome" || (ele.type === "words" && (textContent.startsWith("a tempo") || isKeyWork)) || isLastNoteAndNotClosed) {
+			const indexOf = gradualNotes.findIndex((item) => item.length === 1);
+			if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
+				closed = -1;
+				gradualNotes[indexOf][1] = {
+					start: ele.index,
+					measureIndex: ele.measureIndex,
+					closedMeasureIndex: ele.measureIndex,
+					noteInMeasureIndex: ele.noteInMeasureIndex,
+					allDuration: ele.allDuration,
+					leftDuration: ele.leftDuration,
+					type: textContent,
+				};
+			}
+		}
+		if (ele.type === "words" && isKeyWork) {
+			gradualNotes.push([
+				{
+					start: ele.index,
+					measureIndex: ele.measureIndex,
+					closedMeasureIndex: ele.measureIndex + closed,
+					noteInMeasureIndex: ele.noteInMeasureIndex,
+					allDuration: ele.allDuration,
+					leftDuration: ele.leftDuration,
+					type: textContent,
+				},
+			]);
+		}
+	}
+	return gradualNotes;
+};
 
-export const getGradualLength = (
-  gradualChange: GradualChange,
-  speed: number,
-  osdm: OpenSheetMusicDisplay
-) => {
-  const {
-    startMeasureListIndex,
-    endMeasureListIndex,
-    endXmlNoteIndex,
-    startWord,
-  } = gradualChange
-  const measures: SourceMeasure[] = []
-  for (
-    let index = startMeasureListIndex;
-    index <= endMeasureListIndex;
-    index++
-  ) {
-    const measure = osdm.Sheet.SourceMeasures[index]
-    measures.push(measure)
-  }
-  const allNoteDurations: number[] = []
-  for (const measure of measures) {
-    if (allNoteDurations.length >= endXmlNoteIndex) {
-      break
-    }
-    // @ts-ignore
-    measure.VerticalMeasureList[0]?.vfVoices['1']?.tickables?.forEach((item) =>
-      allNoteDurations.push(
-        noteDuration[item.duration as keyof typeof noteDuration]
-      )
-    )
-  }
-  const minDuration = Math.min(...allNoteDurations)
-  const parts = allNoteDurations.map((item) => item / minDuration)
-  const allParts = parts.reduce((total, val) => val + total, 0)
-  // const startMeasure = osdm.Sheet.SourceMeasures[startMeasureListIndex]
-  // const endMeasure = osdm.Sheet.SourceMeasures[endMeasureListIndex]
-  let surplusSpeed = speed / speedInfo[startWord?.toLocaleLowerCase()] || 1
-  const diffSpeed = speed - surplusSpeed
-  let useSpeed = 0
-  const speeds: number[] = parts.map((item) => {
-    const s = ((diffSpeed - useSpeed) * item) / allParts
-    return s
-  })
-  // 120 111.9 104.4 96.9
-  // 8.1 7.5 7.2 6.9
-  // 0.6 0.3 0.3
-  const lingerSpeed: number[] = []
-  for (let index = 0; index < speeds.length; index++) {
-    const s = speeds[index]
-    let beforeSpeed = 0
-    let afterSpeed = 0
-    for (let j = 0; j < index; j++) {
-      beforeSpeed += speeds[j]
-    }
-    afterSpeed += beforeSpeed
-    afterSpeed += s
+export const getGradualLength = (gradualChange: GradualChange, speed: number, osdm: OpenSheetMusicDisplay) => {
+	const { startMeasureListIndex, endMeasureListIndex, endXmlNoteIndex, startWord } = gradualChange;
+	const measures: SourceMeasure[] = [];
+	for (let index = startMeasureListIndex; index <= endMeasureListIndex; index++) {
+		const measure = osdm.Sheet.SourceMeasures[index];
+		measures.push(measure);
+	}
+	const allNoteDurations: number[] = [];
+	for (const measure of measures) {
+		if (allNoteDurations.length >= endXmlNoteIndex) {
+			break;
+		}
+		// @ts-ignore
+		measure.VerticalMeasureList[0]?.vfVoices["1"]?.tickables?.forEach((item) =>
+			allNoteDurations.push(noteDuration[item.duration as keyof typeof noteDuration])
+		);
+	}
+	const minDuration = Math.min(...allNoteDurations);
+	const parts = allNoteDurations.map((item) => item / minDuration);
+	const allParts = parts.reduce((total, val) => val + total, 0);
+	// const startMeasure = osdm.Sheet.SourceMeasures[startMeasureListIndex]
+	// const endMeasure = osdm.Sheet.SourceMeasures[endMeasureListIndex]
+	let surplusSpeed = speed / speedInfo[startWord?.toLocaleLowerCase()] || 1;
+	const diffSpeed = speed - surplusSpeed;
+	let useSpeed = 0;
+	const speeds: number[] = parts.map((item) => {
+		const s = ((diffSpeed - useSpeed) * item) / allParts;
+		return s;
+	});
+	// 120 111.9 104.4 96.9
+	// 8.1 7.5 7.2 6.9
+	// 0.6 0.3 0.3
+	const lingerSpeed: number[] = [];
+	for (let index = 0; index < speeds.length; index++) {
+		const s = speeds[index];
+		let beforeSpeed = 0;
+		let afterSpeed = 0;
+		for (let j = 0; j < index; j++) {
+			beforeSpeed += speeds[j];
+		}
+		afterSpeed += beforeSpeed;
+		afterSpeed += s;
 
-    lingerSpeed.push((afterSpeed + beforeSpeed) / 2)
-  }
-  // console.log(lingerSpeed, speeds[0], speeds, parts, allParts)
-  return lingerSpeed
-}
+		lingerSpeed.push((afterSpeed + beforeSpeed) / 2);
+	}
+	// console.log(lingerSpeed, speeds[0], speeds, parts, allParts)
+	return lingerSpeed;
+};

+ 4 - 9
src/helpers/formateMusic.ts

@@ -350,7 +350,7 @@ export const isRepeatWord = (text: string): boolean => {
 
 export const onlyVisible = (xml: string, partIndex: number): string => {
 	if (!xml) return "";
-	const detailId = state.examSongId;
+	const detailId = state.examSongId + '';
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 	const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
 	const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0].textContent || "");
@@ -617,7 +617,7 @@ export const formatXML = (xml: string): string => {
 
 /** 获取所有音符的时值,以及格式化音符 */
 export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
-	const detailId = state.examSongId?.toString();
+	const detailId = state.examSongId + "";
 	const partIndex = state.partIndex + "";
 	let fixtime = browserInfo.huawei ? 0.08 : 0; //getFixTime()
 	const allNotes: any[] = [];
@@ -625,6 +625,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	const allMeasures: any[] = [];
 	const { originSpeed: baseSpeed } = state;
 	const formatRealKey = (realKey: number, detail: any) => {
+		// 不是管乐迷, 不处理
+		if (state.appName !== "GYM") return realKey;
 		// 长笛的LEVEL 2-5-1条练习是泛音练习,以每小节第一个音的指法为准,高音不变变指法。
 		const olnyOneIds = ["906"];
 		if (olnyOneIds.includes(detailId)) {
@@ -671,14 +673,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	let totalMultipleRestMeasures = 0;
 	let multipleRestMeasures = 0;
 	const _notes = [] as any[];
-
 	if (state.gradualTimes) {
-		// 管乐迷
-		// if (["12280"].includes(detailId) && ["24"].includes(partIndex)) {
-		// 	state.gradualTimes["8"] = "00:25:63";
-		// 	state.gradualTimes["66"] = "01:53:35";
-		// 	state.gradualTimes["90"] = "02:41:40";
-		// }
 		console.log("合奏速度", state.gradual, state.gradualTimes);
 	}
 

+ 1 - 1
src/page-gym/App.tsx

@@ -35,7 +35,7 @@ export default defineComponent({
 			}
 			setUser();
 			setBehaviorId(getRandomKey())
-			setCampId(query.campId)
+			setCampId(query.campId || '')
 		});
 		onMounted(() => {
 			const _loading = document.getElementById("loading")

+ 14 - 0
src/page-gym/custom-plugins/custom-gradual/index.tsx

@@ -0,0 +1,14 @@
+import state from "/src/state";
+
+/** 设置自定义渐慢 */
+export const setCustomGradual = () => {
+	if (state.gradualTimes) {
+		const detailId = state.examSongId + "";
+		const partIndex = state.partIndex + "";
+		if (["12280"].includes(detailId) && ["24"].includes(partIndex)) {
+			state.gradualTimes["8"] = "00:26:10";
+			state.gradualTimes["66"] = "01:53:35";
+			state.gradualTimes["90"] = "02:41:40";
+		}
+	}
+};

+ 15 - 4
src/page-gym/detail/index.tsx

@@ -3,9 +3,8 @@ import { computed, defineComponent, nextTick, onBeforeMount, onBeforeUnmount, on
 import { useRoute } from "vue-router";
 import { formateTimes } from "../../helpers/formateMusic";
 import Metronome, { metronomeData } from "../../helpers/metronome";
-import state, { isRhythmicExercises } from "../../state";
+import state, { handleSetSpeed, isRhythmicExercises } from "/src/state";
 import { storeData } from "../../store";
-import { setGlobalData } from "../../utils";
 import AudioList from "../../view/audio-list";
 import MusicScore, { resetMusicScore } from "../../view/music-score";
 import { sysMusicScoreAccompanimentQueryPage, sysMusicScoreCategoriesQueryTree } from "../api";
@@ -26,6 +25,8 @@ import HomeWork from "../custom-plugins/HomeWork";
 import EvaluatingWork from "../custom-plugins/EvaluatingWork";
 import VipModel from "../custom-plugins/vip-verify";
 import GuidePage from "../custom-plugins/guide-page";
+import { setCustomGradual } from "../custom-plugins/custom-gradual";
+import { getStorageSpeed, setGlobalData } from "/src/utils";
 
 //特殊教材分类id
 const classIds = [1, 30, 97]; // [大雅金唐, 竖笛教程, 声部训练]
@@ -43,7 +44,7 @@ export default defineComponent({
 		const detailData = reactive({
 			isLoading: true,
 			paddingLeft: "",
-			classList: [],
+			classList: [] as any,
 		});
 		const getAPPData = async () => {
 			const screenData = await isSpecialShapedScreen();
@@ -97,7 +98,8 @@ export default defineComponent({
 		const getCategory = (res: any) => {
 			console.log("特殊曲谱分类ids:");
 			detailData.classList = res.data;
-			console.log(getIds(res.data).toString());
+			state.isSpecialBookCategory = !detailData.classList.includes(state.parentCategoriesId)
+			console.log(getIds(res.data).toString(), state.isSpecialBookCategory);
 		};
 
 		const setState = (data: any, index: number) => {
@@ -117,6 +119,7 @@ export default defineComponent({
 				} catch (error) {
 					console.error("解析扩展字段错误:", error);
 				}
+				state.gradualTimes = state.extConfigJson.gradualTimes
 			}
 			state.isOpenMetronome = !data.isOpenMetronome;
 			state.needTick = data.isOpenMetronome;
@@ -174,12 +177,20 @@ export default defineComponent({
 		const handleRendered = (osmd: any) => {
 			state.musicRendered = true;
 			state.osmd = osmd;
+			setCustomGradual();
 			state.times = formateTimes(osmd);
 			console.log("🚀 ~ state.times:", state.times);
 			try {
 				metronomeData.metro = new Metronome();
 				metronomeData.metro.init(state.times);
 			} catch (error) {}
+
+			// 设置设置过的速度
+			const storeSpeed = getStorageSpeed(state.examSongId)
+			if (storeSpeed){
+				handleSetSpeed(storeSpeed)
+			}
+			
 		};
 		/** 指法配置 */
 		const fingerConfig = computed<any>(() => {

+ 3 - 1
src/page-gym/header-top/index.tsx

@@ -15,6 +15,7 @@ import Settting from "./settting";
 import MusciList from "../musci-list";
 import { handleNoEndExit } from "../custom-plugins/recording-time";
 import { api_back } from "/src/helpers/communication";
+import { getInstrumentName } from "/src/constant/instruments";
 
 export const headData = reactive({
 	speedShow: false,
@@ -38,12 +39,13 @@ export default defineComponent({
 			api_back();
 		}
 		const disabledList = ["evaluating"];
+		const examSongName = `${state.examSongName} - ${state.track} (${getInstrumentName(state.track)})`
 		return () => (
 			<div ref={headRef} class={styles.headerTop}>
 				<div class={styles.back} onClick={handleBack}>
 					<img src={iconBack} />
 				</div>
-				<Title onClick={() => (headerData.listShow = true)} text={state.examSongName} />
+				<Title onClick={() => (headerData.listShow = true)} text={examSongName} />
 
 				<div class={styles.headRight}>
 					<div class={styles.btn} id="tips-step-2" onClick={toggleEvaluat}>

+ 2 - 0
src/state.ts

@@ -8,6 +8,7 @@ import { IFingering } from "src/view/fingering/fingering-config";
 import { handleStartTick } from "./view/tick";
 import { audioListStart, getAudioCurrentTime, getAudioDuration, setAudioCurrentTime, setAudioPlaybackRate } from "./view/audio-list";
 import { toggleFollow } from "./view/follow-practice";
+import { setStorageSpeed } from "./utils";
 
 /** 入门 | 进阶 | 大师 */
 export type IDifficulty = "BEGINNER" | "ADVANCED" | "PERFORMER";
@@ -367,6 +368,7 @@ export const handleResetPlay = () => {
 };
 /** 设置速度 */
 export const handleSetSpeed = (speed: number) => {
+	setStorageSpeed(state.examSongId, speed)
 	state.speed = speed;
 	const playbackRate = speed / state.originSpeed;
 	console.log("🚀 ~ playbackRate:", speed, state.originSpeed);

+ 14 - 1
src/utils/index.ts

@@ -1,3 +1,4 @@
+import store from 'store'
 /** 获取浏览器信息 */
 export const browser = () => {
 	const u = navigator.userAgent;
@@ -69,7 +70,7 @@ export const setCampId = (value: any) => {
 };
 /** 获取 训练营ID */
 export const getCampId = () => {
-	return sessionStorage.getItem(campIdKey);
+	return sessionStorage.getItem(campIdKey) || '';
 };
 
 // 秒转分
@@ -84,3 +85,15 @@ export const getSecondRPM = (second: number, type?: string) => {
 		return `${h > 0 ? h.toString().padStart(2, "0") + ":" : ""}${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
 	}
 };
+const SPEEDKEY = 'speeds'
+/** 设置曲谱速度 */
+export const setStorageSpeed = (id: any, speed: number) => {
+	const speeds = store.get(SPEEDKEY) || {}
+	speeds[id] = speed
+	store.set(SPEEDKEY, speeds)
+}
+/** 获取曲谱速度 */
+export const getStorageSpeed = (id: any) => {
+	const speeds = store.get(SPEEDKEY) || {}
+	return speeds[id] || 0
+}

+ 1 - 1
src/view/audio-list/index.module.less

@@ -3,5 +3,5 @@
     left: 0;
     bottom: 0;
     width: 100%;
-    z-index: 1000000;
+    z-index: -1000000;
 }

+ 7 - 0
src/view/music-score/index.module.less

@@ -17,4 +17,11 @@
             overflow: visible;
         }
     }
+}
+.inGradualRange{
+   :global{
+        #cursorImg-0{
+            opacity: 0;
+        }
+   } 
 }

+ 41 - 19
src/view/music-score/index.tsx

@@ -1,13 +1,14 @@
-import { defineComponent, nextTick, onBeforeMount, reactive, ref } from "vue";
+import { computed, defineComponent, nextTick, onBeforeMount, reactive, ref } from "vue";
 import { formatXML, onlyVisible } from "../../helpers/formateMusic";
 // // @ts-ignore
 import { OpenSheetMusicDisplay } from "/osmd-extended/src";
 import state from "/src/state";
 import Selection from "../selection";
-import "./index.module.less";
+import styles from "./index.module.less";
 import queryString from "query-string";
+import { getGradualLengthByXml } from "/src/helpers/calcSpeed";
 
-export const musicRenderTypeKey = 'musicRenderType'
+export const musicRenderTypeKey = "musicRenderType";
 
 const musicData = reactive({
 	showSelection: false, // 可以加载点击浮层
@@ -23,21 +24,21 @@ export const resetMusicScore = () => {
 		let width: any = svgEL?.getAttribute("width");
 		width = isNaN(Number(width)) ? 0 : Number(width);
 		if (width) {
-			const zoom = svgContainer.offsetWidth / width
-			document.getElementById("musicAndSelection")?.style.setProperty('--music-zoom', zoom + '')
+			const zoom = svgContainer.offsetWidth / width;
+			document.getElementById("musicAndSelection")?.style.setProperty("--music-zoom", zoom + "");
 		}
 	}
 };
 
 /** 重新渲染曲谱 */
 export const resetRenderMusicScore = () => {
-	const search = queryString.parse(location.search)
+	const search = queryString.parse(location.search);
 	const newSearch = queryString.stringify({
 		...search,
-		_t: Date.now()
-	})
-	location.search = "?" + newSearch
-}
+		_t: Date.now(),
+	});
+	location.search = "?" + newSearch;
+};
 
 export default defineComponent({
 	name: "music-score",
@@ -45,13 +46,16 @@ export default defineComponent({
 	setup(prop, { emit }) {
 		/** 设置 曲谱模式,五线谱还是简谱 */
 		const setRenderType = () => {
-			const musicRenderType: any = sessionStorage.getItem(musicRenderTypeKey)
-			state.musicRenderType = ['staff', 'firstTone', 'fixedTone'].includes(musicRenderType) ? musicRenderType : 'staff'
-		}
+			const musicRenderType: any = sessionStorage.getItem(musicRenderTypeKey);
+			state.musicRenderType = ["staff", "firstTone", "fixedTone"].includes(musicRenderType) ? musicRenderType : "staff";
+		};
 		const getXML = async () => {
 			const res = await fetch(state.xmlUrl).then((response) => response.text());
 			const xml = formatXML(res);
 			musicData.score = onlyVisible(xml, state.partIndex);
+			if (state.gradualTimes) {
+				state.gradual = getGradualLengthByXml(xml);
+			}
 		};
 		const init = async () => {
 			const container = document.getElementById("musicAndSelection");
@@ -76,12 +80,12 @@ export default defineComponent({
 			osmd.EngravingRules.PageTopMargin = 3;
 			osmd.EngravingRules.PageLeftMargin = 2;
 			osmd.EngravingRules.PageBottomMargin = 2;
-			osmd.EngravingRules.DYMusicScoreType = state.musicRenderType === 'staff' ? 'staff' : 'jianpu'
+			osmd.EngravingRules.DYMusicScoreType = state.musicRenderType === "staff" ? "staff" : "jianpu";
 			// 如果为固定调,需要加入全局
-			if (state.musicRenderType === 'fixedTone') {
+			if (state.musicRenderType === "fixedTone") {
 				(window as any).sett = {
-					keySignature: true
-				}
+					keySignature: true,
+				};
 			}
 			await osmd.load(musicData.score);
 			osmd.zoom = state.zoom;
@@ -91,11 +95,29 @@ export default defineComponent({
 			musicData.showSelection = true;
 		};
 		onBeforeMount(async () => {
-			setRenderType()
+			setRenderType();
 			await getXML();
 			await init();
 			musicData.isRenderLoading = false;
 		});
-		return () => <div id="musicAndSelection">{musicData.showSelection && <Selection />}</div>;
+
+		const isInTheGradualRange = computed(() => {
+			let result: boolean = false;
+			const activeMeasureIndex = state.times[state.activeNoteIndex]?.measureListIndex || -1;
+			for (const [first, last] of state.gradual) {
+				if (first && last) {
+					result = first.measureIndex <= activeMeasureIndex && activeMeasureIndex < last.measureIndex;
+					if (result) {
+						break;
+					}
+				}
+			}
+			return result;
+		});
+		return () => (
+			<div id="musicAndSelection" class={[isInTheGradualRange.value && styles.inGradualRange]}>
+				{musicData.showSelection && <Selection />}
+			</div>
+		);
 	},
 });