calcSpeed.ts 7.6 KB

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