126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
export const MUSIC_DURATION_MIN = 30
|
|
export const MUSIC_DURATION_MAX = 150
|
|
export const MUSIC_DURATION_DEFAULT = 60
|
|
export const MUSIC_STEPS_MIN = 8
|
|
export const MUSIC_STEPS_MAX = 100
|
|
export const MUSIC_STEPS_DEFAULT = 50
|
|
export const MUSIC_STEPS_DEFAULT_15 = 8
|
|
export const MUSIC_CFG_DEFAULT = 5
|
|
export const MUSIC_CFG_DEFAULT_15 = 1
|
|
export const MUSIC_LYRICS_STRENGTH_DEFAULT = 0.9
|
|
export const MUSIC_TAGS_MAX = 2000
|
|
export const MUSIC_LYRICS_MAX = 8000
|
|
|
|
export const MUSIC_STRUCTURE_CHIPS = [
|
|
'[Verse 1]',
|
|
'[Verse 2]',
|
|
'[Pre-Chorus]',
|
|
'[Chorus]',
|
|
'[Bridge]',
|
|
'[Guitar Solo]',
|
|
'[Instrumental]',
|
|
'[Outro]'
|
|
] as const
|
|
|
|
export const DEFAULT_MUSIC_TAGS = 'modern synthpop, 120 bpm, female vocals, punchy bass, analog synths'
|
|
|
|
export const DEFAULT_MUSIC_LYRICS = `[Verse 1]
|
|
|
|
[Chorus]
|
|
|
|
[Guitar Solo]
|
|
|
|
[Outro]
|
|
`
|
|
|
|
export type MusicEngine = 'yue2'
|
|
|
|
export function parseMusicEngine(raw: unknown): MusicEngine {
|
|
const value = String(raw || '').trim().toLowerCase()
|
|
if (value === 'yue2' || value === 'yue-2' || !value) return 'yue2'
|
|
// Legacy ACE / YuEGP values map to YuE2 — the only supported music engine.
|
|
return 'yue2'
|
|
}
|
|
|
|
export function musicEngineLabel(engine: MusicEngine | string | undefined) {
|
|
void engine
|
|
return 'YuE2'
|
|
}
|
|
|
|
export function parseMusicBpm(tags: unknown, fallback = 120) {
|
|
const match = String(tags || '').match(/(\d{2,3})\s*bpm/i)
|
|
if (!match) return fallback
|
|
const value = Number(match[1])
|
|
if (!Number.isFinite(value)) return fallback
|
|
return Math.min(300, Math.max(10, Math.round(value)))
|
|
}
|
|
|
|
export function clampMusicDuration(raw: unknown, fallback = MUSIC_DURATION_DEFAULT) {
|
|
const value = Number(raw)
|
|
if (!Number.isFinite(value)) return fallback
|
|
return Math.min(MUSIC_DURATION_MAX, Math.max(MUSIC_DURATION_MIN, Math.round(value)))
|
|
}
|
|
|
|
export function clampMusicSteps(raw: unknown, fallback = MUSIC_STEPS_DEFAULT) {
|
|
const value = Number(raw)
|
|
if (!Number.isFinite(value)) return fallback
|
|
return Math.min(MUSIC_STEPS_MAX, Math.max(MUSIC_STEPS_MIN, Math.round(value)))
|
|
}
|
|
|
|
export function clampMusicCfg(raw: unknown, fallback = MUSIC_CFG_DEFAULT) {
|
|
const value = Number(raw)
|
|
if (!Number.isFinite(value)) return fallback
|
|
return Math.min(15, Math.max(1, Math.round(value * 10) / 10))
|
|
}
|
|
|
|
export function clampLyricsStrength(raw: unknown, fallback = MUSIC_LYRICS_STRENGTH_DEFAULT) {
|
|
const value = Number(raw)
|
|
if (!Number.isFinite(value)) return fallback
|
|
return Math.min(2, Math.max(0, Math.round(value * 100) / 100))
|
|
}
|
|
|
|
export function sanitizeMusicTags(raw: unknown) {
|
|
return String(raw || '').replace(/\r\n/g, '\n').trim().slice(0, MUSIC_TAGS_MAX)
|
|
}
|
|
|
|
export function sanitizeMusicLyrics(raw: unknown, instrumental = false) {
|
|
if (instrumental) return ''
|
|
return String(raw || '').replace(/\r\n/g, '\n').trim().slice(0, MUSIC_LYRICS_MAX)
|
|
}
|
|
|
|
/** YuE2 splits on [Section] headers. Returns how many section tags are present. */
|
|
export function countYueLyricSections(lyrics: unknown) {
|
|
const text = String(lyrics || '')
|
|
const matches = text.match(/\[[^\]]+\]/g)
|
|
return matches ? matches.length : 0
|
|
}
|
|
|
|
/** YuE2 allows multiple lyric sections; only empty lyrics are rejected. */
|
|
export function yueLyricsProblem(lyrics: unknown): string | null {
|
|
const text = String(lyrics || '').trim()
|
|
if (!text) return 'YuE2 needs lyrics. Use section tags like [Verse 1] and [Chorus] when you want structure.'
|
|
return null
|
|
}
|
|
|
|
export function parseMusicSeed(raw: unknown) {
|
|
const text = String(raw ?? '').trim().toLowerCase()
|
|
if (!text || text === 'random') return Math.floor(Math.random() * 2_147_483_647)
|
|
const value = Number(text)
|
|
if (!Number.isFinite(value)) return Math.floor(Math.random() * 2_147_483_647)
|
|
return Math.max(0, Math.min(2_147_483_647, Math.floor(value)))
|
|
}
|
|
|
|
export function musicContentType(ext: string) {
|
|
const kind = String(ext || '').replace(/^\./, '').toLowerCase()
|
|
if (kind === 'wav') return 'audio/wav'
|
|
if (kind === 'flac') return 'audio/flac'
|
|
if (kind === 'mp3') return 'audio/mpeg'
|
|
if (kind === 'ogg' || kind === 'opus') return 'audio/ogg'
|
|
return 'application/octet-stream'
|
|
}
|
|
|
|
export function musicExtension(filename: string) {
|
|
const match = String(filename || '').toLowerCase().match(/\.(wav|flac|mp3|ogg|opus)$/)
|
|
return match ? match[1] : 'flac'
|
|
}
|