index.tsx 11 KB

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