xVASynth-TTS / javascript /style_embeddings.js
Pendrokar's picture
xVASynth v3 code for English
19c8b95
raw
history blame
13.2 kB
"use strict"
window.allStyleEmbs = {}
window.styleEmbsMenuState = {
embeddingsDir: `${window.path}/embeddings`,
hasChangedEmb: false, // For clearing the use of the editor state when re-generating a line with a different embedding
selectedEmb: undefined,
activatedEmbeddings: {}
}
window.loadStyleEmbsFromDisk = () => {
window.allStyleEmbs = {}
// Read the activated embeddings file
window.styleEmbsMenuState.activatedEmbeddings = {}
if (fs.existsSync(`./embeddings.txt`)) {
const embeddingsEnabled = fs.readFileSync(`./embeddings.txt`, "utf8").split("\n")
embeddingsEnabled.forEach(emb => {
window.styleEmbsMenuState.activatedEmbeddings[emb.replace("*","")] = emb.includes("*")
})
}
// Read all the embedding files
fs.mkdirSync(window.styleEmbsMenuState.embeddingsDir, {recursive: true})
const embFiles = fs.readdirSync(window.styleEmbsMenuState.embeddingsDir)
embFiles.forEach(jsonFName => {
const jsonData = JSON.parse(fs.readFileSync(`${window.styleEmbsMenuState.embeddingsDir}/${jsonFName}`))
jsonData.fileName = `${window.styleEmbsMenuState.embeddingsDir}/${jsonFName}`
if (!Object.keys(window.styleEmbsMenuState.activatedEmbeddings).includes(jsonData.emb_id)) {
window.styleEmbsMenuState.activatedEmbeddings[jsonData.emb_id] = true
}
jsonData.enabled = window.styleEmbsMenuState.activatedEmbeddings[jsonData.emb_id]
window.allStyleEmbs[jsonData.voiceId] = window.allStyleEmbs[jsonData.voiceId] || []
window.allStyleEmbs[jsonData.voiceId].push(jsonData)
})
window.saveEnabledStyleEmbs()
}
window.saveEnabledStyleEmbs = () => {
fs.writeFileSync(`./embeddings.txt`, Object.keys(window.styleEmbsMenuState.activatedEmbeddings).map(key => {
return `${window.styleEmbsMenuState.activatedEmbeddings[key]?"*":""}${key}`
}).join("\n"), "utf8")
}
window.loadStyleEmbsFromDisk()
window.resetStyleEmbFields = () => {
styleEmbAuthorInput.value = ""
styleEmbGameIdInput.value = ""
styleEmbVoiceIdInput.value = ""
styleEmbNameInput.value = ""
styleEmbDescriptionInput.value = ""
styleEmbIdInput.value = ""
wavFilepathForEmbComputeInput.value = ""
styleEmbValuesInput.value = ""
styleEmbGameIdInput.value = window.currentGame.gameId
if (window.currentModel) {
styleEmbVoiceIdInput.value = window.currentModel.voiceId
}
}
window.refreshStyleEmbsTable = () => {
styleembsRecordsContainer.innerHTML = ""
window.styleEmbsMenuState.selectedEmb = undefined
styleEmbDelete.disabled = true
window.resetStyleEmbFields()
Object.keys(window.allStyleEmbs).sort().forEach(key => {
window.allStyleEmbs[key].forEach((emb,ei) => {
const record = createElem("div")
const enabledCkbx = createElem("input", {type: "checkbox"})
enabledCkbx.checked = emb.enabled
record.appendChild(createElem("div", enabledCkbx))
const embName = createElem("div", emb["embeddingName"])
embName.title = emb["embeddingName"]
record.appendChild(embName)
const embGameID = createElem("div", emb["gameId"])
embGameID.title = emb["gameId"]
record.appendChild(embGameID)
const embVoiceID = createElem("div", emb["voiceId"])
embVoiceID.title = emb["voiceId"]
record.appendChild(embVoiceID)
const embDescription = createElem("div", emb["description"]||"")
embDescription.title = emb["description"]||""
record.appendChild(embDescription)
const embID = createElem("div", emb["emb_id"])
embID.title = emb["emb_id"]
record.appendChild(embID)
const embVersion = createElem("div", emb["version"]||"1.0")
embVersion.title = emb["version"]||"1.0"
record.appendChild(embVersion)
enabledCkbx.addEventListener("click", () => {
window.allStyleEmbs[key][ei].enabled = !window.allStyleEmbs[key][ei].enabled
window.styleEmbsMenuState.activatedEmbeddings[emb["emb_id"]] = window.allStyleEmbs[key][ei].enabled
window.saveEnabledStyleEmbs()
if (window.currentModel) {
window.loadStyleEmbsForVoice(window.currentModel)
}
})
record.addEventListener("click", (e) => {
if (e.target==enabledCkbx || e.target.nodeName=="BUTTON") {
return
}
// Clear visual selection of the old selected item, if there was already an item selected before
if (window.styleEmbsMenuState.selectedEmb) {
window.styleEmbsMenuState.selectedEmb[0].style.background = "none"
Array.from(window.styleEmbsMenuState.selectedEmb[0].children).forEach(child => child.style.color = "white")
}
window.styleEmbsMenuState.selectedEmb = [record, emb]
// Visually show that this row is selected
window.styleEmbsMenuState.selectedEmb[0].style.background = "white"
Array.from(window.styleEmbsMenuState.selectedEmb[0].children).forEach(child => child.style.color = "black")
styleEmbDelete.disabled = false
// Populate the edit fields
styleEmbAuthorInput.value = emb.author||""
styleEmbGameIdInput.value = emb.gameId||""
styleEmbVoiceIdInput.value = emb.voiceId||""
styleEmbNameInput.value = emb.embeddingName||""
styleEmbDescriptionInput.value = emb.description||""
styleEmbIdInput.value = emb.emb_id||""
wavFilepathForEmbComputeInput.value = ""
styleEmbValuesInput.value = emb.emb||""
})
styleembsRecordsContainer.appendChild(record)
})
})
}
styleembs_main.addEventListener("click", (e) => {
if (e.target == styleembs_main) {
window.refreshStyleEmbsTable()
}
})
styleEmbSave.addEventListener("click", () => {
const missingFieldsValues = []
if (!styleEmbAuthorInput.value.trim().length) {
missingFieldsValues.push(window.i18n.AUTHOR)
}
if (!styleEmbGameIdInput.value.trim().length) {
missingFieldsValues.push(window.i18n.GAME_ID)
}
if (!styleEmbVoiceIdInput.value.trim().length) {
missingFieldsValues.push(window.i18n.VOICE_ID)
}
if (!styleEmbNameInput.value.trim().length) {
missingFieldsValues.push(window.i18n.EMB_NAME)
}
if (!styleEmbIdInput.value.trim().length) {
missingFieldsValues.push(window.i18n.EMB_ID)
}
if (!styleEmbValuesInput.value.trim().length) {
missingFieldsValues.push(window.i18n.STYLE_EMB_VALUES)
}
if (missingFieldsValues.length) {
window.errorModal(window.i18n.ERROR_MISSING_FIELDS.replace("_1", missingFieldsValues.join(", ")))
} else {
let outputFilename
if (window.styleEmbsMenuState.selectedEmb) {
outputFilename = window.styleEmbsMenuState.selectedEmb[1].fileName
} else {
outputFilename = `${window.styleEmbsMenuState.embeddingsDir}/${styleEmbVoiceIdInput.value.trim().toLowerCase()}.${styleEmbGameIdInput.value.trim().toLowerCase()}.${styleEmbIdInput.value.trim().toLowerCase()}.${styleEmbAuthorInput.value.trim().toLowerCase()}.json`
}
const jsonData = {
"author": styleEmbAuthorInput.value.trim(),
"version": "1.0", // Should I make this editable in the UI?
"gameId": styleEmbGameIdInput.value.trim().toLowerCase(),
"voiceId": styleEmbVoiceIdInput.value.trim().toLowerCase(),
"description": styleEmbDescriptionInput.value.trim()||"",
"embeddingName": styleEmbNameInput.value.trim(),
"emb": styleEmbValuesInput.value.trim().split(",").map(v=>parseFloat(v)),
"emb_id": styleEmbIdInput.value.trim()
}
fs.writeFileSync(outputFilename, JSON.stringify(jsonData, null, 4), "utf8")
window.loadStyleEmbsFromDisk()
window.refreshStyleEmbsTable()
}
if (window.currentModel) {
window.loadStyleEmbsForVoice(window.currentModel)
}
})
styleEmbDelete.addEventListener("click", () => {
window.confirmModal(window.i18n.CONFIRM_DELETE_STYLE_EMB).then(response => {
if (response) {
fs.unlinkSync(window.styleEmbsMenuState.selectedEmb[1].fileName)
window.loadStyleEmbsFromDisk()
window.refreshStyleEmbsTable()
if (window.currentModel) {
window.loadStyleEmbsForVoice(window.currentModel)
}
}
})
})
// Return the default embedding, plus any other ones
window.loadStyleEmbsForVoice = (currentModel) => {
const embeddings = {}
// Add the default option from the model json
embeddings["default"] = [window.i18n.DEFAULT, currentModel.games[0].base_speaker_emb] // TODO, specialize to specific game?
// Load any other style embeddings available
if (Object.keys(window.allStyleEmbs).includes(currentModel.voiceId)) {
window.allStyleEmbs[currentModel.voiceId].forEach(loadedStyleEmb => {
if (loadedStyleEmb.enabled) {
embeddings[loadedStyleEmb.emb_id] = [loadedStyleEmb.embeddingName, loadedStyleEmb.emb]
}
})
}
// Add every option to the embeddings selection dropdown
style_emb_select.innerHTML = ""
Array.from(seq_edit_edit_select.children).forEach(option => {
if (option.value.startsWith("style_")) {
seq_edit_edit_select.removeChild(option)
}
})
// Add Default first
const opt = createElem("option", embeddings["default"][0])
opt.value = embeddings["default"][1].join(",")
style_emb_select.appendChild(opt)
Object.keys(embeddings).forEach(key => {
if (key=="default") {
return
}
const opt = createElem("option", embeddings[key][0])
opt.value = embeddings[key][1].join(",")
style_emb_select.appendChild(opt)
})
// First remove all existing styles from the dropdown
Array.from(seq_edit_view_select.children).forEach(elem => {
if (elem.value.startsWith("style")) {
seq_edit_view_select.removeChild(elem)
}
})
// Add every option (except Default) to the sliders viewing/editing dropdowns
Object.keys(embeddings).forEach(key => {
if (key=="default") {
return
}
const opt = createElem("option", `${window.i18n.STYLE_EMB_IS} ${embeddings[key][0]}`)
opt.value = `style_${key}`
seq_edit_view_select.appendChild(opt)
const opt2 = createElem("option", `${window.i18n.STYLE_EMB_IS} ${embeddings[key][0]}`)
opt2.value = `style_${key}`
seq_edit_edit_select.appendChild(opt2)
})
window.appState.currentModelEmbeddings = embeddings
}
style_emb_select.addEventListener("change", () => window.styleEmbsMenuState.hasChangedEmb)
window.styleEmbsModalOpenCallback = () => {
styleEmbGameIdInput.value = window.currentGame.gameId
if (window.currentModel) {
styleEmbVoiceIdInput.value = window.currentModel.voiceId
}
window.refreshStyleEmbsTable()
}
window.dragDropWavForEmbComputeFilepathInput = (eType, event) => {
if (["dragenter", "dragover"].includes(eType)) {
wavFileDragDropArea.style.background = "#5b5b5b"
wavFileDragDropArea.style.color = "white"
}
if (["dragleave", "drop"].includes(eType)) {
wavFileDragDropArea.style.background = "rgba(0,0,0,0)"
wavFileDragDropArea.style.color = "white"
}
event.preventDefault()
event.stopPropagation()
const dataLines = []
if (eType=="drop") {
const dataTransfer = event.dataTransfer
const files = Array.from(dataTransfer.files)
if (files[0].path.endsWith(".wav")) {
wavFilepathForEmbComputeInput.value = String(files[0].path).replaceAll(/\\/g, "/")
} else {
window.errorModal(window.i18n.ERROR_FILE_MUST_BE_WAV)
}
}
}
wavFileDragDropArea.addEventListener("dragenter", event => window.dragDropWavForEmbComputeFilepathInput("dragenter", event), false)
wavFileDragDropArea.addEventListener("dragleave", event => window.dragDropWavForEmbComputeFilepathInput("dragleave", event), false)
wavFileDragDropArea.addEventListener("dragover", event => window.dragDropWavForEmbComputeFilepathInput("dragover", event), false)
wavFileDragDropArea.addEventListener("drop", event => window.dragDropWavForEmbComputeFilepathInput("drop", event), false)
getStyleEmbeddingBtn.addEventListener("click", async () => {
if (!wavFilepathForEmbComputeInput.value.trim().length) {
window.errorModal(window.i18n.ERROR_NEED_WAV_FILE)
} else {
const embedding = await window.getSpeakerEmbeddingFromFilePath(wavFilepathForEmbComputeInput.value)
styleEmbValuesInput.value = embedding
}
})