index.tsx 10 KB

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