|
@@ -19,6 +19,7 @@ import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTim
|
|
|
import { storeData } from "/src/store";
|
|
|
import { downloadXmlStr } from "./view/music-score"
|
|
|
import { musicScoreRef } from "/src/page-instrument/view-detail/index"
|
|
|
+import { headTopData } from "/src/page-instrument/header-top/index";
|
|
|
|
|
|
const query: any = getQuery();
|
|
|
|
|
@@ -28,7 +29,7 @@ export type IDifficulty = "BEGINNER" | "ADVANCED" | "PERFORMER";
|
|
|
export enum EnumMusicRenderType {
|
|
|
/** 五线谱 */
|
|
|
staff = "staff",
|
|
|
- /** 简谱 */
|
|
|
+ /** 简谱(首调) */
|
|
|
firstTone = "firstTone",
|
|
|
/** 固定音高 */
|
|
|
fixedTone = "fixedTone",
|
|
@@ -228,21 +229,41 @@ export const musicalInstrumentCodeInfo = [
|
|
|
id: 34
|
|
|
},
|
|
|
{
|
|
|
+ name: '陶笛',
|
|
|
+ code: 'Alto Ocarina',
|
|
|
+ id: 34
|
|
|
+ },
|
|
|
+ {
|
|
|
name: '葫芦丝',
|
|
|
code: 'Woodwind',
|
|
|
id: 35
|
|
|
},
|
|
|
{
|
|
|
+ name: '葫芦丝',
|
|
|
+ code: 'Hulusi',
|
|
|
+ id: 35
|
|
|
+ },
|
|
|
+ {
|
|
|
name: '口风琴',
|
|
|
code: 'Nai',
|
|
|
id: 36
|
|
|
},
|
|
|
{
|
|
|
+ name: '口风琴',
|
|
|
+ code: 'Melodica',
|
|
|
+ id: 36
|
|
|
+ },
|
|
|
+ {
|
|
|
name: '德式竖笛',
|
|
|
code: 'Tenor Recorder',
|
|
|
id: 37
|
|
|
},
|
|
|
{
|
|
|
+ name: '德式竖笛',
|
|
|
+ code: 'German Recorder',
|
|
|
+ id: 37
|
|
|
+ },
|
|
|
+ {
|
|
|
name: '英式竖笛',
|
|
|
code: 'Baroque Recorder',
|
|
|
id: 38
|
|
@@ -252,6 +273,11 @@ export const musicalInstrumentCodeInfo = [
|
|
|
code: 'Whistling',
|
|
|
id: 39
|
|
|
},
|
|
|
+ {
|
|
|
+ name: '高音陶笛',
|
|
|
+ code: 'Soprano Ocarina',
|
|
|
+ id: 39
|
|
|
+ },
|
|
|
]
|
|
|
|
|
|
const state = reactive({
|
|
@@ -277,6 +303,8 @@ const state = reactive({
|
|
|
enableEvaluation: true,
|
|
|
/** 是否支持转谱 */
|
|
|
enableNotation: false,
|
|
|
+ /** 后台设置不能转谱,但是默认谱面不是五线谱时,需要显示转谱按钮,此时只能转首调和固定调 */
|
|
|
+ specialShowNotation: false,
|
|
|
/** 曲谱ID */
|
|
|
examSongId: "",
|
|
|
/** 内容平台的曲谱ID,可能会和业务端的id不一样 */
|
|
@@ -475,7 +503,7 @@ const state = reactive({
|
|
|
/** 音频文件是否加载完成 */
|
|
|
audioDone: false,
|
|
|
/** 是否为单行谱渲染模式 */
|
|
|
- isSingleLine: false,
|
|
|
+ isSingleLine: true,
|
|
|
/** 是否是evxml */
|
|
|
isEvxml: false,
|
|
|
noTimes: [] as any,
|
|
@@ -501,6 +529,8 @@ const state = reactive({
|
|
|
musicComposer: '',
|
|
|
/** 作词家 */
|
|
|
musicLyricist: '',
|
|
|
+ // 加载条
|
|
|
+ isLoading: true,
|
|
|
/** 加载中的文案 */
|
|
|
loadingText: '音频资源加载中,请稍后…',
|
|
|
/** 是否是简单的单行谱模式页面 */
|
|
@@ -511,6 +541,12 @@ const state = reactive({
|
|
|
basePlayRate: 1,
|
|
|
/** 引导页显示状态 */
|
|
|
hasDriverPop: false,
|
|
|
+ /** 播放倍率不等于1,或者是选段评测,APP暂时不支持保存演奏,需要给出提示 */
|
|
|
+ noSavePopShow: true,
|
|
|
+ /** xml里面是否有歌词 */
|
|
|
+ xmlHasLyric: false,
|
|
|
+ /** 生成图片的模式 */
|
|
|
+ isCreateImg: false,
|
|
|
});
|
|
|
const browserInfo = browser();
|
|
|
let offset_duration = 0;
|
|
@@ -607,8 +643,9 @@ export const initSetPlayRate = () => {
|
|
|
}
|
|
|
|
|
|
// 重置播放倍率
|
|
|
-export const resetBaseRate = () => {
|
|
|
- const currentItem: any = state.times[0];
|
|
|
+export const resetBaseRate = (idx?: number) => {
|
|
|
+ const index = idx ? idx : 0;
|
|
|
+ const currentItem: any = state.times[index];
|
|
|
const currentSpeed = currentItem?.measureSpeed ? currentItem.measureSpeed : state.originSpeed;
|
|
|
// console.log('速度2',currentSpeed)
|
|
|
state.speed = currentSpeed
|
|
@@ -724,16 +761,16 @@ export const skipNotePlay = async (itemIndex: number, isStart = false) => {
|
|
|
|
|
|
/**
|
|
|
* 切换曲谱播放状态
|
|
|
- * @param playState 可选: 默认 undefined, 需要切换的状态 play:播放, paused: 暂停
|
|
|
+ * @param playState 需要切换的状态 play:播放, paused: 暂停
|
|
|
*/
|
|
|
-export const togglePlay = async (playState?: "play" | "paused", sourceType?: string) => {
|
|
|
+export const togglePlay = async (playState: "play" | "paused", sourceType?: string) => {
|
|
|
// 如果mp3资源还在加载中,给出提示
|
|
|
if (!state.isAppPlay && !state.audioDone) {
|
|
|
if (sourceType !== 'courseware') showToast('音频资源加载中,请稍后')
|
|
|
return
|
|
|
}
|
|
|
// 播放之前 当为评测模式和不为MIDI时候按 是否禁用节拍器 切换音源
|
|
|
- if ((playState ? playState : state.playState === "paused" ? "play" : "paused") === 'play' && state.modeType === "practise" && state.playMode !== "MIDI") {
|
|
|
+ if (playState === 'play' && state.modeType === "practise" && state.playMode !== "MIDI") {
|
|
|
console.log("设置音源")
|
|
|
changeSongSourceByBate(metronomeData.disable)
|
|
|
}
|
|
@@ -744,6 +781,8 @@ export const togglePlay = async (playState?: "play" | "paused", sourceType?: str
|
|
|
songID: state.examSongId,
|
|
|
})
|
|
|
state.playState = 'paused'
|
|
|
+ // 当在节拍器播放期间暂停的话 就暂停节拍器
|
|
|
+ closeTick()
|
|
|
return
|
|
|
}
|
|
|
skipNotePlay(state.activeNoteIndex, false);
|
|
@@ -756,7 +795,7 @@ export const togglePlay = async (playState?: "play" | "paused", sourceType?: str
|
|
|
const status = cloudGetMediaStatus?.content.status === "suspend" ? "play" : "paused"
|
|
|
state.playState = status
|
|
|
} else {
|
|
|
- state.playState = playState ? playState : state.playState === "paused" ? "play" : "paused";
|
|
|
+ state.playState = playState;
|
|
|
}
|
|
|
if (state.playState === "play" && state.sectionStatus && state.section.length == 2 && state.playProgress === 0) {
|
|
|
resetPlaybackToStart();
|
|
@@ -1222,6 +1261,7 @@ export const isRhythmicExercises = () => {
|
|
|
/** 重置状态 */
|
|
|
export const handleRessetState = () => {
|
|
|
// 切换模式,清除选段
|
|
|
+ state.noSavePopShow = true;
|
|
|
clearSelection();
|
|
|
skipNotePlay(0, true);
|
|
|
resetBaseRate();
|
|
@@ -1322,7 +1362,13 @@ function initMusicSource(data: any, track?: string) {
|
|
|
})
|
|
|
// 当没有任何曲目的时候报错
|
|
|
if (!musicObj?.audioFileUrl && !accompanyObj?.audioFileUrl && !fanSongObj?.audioFileUrl && !banSongObj?.audioFileUrl && !fanSongObj?.solmizationFileUrl) {
|
|
|
- throw new Error("该曲目无任何音源");
|
|
|
+ // 并且是midi没有midi文件的时候
|
|
|
+ if(data.playMode === "MIDI" && !data.midiFileUrl) {
|
|
|
+ // 是预览的时候 不报错
|
|
|
+ if(!query.isPreView){
|
|
|
+ throw new Error("该曲目无任何音源");
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
Object.assign(state, {
|
|
|
music: musicObj?.audioFileUrl,
|
|
@@ -1341,6 +1387,23 @@ function initMusicSource(data: any, track?: string) {
|
|
|
return musicObj
|
|
|
}
|
|
|
const setState = (data: any, index: number) => {
|
|
|
+ // 根据当前文件有没有 设置当前的播放模式
|
|
|
+ if(!state.music){
|
|
|
+ if(state.accompany){
|
|
|
+ state.playSource = "background"
|
|
|
+ }else{
|
|
|
+ if(state.fanSong){
|
|
|
+ state.playType = "sing"
|
|
|
+ state.playSource = "music"
|
|
|
+ }else if(state.banSong){
|
|
|
+ state.playType = "sing"
|
|
|
+ state.playSource = "background"
|
|
|
+ }else if(state.mingSong){
|
|
|
+ state.playType = "sing"
|
|
|
+ state.playSource = "mingSong"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
state.appName = "COLEXIU";
|
|
|
state.detailId = data.bizId;
|
|
|
state.xmlUrl = data.xmlFileUrl;
|
|
@@ -1351,7 +1414,11 @@ const setState = (data: any, index: number) => {
|
|
|
// 声部code
|
|
|
const subjectCode = data.subjectCodes ? data.subjectCodes.split(',')?.[0] : '';
|
|
|
// 乐器code
|
|
|
- let musicalCode = data.musicalInstrumentIdCodes ? data.musicalInstrumentIdCodes.split(',')?.[0] : '';
|
|
|
+ // let musicalCode = data.musicalInstrumentIdCodes ? data.musicalInstrumentIdCodes.split(',')?.[0] : '';
|
|
|
+ /**
|
|
|
+ * 单曲,指法根据用户当前的乐器来显示,如果没有则取musicSheetSoundList第一个track
|
|
|
+ */
|
|
|
+ let musicalCode = !storeData.user?.instrumentId ? data.musicSheetSoundList.find((item:any)=>{ return item.audioPlayType === "PLAY" })?.track || '' : data.musicSheetSoundList?.find((item: any) => item?.musicalInstrumentId == storeData.user?.instrumentId && item.audioPlayType === "PLAY")?.track || '';
|
|
|
const pitchSubject = musicalInstrumentCodeInfo.find((n) => n.code.toLocaleLowerCase() === subjectCode.toLocaleLowerCase())
|
|
|
const pitchMusical = musicalInstrumentCodeInfo.find((n) => n.code.toLocaleLowerCase() === musicalCode.toLocaleLowerCase())
|
|
|
state.subjectCodeId = pitchSubject ? pitchSubject.id : 0
|
|
@@ -1468,10 +1535,17 @@ const setState = (data: any, index: number) => {
|
|
|
pitchTrack = data.musicalInstruments?.find((item: any) => item.code === musicalCode)
|
|
|
}
|
|
|
let musicalRenderType = ''
|
|
|
- if (pitchTrack?.defaultScore) {
|
|
|
- musicalRenderType = pitchTrack?.defaultScore === 'STAVE' ? 'staff' : pitchTrack?.defaultScore === 'JIAN' ? 'fixedTone' : pitchTrack?.defaultScore === 'FIRST' ? 'firstTone' : ''
|
|
|
- }
|
|
|
- state.musicRenderType = query.musicRenderType || musicalRenderType || EnumMusicRenderType.firstTone;
|
|
|
+ // if (pitchTrack?.defaultScore) {
|
|
|
+ // musicalRenderType = pitchTrack?.defaultScore === 'STAVE' ? 'staff' : pitchTrack?.defaultScore === 'JIAN' ? 'fixedTone' : pitchTrack?.defaultScore === 'FIRST' ? 'firstTone' : ''
|
|
|
+ // }
|
|
|
+ // state.musicRenderType = query.musicRenderType || musicalRenderType || EnumMusicRenderType.firstTone;
|
|
|
+ /**
|
|
|
+ * 2024.7.30,使用新的字段
|
|
|
+ * 谱面类型,scoreType
|
|
|
+ * STAVE("五线谱"),JIAN("固定调"),FIRST("首调"),
|
|
|
+ */
|
|
|
+ musicalRenderType = data.scoreType === 'STAVE' ? 'staff' : data.scoreType === 'JIAN' ? 'fixedTone' : data.scoreType === 'FIRST' ? '' : 'firstTone';
|
|
|
+ state.musicRenderType = query.musicRenderType || musicalRenderType || EnumMusicRenderType.firstTone;
|
|
|
/**
|
|
|
* TODO:摇篮曲特殊处理
|
|
|
*/
|
|
@@ -1480,7 +1554,15 @@ const setState = (data: any, index: number) => {
|
|
|
state.musicRenderType = EnumMusicRenderType.firstTone;
|
|
|
}
|
|
|
}
|
|
|
- state.enableNotation = pitchTrack ? data.isConvertibleScore && pitchTrack.transferFlag : data.isConvertibleScore
|
|
|
+ /**
|
|
|
+ * 2024.7.30,使用新的字段
|
|
|
+ * 能否转谱,isConvertibleScore
|
|
|
+ * true:能转谱,false:不能转谱
|
|
|
+ * 额外的逻辑:后台设置不能转谱时,如果默认谱面不是五线谱,需要显示转谱按钮,但是只能转首调和固定调
|
|
|
+ */
|
|
|
+ // state.enableNotation = pitchTrack ? data.isConvertibleScore && pitchTrack.transferFlag : data.isConvertibleScore
|
|
|
+ state.enableNotation = data.isConvertibleScore
|
|
|
+ state.specialShowNotation = !data.isConvertibleScore && data.scoreType !== 'STAVE';
|
|
|
console.log("state对象", state);
|
|
|
// 评测基准频率
|
|
|
state.baseFrequency = data.evaluationFrequency ? data.evaluationFrequency.split(",")[0] : 440
|
|
@@ -1587,21 +1669,23 @@ export const addNoteBBox = (list: any[]) => {
|
|
|
// todo 连续修止小节bug
|
|
|
note.bbox = bbox;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
// 给歌词和音符添加动态颜色
|
|
|
-const fillWordColor = () => {
|
|
|
+export const fillWordColor = () => {
|
|
|
// console.log('当前音符',state.activeNoteIndex)
|
|
|
state.times.forEach((item: any, idx: number) => {
|
|
|
const svgEl = document.getElementById(`vf-${state.times[idx]?.svgElement?.attrs?.id}`)
|
|
|
const stemEl = document.getElementById(`vf-${state.times[idx]?.svgElement?.attrs?.id}-stem`)
|
|
|
+ const stemLine = document.getElementById(`vf-${state.times[idx]?.svgElement?.attrs?.id}-lines`)
|
|
|
if ((item.i === state.activeNoteIndex || item.id === state.times[state.activeNoteIndex].id) && item.svgElement) {
|
|
|
svgEl?.classList.add('noteActive')
|
|
|
stemEl?.classList.add('noteActive')
|
|
|
+ stemLine?.classList.add('noteActive')
|
|
|
} else {
|
|
|
svgEl?.classList.remove('noteActive')
|
|
|
stemEl?.classList.remove('noteActive')
|
|
|
+ stemLine?.classList.remove('noteActive')
|
|
|
}
|
|
|
})
|
|
|
|
|
@@ -1626,10 +1710,15 @@ export const moveSvgDom = (skipNote?: boolean) => {
|
|
|
* 当前选中的音符和第一个音符之间的间距
|
|
|
*/
|
|
|
if (skipNote) {
|
|
|
- const distance = state.times[state.activeNoteIndex].bbox?.x - state.times[0].bbox?.x + state.times[state.activeNoteIndex].bbox?.width / 2 - state.times[0].bbox?.width / 2;
|
|
|
// 点击 清空translateXNum
|
|
|
smoothAnimationState.translateXNum = 0
|
|
|
- moveSmoothAnimation(0, state.activeNoteIndex)
|
|
|
+ moveTranslateXNum(0)
|
|
|
+ // 移动小鸟的位置
|
|
|
+ moveSmoothAnimation(0, state.activeNoteIndex, false)
|
|
|
+ // 移动谱面当当前音符的位置
|
|
|
+ const noteWidth = state.times[state.activeNoteIndex].bbox?.originWidth || state.times[state.activeNoteIndex].bbox?.width;
|
|
|
+ const firstNoteWidth = state.times[0].bbox?.originWidth || state.times[0].bbox?.width;
|
|
|
+ const distance = state.times[state.activeNoteIndex].bbox?.x - state.times[0].bbox?.x + noteWidth / 2 - firstNoteWidth / 2;
|
|
|
smoothAnimationState.osdmScrollDom!.scrollTo({
|
|
|
left: distance,
|
|
|
behavior: "smooth",
|
|
@@ -1646,9 +1735,12 @@ watch(
|
|
|
// 当在播放中暂停 执行这个方法
|
|
|
if (!state.playEnd && state.playState === "paused") {
|
|
|
moveTranslateXNum(0)
|
|
|
- const scrollLeft = smoothAnimationState.osdmScrollDom!.scrollLeft
|
|
|
- smoothAnimationState.osdmScrollDom!.scrollLeft = scrollLeft + smoothAnimationState.translateXNum
|
|
|
- smoothAnimationState.translateXNum = 0
|
|
|
+ // 因为safari浏览器scrollWidth的值一直变化,scrollLeft + smoothAnimationState.translateXNum 为最大宽度的时候,实际上scrollLeft滚不到最大宽度,所以在下一帧处理滚动,能滚动到最大滚动位置
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ const scrollLeft = smoothAnimationState.osdmScrollDom!.scrollLeft
|
|
|
+ smoothAnimationState.osdmScrollDom!.scrollLeft = scrollLeft + smoothAnimationState.translateXNum
|
|
|
+ smoothAnimationState.translateXNum = 0
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -1683,6 +1775,11 @@ watch(
|
|
|
if (measureNum >= 0 && (measureNum === state.activeMeasureIndex || (measureNum < state.activeMeasureIndex && nextMeasureNum > state.activeMeasureIndex)) ) {
|
|
|
item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#132D4C")
|
|
|
item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#040D1E")
|
|
|
+ // 预备小节
|
|
|
+ if(state.sectionFirst && measureNum === state.sectionFirst.MeasureNumberXML){
|
|
|
+ item?.querySelector('.vf-custom-bg')?.setAttribute("fill", "#71B8BD")
|
|
|
+ item?.querySelector('.vf-custom-bot')?.setAttribute("fill", "#448F9C")
|
|
|
+ }
|
|
|
} else {
|
|
|
// 有选段只清除选段处的
|
|
|
if (state.section.length === 2) {
|
|
@@ -1762,7 +1859,7 @@ watch(
|
|
|
/** 刷新谱面 */
|
|
|
export const refreshMusicSvg = () => {
|
|
|
resetBaseRate();
|
|
|
- state.loadingText = '正在加载中,请稍等…'
|
|
|
+ state.activeMeasureIndex = 0;
|
|
|
// 销毁旋律线
|
|
|
destroySmoothAnimation()
|
|
|
musicScoreRef.value?.refreshMusicScore()
|
|
@@ -1772,10 +1869,18 @@ export const refreshMusicSvg = () => {
|
|
|
watch(
|
|
|
() => state.setting.displayFingering,
|
|
|
() => {
|
|
|
- nextTick(() => {
|
|
|
- if (smoothAnimationState.osdmScrollDom) {
|
|
|
- smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom.offsetWidth | 0
|
|
|
- }
|
|
|
- })
|
|
|
+ // 有字符 并且是竖向指法 并且是一行谱
|
|
|
+ if(state.fingeringInfo?.name && state.fingeringInfo.direction === "vertical" && state.isSingleLine){
|
|
|
+ nextTick(() => {
|
|
|
+ if (smoothAnimationState.osdmScrollDom) {
|
|
|
+ smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom.offsetWidth | 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 如果有指法,并且是竖向指法时,切换指法时,谱面宽度变化,需要重新渲染谱面
|
|
|
+ if (state.fingeringInfo?.name && state.fingeringInfo.direction === "vertical" && !state.isSingleLine) {
|
|
|
+ headTopData.settingMode = false;
|
|
|
+ refreshMusicSvg();
|
|
|
+ }
|
|
|
}
|
|
|
)
|