index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { computed, defineComponent, onMounted, reactive, ref, onUnmounted } from "vue";
  2. import { formatXML, onlyVisible, getCustomInfo } from "../../helpers/formateMusic";
  3. // // @ts-ignore
  4. import { OpenSheetMusicDisplay } from "/osmd-extended/src";
  5. import state, { EnumMusicRenderType, IPlatform, resetCursorPosition } from "/src/state";
  6. import Selection from "../selection";
  7. import styles from "./index.module.less";
  8. import queryString from "query-string";
  9. import { getGradualLengthByXml } from "/src/helpers/calcSpeed";
  10. import { resetFormate, resetGivenFormate, setGlobalMusicSheet, limitSingleSvgPageHeight } from "/src/helpers/customMusicScore"
  11. import { setGlobalData } from "/src/utils";
  12. import { storeData } from "/src/store";
  13. import HorizontalDragScroll from './HorizontalDragScroll';
  14. import CombineAudio from './combineAudio';
  15. import { getQuery } from "/src/utils/queryString";
  16. export const musicRenderTypeKey = "musicRenderType";
  17. export const musicData = reactive({
  18. showSelection: false, // 可以加载点击浮层
  19. score: ""
  20. });
  21. /** 重新渲染曲谱 */
  22. export const resetRenderMusicScore = (type?: string) => {
  23. const search = queryString.parse(location.search);
  24. const newSearch = queryString.stringify({
  25. ...search,
  26. _t: Date.now(),
  27. musicRenderType: type,
  28. isSingleLine: state.isSingleLine
  29. });
  30. location.search = "?" + newSearch;
  31. };
  32. // 下载后的xml
  33. export const downloadXmlStr = ref("")
  34. export default defineComponent({
  35. name: "music-score",
  36. emits: ["rendered"],
  37. props: {
  38. /** 是否渲染选择框 */
  39. showSelection: {
  40. type: Boolean,
  41. default: true,
  42. },
  43. renderTypeKey: {
  44. type: String,
  45. default: "",
  46. },
  47. musicColor: {
  48. type: String,
  49. default: "",
  50. },
  51. /** 是否渲染声轨名称 */
  52. showPartNames: {
  53. type: Boolean,
  54. default: false,
  55. },
  56. },
  57. setup(props, { emit, slots, expose }) {
  58. const query: any = getQuery();
  59. let osmd: any = null;
  60. /** 设置 曲谱模式,五线谱还是简谱 */
  61. const setRenderType = () => {
  62. const musicRenderType: any = sessionStorage.getItem(props.renderTypeKey || musicRenderTypeKey);
  63. if (musicRenderType in EnumMusicRenderType) {
  64. state.musicRenderType = musicRenderType;
  65. }
  66. };
  67. const getXML = async () => {
  68. // 当有下载的xml的时候直接使用,否则需要下载
  69. if(!downloadXmlStr.value){
  70. downloadXmlStr.value = await fetch(state.xmlUrl).then((response) => response.text())
  71. }
  72. const xmlStr = downloadXmlStr.value;
  73. console.time('解析xml')
  74. const parseXmlInfo = getCustomInfo(xmlStr);
  75. const xml = formatXML(parseXmlInfo.parsedXML);
  76. musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
  77. if (state.gradualTimes) {
  78. state.gradual = getGradualLengthByXml(xml);
  79. }
  80. console.timeEnd('解析xml')
  81. };
  82. const init = async () => {
  83. console.time("渲染加载耗时");
  84. const container = document.getElementById("musicAndSelection");
  85. if (!container || !musicData.score) return;
  86. setGlobalMusicSheet();
  87. if(!osmd){
  88. osmd = new OpenSheetMusicDisplay(container, {
  89. drawTitle: false,
  90. drawSubtitle: false,
  91. // drawMeasureNumbers: false,
  92. autoResize: false,
  93. followCursor: false,
  94. drawLyricist: false, // 渲染作曲家
  95. drawComposer: false, // 渲染作词家
  96. // pageBackgroundColor: '#609FCF',
  97. // autoGenerateMultipleRestMeasuresFromRestMeasures: state.isSingleLine ? false : true, // 连续休止小节是否合并显示
  98. // darkMode: true, // 暗黑模式
  99. // pageFormat: 'A4_P',
  100. // autoBeam: true,
  101. // drawMetronomeMarks: false,
  102. // ...this.opotions,
  103. colorStemsLikeNoteheads: true, // 是否将音符柄的颜色设置为与它们的音符头相同,默认false
  104. // drawingParameters: "compact" // 使用紧凑布局
  105. // drawLyrics: (((!state.accompany && !state.music ) || state.playType === 'sing' || !state.isEvxml) && !state.isSimplePage) ? true : false, // 演唱模式才渲染歌词,simple页面不显示歌词
  106. // drawPartNames: props.showPartNames, // 是否渲染声轨名称
  107. // defaultColorMusic: props.musicColor, // 颜色
  108. // renderSingleHorizontalStaffline: state.isSingleLine ? true : false
  109. });
  110. }
  111. osmd.setOptions({
  112. drawLyrics: (((!state.accompany && !state.music ) || state.playType === 'sing' || !state.isEvxml) && !state.isSimplePage) ? true : false, // 演唱模式才渲染歌词,simple页面不显示歌词
  113. drawPartNames: props.showPartNames, // 是否渲染声轨名称
  114. defaultColorMusic: props.musicColor, // 颜色
  115. renderSingleHorizontalStaffline: state.isSingleLine ? true : false,
  116. autoGenerateMultipleRestMeasuresFromRestMeasures: state.setting.combineMultipleRest, // 是否自动合并休止小节
  117. })
  118. // osmd.EngravingRules.CompactMode = true // 紧凑模式
  119. // osmd.EngravingRules.PageRightMargin = state.isSingleLine ? (window.innerWidth+200)/10 : 2;
  120. // osmd.EngravingRules.FixedMeasureWidth = state.isSingleLine ? true : false; // 是否固定小节的宽度(小节同一宽度渲染)
  121. //osmd.EngravingRules.PageTopMargin = state.platform === IPlatform.PC ? 0 : 1; // 老师端顶部间距
  122. // 老师端上课页面,左右两边有功能按钮,所以左右边距需要加大
  123. // if (state.isAttendClass && state.platform === IPlatform.PC) {
  124. // osmd.EngravingRules.PageLeftMargin = 7;
  125. // osmd.EngravingRules.PageRightMargin = 7;
  126. // }
  127. //osmd.EngravingRules.PageBottomMargin = state.platform === IPlatform.PC ? 1 : 2;
  128. if (state.isSimplePage) {
  129. osmd.EngravingRules.PageTopMargin = state.musicRenderType === 'staff' ? 2 : 4;
  130. osmd.EngravingRules.PageTopMarginNarrow = 0;
  131. osmd.EngravingRules.PageLeftMargin = 3.6;
  132. osmd.EngravingRules.PageRightMargin = 0;
  133. osmd.EngravingRules.BreathMarkDistance = 0.1;
  134. osmd.EngravingRules.PageBottomMargin = 0;
  135. } else {
  136. // osmd.EngravingRules.PageTopMargin = state.isEvaluatReport ? 7 : 3; // 顶部间距
  137. // osmd.EngravingRules.PageTopMargin = state.isPreView ? 1 : 3;
  138. osmd.EngravingRules.PageTopMargin = (state.isPreView && state.musicRenderType === EnumMusicRenderType.staff) ? 1 : state.isPreView ? 2 : 3;
  139. osmd.EngravingRules.PageTopMarginNarrow = 3;
  140. osmd.EngravingRules.PageLeftMargin = state.isCombineRender ? 8 : 3.6;
  141. osmd.EngravingRules.PageRightMargin = 3;
  142. osmd.EngravingRules.BreathMarkDistance = 0.1; // 呼吸标记距离音符的位置,百分比
  143. osmd.EngravingRules.PageBottomMargin = state.isSingleLine ? 2 : 18;
  144. }
  145. osmd.EngravingRules.DYMusicScoreType =
  146. state.musicRenderType === EnumMusicRenderType.staff ? "staff" : "jianpu";
  147. // 如果为固定调,需要加入全局
  148. if (state.musicRenderType === EnumMusicRenderType.fixedTone) {
  149. (window as any).sett = {
  150. keySignature: true,
  151. };
  152. } else {
  153. (window as any).sett = {
  154. keySignature: false,
  155. };
  156. }
  157. osmd.EngravingRules.DYMusicScoreId = state.examSongId || ''
  158. osmd.EngravingRules.DYCustomRepeatCount = state.maxLyricNum || 0;
  159. osmd.EngravingRules.DYIsSingleLine = state.isSingleLine;
  160. await osmd.load(musicData.score);
  161. // 对外暴露 一行谱时候 缩小谱面
  162. if(state.isSimplePage){
  163. state.zoom = 0.5
  164. }
  165. // 需要渲染总谱的云教练页面
  166. if (!state.isSimplePage && state.isCombineRender) {
  167. const canSelectTracks = state.combinePartIndexs.length > 1 ? state.combinePartIndexs.map(partIndex => { return state.partListNames[partIndex] }) : state.canSelectTracks
  168. for (let i = 0; i < osmd.Sheet.Instruments.length; i++) {
  169. const trackName = state.isEvxml && state.evxmlAddPartName ? osmd.Sheet.Instruments[i].idString || '' : osmd.Sheet.Instruments[i].Name || '';
  170. osmd.Sheet.Instruments[i].Visible = canSelectTracks.includes(trackName.trim())
  171. }
  172. }
  173. if (query.downPng === 'A4') {
  174. osmd.EngravingRules.PageTopMargin = 5
  175. osmd.setPageFormat('794x1100')
  176. osmd.zoom = query.zoom || 0.3;
  177. } else {
  178. osmd.zoom = state.zoom;
  179. }
  180. osmd.render();
  181. console.log("🚀 ~ osmd:", osmd)
  182. emit("rendered", osmd);
  183. resetFormate();
  184. resetGivenFormate();
  185. musicData.showSelection = true;
  186. };
  187. let horizontalDragScroll:HorizontalDragScroll | null
  188. onMounted(async () => {
  189. //setRenderType();
  190. await getXML();
  191. await init();
  192. // pc 端支持 拖动滚动
  193. if(state.platform === "PC" || query.isCbs){
  194. const container = document.querySelector('#musicAndSelection') as HTMLElement;
  195. horizontalDragScroll = new HorizontalDragScroll(container);
  196. }
  197. });
  198. onUnmounted(() => {
  199. horizontalDragScroll?.destroy()
  200. })
  201. const isInTheGradualRange = computed(() => {
  202. let result: boolean = false;
  203. const activeMeasureIndex = state.times[state.activeNoteIndex]?.measureListIndex || -1;
  204. for (const [first, last] of state.gradual) {
  205. if (first && last) {
  206. // console.log('小节',first.measureIndex,last.measureIndex,activeMeasureIndex)
  207. result = first.measureIndex <= activeMeasureIndex && activeMeasureIndex < last.measureIndex;
  208. if (result) {
  209. break;
  210. }
  211. }
  212. }
  213. return result;
  214. });
  215. /** 刷新曲谱 */
  216. const refreshMusicScore = () => {
  217. state.loadingText = "正在加载中,请稍等..."
  218. state.isLoading = true
  219. state.evXmlBeginArr = [];
  220. state.vfmeasures = [];
  221. state.activeNoteIndex = 0;
  222. musicData.showSelection = false;
  223. state.osmd.clear();
  224. const container = document.getElementById('musicAndSelection'), svgDom = document.getElementById('osmdCanvasPage1');
  225. if (container) {
  226. //这里需要删除osmd,不然多行谱和一行谱切换 滚动高度会出问题
  227. svgDom && container?.removeChild(svgDom)
  228. }
  229. // 有可能会有 其他地方的js执行 阻塞 这里确保加载条出来
  230. setTimeout(async () => {
  231. // 在滚动过程中(雄鹰高飞这种marginbottom比较大的) 多行谱和一行谱切换 滚动高度会出问题
  232. container && (container.scrollTop = 0)
  233. //setRenderType();
  234. state.basePlayRate = 1;
  235. await getXML();
  236. await init();
  237. musicData.showSelection = true;
  238. state.isLoading = false
  239. resetCursorPosition()
  240. }, 60);
  241. }
  242. expose({
  243. refreshMusicScore,
  244. })
  245. return () => (
  246. <div
  247. id="musicAndSelection"
  248. style={{ "--music-zoom": state.musicZoom }}
  249. class={[
  250. isInTheGradualRange.value && styles.inGradualRange,
  251. state.musicRenderType == EnumMusicRenderType.staff ? "staff" : "jianpuTone",
  252. state.isSingleLine && "singleLineMusicBox",
  253. (!state.isCreateImg && !state.isPreView && !state.isCbsView && state.musicRenderType === EnumMusicRenderType.staff) ? "blueMusicXml" : "",
  254. state.isSingleLine && state.playState ==="play" && styles.notTouch,
  255. !state.isSingleLine && (state.platform === "PC" || query.isCbs) && styles.pcCursorGrab
  256. ]}
  257. >
  258. {slots.default?.()}
  259. {props.showSelection && musicData.showSelection && !state.isEvaluatReport &&!state.isSimplePage && !state.isPreView && state.musicRendered && <Selection />}
  260. {props.showSelection && musicData.showSelection && state.isCombineRender &&!state.isSimplePage && !state.isPreView && state.musicRendered && <CombineAudio></CombineAudio> }
  261. </div>
  262. );
  263. },
  264. });