Add YuE2 to the music engine picker and generate API.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-14 18:26:55 -05:00
co-authored by Cursor
parent 420dd1279c
commit 98f306a88a
4 changed files with 68 additions and 36 deletions
+15 -6
View File
@@ -99,7 +99,7 @@
</svg> </svg>
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Music</span> <span class="mt-3 block font-display text-lg font-bold text-zinc-100">Music</span>
</NuxtLink> </NuxtLink>
<div class="grid grid-cols-2 border-t border-white/10"> <div class="grid grid-cols-3 border-t border-white/10">
<button <button
type="button" type="button"
class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition" class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
@@ -111,12 +111,21 @@
</button> </button>
<button <button
type="button" type="button"
class="px-3 py-2.5 text-left text-sm transition" class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
:class="engineBtnClass(current === 'music' && musicEngine === 'yue')" :class="engineBtnClass(current === 'music' && musicEngine === 'yue')"
@click="pickMusic('yue')" @click="pickMusic('yue')"
> >
<span class="block font-semibold">YuE</span> <span class="block font-semibold">YuE</span>
<span class="text-xs opacity-80">Lyrics</span> <span class="text-xs opacity-80">v1</span>
</button>
<button
type="button"
class="px-3 py-2.5 text-left text-sm transition"
:class="engineBtnClass(current === 'music' && musicEngine === 'yue2')"
@click="pickMusic('yue2')"
>
<span class="block font-semibold">YuE2</span>
<span class="text-xs opacity-80">3B</span>
</button> </button>
</div> </div>
</div> </div>
@@ -131,7 +140,7 @@ const props = withDefaults(defineProps<{
videoEngine?: 'minimax' | 'ltx' videoEngine?: 'minimax' | 'ltx'
videoStart?: 'still' | 'text' videoStart?: 'still' | 'text'
imageEngine?: 'flux' | 'krea' imageEngine?: 'flux' | 'krea'
musicEngine?: 'ace' | 'yue' musicEngine?: 'ace' | 'yue' | 'yue2'
}>(), { }>(), {
ltxEnabled: false, ltxEnabled: false,
videoEngine: 'minimax', videoEngine: 'minimax',
@@ -144,7 +153,7 @@ const emit = defineEmits<{
select: [kind: 'video' | 'image'] select: [kind: 'video' | 'image']
'video-engine': [engine: 'minimax' | 'ltx'] 'video-engine': [engine: 'minimax' | 'ltx']
'image-engine': [engine: 'flux' | 'krea'] 'image-engine': [engine: 'flux' | 'krea']
'music-engine': [engine: 'ace' | 'yue'] 'music-engine': [engine: 'ace' | 'yue' | 'yue2']
}>() }>()
function engineBtnClass(active: boolean) { function engineBtnClass(active: boolean) {
@@ -162,7 +171,7 @@ function pickImage(engine: 'flux' | 'krea') {
if (props.current !== 'image') emit('select', 'image') if (props.current !== 'image') emit('select', 'image')
} }
function pickMusic(engine: 'ace' | 'yue') { function pickMusic(engine: 'ace' | 'yue' | 'yue2') {
if (props.current === 'music') { if (props.current === 'music') {
emit('music-engine', engine) emit('music-engine', engine)
return return
+34 -16
View File
@@ -25,7 +25,7 @@
</div> </div>
<GenerationSettings :advanced="advanced"> <GenerationSettings :advanced="advanced">
<label class="block space-y-2 text-sm"><span>Model</span><select :value="engineFamily" class="w-full rounded-xl border border-white/15 bg-zinc-900 p-3" @change="selectEngineFamily(($event.target as HTMLSelectElement).value as 'ace' | 'yue')"><option value="ace">ACE-Step</option><option value="yue">YuE</option></select></label> <label class="block space-y-2 text-sm"><span>Model</span><select :value="engineFamily" class="w-full rounded-xl border border-white/15 bg-zinc-900 p-3" @change="selectEngineFamily(($event.target as HTMLSelectElement).value as 'ace' | 'yue' | 'yue2')"><option value="ace">ACE-Step</option><option value="yue">YuE</option><option value="yue2">YuE2</option></select></label>
<label v-if="engineFamily === 'yue'" class="block space-y-2 text-sm"> <label v-if="engineFamily === 'yue'" class="block space-y-2 text-sm">
<span>YuEGP profile</span> <span>YuEGP profile</span>
<select v-model.number="yueProfile" class="w-full rounded-xl border border-white/15 bg-zinc-900 p-3"> <select v-model.number="yueProfile" class="w-full rounded-xl border border-white/15 bg-zinc-900 p-3">
@@ -34,8 +34,9 @@
</select> </select>
<span class="block text-xs text-zinc-400">One lyric section. Compile off. Profile 3 is never selected automatically.</span> <span class="block text-xs text-zinc-400">One lyric section. Compile off. Profile 3 is never selected automatically.</span>
</label> </label>
<p v-else-if="engineFamily === 'yue2'" class="text-xs text-zinc-400">YuE2-3B · one lyric section · plan → synthesize → decode · no score editor</p>
<div v-if="!advanced" class="space-y-4"> <div v-if="!advanced" class="space-y-4">
<label v-if="engineFamily !== 'yue'" class="block text-sm">Steps<input v-model.number="steps" type="number" :min="stepsMin" :max="stepsMax" class="mt-2 w-full rounded-xl border border-white/15 bg-zinc-900 p-3"></label> <label v-if="engineFamily === 'ace'" class="block text-sm">Steps<input v-model.number="steps" type="number" :min="stepsMin" :max="stepsMax" class="mt-2 w-full rounded-xl border border-white/15 bg-zinc-900 p-3"></label>
<label class="block text-sm">Seed<input v-model="seed" placeholder="random" class="mt-2 w-full rounded-xl border border-white/15 bg-zinc-900 p-3"></label> <label class="block text-sm">Seed<input v-model="seed" placeholder="random" class="mt-2 w-full rounded-xl border border-white/15 bg-zinc-900 p-3"></label>
</div> </div>
<div v-if="engineFamily === 'ace'" class="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3 text-sm"> <div v-if="engineFamily === 'ace'" class="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3 text-sm">
@@ -73,7 +74,7 @@
<div class="mb-1 flex flex-wrap items-center justify-between gap-2"> <div class="mb-1 flex flex-wrap items-center justify-between gap-2">
<span class="text-sm font-medium text-zinc-300">Lyrics</span> <span class="text-sm font-medium text-zinc-300">Lyrics</span>
<label class="flex cursor-pointer items-center gap-2 text-xs text-zinc-400"> <label class="flex cursor-pointer items-center gap-2 text-xs text-zinc-400">
<input v-model="instrumental" :disabled="engineFamily === 'yue'" type="checkbox" class="accent-amber-400"> <input v-model="instrumental" :disabled="engineFamily === 'yue' || engineFamily === 'yue2'" type="checkbox" class="accent-amber-400">
Instrumental Instrumental
</label> </label>
</div> </div>
@@ -108,9 +109,9 @@
:max="durationMax" :max="durationMax"
step="5" step="5"
> >
<span class="mt-1 block text-[11px] text-zinc-500">{{ engineFamily === 'yue' ? 'Approximate length · one lyric section' : `${durationMin}–${durationMax} seconds` }}</span> <span class="mt-1 block text-[11px] text-zinc-500">{{ engineFamily === 'yue' || engineFamily === 'yue2' ? 'Approximate length · one lyric section' : `${durationMin}–${durationMax} seconds` }}</span>
</label> </label>
<label v-if="advanced && engineFamily !== 'yue'" class="block text-sm"> <label v-if="advanced && engineFamily === 'ace'" class="block text-sm">
<span class="mb-1 block font-medium text-zinc-300">Steps · {{ steps }}</span> <span class="mb-1 block font-medium text-zinc-300">Steps · {{ steps }}</span>
<input <input
v-model.number="steps" v-model.number="steps"
@@ -208,7 +209,7 @@
</div> </div>
<p v-if="busy && !queued && jobId" class="text-xs text-zinc-400"> <p v-if="busy && !queued && jobId" class="text-xs text-zinc-400">
{{ musicElapsed }} elapsed · {{ musicActivityText }} {{ musicElapsed }} elapsed · {{ musicActivityText }}
<span v-if="activeMusicEngine === 'yue' && !indeterminateMusic" class="mt-1 block">Progress is for the current stage.</span> <span v-if="(activeMusicEngine === 'yue' || activeMusicEngine === 'yue2') && !indeterminateMusic" class="mt-1 block">Progress is for the current stage.</span>
<span v-if="indeterminateMusic" class="mt-1 block">Waiting for a progress report.</span> <span v-if="indeterminateMusic" class="mt-1 block">Waiting for a progress report.</span>
</p> </p>
<AudioPlayer <AudioPlayer
@@ -258,7 +259,7 @@ const tagsPlaceholder = DEFAULT_MUSIC_TAGS
const lyricsPlaceholder = DEFAULT_MUSIC_LYRICS const lyricsPlaceholder = DEFAULT_MUSIC_LYRICS
const chips = MUSIC_STRUCTURE_CHIPS const chips = MUSIC_STRUCTURE_CHIPS
const durationMin = MUSIC_DURATION_MIN const durationMin = MUSIC_DURATION_MIN
const durationMax = computed(() => engineFamily.value === 'yue' ? 150 : MUSIC_DURATION_MAX) const durationMax = computed(() => (engineFamily.value === 'yue' || engineFamily.value === 'yue2') ? 150 : MUSIC_DURATION_MAX)
const stepsMin = MUSIC_STEPS_MIN const stepsMin = MUSIC_STEPS_MIN
const stepsMax = MUSIC_STEPS_MAX const stepsMax = MUSIC_STEPS_MAX
@@ -275,7 +276,7 @@ const folderId = ref('')
const folders = ref<LibraryFolder[]>([]) const folders = ref<LibraryFolder[]>([])
const lyricsEl = ref<HTMLTextAreaElement | null>(null) const lyricsEl = ref<HTMLTextAreaElement | null>(null)
const ace15 = ref(false) const ace15 = ref(false)
const engineFamily = ref<'ace' | 'yue'>('ace') const engineFamily = ref<'ace' | 'yue' | 'yue2'>('ace')
const yueProfile = ref<1 | 3>(1) const yueProfile = ref<1 | 3>(1)
const busy = ref(false) const busy = ref(false)
const forceClearing = ref(false) const forceClearing = ref(false)
@@ -287,7 +288,7 @@ const musicStartedAt = ref(0)
const activeMusicEngine = ref('') const activeMusicEngine = ref('')
const musicActivity = ref<{ checkedAt: number; running: boolean } | null>(null) const musicActivity = ref<{ checkedAt: number; running: boolean } | null>(null)
let musicClockTimer: ReturnType<typeof setInterval> | null = null let musicClockTimer: ReturnType<typeof setInterval> | null = null
const indeterminateMusic = computed(() => !queued.value && activeMusicEngine.value === 'yue' && progress.value < 98 && !musicActivity.value?.running) const indeterminateMusic = computed(() => !queued.value && (activeMusicEngine.value === 'yue' || activeMusicEngine.value === 'yue2') && progress.value < 98 && !musicActivity.value?.running)
const recentMusicCheck = computed(() => Boolean(musicActivity.value?.running && musicClock.value - musicActivity.value.checkedAt < 15000)) const recentMusicCheck = computed(() => Boolean(musicActivity.value?.running && musicClock.value - musicActivity.value.checkedAt < 15000))
const musicElapsed = computed(() => { const musicElapsed = computed(() => {
const seconds = musicStartedAt.value ? Math.max(0, Math.floor((musicClock.value - musicStartedAt.value) / 1000)) : 0 const seconds = musicStartedAt.value ? Math.max(0, Math.floor((musicClock.value - musicStartedAt.value) / 1000)) : 0
@@ -318,27 +319,29 @@ let recoverPoll: ReturnType<typeof setInterval> | null = null
const blocked = computed(() => Boolean(blockReason.value)) const blocked = computed(() => Boolean(blockReason.value))
const headerSubtitle = computed(() => { const headerSubtitle = computed(() => {
if (engineFamily.value === 'yue2') return 'YuE2 · lyrics-to-song'
if (engineFamily.value === 'yue') return 'YuE · lyrics-to-song' if (engineFamily.value === 'yue') return 'YuE · lyrics-to-song'
return ace15.value ? 'ACE-Step 1.5 · vocals' : 'ACE-Step · vocals' return ace15.value ? 'ACE-Step 1.5 · vocals' : 'ACE-Step · vocals'
}) })
const selectedEngine = computed<MusicEngine>(() => { const selectedEngine = computed<MusicEngine>(() => {
if (engineFamily.value === 'yue2') return 'yue2'
if (engineFamily.value === 'yue') return 'yue' if (engineFamily.value === 'yue') return 'yue'
return ace15.value ? 'ace-step-1.5' : 'ace-step' return ace15.value ? 'ace-step-1.5' : 'ace-step'
}) })
const blockReason = computed(() => { const blockReason = computed(() => {
if (!folderId.value) return 'Choose a library folder before generating.' if (!folderId.value) return 'Choose a library folder before generating.'
if (engineFamily.value === 'yue' && instrumental.value) return 'YuEGP requires lyrics. Use ACE for instrumental music.' if ((engineFamily.value === 'yue' || engineFamily.value === 'yue2') && instrumental.value) return 'YuE requires lyrics. Use ACE for instrumental music.'
if (engineFamily.value === 'yue' && duration.value > 150) return 'YuEGP supports up to 150 seconds per section.' if ((engineFamily.value === 'yue' || engineFamily.value === 'yue2') && duration.value > 150) return 'YuE supports up to 150 seconds per section.'
if (!tags.value.trim()) return 'Add genre and style tags.' if (!tags.value.trim()) return 'Add genre and style tags.'
if (engineFamily.value === 'yue') { const problem = yueLyricsProblem(lyrics.value); if (problem) return problem } if (engineFamily.value === 'yue' || engineFamily.value === 'yue2') { const problem = yueLyricsProblem(lyrics.value); if (problem) return problem }
if (!instrumental.value && !lyrics.value.trim()) return 'Write lyrics, or turn on Instrumental.' if (!instrumental.value && !lyrics.value.trim()) return 'Write lyrics, or turn on Instrumental.'
return '' return ''
}) })
function selectEngineFamily(family: 'ace' | 'yue') { function selectEngineFamily(family: 'ace' | 'yue' | 'yue2') {
engineFamily.value = family engineFamily.value = family
if (family === 'yue') { instrumental.value = false; duration.value = Math.min(150, duration.value) } if (family === 'yue' || family === 'yue2') { instrumental.value = false; duration.value = Math.min(150, duration.value) }
if (family === 'yue' && lyrics.value === DEFAULT_MUSIC_LYRICS) lyrics.value = '[Verse 1]\n' if ((family === 'yue' || family === 'yue2') && lyrics.value === DEFAULT_MUSIC_LYRICS) lyrics.value = '[Verse 1]\n'
if (family === 'ace' && ace15.value && steps.value === MUSIC_STEPS_DEFAULT) { if (family === 'ace' && ace15.value && steps.value === MUSIC_STEPS_DEFAULT) {
steps.value = MUSIC_STEPS_DEFAULT_15 steps.value = MUSIC_STEPS_DEFAULT_15
} }
@@ -352,6 +355,11 @@ function goStudio(kind: 'video' | 'image') {
} }
function applyEngine(engine: MusicEngine) { function applyEngine(engine: MusicEngine) {
if (engine === 'yue2') {
engineFamily.value = 'yue2'
ace15.value = false
return
}
if (engine === 'yue') { if (engine === 'yue') {
engineFamily.value = 'yue' engineFamily.value = 'yue'
ace15.value = false ace15.value = false
@@ -454,6 +462,8 @@ async function attachLiveMusic(opts: {
else if (!status.value || /queueing|waiting for a generate|waiting in the job queue|waiting for gpu/i.test(status.value)) { else if (!status.value || /queueing|waiting for a generate|waiting in the job queue|waiting for gpu/i.test(status.value)) {
status.value = selectedEngine.value === 'yue' status.value = selectedEngine.value === 'yue'
? 'YuEGP is generating your song' ? 'YuEGP is generating your song'
: selectedEngine.value === 'yue2'
? 'YuE2 is generating your song'
: 'Generating…' : 'Generating…'
} }
if (jobId.value !== liveId) { if (jobId.value !== liveId) {
@@ -499,6 +509,8 @@ async function refreshStudioQueue() {
? 'Paused in the job queue' ? 'Paused in the job queue'
: (selectedEngine.value === 'yue' : (selectedEngine.value === 'yue'
? 'Waiting for the GPU to run YuEGP' ? 'Waiting for the GPU to run YuEGP'
: selectedEngine.value === 'yue2'
? 'Waiting for the GPU to run YuE2'
: 'Waiting in the job queue…') : 'Waiting in the job queue…')
} }
@@ -511,6 +523,8 @@ async function refreshStudioQueue() {
if (jobId.value && /waiting in the job queue/i.test(status.value)) { if (jobId.value && /waiting in the job queue/i.test(status.value)) {
status.value = selectedEngine.value === 'yue' status.value = selectedEngine.value === 'yue'
? 'YuEGP is generating your song' ? 'YuEGP is generating your song'
: selectedEngine.value === 'yue2'
? 'YuE2 is generating your song'
: 'Generating…' : 'Generating…'
} }
return return
@@ -522,6 +536,8 @@ async function refreshStudioQueue() {
? 'Paused in the job queue' ? 'Paused in the job queue'
: (selectedEngine.value === 'yue' : (selectedEngine.value === 'yue'
? 'Waiting for the GPU to run YuEGP' ? 'Waiting for the GPU to run YuEGP'
: selectedEngine.value === 'yue2'
? 'Waiting for the GPU to run YuE2'
: 'Waiting in the job queue…') : 'Waiting in the job queue…')
return return
} }
@@ -667,6 +683,8 @@ async function generate() {
studioJobId.value = '' studioJobId.value = ''
status.value = engineFamily.value === 'yue' status.value = engineFamily.value === 'yue'
? 'Queueing YuE…' ? 'Queueing YuE…'
: engineFamily.value === 'yue2'
? 'Queueing YuE2…'
: ace15.value : ace15.value
? 'Queueing ACE-Step 1.5…' ? 'Queueing ACE-Step 1.5…'
: 'Queueing ACE-Step…' : 'Queueing ACE-Step…'
@@ -796,7 +814,7 @@ onMounted(async () => {
await loadMusicPresets() await loadMusicPresets()
const route = useRoute() const route = useRoute()
const engineQ = String(route.query.engine || '').toLowerCase() const engineQ = String(route.query.engine || '').toLowerCase()
if (engineQ === 'yue' || engineQ === 'ace') selectEngineFamily(engineQ) if (engineQ === 'yue' || engineQ === 'yue2' || engineQ === 'ace') selectEngineFamily(engineQ as 'ace' | 'yue' | 'yue2')
applyIncomingMusic() applyIncomingMusic()
await resumeActiveMusic() await resumeActiveMusic()
await refreshStudioQueue() await refreshStudioQueue()
+7 -5
View File
@@ -1,4 +1,5 @@
import { yueGpConfigured } from '~/server/utils/yueGp' import { yueGpConfigured } from '~/server/utils/yueGp'
import { yue2Configured } from '~/server/utils/yue2'
import { addStudioJob, kickStudioQueue, listStudioJobs, videoJobsBusy } from '~/server/utils/studioQueue' import { addStudioJob, kickStudioQueue, listStudioJobs, videoJobsBusy } from '~/server/utils/studioQueue'
import { comfyConfigured } from '~/server/utils/comfy' import { comfyConfigured } from '~/server/utils/comfy'
import { import {
@@ -37,7 +38,8 @@ export default defineEventHandler(async (event) => {
const engine = parseMusicEngine(body.engine) const engine = parseMusicEngine(body.engine)
if (engine === 'yue' && !yueGpConfigured()) throw createError({ statusCode: 503, statusMessage: 'YuEGP host is not configured. Set COMFY_CONTROL_URL.' }) if (engine === 'yue' && !yueGpConfigured()) throw createError({ statusCode: 503, statusMessage: 'YuEGP host is not configured. Set COMFY_CONTROL_URL.' })
if (engine !== 'yue' && !comfyConfigured()) { if (engine === 'yue2' && !yue2Configured()) throw createError({ statusCode: 503, statusMessage: 'YuE2 host is not configured. Set COMFY_CONTROL_URL.' })
if (engine !== 'yue' && engine !== 'yue2' && !comfyConfigured()) {
throw createError({ throw createError({
statusCode: 503, statusCode: 503,
statusMessage: 'Beast ComfyUI is not configured. Set COMFY_HOST.' statusMessage: 'Beast ComfyUI is not configured. Set COMFY_HOST.'
@@ -66,15 +68,15 @@ export default defineEventHandler(async (event) => {
const destFolder = library.folders.find(folder => folder.id === folderId) const destFolder = library.folders.find(folder => folder.id === folderId)
const folderLocked = Boolean(destFolder?.protected && !destFolder.unlocked) const folderLocked = Boolean(destFolder?.protected && !destFolder.unlocked)
if (engine === 'yue' && instrumental) throw createError({ statusCode: 400, statusMessage: 'YuEGP currently requires lyrics. Use ACE for instrumental music.' }) if ((engine === 'yue' || engine === 'yue2') && instrumental) throw createError({ statusCode: 400, statusMessage: 'YuE currently requires lyrics. Use ACE for instrumental music.' })
const yueProfile = body.yueProfile ?? 1 const yueProfile = body.yueProfile ?? 1
if (engine === 'yue' && yueProfile !== 1 && yueProfile !== 3) throw createError({ statusCode: 400, statusMessage: 'Choose YuEGP profile 1 or manual fallback 3.' }) if (engine === 'yue' && yueProfile !== 1 && yueProfile !== 3) throw createError({ statusCode: 400, statusMessage: 'Choose YuEGP profile 1 or manual fallback 3.' })
if (engine === 'yue' && !instrumental) { if ((engine === 'yue' || engine === 'yue2') && !instrumental) {
const problem = yueLyricsProblem(lyrics) const problem = yueLyricsProblem(lyrics)
if (problem) throw createError({ statusCode: 400, statusMessage: problem }) if (problem) throw createError({ statusCode: 400, statusMessage: problem })
} }
const duration = clampMusicDuration(body.duration) const duration = clampMusicDuration(body.duration)
if (engine === 'yue' && duration > 150) throw createError({ statusCode: 400, statusMessage: 'YuEGP supports up to 150 seconds per section.' }) if ((engine === 'yue' || engine === 'yue2') && duration > 150) throw createError({ statusCode: 400, statusMessage: 'YuE supports up to 150 seconds per section.' })
const steps = clampMusicSteps( const steps = clampMusicSteps(
body.steps, body.steps,
engine === 'ace-step-1.5' ? MUSIC_STEPS_DEFAULT_15 : MUSIC_STEPS_DEFAULT engine === 'ace-step-1.5' ? MUSIC_STEPS_DEFAULT_15 : MUSIC_STEPS_DEFAULT
@@ -86,7 +88,7 @@ export default defineEventHandler(async (event) => {
const lyricsStrength = clampLyricsStrength(body.lyricsStrength) const lyricsStrength = clampLyricsStrength(body.lyricsStrength)
const seed = parseMusicSeed(body.seed) const seed = parseMusicSeed(body.seed)
const clipName = allocateOutputName(ownerKey, folderId, String(body.name || '').trim(), { const clipName = allocateOutputName(ownerKey, folderId, String(body.name || '').trim(), {
engine: engine === 'ace-step-1.5' ? 'ace15' : engine === 'yue' ? 'yue' : 'ace' engine: engine === 'ace-step-1.5' ? 'ace15' : engine === 'yue' ? 'yue' : engine === 'yue2' ? 'yue2' : 'ace'
}) })
const samplerName = String(body.sampler_name || 'euler') const samplerName = String(body.sampler_name || 'euler')
+5 -2
View File
@@ -33,22 +33,25 @@ export const DEFAULT_MUSIC_LYRICS = `[Verse 1]
[Outro] [Outro]
` `
export type MusicEngine = 'ace-step' | 'ace-step-1.5' | 'yue' export type MusicEngine = 'ace-step' | 'ace-step-1.5' | 'yue' | 'yue2'
export function parseMusicEngine(raw: unknown): MusicEngine { export function parseMusicEngine(raw: unknown): MusicEngine {
const value = String(raw || '').trim().toLowerCase() const value = String(raw || '').trim().toLowerCase()
if (value === 'ace-step-1.5' || value === 'ace15' || value === 'v1.5') return 'ace-step-1.5' if (value === 'ace-step-1.5' || value === 'ace15' || value === 'v1.5') return 'ace-step-1.5'
if (value === 'yue2' || value === 'yue-2') return 'yue2'
if (value === 'yue' || value === 'yue-s1' || value === 'lyrics2song') return 'yue' if (value === 'yue' || value === 'yue-s1' || value === 'lyrics2song') return 'yue'
return 'ace-step' return 'ace-step'
} }
export function isAceMusicEngine(engine: MusicEngine | string | undefined) { export function isAceMusicEngine(engine: MusicEngine | string | undefined) {
return parseMusicEngine(engine) !== 'yue' const value = parseMusicEngine(engine)
return value !== 'yue' && value !== 'yue2'
} }
export function musicEngineLabel(engine: MusicEngine | string | undefined) { export function musicEngineLabel(engine: MusicEngine | string | undefined) {
const value = parseMusicEngine(engine) const value = parseMusicEngine(engine)
if (value === 'ace-step-1.5') return 'ACE-Step 1.5' if (value === 'ace-step-1.5') return 'ACE-Step 1.5'
if (value === 'yue2') return 'YuE2'
if (value === 'yue') return 'YuE' if (value === 'yue') return 'YuE'
return 'ACE-Step' return 'ACE-Step'
} }