calcSpeed.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import { OpenSheetMusicDisplay, SourceMeasure } from "/osmd-extended/src";
  2. import { onlyVisible } from "/src/helpers/formateMusic";
  3. export const noteDuration = {
  4. "1/2": 2,
  5. w: 1,
  6. h: 0.5,
  7. q: 0.25,
  8. "8": 0.125,
  9. "16": 0.0625,
  10. "32": 0.03125,
  11. "64": 0.015625,
  12. "128": 0.0078125,
  13. };
  14. export type GradualChange = {
  15. resetXmlNoteIndex: number;
  16. startXmlNoteIndex: number;
  17. endXmlNoteIndex: number;
  18. startWord: string;
  19. startMeasureListIndex: number;
  20. endMeasureListIndex: number;
  21. resetMeasureListIndex: number;
  22. };
  23. export const speedInfo: { [key in string]: number } = {
  24. "rall.": 1.333333333,
  25. "poco rit.": 1.333333333,
  26. "rit.": 1.333333333,
  27. "molto rit.": 1.333333333,
  28. "molto rall": 1.333333333,
  29. molto: 1.333333333,
  30. lentando: 1.333333333,
  31. allargando: 1.333333333,
  32. morendo: 1.333333333,
  33. "accel.": 0.8,
  34. calando: 2,
  35. "poco accel.": 0.8,
  36. "gradually slowing": 1.333333333,
  37. slowing: 1.333333333,
  38. slow: 1.333333333,
  39. slowly: 1.333333333,
  40. faster: 1.333333333,
  41. "molto allargando": 1.333333333,
  42. stringendo: 0.8,
  43. "poco a poco rit.": 1.333333333,
  44. "rit. poco a poco": 1.333333333,
  45. "Ritardando": 1.333333333,
  46. "Ritenuto": 1.333333333,
  47. "accelerate": 0.8,
  48. "poco a poco accel.": 0.8,
  49. };
  50. /**
  51. * 计算渐变速度
  52. */
  53. export const calcGradual = () => {};
  54. /**
  55. * 2022年9月14日版本 计算渐变速度,此方法不兼容之前的选择范围。
  56. */
  57. export const calcGradual2 = () => {};
  58. /**
  59. * 获取指定元素下一个Note元素
  60. * @param ele 指定元素
  61. * @param selectors 选择器
  62. */
  63. const getNextNote = (ele: Element, selectors: string) => {
  64. let index = 0;
  65. const parentEle = ele.closest(selectors);
  66. let pointer = parentEle;
  67. const measure = parentEle?.closest("measure");
  68. let siblingNote: Element | null | undefined = null;
  69. // 查找到相邻的第一个note元素
  70. while (!siblingNote && index < (measure?.childNodes.length || 50)) {
  71. index++;
  72. if (pointer?.nextElementSibling?.tagName === "note") {
  73. siblingNote = pointer?.nextElementSibling;
  74. }
  75. pointer = pointer?.nextElementSibling!;
  76. }
  77. return siblingNote;
  78. };
  79. export type GradualElement = {
  80. ele: Element;
  81. index: number;
  82. noteInMeasureIndex: number;
  83. textContent: string;
  84. measureIndex: number;
  85. type: "words" | "metronome";
  86. allDuration: number;
  87. leftDuration: number;
  88. };
  89. export type GradualNote = GradualItem[];
  90. export type GradualItem = {
  91. start: number;
  92. measureIndex: number;
  93. noteInMeasureIndex: number;
  94. allDuration: number;
  95. leftDuration: number;
  96. type: string;
  97. closedMeasureIndex: number;
  98. };
  99. // export type GradualItem = {
  100. // start: number
  101. // startMeasureIndex: number
  102. // startNoteInMeasureIndex: number
  103. // allDuration: number
  104. // leftDuration: number
  105. // endNoteInMeasureIndex?: number
  106. // endMeasureIndex?: number
  107. // end?: number
  108. // }
  109. /**
  110. * 按照xml进行减慢速度的计算
  111. * @param xml 始终按照第一分谱进行减慢速度的计算
  112. */
  113. export const getGradualLengthByXml = (xml: string) => {
  114. const firstPartXml = onlyVisible(xml, 0, 'calc')
  115. //console.time('解析xml 耗时2')
  116. const xmlParse = new DOMParser().parseFromString(firstPartXml, "text/xml");
  117. //console.timeEnd('解析xml 耗时2')
  118. const measures = Array.from(xmlParse.querySelectorAll("measure"));
  119. const notes = Array.from(xmlParse.querySelectorAll("note"));
  120. const words = Array.from(xmlParse.querySelectorAll("words"));
  121. const metronomes = Array.from(xmlParse.querySelectorAll("metronome"));
  122. const eles: GradualElement[] = [];
  123. for (const ele of [...words, ...metronomes]) {
  124. const note = getNextNote(ele, "direction");
  125. // console.log(ele, note)
  126. if (note) {
  127. const measure = note?.closest("measure")!;
  128. const measureNotes = Array.from(measure.querySelectorAll("note"));
  129. const noteInMeasureIndex = Array.from(measure.childNodes)
  130. .filter((item) => item.nodeName === "note")
  131. .findIndex((item) => item === note);
  132. let allDuration = 0;
  133. let leftDuration = 0;
  134. for (let i = 0; i < measureNotes.length; i++) {
  135. const n = measureNotes[i];
  136. const duration = +(n.querySelector("duration")?.textContent || "0");
  137. allDuration += duration;
  138. if (i < noteInMeasureIndex) {
  139. leftDuration = allDuration;
  140. }
  141. }
  142. eles.push({
  143. ele,
  144. index: notes.indexOf(note!),
  145. noteInMeasureIndex,
  146. textContent: ele.textContent!,
  147. measureIndex: measures.indexOf(measure!),
  148. type: ele.tagName as GradualElement["type"],
  149. allDuration,
  150. leftDuration,
  151. });
  152. }
  153. }
  154. // 结尾处手动插入一个音符节点
  155. eles.push({
  156. ele: notes[notes.length - 1],
  157. index: notes.length,
  158. noteInMeasureIndex: 0,
  159. textContent: "",
  160. type: "metronome",
  161. allDuration: 1,
  162. leftDuration: 0,
  163. measureIndex: measures.length,
  164. });
  165. const gradualNotes: GradualNote[] = [];
  166. eles.sort((a, b) => a.index - b.index);
  167. const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase());
  168. for (const ele of eles) {
  169. // 是否是同时也是关闭标签
  170. let isLastNoteAndNotClosed = false;
  171. let closed = 0;
  172. const textContent = ele.textContent?.toLocaleLowerCase().trim();
  173. if (ele === eles[eles.length - 1]) {
  174. if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
  175. isLastNoteAndNotClosed = true;
  176. }
  177. }
  178. const isKeyWork = keys.find((k) => {
  179. const ks = k.split(" ");
  180. return textContent && ks.includes(textContent) || k === textContent;
  181. });
  182. if (ele.type === "metronome" || (ele.type === "words" && (textContent.startsWith("a tempo") || isKeyWork)) || isLastNoteAndNotClosed) {
  183. const indexOf = gradualNotes.findIndex((item) => item.length === 1);
  184. if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
  185. closed = -1;
  186. gradualNotes[indexOf][1] = {
  187. start: ele.index,
  188. measureIndex: ele.measureIndex,
  189. closedMeasureIndex: ele.measureIndex,
  190. noteInMeasureIndex: ele.noteInMeasureIndex,
  191. allDuration: ele.allDuration,
  192. leftDuration: ele.leftDuration,
  193. type: textContent,
  194. };
  195. }
  196. }
  197. if (ele.type === "words" && isKeyWork) {
  198. gradualNotes.push([
  199. {
  200. start: ele.index,
  201. measureIndex: ele.measureIndex,
  202. closedMeasureIndex: ele.measureIndex + closed,
  203. noteInMeasureIndex: ele.noteInMeasureIndex,
  204. allDuration: ele.allDuration,
  205. leftDuration: ele.leftDuration,
  206. type: textContent,
  207. },
  208. ]);
  209. }
  210. }
  211. return gradualNotes;
  212. };
  213. export const getGradualLength = (gradualChange: GradualChange, speed: number, osdm: OpenSheetMusicDisplay) => {
  214. const { startMeasureListIndex, endMeasureListIndex, endXmlNoteIndex, startWord } = gradualChange;
  215. const measures: SourceMeasure[] = [];
  216. for (let index = startMeasureListIndex; index <= endMeasureListIndex; index++) {
  217. const measure = osdm.Sheet.SourceMeasures[index];
  218. measures.push(measure);
  219. }
  220. const allNoteDurations: number[] = [];
  221. for (const measure of measures) {
  222. if (allNoteDurations.length >= endXmlNoteIndex) {
  223. break;
  224. }
  225. // @ts-ignore
  226. measure.VerticalMeasureList[0]?.vfVoices["1"]?.tickables?.forEach((item) =>
  227. allNoteDurations.push(noteDuration[item.duration as keyof typeof noteDuration])
  228. );
  229. }
  230. const minDuration = Math.min(...allNoteDurations);
  231. const parts = allNoteDurations.map((item) => item / minDuration);
  232. const allParts = parts.reduce((total, val) => val + total, 0);
  233. // const startMeasure = osdm.Sheet.SourceMeasures[startMeasureListIndex]
  234. // const endMeasure = osdm.Sheet.SourceMeasures[endMeasureListIndex]
  235. let surplusSpeed = speed / speedInfo[startWord?.toLocaleLowerCase()] || 1;
  236. const diffSpeed = speed - surplusSpeed;
  237. let useSpeed = 0;
  238. const speeds: number[] = parts.map((item) => {
  239. const s = ((diffSpeed - useSpeed) * item) / allParts;
  240. return s;
  241. });
  242. // 120 111.9 104.4 96.9
  243. // 8.1 7.5 7.2 6.9
  244. // 0.6 0.3 0.3
  245. const lingerSpeed: number[] = [];
  246. for (let index = 0; index < speeds.length; index++) {
  247. const s = speeds[index];
  248. let beforeSpeed = 0;
  249. let afterSpeed = 0;
  250. for (let j = 0; j < index; j++) {
  251. beforeSpeed += speeds[j];
  252. }
  253. afterSpeed += beforeSpeed;
  254. afterSpeed += s;
  255. lingerSpeed.push((afterSpeed + beforeSpeed) / 2);
  256. }
  257. // console.log(lingerSpeed, speeds[0], speeds, parts, allParts)
  258. return lingerSpeed;
  259. };