calcSpeed.ts 7.8 KB

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